Skip to content

Commit d608791

Browse files
authored
chore: implement Execute() for job_package (#1512)
This follows `svc_package` pretty closely. As mentioned offline, I added the wlType field to Addons and the related error. I didn't end up moving duplicate methods into the `addon` package because they were so entwined with fs, etc. If it would still be better to move them, let me know! Related #1131, #1428 By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
1 parent 5c12bdf commit d608791

16 files changed

+259
-121
lines changed

internal/pkg/addon/addons.go

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,49 +23,50 @@ type workspaceReader interface {
2323
ReadAddon(svcName, fileName string) ([]byte, error)
2424
}
2525

26-
// Addons represents additional resources for a service.
26+
// Addons represents additional resources for a workload.
2727
type Addons struct {
28-
svcName string
28+
wlName string
2929

3030
parser template.Parser
3131
ws workspaceReader
3232
}
3333

34-
// New creates an Addons object given an service name.
35-
func New(svcName string) (*Addons, error) {
34+
// New creates an Addons object given a workload name.
35+
func New(wlName string) (*Addons, error) {
3636
ws, err := workspace.New()
3737
if err != nil {
3838
return nil, fmt.Errorf("workspace cannot be created: %w", err)
3939
}
4040
return &Addons{
41-
svcName: svcName,
42-
parser: template.New(),
43-
ws: ws,
41+
wlName: wlName,
42+
parser: template.New(),
43+
ws: ws,
4444
}, nil
4545
}
4646

47-
// Template merges CloudFormation templates under the "addons/" directory of a service
47+
// Template merges CloudFormation templates under the "addons/" directory of a workload
4848
// into a single CloudFormation template and returns it.
4949
//
50-
// If the addons directory doesn't exist, it returns the empty string and ErrDirNotExist.
50+
// If the addons directory doesn't exist, it returns the empty string and
51+
// ErrAddonsDirNotExist.
5152
func (a *Addons) Template() (string, error) {
52-
fnames, err := a.ws.ReadAddonsDir(a.svcName)
53+
fnames, err := a.ws.ReadAddonsDir(a.wlName)
5354
if err != nil {
54-
return "", &ErrDirNotExist{
55-
SvcName: a.svcName,
55+
return "", &ErrAddonsDirNotExist{
56+
WlName: a.wlName,
5657
ParentErr: err,
5758
}
5859
}
5960

6061
mergedTemplate := newCFNTemplate("merged")
6162
for _, fname := range filterYAMLfiles(fnames) {
62-
out, err := a.ws.ReadAddon(a.svcName, fname)
63+
out, err := a.ws.ReadAddon(a.wlName, fname)
6364
if err != nil {
64-
return "", fmt.Errorf("read addon %s under service %s: %w", fname, a.svcName, err)
65+
return "", fmt.Errorf("read addon %s under %s: %w", fname, a.wlName, err)
6566
}
6667
tpl := newCFNTemplate(fname)
6768
if err := yaml.Unmarshal(out, tpl); err != nil {
68-
return "", fmt.Errorf("unmarshal addon %s under service %s: %w", fname, a.svcName, err)
69+
return "", fmt.Errorf("unmarshal addon %s under %s: %w", fname, a.wlName, err)
6970
}
7071
if err := mergedTemplate.merge(tpl); err != nil {
7172
return "", err

internal/pkg/addon/addons_test.go

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,59 @@ import (
1515
)
1616

1717
func TestAddons_Template(t *testing.T) {
18-
const testSvcName = "mysvc"
18+
const (
19+
testSvcName = "mysvc"
20+
testJobName = "resizer"
21+
)
1922
testErr := errors.New("some error")
2023
testCases := map[string]struct {
2124
mockAddons func(ctrl *gomock.Controller) *Addons
2225

2326
wantedTemplate string
2427
wantedErr error
2528
}{
26-
"return ErrDirNotExist if addons doesn't exist in a service": {
29+
"return ErrAddonsDirNotExist if addons doesn't exist in a service": {
2730
mockAddons: func(ctrl *gomock.Controller) *Addons {
2831
ws := mocks.NewMockworkspaceReader(ctrl)
2932
ws.EXPECT().ReadAddonsDir(testSvcName).
3033
Return(nil, testErr)
3134
return &Addons{
32-
svcName: testSvcName,
33-
ws: ws,
35+
wlName: testSvcName,
36+
ws: ws,
3437
}
3538
},
36-
wantedErr: &ErrDirNotExist{
37-
SvcName: testSvcName,
39+
wantedErr: &ErrAddonsDirNotExist{
40+
WlName: testSvcName,
3841
ParentErr: testErr,
3942
},
4043
},
44+
"return ErrAddonsDirNotExist if addons doesn't exist in a job": {
45+
mockAddons: func(ctrl *gomock.Controller) *Addons {
46+
ws := mocks.NewMockworkspaceReader(ctrl)
47+
ws.EXPECT().ReadAddonsDir(testJobName).
48+
Return(nil, testErr)
49+
return &Addons{
50+
wlName: testJobName,
51+
ws: ws,
52+
}
53+
},
54+
wantedErr: &ErrAddonsDirNotExist{
55+
WlName: testJobName,
56+
ParentErr: testErr,
57+
},
58+
},
59+
"print correct error message for ErrAddonsDirNotExist": {
60+
mockAddons: func(ctrl *gomock.Controller) *Addons {
61+
ws := mocks.NewMockworkspaceReader(ctrl)
62+
ws.EXPECT().ReadAddonsDir(testJobName).
63+
Return(nil, testErr)
64+
return &Addons{
65+
wlName: testJobName,
66+
ws: ws,
67+
}
68+
},
69+
wantedErr: errors.New("read addons directory for resizer: some error"),
70+
},
4171
"return err on invalid Metadata fields": {
4272
mockAddons: func(ctrl *gomock.Controller) *Addons {
4373
ws := mocks.NewMockworkspaceReader(ctrl)
@@ -49,8 +79,8 @@ func TestAddons_Template(t *testing.T) {
4979
second, _ := ioutil.ReadFile(filepath.Join("testdata", "merge", "invalid-metadata.yaml"))
5080
ws.EXPECT().ReadAddon(testSvcName, "invalid-metadata.yaml").Return(second, nil)
5181
return &Addons{
52-
svcName: testSvcName,
53-
ws: ws,
82+
wlName: testSvcName,
83+
ws: ws,
5484
}
5585
},
5686
wantedErr: errors.New(`metadata key "Services" defined in "first.yaml" at Ln 4, Col 7 is different than in "invalid-metadata.yaml" at Ln 3, Col 5`),
@@ -66,8 +96,8 @@ func TestAddons_Template(t *testing.T) {
6696
second, _ := ioutil.ReadFile(filepath.Join("testdata", "merge", "invalid-parameters.yaml"))
6797
ws.EXPECT().ReadAddon(testSvcName, "invalid-parameters.yaml").Return(second, nil)
6898
return &Addons{
69-
svcName: testSvcName,
70-
ws: ws,
99+
wlName: testSvcName,
100+
ws: ws,
71101
}
72102
},
73103
wantedErr: errors.New(`parameter logical ID "Name" defined in "first.yaml" at Ln 15, Col 9 is different than in "invalid-parameters.yaml" at Ln 3, Col 7`),
@@ -83,8 +113,8 @@ func TestAddons_Template(t *testing.T) {
83113
second, _ := ioutil.ReadFile(filepath.Join("testdata", "merge", "invalid-mappings.yaml"))
84114
ws.EXPECT().ReadAddon(testSvcName, "invalid-mappings.yaml").Return(second, nil)
85115
return &Addons{
86-
svcName: testSvcName,
87-
ws: ws,
116+
wlName: testSvcName,
117+
ws: ws,
88118
}
89119
},
90120
wantedErr: errors.New(`mapping "MyTableDynamoDBSettings.test" defined in "first.yaml" at Ln 21, Col 13 is different than in "invalid-mappings.yaml" at Ln 4, Col 7`),
@@ -100,8 +130,8 @@ func TestAddons_Template(t *testing.T) {
100130
second, _ := ioutil.ReadFile(filepath.Join("testdata", "merge", "invalid-conditions.yaml"))
101131
ws.EXPECT().ReadAddon(testSvcName, "invalid-conditions.yaml").Return(second, nil)
102132
return &Addons{
103-
svcName: testSvcName,
104-
ws: ws,
133+
wlName: testSvcName,
134+
ws: ws,
105135
}
106136
},
107137
wantedErr: errors.New(`condition "IsProd" defined in "first.yaml" at Ln 28, Col 13 is different than in "invalid-conditions.yaml" at Ln 2, Col 13`),
@@ -117,8 +147,8 @@ func TestAddons_Template(t *testing.T) {
117147
second, _ := ioutil.ReadFile(filepath.Join("testdata", "merge", "invalid-resources.yaml"))
118148
ws.EXPECT().ReadAddon(testSvcName, "invalid-resources.yaml").Return(second, nil)
119149
return &Addons{
120-
svcName: testSvcName,
121-
ws: ws,
150+
wlName: testSvcName,
151+
ws: ws,
122152
}
123153
},
124154
wantedErr: errors.New(`resource "MyTable" defined in "first.yaml" at Ln 34, Col 9 is different than in "invalid-resources.yaml" at Ln 3, Col 5`),
@@ -134,8 +164,8 @@ func TestAddons_Template(t *testing.T) {
134164
second, _ := ioutil.ReadFile(filepath.Join("testdata", "merge", "invalid-outputs.yaml"))
135165
ws.EXPECT().ReadAddon(testSvcName, "invalid-outputs.yaml").Return(second, nil)
136166
return &Addons{
137-
svcName: testSvcName,
138-
ws: ws,
167+
wlName: testSvcName,
168+
ws: ws,
139169
}
140170
},
141171
wantedErr: errors.New(`output "MyTableAccessPolicy" defined in "first.yaml" at Ln 85, Col 9 is different than in "invalid-outputs.yaml" at Ln 3, Col 5`),
@@ -151,8 +181,8 @@ func TestAddons_Template(t *testing.T) {
151181
second, _ := ioutil.ReadFile(filepath.Join("testdata", "merge", "second.yaml"))
152182
ws.EXPECT().ReadAddon(testSvcName, "second.yaml").Return(second, nil)
153183
return &Addons{
154-
svcName: testSvcName,
155-
ws: ws,
184+
wlName: testSvcName,
185+
ws: ws,
156186
}
157187
},
158188
wantedTemplate: func() string {

internal/pkg/addon/errors.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ import (
1010
"gopkg.in/yaml.v3"
1111
)
1212

13-
// ErrDirNotExist occurs when an addons directory for a service does not exist.
14-
type ErrDirNotExist struct {
15-
SvcName string
13+
// ErrAddonsDirNotExist occurs when an addons directory for a workload does not exist.
14+
type ErrAddonsDirNotExist struct {
15+
WlName string
1616
ParentErr error
1717
}
1818

19-
func (e *ErrDirNotExist) Error() string {
20-
return fmt.Sprintf("read addons directory for service %s: %v", e.SvcName, e.ParentErr)
19+
func (e *ErrAddonsDirNotExist) Error() string {
20+
return fmt.Sprintf("read addons directory for %s: %v", e.WlName, e.ParentErr)
2121
}
2222

2323
type errKeyAlreadyExists struct {

internal/pkg/cli/job_deploy.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"fmt"
99
"strings"
1010

11+
"github.com/aws/copilot-cli/internal/pkg/deploy"
12+
1113
"github.com/aws/copilot-cli/internal/pkg/addon"
1214
"github.com/aws/copilot-cli/internal/pkg/docker"
1315
"github.com/aws/copilot-cli/internal/pkg/repository"
@@ -167,7 +169,7 @@ func (o *deployJobOpts) Execute() error {
167169
func (o *deployJobOpts) pushAddonsTemplateToS3Bucket() (string, error) {
168170
template, err := o.addons.Template()
169171
if err != nil {
170-
var notExistErr *addon.ErrDirNotExist
172+
var notExistErr *addon.ErrAddonsDirNotExist
171173
if errors.As(err, &notExistErr) {
172174
// addons doesn't exist for job, the url is empty.
173175
return "", nil
@@ -180,7 +182,7 @@ func (o *deployJobOpts) pushAddonsTemplateToS3Bucket() (string, error) {
180182
}
181183

182184
reader := strings.NewReader(template)
183-
url, err := o.s3.PutArtifact(resources.S3Bucket, fmt.Sprintf(config.AddonsCfnTemplateNameFormat, o.name), reader)
185+
url, err := o.s3.PutArtifact(resources.S3Bucket, fmt.Sprintf(deploy.AddonsCfnTemplateNameFormat, o.name), reader)
184186
if err != nil {
185187
return "", fmt.Errorf("put addons artifact to bucket %s: %w", resources.S3Bucket, err)
186188
}
@@ -314,7 +316,7 @@ func (o *deployJobOpts) runtimeConfig(addonsURL string) (*stack.RuntimeConfig, e
314316
repoURL, ok := resources.RepositoryURLs[o.name]
315317
if !ok {
316318
return nil, &errRepoNotFound{
317-
svcName: o.name,
319+
wlName: o.name,
318320
envRegion: o.targetEnvironment.Region,
319321
appAccountID: o.targetApp.AccountID,
320322
}

internal/pkg/cli/job_package.go

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,20 @@ package cli
55

66
import (
77
"fmt"
8+
"io/ioutil"
9+
"os"
810

9-
"github.com/spf13/cobra"
10-
11+
"github.com/aws/copilot-cli/internal/pkg/aws/sessions"
1112
"github.com/aws/copilot-cli/internal/pkg/config"
13+
"github.com/aws/copilot-cli/internal/pkg/deploy/cloudformation"
14+
"github.com/aws/copilot-cli/internal/pkg/deploy/cloudformation/stack"
15+
"github.com/aws/copilot-cli/internal/pkg/manifest"
1216
"github.com/aws/copilot-cli/internal/pkg/term/command"
1317
"github.com/aws/copilot-cli/internal/pkg/term/prompt"
1418
"github.com/aws/copilot-cli/internal/pkg/term/selector"
1519
"github.com/aws/copilot-cli/internal/pkg/workspace"
20+
"github.com/spf13/afero"
21+
"github.com/spf13/cobra"
1622
)
1723

1824
const (
@@ -32,11 +38,16 @@ type packageJobOpts struct {
3238
packageJobVars
3339

3440
// Interfaces to interact with dependencies.
35-
ws wsJobDirReader
36-
store store
37-
runner runner
38-
sel wsSelector
39-
prompt prompter
41+
ws wsJobDirReader
42+
store store
43+
runner runner
44+
sel wsSelector
45+
prompt prompter
46+
stackSerializer func(mft interface{}, env *config.Environment, app *config.Application, rc stack.RuntimeConfig) (stackSerializer, error)
47+
48+
// Subcommand implementing svc_package's Execute()
49+
packageCmd actionCommand
50+
newPackageCmd func(*packageJobOpts)
4051
}
4152

4253
func newPackageJobOpts(vars packageJobVars) (*packageJobOpts, error) {
@@ -48,6 +59,11 @@ func newPackageJobOpts(vars packageJobVars) (*packageJobOpts, error) {
4859
if err != nil {
4960
return nil, fmt.Errorf("connect to config store: %w", err)
5061
}
62+
p := sessions.NewProvider()
63+
sess, err := p.Default()
64+
if err != nil {
65+
return nil, fmt.Errorf("retrieve default session: %w", err)
66+
}
5167
prompter := prompt.New()
5268
opts := &packageJobOpts{
5369
packageJobVars: vars,
@@ -57,6 +73,37 @@ func newPackageJobOpts(vars packageJobVars) (*packageJobOpts, error) {
5773
sel: selector.NewWorkspaceSelect(prompter, store, ws),
5874
prompt: prompter,
5975
}
76+
77+
opts.stackSerializer = func(mft interface{}, env *config.Environment, app *config.Application, rc stack.RuntimeConfig) (stackSerializer, error) {
78+
var serializer stackSerializer
79+
jobMft := mft.(*manifest.ScheduledJob)
80+
serializer, err := stack.NewScheduledJob(jobMft, env.Name, app.Name, rc)
81+
if err != nil {
82+
return nil, fmt.Errorf("init scheduled job stack serializer: %w", err)
83+
}
84+
return serializer, nil
85+
}
86+
87+
opts.newPackageCmd = func(o *packageJobOpts) {
88+
opts.packageCmd = &packageSvcOpts{
89+
packageSvcVars: packageSvcVars{
90+
name: o.name,
91+
envName: o.envName,
92+
appName: o.appName,
93+
tag: o.tag,
94+
outputDir: o.outputDir,
95+
},
96+
initAddonsClient: initPackageAddonsClient,
97+
ws: ws,
98+
store: o.store,
99+
appCFN: cloudformation.New(sess),
100+
stackWriter: os.Stdout,
101+
paramsWriter: ioutil.Discard,
102+
addonsWriter: ioutil.Discard,
103+
fs: &afero.Afero{Fs: afero.NewOsFs()},
104+
stackSerializer: o.stackSerializer,
105+
}
106+
}
60107
return opts, nil
61108
}
62109

@@ -100,7 +147,8 @@ func (o *packageJobOpts) Ask() error {
100147

101148
// Execute prints the CloudFormation template of the application for the environment.
102149
func (o *packageJobOpts) Execute() error {
103-
return nil
150+
o.newPackageCmd(o)
151+
return o.packageCmd.Execute()
104152
}
105153

106154
func (o *packageJobOpts) askJobName() error {

0 commit comments

Comments
 (0)