//go:build integration package integration import ( "fmt" "time" "testing" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/ecr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // uniqueRepo returns a collision-resistant repo name scoped to a test. // Matching the E2E source's naming keeps a future side-by-side diff greppable. func uniqueRepo(prefix string) string { return fmt.Sprintf("e2e/%s-%d", prefix, time.Now().UnixNano()) } // TestECRControlPlane round-trips the ECR control-plane surface: repository // CRUD, repository policy, and lifecycle policy. All of it dispatches to // ECRRegistry.Meta over NATS (domains/ecr/awsapi/create_repository.go and friends), so // this needs StartECRDaemonLite subscribed before any call. func TestECRControlPlane(t *testing.T) { gw := StartGateway(t) StartECRDaemonLite(t, gw) c := gw.ECRClient(t) repo := uniqueRepo("ctl") _, err := c.CreateRepository(&ecr.CreateRepositoryInput{RepositoryName: aws.String(repo)}) require.NoError(t, err, "create-repository") t.Run("an invalid name is refused with InvalidParameterException", func(t *testing.T) { _, err := c.CreateRepository(&ecr.CreateRepositoryInput{RepositoryName: aws.String("Bad_Name!")}) var aerr awserr.Error require.ErrorAs(t, err, &aerr) assert.Equal(t, ecr.ErrCodeInvalidParameterException, aerr.Code()) assert.Equal(t, `Invalid parameter at 'repositoryName' failed to satisfy constraint: 'must satisfy regular expression '[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*(/[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*)*''`, aerr.Message()) }) t.Run("DescribeRepositories lists the new repo", func(t *testing.T) { out, err := c.DescribeRepositories(&ecr.DescribeRepositoriesInput{ RepositoryNames: []*string{aws.String(repo)}, }) require.NoError(t, err) require.Len(t, out.Repositories, 1) assert.Equal(t, repo, aws.StringValue(out.Repositories[0].RepositoryName)) assert.Contains(t, aws.StringValue(out.Repositories[0].RepositoryUri), repo) }) t.Run("repository policy round-trips", func(t *testing.T) { policy := `{"Version":"2012-10-17","Statement":[{"Sid":"allow-pull","Effect":"Allow","Principal":"*","Action":["ecr:GetDownloadUrlForLayer","ecr:BatchGetImage"]}]}` _, err := c.SetRepositoryPolicy(&ecr.SetRepositoryPolicyInput{ RepositoryName: aws.String(repo), PolicyText: aws.String(policy), }) require.NoError(t, err) got, err := c.GetRepositoryPolicy(&ecr.GetRepositoryPolicyInput{ RepositoryName: aws.String(repo), }) require.NoError(t, err) assert.JSONEq(t, policy, aws.StringValue(got.PolicyText)) }) t.Run("lifecycle policy round-trips", func(t *testing.T) { policy := `{"rules":[{"rulePriority":1,"selection":{"tagStatus":"untagged","countType":"sinceImagePushed","countUnit":"days","countNumber":14},"action":{"type":"expire"}}]}` _, err := c.PutLifecyclePolicy(&ecr.PutLifecyclePolicyInput{ RepositoryName: aws.String(repo), LifecyclePolicyText: aws.String(policy), }) require.NoError(t, err) got, err := c.GetLifecyclePolicy(&ecr.GetLifecyclePolicyInput{ RepositoryName: aws.String(repo), }) require.NoError(t, err) assert.JSONEq(t, policy, aws.StringValue(got.LifecyclePolicyText)) }) t.Run("DeleteRepository removes it", func(t *testing.T) { _, err := c.DeleteRepository(&ecr.DeleteRepositoryInput{ RepositoryName: aws.String(repo), Force: aws.Bool(true), }) require.NoError(t, err) _, err = c.DescribeRepositories(&ecr.DescribeRepositoriesInput{ RepositoryNames: []*string{aws.String(repo)}, }) require.Error(t, err, "deleted repo must not be describable") assert.Contains(t, err.Error(), "RepositoryNotFoundException") }) } // TestECRGetLoginPassword confirms GetAuthorizationToken returns a usable // AWS: credential — the input to docker login. Minting needs only the // authorization-token capability StartGateway composes, so no daemon-lite // subscription is required here. func TestECRGetLoginPassword(t *testing.T) { gw := StartGateway(t) pass := ecrGetLoginPassword(t, gw.ECRClient(t)) assert.NotEmpty(t, pass) // JWT shape: three dot-separated segments. assert.Len(t, splitDots(pass), 3, "authorization password should be a JWT") } func splitDots(s string) []string { var out []string start := 0 for i := 0; i < len(s); i++ { if s[i] == '.' { out = append(out, s[start:i]) start = i + 1 } } return append(out, s[start:]) }