Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Output artifact versions for ecs deployment #3385

Merged
merged 3 commits into from Mar 10, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions pkg/app/piped/cloudprovider/ecs/BUILD.bazel
Expand Up @@ -15,6 +15,7 @@ go_library(
deps = [
"//pkg/app/piped/cloudprovider:go_default_library",
"//pkg/config:go_default_library",
"//pkg/model:go_default_library",
"@com_github_aws_aws_sdk_go_v2//aws:go_default_library",
"@com_github_aws_aws_sdk_go_v2_config//:go_default_library",
"@com_github_aws_aws_sdk_go_v2_credentials//stscreds:go_default_library",
Expand All @@ -37,6 +38,7 @@ go_test(
],
embed = [":go_default_library"],
deps = [
"//pkg/model:go_default_library",
"@com_github_aws_aws_sdk_go_v2//aws:go_default_library",
"@com_github_aws_aws_sdk_go_v2_service_ecs//types:go_default_library",
"@com_github_stretchr_testify//assert:go_default_library",
Expand Down
33 changes: 32 additions & 1 deletion pkg/app/piped/cloudprovider/ecs/task.go
Expand Up @@ -19,9 +19,10 @@ import (
"os"
"strings"

"github.com/aws/aws-sdk-go-v2/service/ecs/types"
"sigs.k8s.io/yaml"

"github.com/aws/aws-sdk-go-v2/service/ecs/types"
"github.com/pipe-cd/pipecd/pkg/model"
)

func loadTaskDefinition(path string) (types.TaskDefinition, error) {
Expand Down Expand Up @@ -61,3 +62,33 @@ func parseContainerImage(image string) (name, tag string) {
name = paths[len(paths)-1]
return
}

// FindArtifactVersions parses artifact versions from ECS task definition.
func FindArtifactVersions(taskDefinition types.TaskDefinition) ([]*model.ArtifactVersion, error) {
if len(taskDefinition.ContainerDefinitions) == 0 {
return nil, fmt.Errorf("container definition could not be empty")
}

// Remove duplicate images.
imageMap := map[string]struct{}{}
for _, cd := range taskDefinition.ContainerDefinitions {
imageMap[*cd.Image] = struct{}{}
}

versions := make([]*model.ArtifactVersion, 0, len(imageMap))
for i := range imageMap {
name, tag := parseContainerImage(i)
if name == "" {
return nil, fmt.Errorf("image name could not be empty")
}

versions = append(versions, &model.ArtifactVersion{
Kind: model.ArtifactVersion_CONTAINER_IMAGE,
Version: tag,
Name: name,
Url: i,
})
}

return versions, nil
}
204 changes: 204 additions & 0 deletions pkg/app/piped/cloudprovider/ecs/task_test.go
Expand Up @@ -20,6 +20,8 @@ import (
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ecs/types"
"github.com/stretchr/testify/assert"

"github.com/pipe-cd/pipecd/pkg/model"
)

func TestParseTaskDefinition(t *testing.T) {
Expand Down Expand Up @@ -78,3 +80,205 @@ cpu: 256
})
}
}

func TestFindArtifactVersions(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func TestFindArtifactVersions(t *testing.T) {
func TestFindArtifactVersions(t *testing.T) {
t.Parallel()

testcases := []struct {
name string
input []byte
expected []*model.ArtifactVersion
expectedErr bool
}{
{
name: "ok",
input: []byte(`
{
"family": "nginx-canary-fam-1",
"compatibilities": [
"FARGATE"
],
"networkMode": "awsvpc",
"memory": 512,
"cpu": 256,
"containerDefinitions" : [
{
"image": "gcr.io/pipecd/helloworld:v1.0.0",
"name": "helloworld",
"portMappings": [
{
"containerPort": 80,
"hostPort": 9085,
"protocol": "tcp"
}
]
}
]
}
`),
expected: []*model.ArtifactVersion{
{
Kind: model.ArtifactVersion_CONTAINER_IMAGE,
Version: "v1.0.0",
Name: "helloworld",
Url: "gcr.io/pipecd/helloworld:v1.0.0",
},
},
expectedErr: false,
},
{
name: "missing containerDefinitions",
input: []byte(`
{
"family": "nginx-canary-fam-1",
"compatibilities": [
"FARGATE"
],
"networkMode": "awsvpc",
"memory": 512,
"cpu": 256,
}
`),
expected: nil,
expectedErr: true,
},
{
name: "missing image name",
input: []byte(`
{
"family": "nginx-canary-fam-1",
"compatibilities": [
"FARGATE"
],
"networkMode": "awsvpc",
"memory": 512,
"cpu": 256,
"containerDefinitions" : [
{
"image": "gcr.io/pipecd/:v1.0.0",
"name": "helloworld",
"portMappings": [
{
"containerPort": 80,
"hostPort": 9085,
"protocol": "tcp"
}
]
}
]
}
`),
expected: nil,
expectedErr: true,
},
{
name: "multiple containers",
input: []byte(`
{
"family": "nginx-canary-fam-1",
"compatibilities": [
"FARGATE"
],
"networkMode": "awsvpc",
"memory": 512,
"cpu": 256,
"containerDefinitions" : [
{
"image": "gcr.io/pipecd/helloworld:v1.0.0",
"name": "helloworld",
"portMappings": [
{
"containerPort": 80,
"hostPort": 9085,
"protocol": "tcp"
}
]
},
{
"image": "gcr.io/pipecd/my-service:v1.0.0",
"name": "my-service",
"portMappings": [
{
"containerPort": 80,
"hostPort": 9090,
"protocol": "tcp"
}
]
}
]
}
`),
expected: []*model.ArtifactVersion{
{
Kind: model.ArtifactVersion_CONTAINER_IMAGE,
Version: "v1.0.0",
Name: "helloworld",
Url: "gcr.io/pipecd/helloworld:v1.0.0",
},
{
Kind: model.ArtifactVersion_CONTAINER_IMAGE,
Version: "v1.0.0",
Name: "my-service",
Url: "gcr.io/pipecd/my-service:v1.0.0",
},
},
expectedErr: false,
},
{
name: "multiple containers with the same image",
input: []byte(`
{
"family": "nginx-canary-fam-1",
"compatibilities": [
"FARGATE"
],
"networkMode": "awsvpc",
"memory": 512,
"cpu": 256,
"containerDefinitions" : [
{
"image": "gcr.io/pipecd/helloworld:v1.0.0",
"name": "helloworld",
"portMappings": [
{
"containerPort": 80,
"hostPort": 9085,
"protocol": "tcp"
}
]
},
{
"image": "gcr.io/pipecd/helloworld:v1.0.0",
"name": "helloworld-02",
"portMappings": [
{
"containerPort": 80,
"hostPort": 9091,
"protocol": "tcp"
}
]
}
]
}
`),
expected: []*model.ArtifactVersion{
{
Kind: model.ArtifactVersion_CONTAINER_IMAGE,
Version: "v1.0.0",
Name: "helloworld",
Url: "gcr.io/pipecd/helloworld:v1.0.0",
},
},
expectedErr: false,
},
}

for _, tc := range testcases {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for _, tc := range testcases {
for _, tc := range testcases {
tc := tc

t.Run(tc.name, func(t *testing.T) {
t.Parallel()

td, _ := parseTaskDefinition(tc.input)
versions, err := FindArtifactVersions(td)
assert.Equal(t, tc.expectedErr, err != nil)
assert.Equal(t, tc.expected, versions)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
assert.Equal(t, tc.expected, versions)
assert.ElementsMatch(t, tc.expected, versions)

should use this to avoid order of results while checking

})
}
}
20 changes: 20 additions & 0 deletions pkg/app/piped/planner/ecs/ecs.go
Expand Up @@ -62,6 +62,17 @@ func (p *Planner) Plan(ctx context.Context, in planner.Input) (out planner.Outpu
in.Logger.Warn("unable to determine target version", zap.Error(err))
}

out.Versions, err = determineVersions(ds.AppDir, cfg.Input.TaskDefinitionFile)
if err != nil {
in.Logger.Warn("unable to determine target versions", zap.Error(err))
out.Versions = []*model.ArtifactVersion{
{
Kind: model.ArtifactVersion_UNKNOWN,
Version: "unknown",
},
}
}

autoRollback := *cfg.Input.AutoRollback

// In case the strategy has been decided by trigger.
Expand Down Expand Up @@ -133,3 +144,12 @@ func determineVersion(appDir, taskDefinitonFile string) (string, error) {

return provider.FindImageTag(taskDefinition)
}

func determineVersions(appDir, taskDefinitonFile string) ([]*model.ArtifactVersion, error) {
taskDefinition, err := provider.LoadTaskDefinition(appDir, taskDefinitonFile)
if err != nil {
return nil, err
}

return provider.FindArtifactVersions(taskDefinition)
}