Skip to content

Commit d41cbe2

Browse files
authored
Merge branch 'akitasoftware:main' into main (#8)
2 parents 89f7621 + 71b9d41 commit d41cbe2

File tree

7 files changed

+389
-64
lines changed

7 files changed

+389
-64
lines changed

aws_utils/cloudformation/ecs/types.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package ecs_cloudformation_utils
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"strings"
7+
8+
"github.com/akitasoftware/go-utils/slices"
9+
"github.com/aws/aws-sdk-go-v2/service/ecs/types"
10+
"gopkg.in/yaml.v2"
11+
)
12+
13+
// The JSON and YAML representations of this type are suitable for use in AWS
14+
// CloudFormation templates.
15+
//
16+
// XXX This is incomplete. Currently, only those fields we use are listed here.
17+
//
18+
// The fields here are taken from
19+
// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-containerdefinition.html.
20+
// Their types correspond to those defined in the AWS SDK v2.
21+
type containerDefinition struct {
22+
Name *string `json:"Name,omitempty" yaml:"Name,omitempty"`
23+
Image *string `json:"Image,omitempty" yaml:"Image,omitempty"`
24+
Environment []keyValuePair `json:"Environment,omitempty" yaml:"Environment,omitempty"`
25+
EntryPoint []string `json:"EntryPoint,omitempty" yaml:"EntryPoint,omitempty"`
26+
Essential *bool `json:"Essential,omitempty" yaml:"Essential,omitempty"`
27+
}
28+
29+
func convertContainerDefinition(cd types.ContainerDefinition) containerDefinition {
30+
return containerDefinition{
31+
Name: cd.Name,
32+
Image: cd.Image,
33+
Essential: cd.Essential,
34+
EntryPoint: cd.EntryPoint,
35+
Environment: slices.Map(cd.Environment, convertKeyValuePair),
36+
}
37+
}
38+
39+
// The JSON and YAML representations of this type are suitable for use in AWS
40+
// CloudFormation templates.
41+
//
42+
// The fields here are taken from
43+
// https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-definition-template.html.
44+
// Their types correspond to those defined in the AWS SDK v2.
45+
type keyValuePair struct {
46+
Name *string `json:"Name,omitempty" yaml:"Name,omitempty"`
47+
Value *string `json:"Value,omitempty" yaml:"Value,omitempty"`
48+
}
49+
50+
func convertKeyValuePair(kv types.KeyValuePair) keyValuePair {
51+
return keyValuePair{
52+
Name: kv.Name,
53+
Value: kv.Value,
54+
}
55+
}
56+
57+
func ContainerDefinitionToJSONForCloudFormation(
58+
cd types.ContainerDefinition,
59+
) (string, error) {
60+
// Indent five levels to line up with expected indent level of other container
61+
// definitions in a task definition.
62+
prefix := " "
63+
result, err := json.MarshalIndent(convertContainerDefinition(cd), prefix, " ")
64+
result = append([]byte(prefix), result...)
65+
return string(result), err
66+
}
67+
68+
func ContainerDefinitionToYAMLForCloudFormation(
69+
cd types.ContainerDefinition,
70+
) (string, error) {
71+
// Put the container definition in a list, so it can be easily appended to an
72+
// existing list of container definitions.
73+
containerDefs := []containerDefinition{
74+
convertContainerDefinition(cd),
75+
}
76+
77+
yamlBytes, err := yaml.Marshal(
78+
containerDefs,
79+
)
80+
if err != nil {
81+
return "", err
82+
}
83+
84+
// Trim off any extraneous newlines.
85+
yamlBytes = bytes.Trim(yamlBytes, "\n")
86+
87+
// Indent four levels to line up with the expected indent level of the other
88+
// container definitions in a task definition.
89+
prefix := " "
90+
result := prefix + strings.ReplaceAll(string(yamlBytes), "\n", "\n"+prefix)
91+
return result, nil
92+
}

