diff --git a/.github/workflows/s3-integration.yml b/.github/workflows/s3-integration.yml index 5a0a25a..7896c70 100644 --- a/.github/workflows/s3-integration.yml +++ b/.github/workflows/s3-integration.yml @@ -192,6 +192,61 @@ jobs: region_name: ${{ env.REGION_NAME }} stack_name: ${{ env.STACK_NAME }} + # AWS European Sovereign Cloud Integration + aws-s3-esc-integration: + name: AWS S3 European Sovereign Cloud Integration + runs-on: ubuntu-latest + # Run on push/workflow_dispatch, skip forks and Dependabot on PRs + if: | + github.event_name == 'push' || + github.event_name == 'workflow_dispatch' || + (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]') + env: + REGION_NAME: eusc-de-east-1 + STACK_NAME: s3cli-private-bucket + S3_ENDPOINT_HOST: https://s3.eusc-de-east-1.amazonaws.eu + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: go.mod + + - name: Install Ginkgo + run: go install github.com/onsi/ginkgo/v2/ginkgo@latest + + - name: Setup AWS infrastructure + uses: ./.github/actions/s3-integration-setup + with: + access_key_id: ${{ secrets.AWS_ESC_ACCESS_KEY_ID }} + secret_access_key: ${{ secrets.AWS_ESC_SECRET_ACCESS_KEY }} + region_name: ${{ env.REGION_NAME }} + stack_name: ${{ env.STACK_NAME }} + + - name: Run AWS ESC region tests + uses: ./.github/actions/s3-integration-run + with: + access_key_id: ${{ secrets.AWS_ESC_ACCESS_KEY_ID }} + secret_access_key: ${{ secrets.AWS_ESC_SECRET_ACCESS_KEY }} + region_name: ${{ env.REGION_NAME }} + stack_name: ${{ env.STACK_NAME }} + s3_endpoint_host: ${{ env.S3_ENDPOINT_HOST }} + focus_regex: 'AWS ESC' + test_type: 'aws' + + - name: Teardown AWS infrastructure + if: always() + uses: ./.github/actions/s3-integration-teardown + with: + access_key_id: ${{ secrets.AWS_ESC_ACCESS_KEY_ID }} + secret_access_key: ${{ secrets.AWS_ESC_SECRET_ACCESS_KEY }} + region_name: ${{ env.REGION_NAME }} + stack_name: ${{ env.STACK_NAME }} + + s3-compatible-integration: name: S3 Compatible Integration runs-on: ubuntu-latest diff --git a/s3/integration/aws_esc_test.go b/s3/integration/aws_esc_test.go new file mode 100644 index 0000000..930ca62 --- /dev/null +++ b/s3/integration/aws_esc_test.go @@ -0,0 +1,128 @@ +package integration_test + +import ( + "os" + + "github.com/cloudfoundry/storage-cli/s3/config" + "github.com/cloudfoundry/storage-cli/s3/integration" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Testing for AWS European Sovereign Cloud region", func() { + Context("with AWS ESC (static creds) configurations", func() { + accessKeyID := os.Getenv("ACCESS_KEY_ID") + secretAccessKey := os.Getenv("SECRET_ACCESS_KEY") + bucketName := os.Getenv("BUCKET_NAME") + region := os.Getenv("REGION") + s3Host := os.Getenv("S3_HOST") + + BeforeEach(func() { + Expect(accessKeyID).ToNot(BeEmpty(), "ACCESS_KEY_ID must be set") + Expect(secretAccessKey).ToNot(BeEmpty(), "SECRET_ACCESS_KEY must be set") + Expect(bucketName).ToNot(BeEmpty(), "BUCKET_NAME must be set") + Expect(region).ToNot(BeEmpty(), "REGION must be set") + Expect(s3Host).ToNot(BeEmpty(), "S3_HOST must be set") + }) + + configurations := []TableEntry{ + Entry("with region and without host", &config.S3Cli{ + AccessKeyID: accessKeyID, + SecretAccessKey: secretAccessKey, + BucketName: bucketName, + Region: region, + }), + Entry("with folder", &config.S3Cli{ + AccessKeyID: accessKeyID, + SecretAccessKey: secretAccessKey, + BucketName: bucketName, + FolderName: "test-folder/a-folder", + Region: region, + }), + Entry("with host style enabled", &config.S3Cli{ + AccessKeyID: accessKeyID, + SecretAccessKey: secretAccessKey, + BucketName: bucketName, + Region: region, + HostStyle: true, + }), + } + DescribeTable("Blobstore lifecycle works", + func(cfg *config.S3Cli) { integration.AssertLifecycleWorks(s3CLIPath, cfg) }, + configurations, + ) + + DescribeTable("Invoking `ensure-storage-exists` works", + func(cfg *config.S3Cli) { integration.AssertOnStorageExists(s3CLIPath, cfg) }, + configurations, + ) + DescribeTable("Blobstore bulk operations work", + func(cfg *config.S3Cli) { integration.AssertOnBulkOperations(s3CLIPath, cfg) }, + configurations, + ) + DescribeTable("Invoking `s3cli get` on a non-existent-key fails", + func(cfg *config.S3Cli) { integration.AssertGetNonexistentFails(s3CLIPath, cfg) }, + configurations, + ) + DescribeTable("Invoking `s3cli delete` on a non-existent-key does not fail", + func(cfg *config.S3Cli) { integration.AssertDeleteNonexistentWorks(s3CLIPath, cfg) }, + configurations, + ) + DescribeTable("Invoking `s3cli sign` returns a signed URL", + func(cfg *config.S3Cli) { integration.AssertOnSignedURLs(s3CLIPath, cfg) }, + configurations, + ) + DescribeTable("Multipart copy works with low threshold", + func(cfg *config.S3Cli) { integration.AssertMultipartCopyWorks(s3CLIPath, cfg) }, + configurations, + ) + + configurations = []TableEntry{ + Entry("with encryption", &config.S3Cli{ + AccessKeyID: accessKeyID, + SecretAccessKey: secretAccessKey, + BucketName: bucketName, + Region: region, + ServerSideEncryption: "AES256", + }), + Entry("without encryption", &config.S3Cli{ + AccessKeyID: accessKeyID, + SecretAccessKey: secretAccessKey, + BucketName: bucketName, + Region: region, + }), + } + DescribeTable("Invoking `s3cli put` uploads with options", + func(cfg *config.S3Cli) { integration.AssertPutOptionsApplied(s3CLIPath, cfg) }, + configurations, + ) + + Describe("Invoking `s3cli put` with arbitrary upload failures", func() { + It("returns the appropriate error message", func() { + cfg := &config.S3Cli{ + AccessKeyID: accessKeyID, + SecretAccessKey: secretAccessKey, + BucketName: bucketName, + Host: "http://localhost", + } + msg := "upload failure" + integration.AssertOnPutFailures(cfg, largeContent, msg) + }) + }) + + Describe("Invoking `s3cli put` with multipart upload failures", func() { + It("returns the appropriate error message", func() { + cfg := &config.S3Cli{ + AccessKeyID: accessKeyID, + SecretAccessKey: secretAccessKey, + BucketName: bucketName, + Region: region, + MultipartUpload: true, + } + msg := "upload retry limit exceeded" + integration.AssertOnPutFailures(cfg, largeContent, msg) + }) + }) + }) +})