Skip to content

Commit ed78875

Browse files
authored
chore(cli): add Scheduled Job to copilot init. (#1548)
<!-- Provide summary of changes --> #1131 <!-- Issue number, if available. E.g. "Fixes #31", "Addresses #42, 77" --> 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 92eb5b9 commit ed78875

File tree

8 files changed

+311
-136
lines changed

8 files changed

+311
-136
lines changed

internal/pkg/cli/flag.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const (
2020
svcFlag = "svc"
2121
svcTypeFlag = "svc-type"
2222
jobTypeFlag = "job-type"
23+
typeFlag = "type"
2324
profileFlag = "profile"
2425
yesFlag = "yes"
2526
jsonFlag = "json"
@@ -120,6 +121,8 @@ Mutually exclusive with -%s, --%s`, imageFlagShort, imageFlag)
120121
%s`, strings.Join(template.QuoteSliceFunc(storageTypes), ", "))
121122
jobTypeFlagDescription = fmt.Sprintf(`Type of job to create. Must be one of:
122123
%s`, strings.Join(template.QuoteSliceFunc(manifest.JobTypes), ", "))
124+
wkldTypeFlagDescription = fmt.Sprintf(`Type of job or svc to create. Must be one of:
125+
%s`, strings.Join(template.QuoteSliceFunc(manifest.WorkloadTypes), ", "))
123126

124127
subnetsFlagDescription = fmt.Sprintf(`Optional. The subnet IDs for the task to use. Can be specified multiple times.
125128
Cannot be specified with '%s', '%s' or '%s'.`, appFlag, envFlag, taskDefaultFlag)

internal/pkg/cli/init.go

Lines changed: 185 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package cli
77
import (
88
"fmt"
99

10+
"github.com/aws/aws-sdk-go/aws"
1011
"github.com/aws/copilot-cli/cmd/copilot/template"
1112
"github.com/aws/copilot-cli/internal/pkg/aws/identity"
1213
"github.com/aws/copilot-cli/internal/pkg/aws/sessions"
@@ -41,33 +42,45 @@ type initVars struct {
4142
// Flags unique to "init" that's not provided by other sub-commands.
4243
shouldDeploy bool
4344
appName string
44-
svcType string
45+
wkldType string
4546
svcName string
4647
dockerfilePath string
48+
image string
4749
imageTag string
48-
port uint16
50+
51+
// Service specific flags
52+
port uint16
53+
54+
// Scheduled Job specific flags
55+
schedule string
56+
retries int
57+
timeout string
4958
}
5059

5160
type initOpts struct {
61+
initVars
62+
5263
ShouldDeploy bool // true means we should create a test environment and deploy the service to it. Defaults to false.
5364
promptForShouldDeploy bool // true means that the user set the ShouldDeploy flag explicitly.
5465

5566
// Sub-commands to execute.
5667
initAppCmd actionCommand
57-
initSvcCmd actionCommand
68+
initWlCmd actionCommand
5869
initEnvCmd actionCommand
5970
deploySvcCmd actionCommand
71+
deployJobCmd actionCommand
6072

6173
// Pointers to flag values part of sub-commands.
6274
// Since the sub-commands implement the actionCommand interface, without pointers to their internal fields
6375
// we have to resort to type-casting the interface. These pointers simplify data access.
64-
appName *string
65-
svcType *string
66-
svcName *string
67-
svcPort *uint16
68-
dockerfilePath *string
76+
appName *string
77+
port *uint16
78+
schedule *string
79+
initWkldVars *initWkldVars
6980

7081
prompt prompter
82+
83+
setupWorkoadInit func(*initOpts, string) error
7184
}
7285

7386
func newInitOpts(vars initVars) (*initOpts, error) {
@@ -92,7 +105,6 @@ func newInitOpts(vars initVars) (*initOpts, error) {
92105
if err != nil {
93106
return nil, err
94107
}
95-
96108
initAppCmd := &initAppOpts{
97109
initAppVars: initAppVars{
98110
name: vars.appName,
@@ -104,24 +116,6 @@ func newInitOpts(vars initVars) (*initOpts, error) {
104116
cfn: deployer,
105117
prog: spin,
106118
}
107-
wkldInitter := &initialize.WorkloadInitializer{Store: ssm, Ws: ws, Prog: spin, Deployer: deployer}
108-
initSvcCmd := &initSvcOpts{
109-
initWkldVars: initWkldVars{
110-
wkldType: vars.svcType,
111-
name: vars.svcName,
112-
dockerfilePath: vars.dockerfilePath,
113-
port: vars.port,
114-
appName: vars.appName,
115-
},
116-
fs: &afero.Afero{Fs: afero.NewOsFs()},
117-
118-
init: wkldInitter,
119-
sel: sel,
120-
prompt: prompt,
121-
setupParser: func(o *initSvcOpts) {
122-
o.df = dockerfile.New(o.fs, o.dockerfilePath)
123-
},
124-
}
125119
initEnvCmd := &initEnvOpts{
126120
initEnvVars: initEnvVars{
127121
appName: vars.appName,
@@ -153,22 +147,88 @@ func newInitOpts(vars initVars) (*initOpts, error) {
153147
cmd: command.New(),
154148
sessProvider: sessProvider,
155149
}
150+
deployJobCmd := &deployJobOpts{
151+
deployJobVars: deployJobVars{
152+
envName: defaultEnvironmentName,
153+
imageTag: vars.imageTag,
154+
appName: vars.appName,
155+
},
156+
store: ssm,
157+
prompt: prompt,
158+
ws: ws,
159+
unmarshal: manifest.UnmarshalWorkload,
160+
sel: sel,
161+
spinner: spin,
162+
cmd: command.New(),
163+
sessProvider: sessProvider,
164+
}
156165

157166
return &initOpts{
167+
initVars: vars,
158168
ShouldDeploy: vars.shouldDeploy,
159169

160170
initAppCmd: initAppCmd,
161-
initSvcCmd: initSvcCmd,
162171
initEnvCmd: initEnvCmd,
163172
deploySvcCmd: deploySvcCmd,
173+
deployJobCmd: deployJobCmd,
164174

165-
appName: &initAppCmd.name,
166-
svcType: &initSvcCmd.wkldType,
167-
svcName: &initSvcCmd.name,
168-
svcPort: &initSvcCmd.port,
169-
dockerfilePath: &initSvcCmd.dockerfilePath,
175+
appName: &initAppCmd.name,
170176

171177
prompt: prompt,
178+
179+
setupWorkoadInit: func(o *initOpts, wkldType string) error {
180+
wlInitializer := &initialize.WorkloadInitializer{Store: ssm, Ws: ws, Prog: spin, Deployer: deployer}
181+
wkldVars := initWkldVars{
182+
appName: *o.appName,
183+
wkldType: wkldType,
184+
name: vars.svcName,
185+
dockerfilePath: vars.dockerfilePath,
186+
image: vars.image,
187+
}
188+
switch t := wkldType; {
189+
case t == manifest.ScheduledJobType:
190+
jobVars := initJobVars{
191+
initWkldVars: wkldVars,
192+
schedule: vars.schedule,
193+
retries: vars.retries,
194+
timeout: vars.timeout,
195+
}
196+
197+
opts := initJobOpts{
198+
initJobVars: jobVars,
199+
200+
fs: &afero.Afero{Fs: afero.NewOsFs()},
201+
init: wlInitializer,
202+
sel: sel,
203+
prompt: prompt,
204+
}
205+
o.initWlCmd = &opts
206+
o.schedule = &opts.schedule // Surfaced via pointer for logging
207+
o.initWkldVars = &opts.initWkldVars
208+
case t == manifest.LoadBalancedWebServiceType || t == manifest.BackendServiceType:
209+
svcVars := initSvcVars{
210+
initWkldVars: wkldVars,
211+
port: vars.port,
212+
}
213+
opts := initSvcOpts{
214+
initSvcVars: svcVars,
215+
216+
fs: &afero.Afero{Fs: afero.NewOsFs()},
217+
init: wlInitializer,
218+
sel: sel,
219+
prompt: prompt,
220+
setupParser: func(o *initSvcOpts) {
221+
o.df = dockerfile.New(o.fs, o.dockerfilePath)
222+
},
223+
}
224+
o.initWlCmd = &opts
225+
o.port = &opts.port // Surfaced via pointer for logging.
226+
o.initWkldVars = &opts.initWkldVars
227+
default:
228+
return fmt.Errorf("unrecognized workload type")
229+
}
230+
return nil
231+
},
172232
}, nil
173233
}
174234

@@ -185,27 +245,46 @@ containerized services that operate together.`))
185245
if err := o.loadApp(); err != nil {
186246
return err
187247
}
188-
if err := o.loadSvc(); err != nil {
248+
249+
if err := o.loadWkld(); err != nil {
189250
return err
190251
}
191252

192-
log.Infof("Ok great, we'll set up a %s named %s in application %s listening on port %s.\n",
193-
color.HighlightUserInput(*o.svcType), color.HighlightUserInput(*o.svcName), color.HighlightUserInput(*o.appName), color.HighlightUserInput(fmt.Sprintf("%d", *o.svcPort)))
253+
o.logWorkloadTypeAck()
254+
194255
log.Infoln()
195256
if err := o.initAppCmd.Execute(); err != nil {
196257
return fmt.Errorf("execute app init: %w", err)
197258
}
198-
if err := o.initSvcCmd.Execute(); err != nil {
199-
return fmt.Errorf("execute svc init: %w", err)
259+
if err := o.initWlCmd.Execute(); err != nil {
260+
return fmt.Errorf("execute %s init: %w", o.wkldType, err)
200261
}
201262

202263
if err := o.deployEnv(); err != nil {
203264
return err
204265
}
266+
return o.deploy()
267+
}
268+
269+
func (o *initOpts) logWorkloadTypeAck() {
270+
if o.initWkldVars.wkldType == manifest.ScheduledJobType {
271+
log.Infof("Ok great, we'll set up a %s named %s in application %s running on the schedule %s.\n",
272+
color.HighlightUserInput(o.initWkldVars.wkldType), color.HighlightUserInput(o.initWkldVars.name), color.HighlightUserInput(o.initWkldVars.appName), color.HighlightUserInput(*o.schedule))
273+
return
274+
}
275+
if aws.Uint16Value(o.port) != 0 {
276+
log.Infof("Ok great, we'll set up a %s named %s in application %s listening on port %s.\n", color.HighlightUserInput(o.initWkldVars.wkldType), color.HighlightUserInput(o.initWkldVars.name), color.HighlightUserInput(o.initWkldVars.appName), color.HighlightUserInput(fmt.Sprintf("%d", *o.port)))
277+
} else {
278+
log.Infof("Ok great, we'll set up a %s named %s in application %s.\n", color.HighlightUserInput(o.initWkldVars.wkldType), color.HighlightUserInput(o.initWkldVars.name), color.HighlightUserInput(o.initWkldVars.appName))
279+
}
280+
}
205281

282+
func (o *initOpts) deploy() error {
283+
if o.initWkldVars.wkldType == manifest.ScheduledJobType {
284+
return o.deployJob()
285+
}
206286
return o.deploySvc()
207287
}
208-
209288
func (o *initOpts) loadApp() error {
210289
if err := o.initAppCmd.Ask(); err != nil {
211290
return fmt.Errorf("ask app init: %w", err)
@@ -216,16 +295,52 @@ func (o *initOpts) loadApp() error {
216295
return nil
217296
}
218297

219-
func (o *initOpts) loadSvc() error {
220-
if initSvcOpts, ok := o.initSvcCmd.(*initSvcOpts); ok {
221-
// Set the application name from app init to the service init command.
222-
initSvcOpts.appName = *o.appName
298+
func (o *initOpts) loadWkld() error {
299+
err := o.loadWkldCmd()
300+
if err != nil {
301+
return err
302+
}
303+
if err := o.initWlCmd.Ask(); err != nil {
304+
return fmt.Errorf("ask %s: %w", o.wkldType, err)
305+
}
306+
if err := o.initWlCmd.Validate(); err != nil {
307+
return fmt.Errorf("validate %s: %w", o.wkldType, err)
223308
}
224309

225-
if err := o.initSvcCmd.Ask(); err != nil {
226-
return fmt.Errorf("ask svc init: %w", err)
310+
return nil
311+
}
312+
313+
func (o *initOpts) loadWkldCmd() error {
314+
wkldType, err := o.askWorkload()
315+
if err != nil {
316+
return err
227317
}
228-
return o.initSvcCmd.Validate()
318+
if err := o.setupWorkoadInit(o, wkldType); err != nil {
319+
return err
320+
}
321+
322+
return nil
323+
}
324+
325+
func (o *initOpts) askWorkload() (string, error) {
326+
if o.wkldType != "" {
327+
return o.wkldType, nil
328+
}
329+
wkldInitTypePrompt := "Which " + color.Emphasize("workload type") + " best represents your architecture?"
330+
// Build the workload help prompt from existing helps text.
331+
wkldHelp := fmt.Sprintf(fmtSvcInitSvcTypeHelpPrompt,
332+
manifest.LoadBalancedWebServiceType,
333+
manifest.BackendServiceType,
334+
) + `
335+
336+
` + fmt.Sprintf(fmtJobInitTypeHelp, manifest.ScheduledJobType)
337+
338+
t, err := o.prompt.SelectOne(wkldInitTypePrompt, wkldHelp, manifest.WorkloadTypes, prompt.WithFinalMessage("Workload type:"))
339+
if err != nil {
340+
return "", fmt.Errorf("select service type: %w", err)
341+
}
342+
o.wkldType = t
343+
return t, nil
229344
}
230345

231346
// deployEnv prompts the user to deploy a test environment if the application doesn't already have one.
@@ -255,7 +370,7 @@ func (o *initOpts) deploySvc() error {
255370
}
256371
if deployOpts, ok := o.deploySvcCmd.(*deploySvcOpts); ok {
257372
// Set the service's name and app name to the deploy sub-command.
258-
deployOpts.name = *o.svcName
373+
deployOpts.name = o.initWkldVars.name
259374
deployOpts.appName = *o.appName
260375
}
261376

@@ -265,6 +380,22 @@ func (o *initOpts) deploySvc() error {
265380
return o.deploySvcCmd.Execute()
266381
}
267382

383+
func (o *initOpts) deployJob() error {
384+
if !o.ShouldDeploy {
385+
return nil
386+
}
387+
if deployOpts, ok := o.deployJobCmd.(*deployJobOpts); ok {
388+
// Set the service's name and app name to the deploy sub-command.
389+
deployOpts.name = o.initWkldVars.name
390+
deployOpts.appName = *o.appName
391+
}
392+
393+
if err := o.deployJobCmd.Ask(); err != nil {
394+
return err
395+
}
396+
return o.deployJobCmd.Execute()
397+
}
398+
268399
func (o *initOpts) askShouldDeploy() error {
269400
v, err := o.prompt.Confirm(initShouldDeployPrompt, initShouldDeployHelpPrompt, prompt.WithFinalMessage("Deploy:"))
270401
if err != nil {
@@ -294,20 +425,24 @@ func BuildInitCmd() *cobra.Command {
294425
log.Info("\nNo problem, you can deploy your service later:\n")
295426
log.Infof("- Run %s to create your staging environment.\n",
296427
color.HighlightCode(fmt.Sprintf("copilot env init --name %s --profile %s --app %s", defaultEnvironmentName, defaultEnvironmentProfile, *opts.appName)))
297-
for _, followup := range opts.initSvcCmd.RecommendedActions() {
428+
for _, followup := range opts.initWlCmd.RecommendedActions() {
298429
log.Infof("- %s\n", followup)
299430
}
300431
}
301432
return nil
302433
}),
303434
}
304435
cmd.Flags().StringVarP(&vars.appName, appFlag, appFlagShort, tryReadingAppName(), appFlagDescription)
305-
cmd.Flags().StringVarP(&vars.svcName, svcFlag, svcFlagShort, "", svcFlagDescription)
306-
cmd.Flags().StringVarP(&vars.svcType, svcTypeFlag, svcTypeFlagShort, "", svcTypeFlagDescription)
436+
cmd.Flags().StringVarP(&vars.svcName, nameFlag, svcFlagShort, "", svcFlagDescription)
437+
cmd.Flags().StringVarP(&vars.wkldType, typeFlag, svcTypeFlagShort, "", wkldTypeFlagDescription)
307438
cmd.Flags().StringVarP(&vars.dockerfilePath, dockerFileFlag, dockerFileFlagShort, "", dockerFileFlagDescription)
439+
cmd.Flags().StringVarP(&vars.image, imageFlag, imageFlagShort, "", imageFlagDescription)
308440
cmd.Flags().BoolVar(&vars.shouldDeploy, deployFlag, false, deployTestFlagDescription)
309441
cmd.Flags().StringVar(&vars.imageTag, imageTagFlag, "", imageTagFlagDescription)
310442
cmd.Flags().Uint16Var(&vars.port, svcPortFlag, 0, svcPortFlagDescription)
443+
cmd.Flags().StringVar(&vars.schedule, scheduleFlag, "", scheduleFlagDescription)
444+
cmd.Flags().StringVar(&vars.timeout, timeoutFlag, "", timeoutFlagDescription)
445+
cmd.Flags().IntVar(&vars.retries, retriesFlag, 0, retriesFlagDescription)
311446
cmd.SetUsageTemplate(template.Usage)
312447
cmd.Annotations = map[string]string{
313448
"group": group.GettingStarted,

0 commit comments

Comments
 (0)