aws_utils/console/ecs/types.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package ecs_console_utils
2+
3+
import (
4+
"encoding/json"
5+
6+
"github.com/akitasoftware/go-utils/slices"
7+
"github.com/aws/aws-sdk-go-v2/service/ecs/types"
8+
)
9+
10+
// A type whose JSON representation is suitable for use with the AWS console for
11+
// creating a ECS task definition.
12+
//
13+
// XXX This is incomplete. Currently, only those fields we use are listed here.
14+
//
15+
// The fields here are taken from
16+
// https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-definition-template.html.
17+
// Their types correspond to those defined in the AWS SDK v2.
18+
type taskDefinition struct {
19+
Family *string `json:"family,omitempty"`
20+
NetworkMode types.NetworkMode `json:"networkMode,omitempty"`
21+
ContainerDefinitions []containerDefinition `json:"containerDefinitions,omitempty"`
22+
RequiresCompatibilities []types.Compatibility `json:"requiresCompatibilities,omitempty"`
23+
CPU *string `json:"cpu,omitempty"`
24+
Memory *string `json:"memory,omitempty"`
25+
RuntimePlatform *runtimePlatform `json:"runtimePlatform,omitempty"`
26+
}
27+
28+
func convertTaskDefinition(td types.TaskDefinition) taskDefinition {
29+
return taskDefinition{
30+
Family: td.Family,
31+
NetworkMode: td.NetworkMode,
32+
ContainerDefinitions: slices.Map(td.ContainerDefinitions, convertContainerDefinition),
33+
RequiresCompatibilities: td.RequiresCompatibilities,
34+
CPU: td.Cpu,
35+
Memory: td.Memory,
36+
RuntimePlatform: convertRuntimePlatform(td.RuntimePlatform),
37+
}
38+
}
39+
40+
// A container definition within a task definition. The JSON representation of
41+
// this type is suitable for use with the AWS console.
42+
//
43+
// XXX This is incomplete. Currently, only those fields we use are listed here.
44+
//
45+
// The fields here are taken from
46+
// https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-definition-template.html.
47+
// Their types correspond to those defined in the AWS SDK v2.
48+
type containerDefinition struct {
49+
Name *string `json:"name,omitempty"`
50+
Image *string `json:"image,omitempty"`
51+
Essential *bool `json:"essential,omitempty"`
52+
EntryPoint []string `json:"entryPoint,omitempty"`
53+
Environment []keyValuePair `json:"environment,omitempty"`
54+
}
55+
56+
func convertContainerDefinition(cd types.ContainerDefinition) containerDefinition {
57+
return containerDefinition{
58+
Name: cd.Name,
59+
Image: cd.Image,
60+
Essential: cd.Essential,
61+
EntryPoint: cd.EntryPoint,
62+
Environment: slices.Map(cd.Environment, convertKeyValuePair),
63+
}
64+
}
65+
66+
// The JSON representation of this type is suitable for use with the AWS
67+
// console.
68+
//
69+
// The fields here are taken from
70+
// https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-definition-template.html.
71+
// Their types correspond to those defined in the AWS SDK v2.
72+
type keyValuePair struct {
73+
Name *string `json:"name,omitempty"`
74+
Value *string `json:"value,omitempty"`
75+
}
76+
77+
func convertKeyValuePair(kv types.KeyValuePair) keyValuePair {
78+
return keyValuePair{
79+
Name: kv.Name,
80+
Value: kv.Value,
81+
}
82+
}
83+
84+
// The JSON representation of this type is suitable for use with the AWS
85+
// console.
86+
//
87+
// The fields here are taken from
88+
// https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-definition-template.html.
89+
// Their types correspond to those defined in the AWS SDK v2.
90+
type runtimePlatform struct {
91+
CpuArchitecture types.CPUArchitecture `json:"cpuArchitecture,omitempty"`
92+
OperatingSystemFamily types.OSFamily `json:"operatingSystemFamily,omitempty"`
93+
}
94+
95+
func convertRuntimePlatform(rp *types.RuntimePlatform) *runtimePlatform {
96+
return &runtimePlatform{
97+
CpuArchitecture: rp.CpuArchitecture,
98+
OperatingSystemFamily: rp.OperatingSystemFamily,
99+
}
100+
}
101+
102+
func TaskDefinitionToJSONForConsole(td types.TaskDefinition) ([]byte, error) {
103+
return json.MarshalIndent(convertTaskDefinition(td), "", " ")
104+
}

cmd/internal/ecs/add.go

Lines changed: 62 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -861,35 +861,13 @@ func modifyTaskState(wf *AddWorkflow) (nextState optionals.Optional[AddWorkflowS
861861
Value: aws.String(akitaCreationTagValue),
862862
})
863863

