mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-08-22 10:21:13 +00:00
Some checks failed
testing-integration / test-sqlite (push) Has been skipped
testing-integration / test-unit (push) Has been skipped
testing / frontend-checks (push) Has been skipped
testing / backend-checks (push) Has been skipped
testing / test-e2e (push) Has been skipped
testing / test-unit (push) Has been skipped
testing / test-pgsql (push) Has been skipped
testing / test-mysql (push) Has been skipped
testing / test-remote-cacher (redis) (push) Has been skipped
testing / test-sqlite (push) Has been skipped
testing / test-remote-cacher (garnet) (push) Has been skipped
testing / test-remote-cacher (valkey) (push) Has been skipped
testing / test-remote-cacher (redict) (push) Has been skipped
testing / security-check (push) Has been skipped
/ release (push) Has been cancelled
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/8897 Fixes #8893 by using a 30 second initialization timeout on the minio storage. Manually tested by configuring `MINIO_ENDPOINT=100.64.123.123`... ``` 2025/08/14 11:29:29 ...s/storage/storage.go:157:initAttachments() [I] Initialising Attachment storage with type: minio 2025/08/14 11:29:29 ...les/storage/minio.go💯NewMinioStorage() [I] Creating Minio storage at 100.64.123.123:mfenniak-forgejo with base path attachments/ 2025/08/14 11:29:59 routers/init.go:63:mustInit() [F] forgejo.org/modules/storage.Init failed: Get "http://100.64.123.123/mfenniak-forgejo/?versioning=": context deadline exceeded ``` ## Checklist The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). ### Tests - I added test coverage for Go changes... - [ ] in their respective `*_test.go` for unit tests. - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server. - I added test coverage for JavaScript changes... - [ ] in `web_src/js/*.test.js` if it can be unit tested. - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)). ### Documentation - [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change. - [x] I did not document these changes and I do not expect someone else to do it. ### Release notes - [ ] I do not want this change to show in the release notes. - [x] I want the title to show in the release notes with a link to this pull request. - [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title. Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8914 Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org> Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org> Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
259 lines
8.3 KiB
Go
259 lines
8.3 KiB
Go
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package storage
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"forgejo.org/modules/setting"
|
|
"forgejo.org/modules/test"
|
|
|
|
"github.com/minio/minio-go/v7"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestMinioStorageIterator(t *testing.T) {
|
|
endpoint := os.Getenv("TEST_MINIO_ENDPOINT")
|
|
if endpoint == "" {
|
|
t.Skip("TEST_MINIO_ENDPOINT not set")
|
|
return
|
|
}
|
|
testStorageIterator(t, setting.MinioStorageType, &setting.Storage{
|
|
MinioConfig: setting.MinioStorageConfig{
|
|
Endpoint: endpoint,
|
|
AccessKeyID: "123456",
|
|
SecretAccessKey: "12345678",
|
|
Bucket: "gitea",
|
|
Location: "us-east-1",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestVirtualHostMinioStorage(t *testing.T) {
|
|
endpoint := os.Getenv("TEST_MINIO_ENDPOINT")
|
|
if endpoint == "" {
|
|
t.Skip("TEST_MINIO_ENDPOINT not set")
|
|
return
|
|
}
|
|
testStorageIterator(t, setting.MinioStorageType, &setting.Storage{
|
|
MinioConfig: setting.MinioStorageConfig{
|
|
Endpoint: endpoint,
|
|
AccessKeyID: "123456",
|
|
SecretAccessKey: "12345678",
|
|
Bucket: "gitea",
|
|
Location: "us-east-1",
|
|
BucketLookup: "dns",
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestMinioStoragePath(t *testing.T) {
|
|
m := &MinioStorage{basePath: ""}
|
|
assert.Empty(t, m.buildMinioPath("/"))
|
|
assert.Empty(t, m.buildMinioPath("."))
|
|
assert.Equal(t, "a", m.buildMinioPath("/a"))
|
|
assert.Equal(t, "a/b", m.buildMinioPath("/a/b/"))
|
|
assert.Empty(t, m.buildMinioDirPrefix(""))
|
|
assert.Equal(t, "a/", m.buildMinioDirPrefix("/a/"))
|
|
|
|
m = &MinioStorage{basePath: "/"}
|
|
assert.Empty(t, m.buildMinioPath("/"))
|
|
assert.Empty(t, m.buildMinioPath("."))
|
|
assert.Equal(t, "a", m.buildMinioPath("/a"))
|
|
assert.Equal(t, "a/b", m.buildMinioPath("/a/b/"))
|
|
assert.Empty(t, m.buildMinioDirPrefix(""))
|
|
assert.Equal(t, "a/", m.buildMinioDirPrefix("/a/"))
|
|
|
|
m = &MinioStorage{basePath: "/base"}
|
|
assert.Equal(t, "base", m.buildMinioPath("/"))
|
|
assert.Equal(t, "base", m.buildMinioPath("."))
|
|
assert.Equal(t, "base/a", m.buildMinioPath("/a"))
|
|
assert.Equal(t, "base/a/b", m.buildMinioPath("/a/b/"))
|
|
assert.Equal(t, "base/", m.buildMinioDirPrefix(""))
|
|
assert.Equal(t, "base/a/", m.buildMinioDirPrefix("/a/"))
|
|
|
|
m = &MinioStorage{basePath: "/base/"}
|
|
assert.Equal(t, "base", m.buildMinioPath("/"))
|
|
assert.Equal(t, "base", m.buildMinioPath("."))
|
|
assert.Equal(t, "base/a", m.buildMinioPath("/a"))
|
|
assert.Equal(t, "base/a/b", m.buildMinioPath("/a/b/"))
|
|
assert.Equal(t, "base/", m.buildMinioDirPrefix(""))
|
|
assert.Equal(t, "base/a/", m.buildMinioDirPrefix("/a/"))
|
|
}
|
|
|
|
func TestS3StorageBadRequest(t *testing.T) {
|
|
endpoint := os.Getenv("TEST_MINIO_ENDPOINT")
|
|
if endpoint == "" {
|
|
t.Skip("TEST_MINIO_ENDPOINT not set")
|
|
return
|
|
}
|
|
cfg := &setting.Storage{
|
|
MinioConfig: setting.MinioStorageConfig{
|
|
Endpoint: endpoint,
|
|
AccessKeyID: "123456",
|
|
SecretAccessKey: "12345678",
|
|
Bucket: "bucket",
|
|
Location: "us-east-1",
|
|
},
|
|
}
|
|
message := "ERROR"
|
|
old := getBucketVersioning
|
|
defer func() { getBucketVersioning = old }()
|
|
getBucketVersioning = func(ctx context.Context, minioClient *minio.Client, bucket string) error {
|
|
return minio.ErrorResponse{
|
|
StatusCode: http.StatusBadRequest,
|
|
Code: "FixtureError",
|
|
Message: message,
|
|
}
|
|
}
|
|
_, err := NewStorage(setting.MinioStorageType, cfg)
|
|
require.ErrorContains(t, err, message)
|
|
}
|
|
|
|
func TestMinioCredentials(t *testing.T) {
|
|
const (
|
|
ExpectedAccessKey = "ExampleAccessKeyID"
|
|
ExpectedSecretAccessKey = "ExampleSecretAccessKeyID"
|
|
// Use a FakeEndpoint for IAM credentials to avoid logging any
|
|
// potential real IAM credentials when running in EC2.
|
|
FakeEndpoint = "http://localhost"
|
|
)
|
|
|
|
t.Run("Static Credentials", func(t *testing.T) {
|
|
cfg := setting.MinioStorageConfig{
|
|
AccessKeyID: ExpectedAccessKey,
|
|
SecretAccessKey: ExpectedSecretAccessKey,
|
|
}
|
|
creds := buildMinioCredentials(cfg, FakeEndpoint)
|
|
v, err := creds.Get()
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, ExpectedAccessKey, v.AccessKeyID)
|
|
assert.Equal(t, ExpectedSecretAccessKey, v.SecretAccessKey)
|
|
})
|
|
|
|
t.Run("Chain", func(t *testing.T) {
|
|
cfg := setting.MinioStorageConfig{}
|
|
|
|
t.Run("EnvMinio", func(t *testing.T) {
|
|
t.Setenv("MINIO_ACCESS_KEY", ExpectedAccessKey+"Minio")
|
|
t.Setenv("MINIO_SECRET_KEY", ExpectedSecretAccessKey+"Minio")
|
|
|
|
creds := buildMinioCredentials(cfg, FakeEndpoint)
|
|
v, err := creds.Get()
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, ExpectedAccessKey+"Minio", v.AccessKeyID)
|
|
assert.Equal(t, ExpectedSecretAccessKey+"Minio", v.SecretAccessKey)
|
|
})
|
|
|
|
t.Run("EnvAWS", func(t *testing.T) {
|
|
t.Setenv("AWS_ACCESS_KEY", ExpectedAccessKey+"AWS")
|
|
t.Setenv("AWS_SECRET_KEY", ExpectedSecretAccessKey+"AWS")
|
|
|
|
creds := buildMinioCredentials(cfg, FakeEndpoint)
|
|
v, err := creds.Get()
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, ExpectedAccessKey+"AWS", v.AccessKeyID)
|
|
assert.Equal(t, ExpectedSecretAccessKey+"AWS", v.SecretAccessKey)
|
|
})
|
|
|
|
t.Run("FileMinio", func(t *testing.T) {
|
|
t.Setenv("MINIO_SHARED_CREDENTIALS_FILE", "testdata/minio.json")
|
|
// prevent loading any actual credentials files from the user
|
|
t.Setenv("AWS_SHARED_CREDENTIALS_FILE", "testdata/fake")
|
|
|
|
creds := buildMinioCredentials(cfg, FakeEndpoint)
|
|
v, err := creds.Get()
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, ExpectedAccessKey+"MinioFile", v.AccessKeyID)
|
|
assert.Equal(t, ExpectedSecretAccessKey+"MinioFile", v.SecretAccessKey)
|
|
})
|
|
|
|
t.Run("FileAWS", func(t *testing.T) {
|
|
// prevent loading any actual credentials files from the user
|
|
t.Setenv("MINIO_SHARED_CREDENTIALS_FILE", "testdata/fake.json")
|
|
t.Setenv("AWS_SHARED_CREDENTIALS_FILE", "testdata/aws_credentials")
|
|
|
|
creds := buildMinioCredentials(cfg, FakeEndpoint)
|
|
v, err := creds.Get()
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, ExpectedAccessKey+"AWSFile", v.AccessKeyID)
|
|
assert.Equal(t, ExpectedSecretAccessKey+"AWSFile", v.SecretAccessKey)
|
|
})
|
|
|
|
t.Run("IAM", func(t *testing.T) {
|
|
// prevent loading any actual credentials files from the user
|
|
t.Setenv("MINIO_SHARED_CREDENTIALS_FILE", "testdata/fake.json")
|
|
t.Setenv("AWS_SHARED_CREDENTIALS_FILE", "testdata/fake")
|
|
|
|
// Spawn a server to emulate the EC2 Instance Metadata
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// The client will actually make 3 requests here,
|
|
// first will be to get the IMDSv2 token, second to
|
|
// get the role, and third for the actual
|
|
// credentials. However, we can return credentials
|
|
// every request since we're not emulating a full
|
|
// IMDSv2 flow.
|
|
w.Write([]byte(`{"Code":"Success","AccessKeyId":"ExampleAccessKeyIDIAM","SecretAccessKey":"ExampleSecretAccessKeyIDIAM"}`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
// Use the provided EC2 Instance Metadata server
|
|
creds := buildMinioCredentials(cfg, server.URL)
|
|
v, err := creds.Get()
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, ExpectedAccessKey+"IAM", v.AccessKeyID)
|
|
assert.Equal(t, ExpectedSecretAccessKey+"IAM", v.SecretAccessKey)
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestNewMinioStorageInitializationTimeout(t *testing.T) {
|
|
defer test.MockVariableValue(&getBucketVersioning, func(ctx context.Context, minioClient *minio.Client, bucket string) error {
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
case <-time.After(1 * time.Millisecond):
|
|
return minio.ErrorResponse{
|
|
StatusCode: http.StatusBadRequest,
|
|
Code: "TestError",
|
|
Message: "Mocked error for testing",
|
|
}
|
|
}
|
|
})()
|
|
|
|
settings := &setting.Storage{
|
|
MinioConfig: setting.MinioStorageConfig{
|
|
Endpoint: "localhost",
|
|
AccessKeyID: "123456",
|
|
SecretAccessKey: "12345678",
|
|
Bucket: "bucket",
|
|
Location: "us-east-1",
|
|
},
|
|
}
|
|
|
|
// Verify that we reach `getBucketVersioning` and return the error from our mock.
|
|
storage, err := NewMinioStorage(t.Context(), settings)
|
|
require.ErrorContains(t, err, "Mocked error for testing")
|
|
assert.Nil(t, storage)
|
|
|
|
defer test.MockVariableValue(&initializationTimeout, 1*time.Nanosecond)()
|
|
|
|
// Now that the timeout is super low, verify that we get a context deadline exceeded error from our mock.
|
|
storage, err = NewMinioStorage(t.Context(), settings)
|
|
require.Error(t, err)
|
|
require.ErrorIs(t, err, context.DeadlineExceeded, "err must be a context deadline exceeded error, but was %v", err)
|
|
assert.Nil(t, storage)
|
|
}
|