From c4f4b97c82ea841112408042a68b72e318867aad Mon Sep 17 00:00:00 2001 From: penghaoh Date: Tue, 21 Sep 2021 20:42:28 -0700 Subject: [PATCH] chore(manifest): upgrade UnmarshalYAML to v3 and add for Image --- .../cloudformation/stack/backend_svc.go | 6 +- .../deploy/cloudformation/stack/lb_web_svc.go | 6 +- .../deploy/cloudformation/stack/rd_web_svc.go | 2 +- .../cloudformation/stack/rd_web_svc_test.go | 6 +- .../cloudformation/stack/scheduled_job.go | 6 +- .../deploy/cloudformation/stack/worker_svc.go | 6 +- internal/pkg/initialize/workload_test.go | 10 +-- internal/pkg/manifest/applyenv_test.go | 16 ++--- internal/pkg/manifest/backend_svc.go | 4 +- internal/pkg/manifest/job.go | 6 +- internal/pkg/manifest/lb_web_svc.go | 11 +-- internal/pkg/manifest/rd_web_svc.go | 4 +- internal/pkg/manifest/storage.go | 8 +-- internal/pkg/manifest/svc.go | 24 +++---- internal/pkg/manifest/validate.go | 7 +- internal/pkg/manifest/validate_test.go | 30 ++++++++ internal/pkg/manifest/worker_svc.go | 4 +- internal/pkg/manifest/workload.go | 68 ++++++++++++------- internal/pkg/manifest/workload_test.go | 31 +++++++++ .../workloads/jobs/scheduled-job/manifest.yml | 8 +-- .../workloads/services/backend/manifest.yml | 4 +- .../workloads/services/lb-web/manifest.yml | 4 +- .../workloads/services/rd-web/manifest.yml | 8 +-- .../workloads/services/worker/manifest.yml | 4 +- 24 files changed, 184 insertions(+), 99 deletions(-) diff --git a/internal/pkg/deploy/cloudformation/stack/backend_svc.go b/internal/pkg/deploy/cloudformation/stack/backend_svc.go index 586b584baab..f3858171c91 100644 --- a/internal/pkg/deploy/cloudformation/stack/backend_svc.go +++ b/internal/pkg/deploy/cloudformation/stack/backend_svc.go @@ -52,7 +52,7 @@ func NewBackendService(mft *manifest.BackendService, env, app string, rc Runtime env: env, app: app, rc: rc, - image: mft.ImageConfig, + image: mft.ImageConfig.Image, parser: parser, addons: addons, }, @@ -136,7 +136,7 @@ func (s *BackendService) Template() (string, error) { WorkloadType: manifest.BackendServiceType, HealthCheck: convertContainerHealthCheck(s.manifest.BackendServiceConfig.ImageConfig.HealthCheck), LogConfig: convertLogging(s.manifest.Logging), - DockerLabels: s.manifest.ImageConfig.DockerLabels, + DockerLabels: s.manifest.ImageConfig.Image.DockerLabels, DesiredCountLambda: desiredCountLambda.String(), EnvControllerLambda: envControllerLambda.String(), Storage: storage, @@ -144,7 +144,7 @@ func (s *BackendService) Template() (string, error) { EntryPoint: entrypoint, Command: command, DependsOn: dependencies, - CredentialsParameter: aws.StringValue(s.manifest.ImageConfig.Credentials), + CredentialsParameter: aws.StringValue(s.manifest.ImageConfig.Image.Credentials), ServiceDiscoveryEndpoint: s.rc.ServiceDiscoveryEndpoint, Publish: publishers, }) diff --git a/internal/pkg/deploy/cloudformation/stack/lb_web_svc.go b/internal/pkg/deploy/cloudformation/stack/lb_web_svc.go index 605a35e1b35..e6f1cb7b956 100644 --- a/internal/pkg/deploy/cloudformation/stack/lb_web_svc.go +++ b/internal/pkg/deploy/cloudformation/stack/lb_web_svc.go @@ -60,7 +60,7 @@ func NewLoadBalancedWebService(mft *manifest.LoadBalancedWebService, env, app st env: env, app: app, rc: rc, - image: mft.ImageConfig, + image: mft.ImageConfig.Image, parser: parser, addons: addons, }, @@ -174,7 +174,7 @@ func (s *LoadBalancedWebService) Template() (string, error) { NestedStack: outputs, Sidecars: sidecars, LogConfig: convertLogging(s.manifest.Logging), - DockerLabels: s.manifest.ImageConfig.DockerLabels, + DockerLabels: s.manifest.ImageConfig.Image.DockerLabels, Autoscaling: autoscaling, CapacityProviders: capacityProviders, DesiredCountOnSpot: desiredCountOnSpot, @@ -192,7 +192,7 @@ func (s *LoadBalancedWebService) Template() (string, error) { EntryPoint: entrypoint, Command: command, DependsOn: dependencies, - CredentialsParameter: aws.StringValue(s.manifest.ImageConfig.Credentials), + CredentialsParameter: aws.StringValue(s.manifest.ImageConfig.Image.Credentials), ServiceDiscoveryEndpoint: s.rc.ServiceDiscoveryEndpoint, Publish: publishers, }) diff --git a/internal/pkg/deploy/cloudformation/stack/rd_web_svc.go b/internal/pkg/deploy/cloudformation/stack/rd_web_svc.go index b19ff4eae53..ea9e32f2948 100644 --- a/internal/pkg/deploy/cloudformation/stack/rd_web_svc.go +++ b/internal/pkg/deploy/cloudformation/stack/rd_web_svc.go @@ -78,7 +78,7 @@ func NewRequestDrivenWebService(mft *manifest.RequestDrivenWebService, env strin env: env, app: app.Name, rc: rc, - image: mft.ImageConfig, + image: mft.ImageConfig.Image, addons: addons, parser: parser, }, diff --git a/internal/pkg/deploy/cloudformation/stack/rd_web_svc_test.go b/internal/pkg/deploy/cloudformation/stack/rd_web_svc_test.go index 3bdaaf51a9b..9c61b902ebd 100644 --- a/internal/pkg/deploy/cloudformation/stack/rd_web_svc_test.go +++ b/internal/pkg/deploy/cloudformation/stack/rd_web_svc_test.go @@ -87,7 +87,7 @@ func TestRequestDrivenWebService_NewRequestDrivenWebService(t *testing.T) { env: testEnvName, app: testAppName, rc: RuntimeConfig{}, - image: testRDWebServiceManifest.ImageConfig, + image: testRDWebServiceManifest.ImageConfig.Image, }, instanceConfig: testRDWebServiceManifest.InstanceConfig, imageConfig: testRDWebServiceManifest.ImageConfig, @@ -165,7 +165,7 @@ func TestRequestDrivenWebService_NewRequestDrivenWebServiceWithAlias(t *testing. env: testEnvName, app: testAppName, rc: RuntimeConfig{}, - image: testRDWebServiceManifest.ImageConfig, + image: testRDWebServiceManifest.ImageConfig.Image, }, instanceConfig: testRDWebServiceManifest.InstanceConfig, imageConfig: testRDWebServiceManifest.ImageConfig, @@ -459,7 +459,7 @@ func TestRequestDrivenWebService_Parameters(t *testing.T) { name: aws.StringValue(testRDWebServiceManifest.Name), env: testEnvName, app: testAppName, - image: tc.imageConfig, + image: tc.imageConfig.Image, }, instanceConfig: tc.instanceConfig, imageConfig: tc.imageConfig, diff --git a/internal/pkg/deploy/cloudformation/stack/scheduled_job.go b/internal/pkg/deploy/cloudformation/stack/scheduled_job.go index abb08fb5835..26e3975e0f6 100644 --- a/internal/pkg/deploy/cloudformation/stack/scheduled_job.go +++ b/internal/pkg/deploy/cloudformation/stack/scheduled_job.go @@ -103,7 +103,7 @@ func NewScheduledJob(mft *manifest.ScheduledJob, env, app string, rc RuntimeConf env: env, app: app, rc: rc, - image: mft.ImageConfig, + image: mft.ImageConfig.Image, parser: parser, addons: addons, }, @@ -178,13 +178,13 @@ func (j *ScheduledJob) Template() (string, error) { StateMachine: stateMachine, HealthCheck: convertContainerHealthCheck(j.manifest.ImageConfig.HealthCheck), LogConfig: convertLogging(j.manifest.Logging), - DockerLabels: j.manifest.ImageConfig.DockerLabels, + DockerLabels: j.manifest.ImageConfig.Image.DockerLabels, Storage: storage, Network: convertNetworkConfig(j.manifest.Network), EntryPoint: entrypoint, Command: command, DependsOn: dependencies, - CredentialsParameter: aws.StringValue(j.manifest.ImageConfig.Credentials), + CredentialsParameter: aws.StringValue(j.manifest.ImageConfig.Image.Credentials), ServiceDiscoveryEndpoint: j.rc.ServiceDiscoveryEndpoint, Publish: publishers, diff --git a/internal/pkg/deploy/cloudformation/stack/worker_svc.go b/internal/pkg/deploy/cloudformation/stack/worker_svc.go index bed07638577..fb160807906 100644 --- a/internal/pkg/deploy/cloudformation/stack/worker_svc.go +++ b/internal/pkg/deploy/cloudformation/stack/worker_svc.go @@ -47,7 +47,7 @@ func NewWorkerService(mft *manifest.WorkerService, env, app string, rc RuntimeCo env: env, app: app, rc: rc, - image: mft.ImageConfig, + image: mft.ImageConfig.Image, parser: parser, addons: addons, }, @@ -135,7 +135,7 @@ func (s *WorkerService) Template() (string, error) { WorkloadType: manifest.WorkerServiceType, HealthCheck: convertContainerHealthCheck(s.manifest.WorkerServiceConfig.ImageConfig.HealthCheck), LogConfig: convertLogging(s.manifest.Logging), - DockerLabels: s.manifest.ImageConfig.DockerLabels, + DockerLabels: s.manifest.ImageConfig.Image.DockerLabels, DesiredCountLambda: desiredCountLambda.String(), EnvControllerLambda: envControllerLambda.String(), BacklogPerTaskCalculatorLambda: backlogPerTaskLambda.String(), @@ -144,7 +144,7 @@ func (s *WorkerService) Template() (string, error) { EntryPoint: entrypoint, Command: command, DependsOn: dependencies, - CredentialsParameter: aws.StringValue(s.manifest.ImageConfig.Credentials), + CredentialsParameter: aws.StringValue(s.manifest.ImageConfig.Image.Credentials), ServiceDiscoveryEndpoint: s.rc.ServiceDiscoveryEndpoint, Subscribe: subscribe, }) diff --git a/internal/pkg/initialize/workload_test.go b/internal/pkg/initialize/workload_test.go index 2a0f1618767..0aae2cb0cca 100644 --- a/internal/pkg/initialize/workload_test.go +++ b/internal/pkg/initialize/workload_test.go @@ -85,7 +85,7 @@ func TestWorkloadInitializer_Job(t *testing.T) { mockWriter: func(m *mocks.MockWorkspace) { m.EXPECT().WriteJobManifest(gomock.Any(), "resizer").Do(func(m *manifest.ScheduledJob, _ string) { require.Equal(t, *m.Workload.Type, manifest.ScheduledJobType) - require.Equal(t, *m.ImageConfig.Location, "mockImage") + require.Equal(t, *m.ImageConfig.Image.Location, "mockImage") }).Return("/resizer/manifest.yml", nil) }, mockstore: func(m *mocks.MockStore) { @@ -363,7 +363,7 @@ func TestAppInitOpts_createLoadBalancedAppManifest(t *testing.T) { require.NoError(t, err) require.Equal(t, tc.inSvcName, aws.StringValue(manifest.Workload.Name)) require.Equal(t, tc.inSvcPort, aws.Uint16Value(manifest.ImageConfig.Port)) - require.Contains(t, tc.inDockerfilePath, aws.StringValue(manifest.ImageConfig.Build.BuildArgs.Dockerfile)) + require.Contains(t, tc.inDockerfilePath, aws.StringValue(manifest.ImageConfig.Image.Build.BuildArgs.Dockerfile)) require.Equal(t, tc.wantedPath, aws.StringValue(manifest.Path)) } else { require.EqualError(t, err, tc.wantedErr.Error()) @@ -418,10 +418,10 @@ func TestAppInitOpts_createRequestDrivenWebServiceManifest(t *testing.T) { require.Equal(t, tc.inSvcName, *manifest.Name) require.Equal(t, tc.inSvcPort, *manifest.ImageConfig.Port) if tc.inImage != "" { - require.Equal(t, tc.inImage, *manifest.ImageConfig.Location) + require.Equal(t, tc.inImage, *manifest.ImageConfig.Image.Location) } if tc.inDockerfilePath != "" { - require.Equal(t, tc.inDockerfilePath, *manifest.ImageConfig.Build.BuildArgs.Dockerfile) + require.Equal(t, tc.inDockerfilePath, *manifest.ImageConfig.Image.Build.BuildArgs.Dockerfile) } }) } @@ -588,7 +588,7 @@ func TestWorkloadInitializer_Service(t *testing.T) { m.EXPECT().WriteServiceManifest(gomock.Any(), "backend"). Do(func(m *manifest.BackendService, _ string) { require.Equal(t, *m.Workload.Type, manifest.BackendServiceType) - require.Equal(t, *m.ImageConfig.Location, "mockImage") + require.Equal(t, *m.ImageConfig.Image.Location, "mockImage") require.Empty(t, m.ImageConfig.HealthCheck) }).Return("/backend/manifest.yml", nil) }, diff --git a/internal/pkg/manifest/applyenv_test.go b/internal/pkg/manifest/applyenv_test.go index 22730a81de5..cb1f64ff133 100644 --- a/internal/pkg/manifest/applyenv_test.go +++ b/internal/pkg/manifest/applyenv_test.go @@ -475,28 +475,28 @@ func TestApplyEnv_String(t *testing.T) { }{ "string overridden": { inSvc: func(svc *LoadBalancedWebService) { - svc.ImageConfig.Location = aws.String("cairo") - svc.Environments["test"].ImageConfig.Location = aws.String("nerac") + svc.ImageConfig.Image.Location = aws.String("cairo") + svc.Environments["test"].ImageConfig.Image.Location = aws.String("nerac") }, wanted: func(svc *LoadBalancedWebService) { - svc.ImageConfig.Location = aws.String("nerac") + svc.ImageConfig.Image.Location = aws.String("nerac") }, }, "string overridden by zero value": { inSvc: func(svc *LoadBalancedWebService) { - svc.ImageConfig.Location = aws.String("cairo") - svc.Environments["test"].ImageConfig.Location = aws.String("") + svc.ImageConfig.Image.Location = aws.String("cairo") + svc.Environments["test"].ImageConfig.Image.Location = aws.String("") }, wanted: func(svc *LoadBalancedWebService) { - svc.ImageConfig.Location = aws.String("") + svc.ImageConfig.Image.Location = aws.String("") }, }, "string not overridden": { inSvc: func(svc *LoadBalancedWebService) { - svc.ImageConfig.Location = aws.String("cairo") + svc.ImageConfig.Image.Location = aws.String("cairo") }, wanted: func(svc *LoadBalancedWebService) { - svc.ImageConfig.Location = aws.String("cairo") + svc.ImageConfig.Image.Location = aws.String("cairo") }, }, } diff --git a/internal/pkg/manifest/backend_svc.go b/internal/pkg/manifest/backend_svc.go index d54e97915b0..e67f5171c4a 100644 --- a/internal/pkg/manifest/backend_svc.go +++ b/internal/pkg/manifest/backend_svc.go @@ -50,7 +50,7 @@ func NewBackendService(props BackendServiceProps) *BackendService { // Apply overrides. svc.Name = stringP(props.Name) svc.BackendServiceConfig.ImageConfig.Image.Location = stringP(props.Image) - svc.BackendServiceConfig.ImageConfig.Build.BuildArgs.Dockerfile = stringP(props.Dockerfile) + svc.BackendServiceConfig.ImageConfig.Image.Build.BuildArgs.Dockerfile = stringP(props.Dockerfile) svc.BackendServiceConfig.ImageConfig.Port = uint16P(props.Port) svc.BackendServiceConfig.ImageConfig.HealthCheck = props.HealthCheck svc.BackendServiceConfig.Platform = props.Platform @@ -93,7 +93,7 @@ func (s *BackendService) BuildRequired() (bool, error) { // BuildArgs returns a docker.BuildArguments object for the service given a workspace root directory. func (s *BackendService) BuildArgs(wsRoot string) *DockerBuildArgs { - return s.ImageConfig.BuildConfig(wsRoot) + return s.ImageConfig.Image.BuildConfig(wsRoot) } // ApplyEnv returns the service manifest with environment overrides. diff --git a/internal/pkg/manifest/job.go b/internal/pkg/manifest/job.go index 6d9d090a224..b54f32a4f4d 100644 --- a/internal/pkg/manifest/job.go +++ b/internal/pkg/manifest/job.go @@ -74,8 +74,8 @@ func NewScheduledJob(props *ScheduledJobProps) *ScheduledJob { job := newDefaultScheduledJob() // Apply overrides. job.Name = stringP(props.Name) - job.ImageConfig.Build.BuildArgs.Dockerfile = stringP(props.Dockerfile) - job.ImageConfig.Location = stringP(props.Image) + job.ImageConfig.Image.Build.BuildArgs.Dockerfile = stringP(props.Dockerfile) + job.ImageConfig.Image.Location = stringP(props.Image) job.ImageConfig.HealthCheck = props.HealthCheck job.On.Schedule = stringP(props.Schedule) if props.Retries != 0 { @@ -124,7 +124,7 @@ func (j *ScheduledJob) Publish() []Topic { // BuildArgs returns a docker.BuildArguments object for the job given a workspace root. func (j *ScheduledJob) BuildArgs(wsRoot string) *DockerBuildArgs { - return j.ImageConfig.BuildConfig(wsRoot) + return j.ImageConfig.Image.BuildConfig(wsRoot) } // BuildRequired returns if the service requires building from the local Dockerfile. diff --git a/internal/pkg/manifest/lb_web_svc.go b/internal/pkg/manifest/lb_web_svc.go index 47c21604f4e..503b8cdbb70 100644 --- a/internal/pkg/manifest/lb_web_svc.go +++ b/internal/pkg/manifest/lb_web_svc.go @@ -10,6 +10,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/copilot-cli/internal/pkg/template" "github.com/imdario/mergo" + "gopkg.in/yaml.v3" ) const ( @@ -72,7 +73,7 @@ func NewLoadBalancedWebService(props *LoadBalancedWebServiceProps) *LoadBalanced // Apply overrides. svc.Name = stringP(props.Name) svc.LoadBalancedWebServiceConfig.ImageConfig.Image.Location = stringP(props.Image) - svc.LoadBalancedWebServiceConfig.ImageConfig.Build.BuildArgs.Dockerfile = stringP(props.Dockerfile) + svc.LoadBalancedWebServiceConfig.ImageConfig.Image.Build.BuildArgs.Dockerfile = stringP(props.Dockerfile) svc.LoadBalancedWebServiceConfig.ImageConfig.Port = aws.Uint16(props.Port) svc.LoadBalancedWebServiceConfig.ImageConfig.HealthCheck = props.HealthCheck svc.LoadBalancedWebServiceConfig.Platform = props.Platform @@ -144,7 +145,7 @@ func (s *LoadBalancedWebService) BuildRequired() (bool, error) { // BuildArgs returns a docker.BuildArguments object given a ws root directory. func (s *LoadBalancedWebService) BuildArgs(wsRoot string) *DockerBuildArgs { - return s.ImageConfig.BuildConfig(wsRoot) + return s.ImageConfig.Image.BuildConfig(wsRoot) } // ApplyEnv returns the service manifest with environment overrides. @@ -201,9 +202,9 @@ func (e *Alias) IsEmpty() bool { // UnmarshalYAML overrides the default YAML unmarshaling logic for the Alias // struct, allowing it to perform more complex unmarshaling behavior. -// This method implements the yaml.Unmarshaler (v2) interface. -func (e *Alias) UnmarshalYAML(unmarshal func(interface{}) error) error { - if err := unmarshalYAMLToStringSliceOrString((*stringSliceOrString)(e), unmarshal); err != nil { +// This method implements the yaml.Unmarshaler (v3) interface. +func (e *Alias) UnmarshalYAML(value *yaml.Node) error { + if err := unmarshalYAMLToStringSliceOrString((*stringSliceOrString)(e), value); err != nil { return errUnmarshalEntryPoint } return nil diff --git a/internal/pkg/manifest/rd_web_svc.go b/internal/pkg/manifest/rd_web_svc.go index 78aa3f5743c..2d88bdd9604 100644 --- a/internal/pkg/manifest/rd_web_svc.go +++ b/internal/pkg/manifest/rd_web_svc.go @@ -57,7 +57,7 @@ func NewRequestDrivenWebService(props *RequestDrivenWebServiceProps) *RequestDri svc := newDefaultRequestDrivenWebService() svc.Name = aws.String(props.Name) svc.RequestDrivenWebServiceConfig.ImageConfig.Image.Location = stringP(props.Image) - svc.RequestDrivenWebServiceConfig.ImageConfig.Build.BuildArgs.Dockerfile = stringP(props.Dockerfile) + svc.RequestDrivenWebServiceConfig.ImageConfig.Image.Build.BuildArgs.Dockerfile = stringP(props.Dockerfile) svc.RequestDrivenWebServiceConfig.ImageConfig.Port = aws.Uint16(props.Port) svc.RequestDrivenWebServiceConfig.InstanceConfig.Platform = props.Platform svc.parser = template.New() @@ -101,7 +101,7 @@ func (s *RequestDrivenWebService) TaskPlatform() (*string, error) { // BuildArgs returns a docker.BuildArguments object given a ws root directory. func (s *RequestDrivenWebService) BuildArgs(wsRoot string) *DockerBuildArgs { - return s.ImageConfig.BuildConfig(wsRoot) + return s.ImageConfig.Image.BuildConfig(wsRoot) } // ApplyEnv returns the service manifest with environment overrides. diff --git a/internal/pkg/manifest/storage.go b/internal/pkg/manifest/storage.go index 782df8f1b20..36760eaa79c 100644 --- a/internal/pkg/manifest/storage.go +++ b/internal/pkg/manifest/storage.go @@ -81,10 +81,10 @@ func (e *EFSConfigOrBool) IsEmpty() bool { return e.Advanced.IsEmpty() && e.Enabled == nil } -// UnmarshalYAML implements the yaml(v2) interface. It allows EFS to be specified as a +// UnmarshalYAML implements the yaml(v3) interface. It allows EFS to be specified as a // string or a struct alternately. -func (e *EFSConfigOrBool) UnmarshalYAML(unmarshal func(interface{}) error) error { - if err := unmarshal(&e.Advanced); err != nil { +func (e *EFSConfigOrBool) UnmarshalYAML(value *yaml.Node) error { + if err := value.Decode(&e.Advanced); err != nil { switch err.(type) { case *yaml.TypeError: break @@ -102,7 +102,7 @@ func (e *EFSConfigOrBool) UnmarshalYAML(unmarshal func(interface{}) error) error return nil } - if err := unmarshal(&e.Enabled); err != nil { + if err := value.Decode(&e.Enabled); err != nil { return errUnmarshalEFSOpts } return nil diff --git a/internal/pkg/manifest/svc.go b/internal/pkg/manifest/svc.go index 66000f6a46b..b32c5f925a9 100644 --- a/internal/pkg/manifest/svc.go +++ b/internal/pkg/manifest/svc.go @@ -56,9 +56,9 @@ func (r *Range) Parse() (min int, max int, err error) { // UnmarshalYAML overrides the default YAML unmarshaling logic for the RangeOpts // struct, allowing it to perform more complex unmarshaling behavior. -// This method implements the yaml.Unmarshaler (v2) interface. -func (r *Range) UnmarshalYAML(unmarshal func(interface{}) error) error { - if err := unmarshal(&r.RangeConfig); err != nil { +// This method implements the yaml.Unmarshaler (v3) interface. +func (r *Range) UnmarshalYAML(value *yaml.Node) error { + if err := value.Decode(&r.RangeConfig); err != nil { switch err.(type) { case *yaml.TypeError: break @@ -73,7 +73,7 @@ func (r *Range) UnmarshalYAML(unmarshal func(interface{}) error) error { return nil } - if err := unmarshal(&r.Value); err != nil { + if err := value.Decode(&r.Value); err != nil { return errUnmarshalRangeOpts } return nil @@ -125,9 +125,9 @@ type Count struct { // UnmarshalYAML overrides the default YAML unmarshaling logic for the Count // struct, allowing it to perform more complex unmarshaling behavior. -// This method implements the yaml.Unmarshaler (v2) interface. -func (c *Count) UnmarshalYAML(unmarshal func(interface{}) error) error { - if err := unmarshal(&c.AdvancedCount); err != nil { +// This method implements the yaml.Unmarshaler (v3) interface. +func (c *Count) UnmarshalYAML(value *yaml.Node) error { + if err := value.Decode(&c.AdvancedCount); err != nil { switch err.(type) { case *yaml.TypeError: break @@ -148,7 +148,7 @@ func (c *Count) UnmarshalYAML(unmarshal func(interface{}) error) error { return nil } - if err := unmarshal(&c.Value); err != nil { + if err := value.Decode(&c.Value); err != nil { return errUnmarshalCountOpts } return nil @@ -308,9 +308,9 @@ type HealthCheckArgsOrString struct { // UnmarshalYAML overrides the default YAML unmarshaling logic for the HealthCheckArgsOrString // struct, allowing it to perform more complex unmarshaling behavior. -// This method implements the yaml.Unmarshaler (v2) interface. -func (hc *HealthCheckArgsOrString) UnmarshalYAML(unmarshal func(interface{}) error) error { - if err := unmarshal(&hc.HealthCheckArgs); err != nil { +// This method implements the yaml.Unmarshaler (v3) interface. +func (hc *HealthCheckArgsOrString) UnmarshalYAML(value *yaml.Node) error { + if err := value.Decode(&hc.HealthCheckArgs); err != nil { switch err.(type) { case *yaml.TypeError: break @@ -325,7 +325,7 @@ func (hc *HealthCheckArgsOrString) UnmarshalYAML(unmarshal func(interface{}) err return nil } - if err := unmarshal(&hc.HealthCheckPath); err != nil { + if err := value.Decode(&hc.HealthCheckPath); err != nil { return errUnmarshalHealthCheckArgs } return nil diff --git a/internal/pkg/manifest/validate.go b/internal/pkg/manifest/validate.go index 80b7659e931..d621e0658e4 100644 --- a/internal/pkg/manifest/validate.go +++ b/internal/pkg/manifest/validate.go @@ -667,7 +667,12 @@ func (r *RequestDrivenWebServiceHttpConfig) Validate() error { } // Validate returns nil if JobTriggerConfig is configured correctly. -func (*JobTriggerConfig) Validate() error { +func (c *JobTriggerConfig) Validate() error { + if c.Schedule == nil { + return &errFieldMustBeSpecified{ + missingField: "schedule", + } + } return nil } diff --git a/internal/pkg/manifest/validate_test.go b/internal/pkg/manifest/validate_test.go index 433da22297a..05c614e2708 100644 --- a/internal/pkg/manifest/validate_test.go +++ b/internal/pkg/manifest/validate_test.go @@ -241,6 +241,13 @@ func TestScheduledJobConfig_Validate(t *testing.T) { }, wantedErrorPrefix: `validate "network": `, }, + "error if fail to validate on": { + config: ScheduledJobConfig{ + ImageConfig: testImageConfig, + On: JobTriggerConfig{}, + }, + wantedErrorPrefix: `validate "on": `, + }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { @@ -935,3 +942,26 @@ func TestPlacement_Validate(t *testing.T) { }) } } + +func TestJobTriggerConfig_Validate(t *testing.T) { + testCases := map[string]struct { + in *JobTriggerConfig + wanted error + }{ + "should return an error if schedule is empty": { + in: &JobTriggerConfig{}, + wanted: errors.New(`"schedule" must be specified`), + }, + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + err := tc.in.Validate() + + if tc.wanted != nil { + require.EqualError(t, err, tc.wanted.Error()) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/internal/pkg/manifest/worker_svc.go b/internal/pkg/manifest/worker_svc.go index 0efdb8c4c59..c2020356189 100644 --- a/internal/pkg/manifest/worker_svc.go +++ b/internal/pkg/manifest/worker_svc.go @@ -84,7 +84,7 @@ func NewWorkerService(props WorkerServiceProps) *WorkerService { // Apply overrides. svc.Name = stringP(props.Name) svc.WorkerServiceConfig.ImageConfig.Image.Location = stringP(props.Image) - svc.WorkerServiceConfig.ImageConfig.Build.BuildArgs.Dockerfile = stringP(props.Dockerfile) + svc.WorkerServiceConfig.ImageConfig.Image.Build.BuildArgs.Dockerfile = stringP(props.Dockerfile) svc.WorkerServiceConfig.ImageConfig.HealthCheck = props.HealthCheck svc.WorkerServiceConfig.Subscribe.Topics = props.Topics svc.WorkerServiceConfig.Platform = props.Platform @@ -112,7 +112,7 @@ func (s *WorkerService) BuildRequired() (bool, error) { // BuildArgs returns a docker.BuildArguments object for the service given a workspace root directory func (s *WorkerService) BuildArgs(wsRoot string) *DockerBuildArgs { - return s.ImageConfig.BuildConfig(wsRoot) + return s.ImageConfig.Image.BuildConfig(wsRoot) } // Subscriptions returns a list of TopicSubscriotion objects which represent the SNS topics the service diff --git a/internal/pkg/manifest/workload.go b/internal/pkg/manifest/workload.go index 220cd775672..269416a0f93 100644 --- a/internal/pkg/manifest/workload.go +++ b/internal/pkg/manifest/workload.go @@ -84,9 +84,27 @@ type Image struct { DependsOn map[string]string `yaml:"depends_on,flow"` // Add any sidecar dependencies. } +// UnmarshalYAML overrides the default YAML unmarshaling logic for the Image +// struct, allowing it to perform more complex unmarshaling behavior. +// This method implements the yaml.Unmarshaler (v3) interface. +func (i *Image) UnmarshalYAML(node *yaml.Node) error { + type image Image + if err := node.Decode((*image)(i)); err != nil { + return err + } + if !i.Build.isEmpty() && i.Location != nil { + return &errFieldMutualExclusive{ + firstField: "build", + secondField: "location", + mustExist: true, + } + } + return nil +} + // ImageWithHealthcheck represents a container image with health check. type ImageWithHealthcheck struct { - Image `yaml:",inline"` + Image Image `yaml:",inline"` HealthCheck ContainerHealthCheck `yaml:"healthcheck"` } @@ -98,7 +116,7 @@ type ImageWithPortAndHealthcheck struct { // ImageWithPort represents a container image with an exposed port. type ImageWithPort struct { - Image `yaml:",inline"` + Image Image `yaml:",inline"` Port *uint16 `yaml:"port"` } @@ -195,9 +213,9 @@ type CommandOverride stringSliceOrString // UnmarshalYAML overrides the default YAML unmarshalling logic for the EntryPointOverride // struct, allowing it to perform more complex unmarshalling behavior. -// This method implements the yaml.Unmarshaler (v2) interface. -func (e *EntryPointOverride) UnmarshalYAML(unmarshal func(interface{}) error) error { - if err := unmarshalYAMLToStringSliceOrString((*stringSliceOrString)(e), unmarshal); err != nil { +// This method implements the yaml.Unmarshaler (v3) interface. +func (e *EntryPointOverride) UnmarshalYAML(value *yaml.Node) error { + if err := unmarshalYAMLToStringSliceOrString((*stringSliceOrString)(e), value); err != nil { return errUnmarshalEntryPoint } return nil @@ -214,9 +232,9 @@ func (e *EntryPointOverride) ToStringSlice() ([]string, error) { // UnmarshalYAML overrides the default YAML unmarshaling logic for the CommandOverride // struct, allowing it to perform more complex unmarshaling behavior. -// This method implements the yaml.Unmarshaler (v2) interface. -func (c *CommandOverride) UnmarshalYAML(unmarshal func(interface{}) error) error { - if err := unmarshalYAMLToStringSliceOrString((*stringSliceOrString)(c), unmarshal); err != nil { +// This method implements the yaml.Unmarshaler (v3) interface. +func (c *CommandOverride) UnmarshalYAML(value *yaml.Node) error { + if err := unmarshalYAMLToStringSliceOrString((*stringSliceOrString)(c), value); err != nil { return errUnmarshalCommand } return nil @@ -236,8 +254,8 @@ type stringSliceOrString struct { StringSlice []string } -func unmarshalYAMLToStringSliceOrString(s *stringSliceOrString, unmarshal func(interface{}) error) error { - if err := unmarshal(&s.StringSlice); err != nil { +func unmarshalYAMLToStringSliceOrString(s *stringSliceOrString, value *yaml.Node) error { + if err := value.Decode(&s.StringSlice); err != nil { switch err.(type) { case *yaml.TypeError: break @@ -252,7 +270,7 @@ func unmarshalYAMLToStringSliceOrString(s *stringSliceOrString, unmarshal func(i return nil } - return unmarshal(&s.String) + return value.Decode(&s.String) } func toStringSlice(s *stringSliceOrString) ([]string, error) { @@ -288,9 +306,9 @@ func (b *BuildArgsOrString) isEmpty() bool { // UnmarshalYAML overrides the default YAML unmarshaling logic for the BuildArgsOrString // struct, allowing it to perform more complex unmarshaling behavior. -// This method implements the yaml.Unmarshaler (v2) interface. -func (b *BuildArgsOrString) UnmarshalYAML(unmarshal func(interface{}) error) error { - if err := unmarshal(&b.BuildArgs); err != nil { +// This method implements the yaml.Unmarshaler (v3) interface. +func (b *BuildArgsOrString) UnmarshalYAML(value *yaml.Node) error { + if err := value.Decode(&b.BuildArgs); err != nil { switch err.(type) { case *yaml.TypeError: break @@ -305,7 +323,7 @@ func (b *BuildArgsOrString) UnmarshalYAML(unmarshal func(interface{}) error) err return nil } - if err := unmarshal(&b.BuildString); err != nil { + if err := value.Decode(&b.BuildString); err != nil { return errUnmarshalBuildOpts } return nil @@ -338,9 +356,9 @@ type ExecuteCommand struct { // UnmarshalYAML overrides the default YAML unmarshaling logic for the ExecuteCommand // struct, allowing it to perform more complex unmarshaling behavior. -// This method implements the yaml.Unmarshaler (v2) interface. -func (e *ExecuteCommand) UnmarshalYAML(unmarshal func(interface{}) error) error { - if err := unmarshal(&e.Config); err != nil { +// This method implements the yaml.Unmarshaler (v3) interface. +func (e *ExecuteCommand) UnmarshalYAML(value *yaml.Node) error { + if err := value.Decode(&e.Config); err != nil { switch err.(type) { case *yaml.TypeError: break @@ -353,7 +371,7 @@ func (e *ExecuteCommand) UnmarshalYAML(unmarshal func(interface{}) error) error return nil } - if err := unmarshal(&e.Enable); err != nil { + if err := value.Decode(&e.Enable); err != nil { return errUnmarshalExec } return nil @@ -460,7 +478,7 @@ func (c *NetworkConfig) IsEmpty() bool { // UnmarshalYAML ensures that a NetworkConfig always defaults to public subnets. // If the user specified a placement that's not valid then throw an error. -func (c *NetworkConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { +func (c *NetworkConfig) UnmarshalYAML(value *yaml.Node) error { type networkWithDefaults NetworkConfig publicPlacement := Placement(PublicSubnetPlacement) defaultVPCConf := vpcConfig{ @@ -469,7 +487,7 @@ func (c *NetworkConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { conf := networkWithDefaults{ VPC: defaultVPCConf, } - if err := unmarshal(&conf); err != nil { + if err := value.Decode(&conf); err != nil { return err } if conf.VPC.isEmpty() { // If after unmarshaling the user did not specify VPC configuration then reset it to public. @@ -612,9 +630,9 @@ type PlatformArgsOrString struct { // UnmarshalYAML overrides the default YAML unmarshaling logic for the PlatformArgsOrString // struct, allowing it to perform more complex unmarshaling behavior. -// This method implements the yaml.Unmarshaler (v2) interface. -func (p *PlatformArgsOrString) UnmarshalYAML(unmarshal func(interface{}) error) error { - if err := unmarshal(&p.PlatformArgs); err != nil { +// This method implements the yaml.Unmarshaler (v3) interface. +func (p *PlatformArgsOrString) UnmarshalYAML(value *yaml.Node) error { + if err := value.Decode(&p.PlatformArgs); err != nil { switch err.(type) { case *yaml.TypeError: break @@ -637,7 +655,7 @@ func (p *PlatformArgsOrString) UnmarshalYAML(unmarshal func(interface{}) error) p.PlatformString = nil return nil } - if err := unmarshal(&p.PlatformString); err != nil { + if err := value.Decode(&p.PlatformString); err != nil { return errUnmarshalPlatformOpts } if err := validatePlatform(p.PlatformString); err != nil { diff --git a/internal/pkg/manifest/workload_test.go b/internal/pkg/manifest/workload_test.go index 5947955b24f..aa145ade254 100644 --- a/internal/pkg/manifest/workload_test.go +++ b/internal/pkg/manifest/workload_test.go @@ -5,6 +5,7 @@ package manifest import ( "errors" + "fmt" "path/filepath" "testing" @@ -13,6 +14,36 @@ import ( "gopkg.in/yaml.v3" ) +func TestImage_UnmarshalYAML(t *testing.T) { + testCases := map[string]struct { + inContent []byte + + wantedError error + }{ + "error if both build and location are set": { + inContent: []byte(`build: mockBuild +location: mockLocation`), + wantedError: fmt.Errorf(`must specify one of "build" and "location"`), + }, + "success": { + inContent: []byte(`location: mockLocation`), + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + i := Image{} + err := yaml.Unmarshal(tc.inContent, &i) + if tc.wantedError != nil { + require.EqualError(t, err, tc.wantedError.Error()) + } else { + require.NoError(t, err) + require.Equal(t, "mockLocation", aws.StringValue(i.Location)) + } + }) + } +} + func TestEntryPointOverride_UnmarshalYAML(t *testing.T) { testCases := map[string]struct { inContent []byte diff --git a/internal/pkg/template/templates/workloads/jobs/scheduled-job/manifest.yml b/internal/pkg/template/templates/workloads/jobs/scheduled-job/manifest.yml index bc588d8cf0b..646b2612e4c 100644 --- a/internal/pkg/template/templates/workloads/jobs/scheduled-job/manifest.yml +++ b/internal/pkg/template/templates/workloads/jobs/scheduled-job/manifest.yml @@ -24,12 +24,12 @@ timeout: {{.Timeout}} # Optional. The timeout after which to stop the job if # Configuration for your container and task. image: -{{- if .ImageConfig.Build.BuildArgs.Dockerfile}} +{{- if .ImageConfig.Image.Build.BuildArgs.Dockerfile}} # Docker build arguments. For additional overrides: https://aws.github.io/copilot-cli/docs/manifest/scheduled-job/#image-build - build: {{.ImageConfig.Build.BuildArgs.Dockerfile}} + build: {{.ImageConfig.Image.Build.BuildArgs.Dockerfile}} {{- end}} -{{- if .ImageConfig.Location}} - location: {{.ImageConfig.Location}} +{{- if .ImageConfig.Image.Location}} + location: {{.ImageConfig.Image.Location}} {{- end}} cpu: {{.CPU}} # Number of CPU units for the task. diff --git a/internal/pkg/template/templates/workloads/services/backend/manifest.yml b/internal/pkg/template/templates/workloads/services/backend/manifest.yml index 0e566cc63ba..66f25033034 100644 --- a/internal/pkg/template/templates/workloads/services/backend/manifest.yml +++ b/internal/pkg/template/templates/workloads/services/backend/manifest.yml @@ -13,9 +13,9 @@ type: {{.Type}} # Configuration for your containers and service. image: -{{- if .ImageConfig.Build.BuildArgs.Dockerfile}} +{{- if .ImageConfig.Image.Build.BuildArgs.Dockerfile}} # Docker build arguments. For additional overrides: https://aws.github.io/copilot-cli/docs/manifest/backend-service/#image-build - build: {{.ImageConfig.Build.BuildArgs.Dockerfile}} + build: {{.ImageConfig.Image.Build.BuildArgs.Dockerfile}} {{- end}} {{- if .ImageConfig.Image.Location}} location: {{.ImageConfig.Image.Location}} diff --git a/internal/pkg/template/templates/workloads/services/lb-web/manifest.yml b/internal/pkg/template/templates/workloads/services/lb-web/manifest.yml index 1ec56751edc..92c82863aa0 100644 --- a/internal/pkg/template/templates/workloads/services/lb-web/manifest.yml +++ b/internal/pkg/template/templates/workloads/services/lb-web/manifest.yml @@ -16,9 +16,9 @@ http: # Configuration for your containers and service. image: -{{- if .ImageConfig.Build.BuildArgs.Dockerfile}} +{{- if .ImageConfig.Image.Build.BuildArgs.Dockerfile}} # Docker build arguments. For additional overrides: https://aws.github.io/copilot-cli/docs/manifest/lb-web-service/#image-build - build: {{.ImageConfig.Build.BuildArgs.Dockerfile}} + build: {{.ImageConfig.Image.Build.BuildArgs.Dockerfile}} {{- end}} {{- if .ImageConfig.Image.Location}} location: {{.ImageConfig.Image.Location}} diff --git a/internal/pkg/template/templates/workloads/services/rd-web/manifest.yml b/internal/pkg/template/templates/workloads/services/rd-web/manifest.yml index 30313036c5a..510b267c673 100644 --- a/internal/pkg/template/templates/workloads/services/rd-web/manifest.yml +++ b/internal/pkg/template/templates/workloads/services/rd-web/manifest.yml @@ -8,14 +8,14 @@ name: {{.Name}} type: {{.Type}} image: -{{- if .ImageConfig.Build.BuildArgs.Dockerfile}} +{{- if .ImageConfig.Image.Build.BuildArgs.Dockerfile}} # Docker build arguments. # For additional overrides: https://aws.github.io/copilot-cli/docs/manifest/rd-web-service/#image-build - build: {{.ImageConfig.Build.BuildArgs.Dockerfile}} + build: {{.ImageConfig.Image.Build.BuildArgs.Dockerfile}} {{- end}} -{{- if .ImageConfig.Location}} +{{- if .ImageConfig.Image.Location}} # The name of the Docker image. - location: {{.ImageConfig.Location}} + location: {{.ImageConfig.Image.Location}} {{- end}} # Port exposed through your container to route traffic to it. port: {{.ImageConfig.Port}} diff --git a/internal/pkg/template/templates/workloads/services/worker/manifest.yml b/internal/pkg/template/templates/workloads/services/worker/manifest.yml index f3ba60b0703..dd0066a21eb 100644 --- a/internal/pkg/template/templates/workloads/services/worker/manifest.yml +++ b/internal/pkg/template/templates/workloads/services/worker/manifest.yml @@ -8,9 +8,9 @@ type: {{.Type}} # Configuration for your containers and service. image: -{{- if .ImageConfig.Build.BuildArgs.Dockerfile}} +{{- if .ImageConfig.Image.Build.BuildArgs.Dockerfile}} # Docker build arguments. - build: {{.ImageConfig.Build.BuildArgs.Dockerfile}} + build: {{.ImageConfig.Image.Build.BuildArgs.Dockerfile}} {{- end}} {{- if .ImageConfig.Image.Location}} location: {{.ImageConfig.Image.Location}}