864-
pKey, pEnv := cfg.GetPostmanAPIKeyAndEnvironment()
865-
envs := []types.KeyValuePair{}
866-
if pEnv != "" {
867-
envs = append(envs, []types.KeyValuePair{
868-
{Name: aws.String("POSTMAN_ENV"), Value: &pEnv},
869-
}...)
870-
}
871-
872-
var entryPoint []string
873-
874-
if collectionId != "" {
875-
entryPoint = []string{"/postman-insights-agent", "apidump", "--collection", collectionId}
876-
} else {
877-
entryPoint = []string{"/postman-insights-agent", "apidump", "--project", projectId}
878-
}
879-
880-
agentContainer := types.ContainerDefinition{
881-
Name: aws.String("postman-insights-agent"),
882-
EntryPoint: entryPoint,
883-
Environment: append(envs, []types.KeyValuePair{
884-
{Name: aws.String("POSTMAN_API_KEY"), Value: &pKey},
885-
// Setting these environment variables will cause the traces to be tagged.
886-
{Name: aws.String("POSTMAN_AWS_REGION"), Value: &wf.awsRegion},
887-
{Name: aws.String("POSTMAN_ECS_SERVICE"), Value: &wf.ecsService},
888-
{Name: aws.String("POSTMAN_ECS_TASK"), Value: &wf.ecsTaskDefinitionFamily},
889-
}...),
890-
Essential: aws.Bool(false),
891-
Image: aws.String(postmanECRImage),
892-
}
864+
const isEssential = false
865+
agentContainer := makeAgentContainerDefinition(
866+
optionals.Some(wf.awsRegion),
867+
optionals.Some(wf.ecsService),
868+
optionals.Some(wf.ecsTaskDefinitionFamily),
869+
isEssential,
870+
)
893871

894872
// If running on EC2, a memory size is required if no task-level memory size is specified.
895873
// If running on Fargate, a task-level memory size is required, and the container-level
@@ -929,6 +907,61 @@ func modifyTaskState(wf *AddWorkflow) (nextState optionals.Optional[AddWorkflowS
929907
return awf_next(updateServiceState)
930908
}
931909

910+
func makeAgentContainerDefinition(
911+
awsRegion optionals.Optional[string],
912+
ecsService optionals.Optional[string],
913+
ecsTaskDefinitionFamily optionals.Optional[string],
914+
essential bool,
915+
) types.ContainerDefinition {
916+
pKey, pEnv := cfg.GetPostmanAPIKeyAndEnvironment()
917+
918+
envs := []types.KeyValuePair{}
919+
addToEnv := func(name string, value string) {
920+
envs = append(envs, types.KeyValuePair{
921+
Name: &name,
922+
Value: &value,
923+
})
924+
}
925+
926+
// This is a no-op if valueOpt is None.
927+
addOptToEnv := func(name string, valueOpt optionals.Optional[string]) {
928+
value, exists := valueOpt.Get()
929+
if exists {
930+
addToEnv(name, value)
931+
}
932+
}
933+
934+
if pEnv != "" {
935+
addToEnv("POSTMAN_ENV", pEnv)
936+
}
937+
938+
addToEnv("POSTMAN_API_KEY", pKey)
939+
940+
// Setting these optional environment variables will cause the traces to be
941+
// tagged.
942+
addOptToEnv("POSTMAN_AWS_REGION", awsRegion)
943+
addOptToEnv("POSTMAN_ECS_SERVICE", ecsService)
944+
addOptToEnv("POSTMAN_ECS_TASK", ecsTaskDefinitionFamily)
945+
946+
entryPoint := []string{
947+
"/postman-insights-agent",
948+
"apidump",
949+
"--project",
950+
projectId,
951+
}
952+
953+
// XXX If we instantiate any new fields in the container definition here, we
954+
// need to remember to update the code in the ecs_console_utils and the
955+
// ecs_cloudformation_utils packages.
956+
return types.ContainerDefinition{
957+
Name: aws.String("postman-insights-agent"),
958+
EntryPoint: entryPoint,
959+
Environment: envs,
960+
Essential: aws.Bool(essential),
961+
Image: aws.String(postmanECRImage),
962+
}
963+
}
964+
932965
// Update a service with the newly created task definition
933966
func updateServiceState(wf *AddWorkflow) (nextState optionals.Optional[AddWorkflowState], err error) {
934967
reportStep("Update ECS Service")

0 commit comments

Comments
 (0)