diff --git a/internal/pkg/cli/deploy.go b/internal/pkg/cli/deploy.go index e381cad7d6a..5a9e0cda11a 100644 --- a/internal/pkg/cli/deploy.go +++ b/internal/pkg/cli/deploy.go @@ -71,27 +71,29 @@ func newDeployOpts(vars deployWkldVars) (*deployOpts, error) { o.deployWkld = &deployJobOpts{ deployWkldVars: o.deployWkldVars, - store: o.store, - ws: o.ws, - unmarshal: manifest.UnmarshalWorkload, - spinner: termprogress.NewSpinner(log.DiagnosticWriter), - sel: selector.NewWorkspaceSelect(o.prompt, o.store, o.ws), - prompt: o.prompt, - cmd: exec.NewCmd(), - sessProvider: sessions.NewProvider(), + store: o.store, + ws: o.ws, + newInterpolator: newManifestInterpolator, + unmarshal: manifest.UnmarshalWorkload, + spinner: termprogress.NewSpinner(log.DiagnosticWriter), + sel: selector.NewWorkspaceSelect(o.prompt, o.store, o.ws), + prompt: o.prompt, + cmd: exec.NewCmd(), + sessProvider: sessions.NewProvider(), } case contains(workloadType, manifest.ServiceTypes): opts := &deploySvcOpts{ deployWkldVars: o.deployWkldVars, - store: o.store, - ws: o.ws, - unmarshal: manifest.UnmarshalWorkload, - spinner: termprogress.NewSpinner(log.DiagnosticWriter), - sel: selector.NewWorkspaceSelect(o.prompt, o.store, o.ws), - prompt: o.prompt, - cmd: exec.NewCmd(), - sessProvider: sessions.NewProvider(), + store: o.store, + ws: o.ws, + newInterpolator: newManifestInterpolator, + unmarshal: manifest.UnmarshalWorkload, + spinner: termprogress.NewSpinner(log.DiagnosticWriter), + sel: selector.NewWorkspaceSelect(o.prompt, o.store, o.ws), + prompt: o.prompt, + cmd: exec.NewCmd(), + sessProvider: sessions.NewProvider(), newAppVersionGetter: func(appName string) (versionGetter, error) { return describe.NewAppDescriber(appName) }, diff --git a/internal/pkg/cli/init.go b/internal/pkg/cli/init.go index 7ce184d2bb6..fba36cbfd97 100644 --- a/internal/pkg/cli/init.go +++ b/internal/pkg/cli/init.go @@ -162,15 +162,16 @@ func newInitOpts(vars initVars) (*initOpts, error) { appName: vars.appName, }, - store: ssm, - prompt: prompt, - ws: ws, - unmarshal: manifest.UnmarshalWorkload, - sel: sel, - spinner: spin, - cmd: exec.NewCmd(), - sessProvider: sessProvider, - snsTopicGetter: deployStore, + store: ssm, + prompt: prompt, + ws: ws, + newInterpolator: newManifestInterpolator, + unmarshal: manifest.UnmarshalWorkload, + sel: sel, + spinner: spin, + cmd: exec.NewCmd(), + sessProvider: sessProvider, + snsTopicGetter: deployStore, newAppVersionGetter: func(appName string) (versionGetter, error) { return describe.NewAppDescriber(appName) @@ -182,14 +183,15 @@ func newInitOpts(vars initVars) (*initOpts, error) { imageTag: vars.imageTag, appName: vars.appName, }, - store: ssm, - prompt: prompt, - ws: ws, - unmarshal: manifest.UnmarshalWorkload, - sel: sel, - spinner: spin, - cmd: exec.NewCmd(), - sessProvider: sessProvider, + store: ssm, + prompt: prompt, + ws: ws, + newInterpolator: newManifestInterpolator, + unmarshal: manifest.UnmarshalWorkload, + sel: sel, + spinner: spin, + cmd: exec.NewCmd(), + sessProvider: sessProvider, } fs := &afero.Afero{Fs: afero.NewOsFs()} cmd := exec.NewCmd() diff --git a/internal/pkg/cli/interfaces.go b/internal/pkg/cli/interfaces.go index 71d8e862db5..095e1203c35 100644 --- a/internal/pkg/cli/interfaces.go +++ b/internal/pkg/cli/interfaces.go @@ -623,3 +623,7 @@ type timeoutError interface { error Timeout() bool } + +type interpolator interface { + Interpolate(s string) (string, error) +} diff --git a/internal/pkg/cli/job_deploy.go b/internal/pkg/cli/job_deploy.go index eb05cdac0f8..d8a262ac6f1 100644 --- a/internal/pkg/cli/job_deploy.go +++ b/internal/pkg/cli/job_deploy.go @@ -42,6 +42,7 @@ type deployJobOpts struct { store store ws wsJobDirReader unmarshal func(in []byte) (manifest.WorkloadManifest, error) + newInterpolator func(app, env string) interpolator cmd runner addons templater appCFN appResourcesGetter @@ -80,14 +81,15 @@ func newJobDeployOpts(vars deployWkldVars) (*deployJobOpts, error) { return &deployJobOpts{ deployWkldVars: vars, - store: store, - ws: ws, - unmarshal: manifest.UnmarshalWorkload, - spinner: termprogress.NewSpinner(log.DiagnosticWriter), - sel: selector.NewWorkspaceSelect(prompter, store, ws), - prompt: prompter, - cmd: exec.NewCmd(), - sessProvider: sessions.NewProvider(), + store: store, + ws: ws, + unmarshal: manifest.UnmarshalWorkload, + spinner: termprogress.NewSpinner(log.DiagnosticWriter), + sel: selector.NewWorkspaceSelect(prompter, store, ws), + prompt: prompter, + cmd: exec.NewCmd(), + sessProvider: sessions.NewProvider(), + newInterpolator: newManifestInterpolator, }, nil } @@ -356,7 +358,11 @@ func (o *deployJobOpts) manifest() (interface{}, error) { if err != nil { return nil, fmt.Errorf("read job %s manifest: %w", o.name, err) } - mft, err := o.unmarshal(raw) + interpolated, err := o.newInterpolator(o.appName, o.envName).Interpolate(string(raw)) + if err != nil { + return nil, fmt.Errorf("interpolate environment variables for %s manifest: %w", o.name, err) + } + mft, err := o.unmarshal([]byte(interpolated)) if err != nil { return nil, fmt.Errorf("unmarshal job %s manifest: %w", o.name, err) } diff --git a/internal/pkg/cli/job_deploy_test.go b/internal/pkg/cli/job_deploy_test.go index 6d5328bdab0..c4ae555ee2f 100644 --- a/internal/pkg/cli/job_deploy_test.go +++ b/internal/pkg/cli/job_deploy_test.go @@ -21,6 +21,7 @@ import ( type deployJobMocks struct { mockWs *mocks.MockwsJobDirReader mockimageBuilderPusher *mocks.MockimageBuilderPusher + mockInterpolator *mocks.Mockinterpolator } func TestJobDeployOpts_Validate(t *testing.T) { @@ -240,11 +241,22 @@ on: }, wantErr: fmt.Errorf("read job %s manifest: %w", "mailer", mockError), }, + "should return error if interpolation fail": { + inputSvc: "mailer", + setupMocks: func(m deployJobMocks) { + gomock.InOrder( + m.mockWs.EXPECT().ReadJobManifest(gomock.Any()).Return(mockManifest, nil), + m.mockInterpolator.EXPECT().Interpolate(string(mockManifest)).Return("", mockError), + ) + }, + wantErr: fmt.Errorf("interpolate environment variables for mailer manifest: %w", mockError), + }, "should return error if workspace methods fail": { inputSvc: "mailer", setupMocks: func(m deployJobMocks) { gomock.InOrder( m.mockWs.EXPECT().ReadJobManifest(gomock.Any()).Return(mockManifest, nil), + m.mockInterpolator.EXPECT().Interpolate(string(mockManifest)).Return(string(mockManifest), nil), m.mockWs.EXPECT().CopilotDirPath().Return("", mockError), ) }, @@ -255,6 +267,7 @@ on: setupMocks: func(m deployJobMocks) { gomock.InOrder( m.mockWs.EXPECT().ReadJobManifest("mailer").Return(mockMftNoBuild, nil), + m.mockInterpolator.EXPECT().Interpolate(string(mockMftNoBuild)).Return(string(mockMftNoBuild), nil), m.mockWs.EXPECT().CopilotDirPath().Times(0), m.mockimageBuilderPusher.EXPECT().BuildAndPush(gomock.Any(), gomock.Any()).Times(0), ) @@ -265,6 +278,7 @@ on: setupMocks: func(m deployJobMocks) { gomock.InOrder( m.mockWs.EXPECT().ReadJobManifest("mailer").Return(mockManifest, nil), + m.mockInterpolator.EXPECT().Interpolate(string(mockManifest)).Return(string(mockManifest), nil), m.mockWs.EXPECT().CopilotDirPath().Return("/ws/root/copilot", nil), m.mockimageBuilderPusher.EXPECT().BuildAndPush(gomock.Any(), gomock.Any()).Return("", mockError), ) @@ -276,6 +290,7 @@ on: setupMocks: func(m deployJobMocks) { gomock.InOrder( m.mockWs.EXPECT().ReadJobManifest("mailer").Return(mockManifest, nil), + m.mockInterpolator.EXPECT().Interpolate(string(mockManifest)).Return(string(mockManifest), nil), m.mockWs.EXPECT().CopilotDirPath().Return("/ws/root/copilot", nil), m.mockimageBuilderPusher.EXPECT().BuildAndPush(gomock.Any(), &dockerengine.BuildArguments{ Dockerfile: filepath.Join("/ws", "root", "path", "to", "Dockerfile"), @@ -290,6 +305,7 @@ on: setupMocks: func(m deployJobMocks) { gomock.InOrder( m.mockWs.EXPECT().ReadJobManifest("mailer").Return(mockMftBuildString, nil), + m.mockInterpolator.EXPECT().Interpolate(string(mockMftBuildString)).Return(string(mockMftBuildString), nil), m.mockWs.EXPECT().CopilotDirPath().Return("/ws/root/copilot", nil), m.mockimageBuilderPusher.EXPECT().BuildAndPush(gomock.Any(), &dockerengine.BuildArguments{ Dockerfile: filepath.Join("/ws", "root", "path", "to", "Dockerfile"), @@ -304,6 +320,7 @@ on: setupMocks: func(m deployJobMocks) { gomock.InOrder( m.mockWs.EXPECT().ReadJobManifest("mailer").Return(mockMftNoContext, nil), + m.mockInterpolator.EXPECT().Interpolate(string(mockMftNoContext)).Return(string(mockMftNoContext), nil), m.mockWs.EXPECT().CopilotDirPath().Return("/ws/root/copilot", nil), m.mockimageBuilderPusher.EXPECT().BuildAndPush(gomock.Any(), &dockerengine.BuildArguments{ Dockerfile: filepath.Join("/ws", "root", "path", "to", "Dockerfile"), @@ -322,9 +339,11 @@ on: mockWorkspace := mocks.NewMockwsJobDirReader(ctrl) mockimageBuilderPusher := mocks.NewMockimageBuilderPusher(ctrl) + mockInterpolator := mocks.NewMockinterpolator(ctrl) mocks := deployJobMocks{ mockWs: mockWorkspace, mockimageBuilderPusher: mockimageBuilderPusher, + mockInterpolator: mockInterpolator, } test.setupMocks(mocks) opts := deployJobOpts{ @@ -334,6 +353,9 @@ on: unmarshal: manifest.UnmarshalWorkload, imageBuilderPusher: mockimageBuilderPusher, ws: mockWorkspace, + newInterpolator: func(app, env string) interpolator { + return mockInterpolator + }, } gotErr := opts.configureContainerImage() diff --git a/internal/pkg/cli/mocks/mock_interfaces.go b/internal/pkg/cli/mocks/mock_interfaces.go index 884ad4e18ae..822542e660c 100644 --- a/internal/pkg/cli/mocks/mock_interfaces.go +++ b/internal/pkg/cli/mocks/mock_interfaces.go @@ -6566,3 +6566,41 @@ func (mr *MocktimeoutErrorMockRecorder) Timeout() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Timeout", reflect.TypeOf((*MocktimeoutError)(nil).Timeout)) } + +// Mockinterpolator is a mock of interpolator interface. +type Mockinterpolator struct { + ctrl *gomock.Controller + recorder *MockinterpolatorMockRecorder +} + +// MockinterpolatorMockRecorder is the mock recorder for Mockinterpolator. +type MockinterpolatorMockRecorder struct { + mock *Mockinterpolator +} + +// NewMockinterpolator creates a new mock instance. +func NewMockinterpolator(ctrl *gomock.Controller) *Mockinterpolator { + mock := &Mockinterpolator{ctrl: ctrl} + mock.recorder = &MockinterpolatorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *Mockinterpolator) EXPECT() *MockinterpolatorMockRecorder { + return m.recorder +} + +// Interpolate mocks base method. +func (m *Mockinterpolator) Interpolate(s string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Interpolate", s) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Interpolate indicates an expected call of Interpolate. +func (mr *MockinterpolatorMockRecorder) Interpolate(s interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Interpolate", reflect.TypeOf((*Mockinterpolator)(nil).Interpolate), s) +} diff --git a/internal/pkg/cli/svc_deploy.go b/internal/pkg/cli/svc_deploy.go index dfe7207105a..15a03f0966d 100644 --- a/internal/pkg/cli/svc_deploy.go +++ b/internal/pkg/cli/svc_deploy.go @@ -76,6 +76,7 @@ type deploySvcOpts struct { ws wsSvcDirReader imageBuilderPusher imageBuilderPusher unmarshal func([]byte) (manifest.WorkloadManifest, error) + newInterpolator func(app, env string) interpolator s3 artifactUploader cmd runner addons templater @@ -139,14 +140,19 @@ func newSvcDeployOpts(vars deployWkldVars) (*deploySvcOpts, error) { } return d, nil }, - cmd: exec.NewCmd(), - sessProvider: sessions.NewProvider(), - snsTopicGetter: deployStore, + newInterpolator: newManifestInterpolator, + cmd: exec.NewCmd(), + sessProvider: sessions.NewProvider(), + snsTopicGetter: deployStore, } opts.uploadOpts = newUploadCustomResourcesOpts(opts) return opts, err } +func newManifestInterpolator(app, env string) interpolator { + return manifest.NewInterpolator(app, env) +} + // Validate returns an error if the user inputs are invalid. func (o *deploySvcOpts) Validate() error { if o.appName == "" { @@ -451,7 +457,11 @@ func (o *deploySvcOpts) manifest() (interface{}, error) { if err != nil { return nil, fmt.Errorf("read service %s manifest file: %w", o.name, err) } - mft, err := o.unmarshal(raw) + interpolated, err := o.newInterpolator(o.appName, o.envName).Interpolate(string(raw)) + if err != nil { + return nil, fmt.Errorf("interpolate environment variables for %s manifest: %w", o.name, err) + } + mft, err := o.unmarshal([]byte(interpolated)) if err != nil { return nil, fmt.Errorf("unmarshal service %s manifest: %w", o.name, err) } diff --git a/internal/pkg/cli/svc_deploy_test.go b/internal/pkg/cli/svc_deploy_test.go index baa4bd46a25..819726262f4 100644 --- a/internal/pkg/cli/svc_deploy_test.go +++ b/internal/pkg/cli/svc_deploy_test.go @@ -39,6 +39,8 @@ type deploySvcMocks struct { mockServiceDeployer *mocks.MockserviceDeployer mockSpinner *mocks.Mockprogress mockServiceUpdater *mocks.MockserviceUpdater + mockInterpolator *mocks.Mockinterpolator + mockDeployStore *mocks.MockdeployedEnvironmentLister } func TestSvcDeployOpts_Validate(t *testing.T) { @@ -275,11 +277,22 @@ image: }, wantErr: fmt.Errorf("read service %s manifest file: %w", "serviceA", mockError), }, + "should return error if interpolation fail": { + inputSvc: "serviceA", + setupMocks: func(m deploySvcMocks) { + gomock.InOrder( + m.mockWs.EXPECT().ReadServiceManifest(gomock.Any()).Return(mockManifest, nil), + m.mockInterpolator.EXPECT().Interpolate(string(mockManifest)).Return("", mockError), + ) + }, + wantErr: fmt.Errorf("interpolate environment variables for serviceA manifest: %w", mockError), + }, "should return error if workspace methods fail": { inputSvc: "serviceA", setupMocks: func(m deploySvcMocks) { gomock.InOrder( m.mockWs.EXPECT().ReadServiceManifest(gomock.Any()).Return(mockManifest, nil), + m.mockInterpolator.EXPECT().Interpolate(string(mockManifest)).Return(string(mockManifest), nil), m.mockWs.EXPECT().CopilotDirPath().Return("", mockError), ) }, @@ -290,6 +303,7 @@ image: setupMocks: func(m deploySvcMocks) { gomock.InOrder( m.mockWs.EXPECT().ReadServiceManifest("serviceA").Return(mockManifestWithBadPlatform, nil), + m.mockInterpolator.EXPECT().Interpolate(string(mockManifestWithBadPlatform)).Return(string(mockManifestWithBadPlatform), nil), ) }, wantErr: fmt.Errorf("unmarshal service serviceA manifest: unmarshal to load balanced web service: validate platform: platform %s is invalid; the valid platform is: %s", "linus/abc123", "linux/amd64"), @@ -299,6 +313,7 @@ image: setupMocks: func(m deploySvcMocks) { gomock.InOrder( m.mockWs.EXPECT().ReadServiceManifest("serviceA").Return(mockManifestWithGoodPlatform, nil), + m.mockInterpolator.EXPECT().Interpolate(string(mockManifestWithGoodPlatform)).Return(string(mockManifestWithGoodPlatform), nil), m.mockWs.EXPECT().CopilotDirPath().Return("/ws/root/copilot", nil), m.mockimageBuilderPusher.EXPECT().BuildAndPush(gomock.Any(), &dockerengine.BuildArguments{ Dockerfile: filepath.Join("/ws", "root", "path", "to", "Dockerfile"), @@ -314,6 +329,7 @@ image: setupMocks: func(m deploySvcMocks) { gomock.InOrder( m.mockWs.EXPECT().ReadServiceManifest("serviceA").Return(mockMftNoBuild, nil), + m.mockInterpolator.EXPECT().Interpolate(string(mockMftNoBuild)).Return(string(mockMftNoBuild), nil), m.mockWs.EXPECT().CopilotDirPath().Times(0), m.mockimageBuilderPusher.EXPECT().BuildAndPush(gomock.Any(), gomock.Any()).Times(0), ) @@ -324,6 +340,7 @@ image: setupMocks: func(m deploySvcMocks) { gomock.InOrder( m.mockWs.EXPECT().ReadServiceManifest("serviceA").Return(mockManifest, nil), + m.mockInterpolator.EXPECT().Interpolate(string(mockManifest)).Return(string(mockManifest), nil), m.mockWs.EXPECT().CopilotDirPath().Return("/ws/root/copilot", nil), m.mockimageBuilderPusher.EXPECT().BuildAndPush(gomock.Any(), gomock.Any()).Return("", mockError), ) @@ -335,6 +352,7 @@ image: setupMocks: func(m deploySvcMocks) { gomock.InOrder( m.mockWs.EXPECT().ReadServiceManifest("serviceA").Return(mockManifest, nil), + m.mockInterpolator.EXPECT().Interpolate(string(mockManifest)).Return(string(mockManifest), nil), m.mockWs.EXPECT().CopilotDirPath().Return("/ws/root/copilot", nil), m.mockimageBuilderPusher.EXPECT().BuildAndPush(gomock.Any(), &dockerengine.BuildArguments{ Dockerfile: filepath.Join("/ws", "root", "path", "to", "Dockerfile"), @@ -349,6 +367,7 @@ image: setupMocks: func(m deploySvcMocks) { gomock.InOrder( m.mockWs.EXPECT().ReadServiceManifest("serviceA").Return(mockMftBuildString, nil), + m.mockInterpolator.EXPECT().Interpolate(string(mockMftBuildString)).Return(string(mockMftBuildString), nil), m.mockWs.EXPECT().CopilotDirPath().Return("/ws/root/copilot", nil), m.mockimageBuilderPusher.EXPECT().BuildAndPush(gomock.Any(), &dockerengine.BuildArguments{ Dockerfile: filepath.Join("/ws", "root", "path", "to", "Dockerfile"), @@ -363,6 +382,7 @@ image: setupMocks: func(m deploySvcMocks) { gomock.InOrder( m.mockWs.EXPECT().ReadServiceManifest("serviceA").Return(mockMftNoContext, nil), + m.mockInterpolator.EXPECT().Interpolate(string(mockMftNoContext)).Return(string(mockMftNoContext), nil), m.mockWs.EXPECT().CopilotDirPath().Return("/ws/root/copilot", nil), m.mockimageBuilderPusher.EXPECT().BuildAndPush(gomock.Any(), &dockerengine.BuildArguments{ Dockerfile: filepath.Join("/ws", "root", "path", "to", "Dockerfile"), @@ -381,9 +401,11 @@ image: mockWorkspace := mocks.NewMockwsSvcDirReader(ctrl) mockimageBuilderPusher := mocks.NewMockimageBuilderPusher(ctrl) + mockInterpolator := mocks.NewMockinterpolator(ctrl) mocks := deploySvcMocks{ mockWs: mockWorkspace, mockimageBuilderPusher: mockimageBuilderPusher, + mockInterpolator: mockInterpolator, } test.setupMocks(mocks) opts := deploySvcOpts{ @@ -393,6 +415,9 @@ image: unmarshal: manifest.UnmarshalWorkload, imageBuilderPusher: mockimageBuilderPusher, ws: mockWorkspace, + newInterpolator: func(app, env string) interpolator { + return mockInterpolator + }, } gotErr := opts.configureContainerImage() @@ -585,6 +610,13 @@ func TestSvcDeployOpts_deploySvc(t *testing.T) { }, wantErr: fmt.Errorf("read service %s manifest file: %w", mockSvcName, mockError), }, + "fail to interpolate manifest": { + mock: func(m *deploySvcMocks) { + m.mockWs.EXPECT().ReadServiceManifest(mockSvcName).Return([]byte{}, nil) + m.mockInterpolator.EXPECT().Interpolate("").Return("", mockError) + }, + wantErr: fmt.Errorf("interpolate environment variables for mockSvc manifest: %w", mockError), + }, "fail to get app resources": { inBuildRequire: true, inEnvironment: &config.Environment{ @@ -596,6 +628,7 @@ func TestSvcDeployOpts_deploySvc(t *testing.T) { }, mock: func(m *deploySvcMocks) { m.mockWs.EXPECT().ReadServiceManifest(mockSvcName).Return([]byte{}, nil) + m.mockInterpolator.EXPECT().Interpolate("").Return("", nil) m.mockAppResourcesGetter.EXPECT().GetAppResourcesByRegion(&config.Application{ Name: mockAppName, }, "us-west-2").Return(nil, mockError) @@ -614,6 +647,7 @@ func TestSvcDeployOpts_deploySvc(t *testing.T) { }, mock: func(m *deploySvcMocks) { m.mockWs.EXPECT().ReadServiceManifest(mockSvcName).Return([]byte{}, nil) + m.mockInterpolator.EXPECT().Interpolate("").Return("", nil) m.mockEndpointGetter.EXPECT().ServiceDiscoveryEndpoint().Return("mockApp.local", nil) }, wantErr: errors.New("alias specified when application is not associated with a domain"), @@ -630,6 +664,7 @@ func TestSvcDeployOpts_deploySvc(t *testing.T) { }, mock: func(m *deploySvcMocks) { m.mockWs.EXPECT().ReadServiceManifest(mockSvcName).Return([]byte{}, nil) + m.mockInterpolator.EXPECT().Interpolate("").Return("", nil) m.mockAppResourcesGetter.EXPECT().GetAppResourcesByRegion(&config.Application{ Name: mockAppName, AccountID: "1234567890", @@ -652,6 +687,7 @@ func TestSvcDeployOpts_deploySvc(t *testing.T) { }, mock: func(m *deploySvcMocks) { m.mockWs.EXPECT().ReadServiceManifest(mockSvcName).Return([]byte{}, nil) + m.mockInterpolator.EXPECT().Interpolate("").Return("", nil) m.mockAppVersionGetter.EXPECT().Version().Return("", mockError) m.mockEndpointGetter.EXPECT().ServiceDiscoveryEndpoint().Return("mockApp.local", nil) }, @@ -669,6 +705,7 @@ func TestSvcDeployOpts_deploySvc(t *testing.T) { }, mock: func(m *deploySvcMocks) { m.mockWs.EXPECT().ReadServiceManifest(mockSvcName).Return([]byte{}, nil) + m.mockInterpolator.EXPECT().Interpolate("").Return("", nil) m.mockAppVersionGetter.EXPECT().Version().Return("v0.0.0", nil) m.mockEndpointGetter.EXPECT().ServiceDiscoveryEndpoint().Return("mockApp.local", nil) }, @@ -686,6 +723,7 @@ func TestSvcDeployOpts_deploySvc(t *testing.T) { }, mock: func(m *deploySvcMocks) { m.mockWs.EXPECT().ReadServiceManifest(mockSvcName).Return([]byte{}, nil) + m.mockInterpolator.EXPECT().Interpolate("").Return("", nil) m.mockAppVersionGetter.EXPECT().Version().Return("v1.0.0", nil) m.mockEndpointGetter.EXPECT().ServiceDiscoveryEndpoint().Return("mockApp.local", nil) @@ -703,6 +741,7 @@ func TestSvcDeployOpts_deploySvc(t *testing.T) { }, mock: func(m *deploySvcMocks) { m.mockWs.EXPECT().ReadServiceManifest(mockSvcName).Return([]byte{}, nil) + m.mockInterpolator.EXPECT().Interpolate("").Return("", nil) m.mockEndpointGetter.EXPECT().ServiceDiscoveryEndpoint().Return("mockApp.local", nil) m.mockServiceDeployer.EXPECT().DeployService(gomock.Any(), gomock.Any(), gomock.Any()).Return(errors.New("some error")) }, @@ -719,6 +758,7 @@ func TestSvcDeployOpts_deploySvc(t *testing.T) { }, mock: func(m *deploySvcMocks) { m.mockWs.EXPECT().ReadServiceManifest(mockSvcName).Return([]byte{}, nil) + m.mockInterpolator.EXPECT().Interpolate("").Return("", nil) m.mockEndpointGetter.EXPECT().ServiceDiscoveryEndpoint().Return("mockApp.local", nil) m.mockServiceDeployer.EXPECT().DeployService(gomock.Any(), gomock.Any(), gomock.Any()).Return(cloudformation.NewMockErrChangeSetEmpty()) }, @@ -736,6 +776,7 @@ func TestSvcDeployOpts_deploySvc(t *testing.T) { }, mock: func(m *deploySvcMocks) { m.mockWs.EXPECT().ReadServiceManifest(mockSvcName).Return([]byte{}, nil) + m.mockInterpolator.EXPECT().Interpolate("").Return("", nil) m.mockEndpointGetter.EXPECT().ServiceDiscoveryEndpoint().Return("mockApp.local", nil) m.mockServiceDeployer.EXPECT().DeployService(gomock.Any(), gomock.Any(), gomock.Any()). Return(cloudformation.NewMockErrChangeSetEmpty()) @@ -757,6 +798,7 @@ func TestSvcDeployOpts_deploySvc(t *testing.T) { }, mock: func(m *deploySvcMocks) { m.mockWs.EXPECT().ReadServiceManifest(mockSvcName).Return([]byte{}, nil) + m.mockInterpolator.EXPECT().Interpolate("").Return("", nil) m.mockEndpointGetter.EXPECT().ServiceDiscoveryEndpoint().Return("mockApp.local", nil) m.mockServiceDeployer.EXPECT().DeployService(gomock.Any(), gomock.Any(), gomock.Any()). Return(cloudformation.NewMockErrChangeSetEmpty()) @@ -787,6 +829,7 @@ func TestSvcDeployOpts_deploySvc(t *testing.T) { }, mock: func(m *deploySvcMocks) { m.mockWs.EXPECT().ReadServiceManifest(mockSvcName).Return([]byte{}, nil) + m.mockInterpolator.EXPECT().Interpolate("").Return("", nil) m.mockAppVersionGetter.EXPECT().Version().Return("v1.0.0", nil) m.mockEndpointGetter.EXPECT().ServiceDiscoveryEndpoint().Return("mockApp.local", nil) m.mockServiceDeployer.EXPECT().DeployService(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) @@ -804,6 +847,7 @@ func TestSvcDeployOpts_deploySvc(t *testing.T) { }, mock: func(m *deploySvcMocks) { m.mockWs.EXPECT().ReadServiceManifest(mockSvcName).Return([]byte{}, nil) + m.mockInterpolator.EXPECT().Interpolate("").Return("", nil) m.mockEndpointGetter.EXPECT().ServiceDiscoveryEndpoint().Return("mockApp.local", nil) m.mockServiceDeployer.EXPECT().DeployService(gomock.Any(), gomock.Any(), gomock.Any()).Return(cloudformation.NewMockErrChangeSetEmpty()) m.mockSpinner.EXPECT().Start(fmt.Sprintf(fmtForceUpdateSvcStart, mockSvcName, mockEnvName)) @@ -826,6 +870,7 @@ func TestSvcDeployOpts_deploySvc(t *testing.T) { mockServiceDeployer: mocks.NewMockserviceDeployer(ctrl), mockServiceUpdater: mocks.NewMockserviceUpdater(ctrl), mockSpinner: mocks.NewMockprogress(ctrl), + mockInterpolator: mocks.NewMockinterpolator(ctrl), } tc.mock(m) @@ -845,6 +890,9 @@ func TestSvcDeployOpts_deploySvc(t *testing.T) { endpointGetter: m.mockEndpointGetter, targetApp: tc.inApp, targetEnvironment: tc.inEnvironment, + newInterpolator: func(app, env string) interpolator { + return m.mockInterpolator + }, unmarshal: func(b []byte) (manifest.WorkloadManifest, error) { return &manifest.LoadBalancedWebService{ Workload: manifest.Workload{ @@ -889,6 +937,7 @@ type deployRDSvcMocks struct { mockEndpointGetter *mocks.MockendpointGetter mockIdentity *mocks.MockidentityService mockUploader *mocks.MockcustomResourcesUploader + mockInterpolator *mocks.Mockinterpolator } func TestSvcDeployOpts_rdWebServiceStackConfiguration(t *testing.T) { @@ -920,6 +969,7 @@ func TestSvcDeployOpts_rdWebServiceStackConfiguration(t *testing.T) { }, mock: func(m *deployRDSvcMocks) { m.mockWorkspace.EXPECT().ReadServiceManifest(mockSvcName).Return([]byte{}, nil) + m.mockInterpolator.EXPECT().Interpolate("").Return("", nil) m.mockEndpointGetter.EXPECT().ServiceDiscoveryEndpoint().Return("mockApp.local", nil) }, @@ -937,6 +987,7 @@ func TestSvcDeployOpts_rdWebServiceStackConfiguration(t *testing.T) { }, mock: func(m *deployRDSvcMocks) { m.mockWorkspace.EXPECT().ReadServiceManifest(mockSvcName).Return([]byte{}, nil) + m.mockInterpolator.EXPECT().Interpolate("").Return("", nil) m.mockEndpointGetter.EXPECT().ServiceDiscoveryEndpoint().Return("mockApp.local", nil) m.mockIdentity.EXPECT().Get().Return(identity.Caller{}, errors.New("some error")) }, @@ -955,6 +1006,7 @@ func TestSvcDeployOpts_rdWebServiceStackConfiguration(t *testing.T) { }, mock: func(m *deployRDSvcMocks) { m.mockWorkspace.EXPECT().ReadServiceManifest(mockSvcName).Return([]byte{}, nil) + m.mockInterpolator.EXPECT().Interpolate("").Return("", nil) m.mockAppVersionGetter.EXPECT().Version().Return("v1.0.0", nil) m.mockEndpointGetter.EXPECT().ServiceDiscoveryEndpoint().Return("mockApp.local", nil) m.mockIdentity.EXPECT().Get().Return(identity.Caller{ @@ -980,6 +1032,7 @@ func TestSvcDeployOpts_rdWebServiceStackConfiguration(t *testing.T) { }, mock: func(m *deployRDSvcMocks) { m.mockWorkspace.EXPECT().ReadServiceManifest(mockSvcName).Return([]byte{}, nil) + m.mockInterpolator.EXPECT().Interpolate("").Return("", nil) m.mockAppVersionGetter.EXPECT().Version().Return("v1.0.0", nil) m.mockEndpointGetter.EXPECT().ServiceDiscoveryEndpoint().Return("mockApp.local", nil) m.mockIdentity.EXPECT().Get().Return(identity.Caller{ @@ -1001,6 +1054,7 @@ func TestSvcDeployOpts_rdWebServiceStackConfiguration(t *testing.T) { }, mock: func(m *deployRDSvcMocks) { m.mockWorkspace.EXPECT().ReadServiceManifest(mockSvcName).Return([]byte{}, nil) + m.mockInterpolator.EXPECT().Interpolate("").Return("", nil) m.mockAppVersionGetter.EXPECT().Version().Return("v1.0.0", nil) m.mockEndpointGetter.EXPECT().ServiceDiscoveryEndpoint().Return("mockApp.local", nil) m.mockIdentity.EXPECT().Get().Return(identity.Caller{ @@ -1022,6 +1076,7 @@ func TestSvcDeployOpts_rdWebServiceStackConfiguration(t *testing.T) { }, mock: func(m *deployRDSvcMocks) { m.mockWorkspace.EXPECT().ReadServiceManifest(mockSvcName).Return([]byte{}, nil) + m.mockInterpolator.EXPECT().Interpolate("").Return("", nil) m.mockAppVersionGetter.EXPECT().Version().Return("v1.0.0", nil) m.mockEndpointGetter.EXPECT().ServiceDiscoveryEndpoint().Return("mockApp.local", nil) m.mockIdentity.EXPECT().Get().Return(identity.Caller{ @@ -1043,6 +1098,7 @@ func TestSvcDeployOpts_rdWebServiceStackConfiguration(t *testing.T) { }, mock: func(m *deployRDSvcMocks) { m.mockWorkspace.EXPECT().ReadServiceManifest(mockSvcName).Return([]byte{}, nil) + m.mockInterpolator.EXPECT().Interpolate("").Return("", nil) m.mockAppVersionGetter.EXPECT().Version().Return("v1.0.0", nil) m.mockEndpointGetter.EXPECT().ServiceDiscoveryEndpoint().Return("mockApp.local", nil) m.mockIdentity.EXPECT().Get().Return(identity.Caller{ @@ -1064,6 +1120,7 @@ func TestSvcDeployOpts_rdWebServiceStackConfiguration(t *testing.T) { }, mock: func(m *deployRDSvcMocks) { m.mockWorkspace.EXPECT().ReadServiceManifest(mockSvcName).Return([]byte{}, nil) + m.mockInterpolator.EXPECT().Interpolate("").Return("", nil) m.mockAppVersionGetter.EXPECT().Version().Return("v1.0.0", nil) m.mockEndpointGetter.EXPECT().ServiceDiscoveryEndpoint().Return("mockApp.local", nil) m.mockIdentity.EXPECT().Get().Return(identity.Caller{ @@ -1092,6 +1149,7 @@ func TestSvcDeployOpts_rdWebServiceStackConfiguration(t *testing.T) { }, mock: func(m *deployRDSvcMocks) { m.mockWorkspace.EXPECT().ReadServiceManifest(mockSvcName).Return([]byte{}, nil) + m.mockInterpolator.EXPECT().Interpolate("").Return("", nil) m.mockAppVersionGetter.EXPECT().Version().Return("v1.0.0", nil) m.mockEndpointGetter.EXPECT().ServiceDiscoveryEndpoint().Return("mockApp.local", nil) m.mockIdentity.EXPECT().Get().Return(identity.Caller{ @@ -1126,6 +1184,7 @@ func TestSvcDeployOpts_rdWebServiceStackConfiguration(t *testing.T) { mockEndpointGetter: mocks.NewMockendpointGetter(ctrl), mockIdentity: mocks.NewMockidentityService(ctrl), mockUploader: mocks.NewMockcustomResourcesUploader(ctrl), + mockInterpolator: mocks.NewMockinterpolator(ctrl), } tc.mock(m) @@ -1140,6 +1199,9 @@ func TestSvcDeployOpts_rdWebServiceStackConfiguration(t *testing.T) { newAppVersionGetter: func(s string) (versionGetter, error) { return m.mockAppVersionGetter, nil }, + newInterpolator: func(app, env string) interpolator { + return m.mockInterpolator + }, newSvcUpdater: func(f func(*session.Session) serviceUpdater) {}, endpointGetter: m.mockEndpointGetter, identity: m.mockIdentity, @@ -1197,23 +1259,15 @@ func TestSvcDeployOpts_stackConfiguration_worker(t *testing.T) { inEnvironment *config.Environment inBuildRequire bool - mockWorkspace func(m *mocks.MockwsSvcDirReader) - mockAppResourcesGetter func(m *mocks.MockappResourcesGetter) - mockAppVersionGetter func(m *mocks.MockversionGetter) - mockEndpointGetter func(m *mocks.MockendpointGetter) - mockDeployStore func(m *mocks.MockdeployedEnvironmentLister) + mock func(m *deploySvcMocks) wantErr error }{ "fail to read service manifest": { - mockWorkspace: func(m *mocks.MockwsSvcDirReader) { - m.EXPECT().ReadServiceManifest(mockSvcName).Return(nil, mockError) - }, - mockAppResourcesGetter: func(m *mocks.MockappResourcesGetter) {}, - mockAppVersionGetter: func(m *mocks.MockversionGetter) {}, - mockEndpointGetter: func(m *mocks.MockendpointGetter) {}, - mockDeployStore: func(m *mocks.MockdeployedEnvironmentLister) {}, - wantErr: fmt.Errorf("read service %s manifest file: %w", mockSvcName, mockError), + mock: func(m *deploySvcMocks) { + m.mockWs.EXPECT().ReadServiceManifest(mockSvcName).Return(nil, mockError) + }, + wantErr: fmt.Errorf("read service %s manifest file: %w", mockSvcName, mockError), }, "fail to get deployed topics": { inEnvironment: &config.Environment{ @@ -1224,16 +1278,11 @@ func TestSvcDeployOpts_stackConfiguration_worker(t *testing.T) { Name: mockAppName, Domain: "mockDomain", }, - mockWorkspace: func(m *mocks.MockwsSvcDirReader) { - m.EXPECT().ReadServiceManifest(mockSvcName).Return([]byte{}, nil) - }, - mockAppResourcesGetter: func(m *mocks.MockappResourcesGetter) {}, - mockAppVersionGetter: func(m *mocks.MockversionGetter) {}, - mockEndpointGetter: func(m *mocks.MockendpointGetter) { - m.EXPECT().ServiceDiscoveryEndpoint().Return("mockApp.local", nil) - }, - mockDeployStore: func(m *mocks.MockdeployedEnvironmentLister) { - m.EXPECT().ListSNSTopics(mockAppName, mockEnvName).Return(nil, mockError) + mock: func(m *deploySvcMocks) { + m.mockWs.EXPECT().ReadServiceManifest(mockSvcName).Return([]byte{}, nil) + m.mockInterpolator.EXPECT().Interpolate("").Return("", nil) + m.mockEndpointGetter.EXPECT().ServiceDiscoveryEndpoint().Return("mockApp.local", nil) + m.mockDeployStore.EXPECT().ListSNSTopics(mockAppName, mockEnvName).Return(nil, mockError) }, wantErr: fmt.Errorf("get SNS topics for app mockApp and environment mockEnv: %w", mockError), }, @@ -1246,16 +1295,11 @@ func TestSvcDeployOpts_stackConfiguration_worker(t *testing.T) { Name: mockAppName, Domain: "mockDomain", }, - mockWorkspace: func(m *mocks.MockwsSvcDirReader) { - m.EXPECT().ReadServiceManifest(mockSvcName).Return([]byte{}, nil) - }, - mockAppResourcesGetter: func(m *mocks.MockappResourcesGetter) {}, - mockAppVersionGetter: func(m *mocks.MockversionGetter) {}, - mockEndpointGetter: func(m *mocks.MockendpointGetter) { - m.EXPECT().ServiceDiscoveryEndpoint().Return("mockEnv.mockApp.local", nil) - }, - mockDeployStore: func(m *mocks.MockdeployedEnvironmentLister) { - m.EXPECT().ListSNSTopics(mockAppName, mockEnvName).Return([]deploy.Topic{ + mock: func(m *deploySvcMocks) { + m.mockWs.EXPECT().ReadServiceManifest(mockSvcName).Return([]byte{}, nil) + m.mockInterpolator.EXPECT().Interpolate("").Return("", nil) + m.mockEndpointGetter.EXPECT().ServiceDiscoveryEndpoint().Return("mockEnv.mockApp.local", nil) + m.mockDeployStore.EXPECT().ListSNSTopics(mockAppName, mockEnvName).Return([]deploy.Topic{ *topic, }, nil) }, @@ -1266,17 +1310,13 @@ func TestSvcDeployOpts_stackConfiguration_worker(t *testing.T) { t.Run(name, func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - - mockWorkspace := mocks.NewMockwsSvcDirReader(ctrl) - mockAppResourcesGetter := mocks.NewMockappResourcesGetter(ctrl) - mockAppVersionGetter := mocks.NewMockversionGetter(ctrl) - mockEndpointGetter := mocks.NewMockendpointGetter(ctrl) - mockDeployStore := mocks.NewMockdeployedEnvironmentLister(ctrl) - tc.mockWorkspace(mockWorkspace) - tc.mockAppResourcesGetter(mockAppResourcesGetter) - tc.mockAppVersionGetter(mockAppVersionGetter) - tc.mockEndpointGetter(mockEndpointGetter) - tc.mockDeployStore(mockDeployStore) + m := &deploySvcMocks{ + mockWs: mocks.NewMockwsSvcDirReader(ctrl), + mockEndpointGetter: mocks.NewMockendpointGetter(ctrl), + mockDeployStore: mocks.NewMockdeployedEnvironmentLister(ctrl), + mockInterpolator: mocks.NewMockinterpolator(ctrl), + } + tc.mock(m) opts := deploySvcOpts{ deployWkldVars: deployWkldVars{ @@ -1284,17 +1324,16 @@ func TestSvcDeployOpts_stackConfiguration_worker(t *testing.T) { appName: mockAppName, envName: mockEnvName, }, - ws: mockWorkspace, - buildRequired: tc.inBuildRequire, - appCFN: mockAppResourcesGetter, - newAppVersionGetter: func(s string) (versionGetter, error) { - return mockAppVersionGetter, nil - }, + ws: m.mockWs, + buildRequired: tc.inBuildRequire, newSvcUpdater: func(f func(*session.Session) serviceUpdater) {}, - endpointGetter: mockEndpointGetter, - snsTopicGetter: mockDeployStore, + endpointGetter: m.mockEndpointGetter, + snsTopicGetter: m.mockDeployStore, targetApp: tc.inApp, targetEnvironment: tc.inEnvironment, + newInterpolator: func(app, env string) interpolator { + return m.mockInterpolator + }, unmarshal: func(b []byte) (manifest.WorkloadManifest, error) { return &manifest.WorkerService{ Workload: manifest.Workload{ diff --git a/internal/pkg/cli/svc_package.go b/internal/pkg/cli/svc_package.go index d25e5412ddc..f1aa818ad33 100644 --- a/internal/pkg/cli/svc_package.go +++ b/internal/pkg/cli/svc_package.go @@ -73,6 +73,7 @@ type packageSvcOpts struct { sel wsSelector prompt prompter identity identityService + newInterpolator func(app, env string) interpolator stackSerializer func(mft interface{}, env *config.Environment, app *config.Application, rc stack.RuntimeConfig) (stackSerializer, error) newEndpointGetter func(app, env string) (endpointGetter, error) snsTopicGetter deployedEnvironmentLister @@ -112,6 +113,7 @@ func newPackageSvcOpts(vars packageSvcVars) (*packageSvcOpts, error) { addonsWriter: ioutil.Discard, fs: &afero.Afero{Fs: afero.NewOsFs()}, snsTopicGetter: deployStore, + newInterpolator: newManifestInterpolator, } appVersionGetter, err := describe.NewAppDescriber(vars.appName) if err != nil { @@ -352,11 +354,15 @@ type svcCfnTemplates struct { func (o *packageSvcOpts) getSvcTemplates(env *config.Environment) (*svcCfnTemplates, error) { raw, err := o.ws.ReadServiceManifest(o.name) if err != nil { - return nil, err + return nil, fmt.Errorf("read service manifest: %w", err) } - mft, err := manifest.UnmarshalWorkload(raw) + interpolated, err := o.newInterpolator(o.appName, env.Name).Interpolate(string(raw)) if err != nil { - return nil, err + return nil, fmt.Errorf("interpolate environment variables for %s manifest: %w", o.name, err) + } + mft, err := manifest.UnmarshalWorkload([]byte(interpolated)) + if err != nil { + return nil, fmt.Errorf("unmarshal workload: %w", err) } envMft, err := mft.ApplyEnv(o.envName) if err != nil { diff --git a/internal/pkg/cli/svc_package_test.go b/internal/pkg/cli/svc_package_test.go index 18e5c901995..2e93411e139 100644 --- a/internal/pkg/cli/svc_package_test.go +++ b/internal/pkg/cli/svc_package_test.go @@ -211,6 +211,26 @@ func TestPackageSvcOpts_Ask(t *testing.T) { } func TestPackageSvcOpts_Execute(t *testing.T) { + lbwsMft := `name: api +type: Load Balanced Web Service +image: + build: ./Dockerfile + port: 80 +http: + path: 'api' +cpu: 256 +memory: 512 +count: 1` + rdwsMft := `name: api +type: Request-Driven Web Service +image: + build: ./Dockerfile + port: 80 +http: + alias: 'hunter.com' +cpu: 256 +memory: 512 +count: 1` testCases := map[string]struct { inVars packageSvcVars @@ -252,16 +272,10 @@ func TestPackageSvcOpts_Execute(t *testing.T) { mockWs := mocks.NewMockwsSvcReader(ctrl) mockWs.EXPECT(). ReadServiceManifest("api"). - Return([]byte(`name: api -type: Load Balanced Web Service -image: - build: ./Dockerfile - port: 80 -http: - path: 'api' -cpu: 256 -memory: 512 -count: 1`), nil) + Return([]byte(lbwsMft), nil) + + mockItpl := mocks.NewMockinterpolator(ctrl) + mockItpl.EXPECT().Interpolate(lbwsMft).Return(lbwsMft, nil) mockCfn := mocks.NewMockappResourcesGetter(ctrl) mockCfn.EXPECT(). @@ -283,6 +297,9 @@ count: 1`), nil) opts.addonsClient = mockAddons return nil } + opts.newInterpolator = func(app, env string) interpolator { + return mockItpl + } opts.stackSerializer = func(_ interface{}, _ *config.Environment, _ *config.Application, _ stack.RuntimeConfig) (stackSerializer, error) { mockStackSerializer := mocks.NewMockstackSerializer(ctrl) mockStackSerializer.EXPECT().Template().Return("mystack", nil) @@ -330,16 +347,10 @@ count: 1`), nil) mockWs := mocks.NewMockwsSvcReader(ctrl) mockWs.EXPECT(). ReadServiceManifest("api"). - Return([]byte(`name: api -type: Request-Driven Web Service -image: - build: ./Dockerfile - port: 80 -http: - alias: 'hunter.com' -cpu: 256 -memory: 512 -count: 1`), nil) + Return([]byte(rdwsMft), nil) + + mockItpl := mocks.NewMockinterpolator(ctrl) + mockItpl.EXPECT().Interpolate(rdwsMft).Return(rdwsMft, nil) mockCfn := mocks.NewMockappResourcesGetter(ctrl) mockCfn.EXPECT(). @@ -361,6 +372,9 @@ count: 1`), nil) opts.addonsClient = mockAddons return nil } + opts.newInterpolator = func(app, env string) interpolator { + return mockItpl + } opts.stackSerializer = func(_ interface{}, _ *config.Environment, _ *config.Application, _ stack.RuntimeConfig) (stackSerializer, error) { mockStackSerializer := mocks.NewMockstackSerializer(ctrl) mockStackSerializer.EXPECT().Template().Return("mystack", nil) diff --git a/internal/pkg/deploy/cloudformation/stack/lb_web_service_integration_test.go b/internal/pkg/deploy/cloudformation/stack/lb_web_service_integration_test.go index 84440b77de2..385f3a6e9ec 100644 --- a/internal/pkg/deploy/cloudformation/stack/lb_web_service_integration_test.go +++ b/internal/pkg/deploy/cloudformation/stack/lb_web_service_integration_test.go @@ -8,6 +8,7 @@ package stack_test import ( "fmt" "io/ioutil" + "os" "path/filepath" "regexp" "strings" @@ -51,13 +52,23 @@ func TestLoadBalancedWebService_Template(t *testing.T) { svcParamsPath: "svc-prod.params.json", }, } + val, exist := os.LookupEnv("TAG") + require.NoError(t, os.Setenv("TAG", "cicdtest")) + defer func() { + if !exist { + require.NoError(t, os.Unsetenv("TAG")) + return + } + require.NoError(t, os.Setenv("TAG", val)) + }() path := filepath.Join("testdata", "workloads", svcManifestPath) manifestBytes, err := ioutil.ReadFile(path) require.NoError(t, err) for name, tc := range testCases { - mft, err := manifest.UnmarshalWorkload(manifestBytes) + interpolated, err := manifest.NewInterpolator(appName, tc.envName).Interpolate(string(manifestBytes)) + require.NoError(t, err) + mft, err := manifest.UnmarshalWorkload([]byte(interpolated)) require.NoError(t, err) - envMft, err := mft.ApplyEnv(tc.envName) require.NoError(t, err) diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-manifest.yml b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-manifest.yml index 3b82f55148c..1064412ea94 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-manifest.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-manifest.yml @@ -45,6 +45,8 @@ variables: # Pass environment variables as key value pairs. # You can override any of the values defined above by environment. environments: staging: + image: + location: 123456789000.dkr.ecr.us-east-1.amazonaws.com/vault/e2e:${TAG} count: spot: 5 http: diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-staging.params.json b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-staging.params.json index 032a2d581b3..a6972f8f490 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-staging.params.json +++ b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/svc-staging.params.json @@ -3,7 +3,7 @@ "AppName": "my-app", "EnvName": "staging", "WorkloadName": "fe", - "ContainerImage": "", + "ContainerImage": "123456789000.dkr.ecr.us-east-1.amazonaws.com/vault/e2e:cicdtest", "AddonsTemplateURL": "", "TaskCPU": "256", "TaskMemory": "512", diff --git a/internal/pkg/manifest/interpolate.go b/internal/pkg/manifest/interpolate.go index 3f0af9466c7..8552b5c92c8 100644 --- a/internal/pkg/manifest/interpolate.go +++ b/internal/pkg/manifest/interpolate.go @@ -22,12 +22,14 @@ var ( interpolatorEnvVarRegExp = regexp.MustCompile(`\${([_a-zA-Z][_a-zA-Z0-9]*)}`) ) -type interpolator struct { +// Interpolator substitutes variables in a manifest. +type Interpolator struct { predefinedEnvVars map[string]string } -func newInterpolator(appName, envName string) *interpolator { - return &interpolator{ +// NewInterpolator initiates a new Interpolator. +func NewInterpolator(appName, envName string) *Interpolator { + return &Interpolator{ predefinedEnvVars: map[string]string{ reservedEnvVarKeyForAppName: appName, reservedEnvVarKeyForEnvName: envName, @@ -35,10 +37,11 @@ func newInterpolator(appName, envName string) *interpolator { } } -func (i *interpolator) substitute(s string) (string, error) { +// Interpolate substitutes environment variables in a string. +func (i *Interpolator) Interpolate(s string) (string, error) { matches := interpolatorEnvVarRegExp.FindAllStringSubmatch(s, -1) if len(matches) == 0 { - return "", nil + return s, nil } replaced := s for _, match := range matches { diff --git a/internal/pkg/manifest/interpolate_test.go b/internal/pkg/manifest/interpolate_test.go index 6271f136714..19228596694 100644 --- a/internal/pkg/manifest/interpolate_test.go +++ b/internal/pkg/manifest/interpolate_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestInterpolator_substitute(t *testing.T) { +func TestInterpolator_Interpolate(t *testing.T) { testCases := map[string]struct { inputEnvVar map[string]string inputStr string @@ -44,12 +44,17 @@ func TestInterpolator_substitute(t *testing.T) { wanted: "${0accountID}.dkr.${repo-provider}..amazonaws.com/vault/test:latest", }, + "success with no matches": { + inputStr: "1234567890.dkr.ecr.us-west-2.amazonaws.com/vault/test:latest", + + wanted: "1234567890.dkr.ecr.us-west-2.amazonaws.com/vault/test:latest", + }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { // WHEN - itpl := newInterpolator( + itpl := NewInterpolator( "myApp", "test", ) @@ -59,7 +64,7 @@ func TestInterpolator_substitute(t *testing.T) { require.NoError(t, os.Unsetenv(key)) }(k) } - actual, actualErr := itpl.substitute(tc.inputStr) + actual, actualErr := itpl.Interpolate(tc.inputStr) // THEN if tc.wantedErr != nil {