internal/pkg/cli/deploy.go (665 lines of code) (raw):
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cli
import (
"errors"
"fmt"
"math"
"slices"
"strconv"
"strings"
"github.com/aws/copilot-cli/internal/pkg/queue"
"github.com/dustin/go-humanize"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ssm"
"github.com/dustin/go-humanize/english"
"github.com/spf13/afero"
"github.com/spf13/cobra"
"github.com/aws/copilot-cli/cmd/copilot/template"
"github.com/aws/copilot-cli/internal/pkg/aws/identity"
"github.com/aws/copilot-cli/internal/pkg/aws/sessions"
"github.com/aws/copilot-cli/internal/pkg/cli/group"
"github.com/aws/copilot-cli/internal/pkg/config"
"github.com/aws/copilot-cli/internal/pkg/deploy/cloudformation"
"github.com/aws/copilot-cli/internal/pkg/exec"
"github.com/aws/copilot-cli/internal/pkg/initialize"
"github.com/aws/copilot-cli/internal/pkg/manifest"
"github.com/aws/copilot-cli/internal/pkg/manifest/manifestinfo"
"github.com/aws/copilot-cli/internal/pkg/term/color"
"github.com/aws/copilot-cli/internal/pkg/term/log"
termprogress "github.com/aws/copilot-cli/internal/pkg/term/progress"
"github.com/aws/copilot-cli/internal/pkg/term/prompt"
"github.com/aws/copilot-cli/internal/pkg/term/selector"
"github.com/aws/copilot-cli/internal/pkg/version"
"github.com/aws/copilot-cli/internal/pkg/workspace"
)
const (
svcWkldType = "svc"
jobWkldType = "job"
optionAllRemainingWorkloads = "All remaining workloads"
)
// filterItemsByStrings is a generic function to return the subset of wantedStrings that exists in possibleItems.
// stringFunc is a method to convert the generic item type (T) to a string; for example, one can convert a struct of type
// *config.Workload to a string by passing
//
// func(w *config.Workload) string { return w.Name }.
//
// Likewise, filterItemsByStrings can work on a list of strings by returning the unmodified item:
//
// filterItemsByStrings(wantedStrings, stringSlice2, func(s string) string { return s })
//
// It returns a list of strings (items whose stringFunc() exists in the list of wantedStrings).
func filterItemsByStrings[T any](wantedStrings []string, possibleItems []T, stringFunc func(T) string) []string {
m := make(map[string]bool)
for _, item := range wantedStrings {
m[item] = true
}
res := make([]string, 0, len(wantedStrings))
for _, item := range possibleItems {
if m[stringFunc(item)] {
res = append(res, stringFunc(item))
}
}
return res
}
type deployVars struct {
deployWkldVars
workloadNames []string
deployAllWorkloads bool
yesInitWkld bool
deployEnv *bool
yesInitEnv *bool
region string
tempCreds tempCredsVars
profile string
}
type deployOpts struct {
deployVars
newWorkloadAdder func() wkldInitializerWithoutManifest
setupDeployCmd func(*deployOpts, string, string) (actionCommand, error)
newInitEnvCmd func(o *deployOpts) (cmd, error)
newDeployEnvCmd func(o *deployOpts) (cmd, error)
sel wsSelector
store store
ws wsWlDirReader
prompt prompter
// values for logging
wlType string
// values for initialization logic
envExistsInApp bool
envExistsInWs bool
// Cached variables
wsEnvironments []string
wsWorkloads []string
storeWorkloads []*config.Workload
initializedWsWorkloads []string
deploymentOrderMap map[string]int
}
func newDeployOpts(vars deployVars) (*deployOpts, error) {
sessProvider := sessions.ImmutableProvider(sessions.UserAgentExtras("deploy"))
defaultSess, err := sessProvider.Default()
if err != nil {
return nil, fmt.Errorf("default session: %v", err)
}
store := config.NewSSMStore(identity.New(defaultSess), ssm.New(defaultSess), aws.StringValue(defaultSess.Config.Region))
ws, err := workspace.Use(afero.NewOsFs())
if err != nil {
return nil, err
}
prompter := prompt.New()
return &deployOpts{
deployVars: vars,
store: store,
sel: selector.NewLocalWorkloadSelector(prompter, store, ws),
ws: ws,
prompt: prompter,
newWorkloadAdder: func() wkldInitializerWithoutManifest {
return &initialize.WorkloadInitializer{
Store: store,
Deployer: cloudformation.New(defaultSess),
Ws: ws,
Prog: termprogress.NewSpinner(log.DiagnosticWriter),
}
},
newDeployEnvCmd: func(o *deployOpts) (cmd, error) {
// This command passes flags down from
return newEnvDeployOpts(deployEnvVars{
appName: o.appName,
name: o.envName,
forceNewUpdate: o.forceNewUpdate,
disableRollback: o.disableRollback,
showDiff: o.showDiff,
skipDiffPrompt: o.skipDiffPrompt,
allowEnvDowngrade: o.allowWkldDowngrade,
detach: o.detach,
})
},
newInitEnvCmd: func(o *deployOpts) (cmd, error) {
// This vars struct sets "default config" so that no vpc questions are asked during env init and the manifest
// is not written. It passes in credential flags and allow-downgrade from the parent command.
return newInitEnvOpts(initEnvVars{
appName: o.appName,
name: o.envName,
profile: o.profile,
defaultConfig: true,
allowAppDowngrade: o.allowWkldDowngrade,
tempCreds: o.tempCreds,
region: o.region,
})
},
setupDeployCmd: func(o *deployOpts, workloadName, workloadType string) (actionCommand, error) {
switch {
case slices.Contains(manifestinfo.JobTypes(), workloadType):
opts := &deployJobOpts{
deployWkldVars: o.deployWkldVars,
store: o.store,
ws: o.ws,
newInterpolator: newManifestInterpolator,
unmarshal: manifest.UnmarshalWorkload,
sel: selector.NewLocalWorkloadSelector(o.prompt, o.store, ws),
cmd: exec.NewCmd(),
templateVersion: version.LatestTemplateVersion(),
sessProvider: sessProvider,
}
opts.newJobDeployer = func() (workloadDeployer, error) {
return newJobDeployer(opts)
}
opts.name = workloadName
return opts, nil
case slices.Contains(manifestinfo.ServiceTypes(), workloadType):
opts := &deploySvcOpts{
deployWkldVars: o.deployWkldVars,
store: o.store,
ws: o.ws,
newInterpolator: newManifestInterpolator,
unmarshal: manifest.UnmarshalWorkload,
spinner: termprogress.NewSpinner(log.DiagnosticWriter),
sel: selector.NewLocalWorkloadSelector(o.prompt, o.store, ws),
prompt: o.prompt,
cmd: exec.NewCmd(),
sessProvider: sessProvider,
templateVersion: version.LatestTemplateVersion(),
}
opts.newSvcDeployer = func() (workloadDeployer, error) {
return newSvcDeployer(opts)
}
opts.name = workloadName
// Multi-deployments can have flags specified which are not compatible with all service types.
// Currently only forceNewUpdate is incompatible with Static Site types.
if workloadType == manifestinfo.StaticSiteType {
opts.forceNewUpdate = false
}
return opts, nil
}
return nil, fmt.Errorf("unrecognized workload type %s", workloadType)
},
}, nil
}
// maybeInitWkld decides whether a workload needs to be initialized before deployment.
// We do not prompt for workload initialization; specifying the workload by name suffices
// to convey the customer intention. When the customer specifies --all and --init-wkld,
// we will add all un-initialized local workloads to the list to be deployed.
// When the customer does not specify --init-wkld with --all, we will only deploy initialized workloads.
func (o *deployOpts) maybeInitWkld(name string) error {
// Confirm that the workload needs to be initialized after asking for the name.
initializedWorkloads, err := o.listInitializedLocalWorkloads()
if err != nil {
return err
}
// Workload is already initialized. Return early.
if slices.Contains(initializedWorkloads, name) {
return nil
}
// Get workload type and confirm readable manifest.
mf, err := o.ws.ReadWorkloadManifest(name)
if err != nil {
return fmt.Errorf("read manifest for workload %s: %w", name, err)
}
workloadType, err := mf.WorkloadType()
if err != nil {
return fmt.Errorf("get workload type from manifest for workload %s: %w", name, err)
}
if !slices.Contains(manifestinfo.WorkloadTypes(), workloadType) {
return fmt.Errorf("unrecognized workload type %q in manifest for workload %s", workloadType, name)
}
wkldAdder := o.newWorkloadAdder()
if err = wkldAdder.AddWorkloadToApp(o.appName, name, workloadType); err != nil {
return fmt.Errorf("add workload to app: %w", err)
}
return nil
}
type workloadPriority struct {
priority int
workloads []string
}
func (w workloadPriority) LessThan(a workloadPriority) bool {
return w.priority < a.priority
}
// parseDeploymentOrderTags takes a list of workload names, optionally tagged with a priority. Lower priorities will be
// deployed first.
//
// []string{"fe/1", "be/1", "worker/2"}
//
// It returns a map from the workload name to its priority, and errors out if the order tag is incorrectly formatted.
//
// map[string]int{"fe": 1, "be": 1, "worker": 2}
//
// It is possible to have multiple workloads of the same priority. We don't guarantee that workloads within priority
// groups will have identical deployment orders each time.
func (o *deployOpts) parseDeploymentOrderTags(namesWithOptionalOrder []string) error {
if len(o.deploymentOrderMap) > 0 {
return nil
}
prioritiesMap := make(map[string]int)
// First pass through flags to identify priority groups
for _, wkldName := range namesWithOptionalOrder {
parts := strings.Split(wkldName, "/")
// If there's no priority tag, deploy this service after everything else. Signify this with math.MaxInt priority.
// If there's a valid tag, add it to the map of priorities.
if len(parts) == 1 {
prioritiesMap[parts[0]] = math.MaxInt
} else if len(parts) == 2 {
order, err := strconv.Atoi(parts[1])
if err != nil {
return fmt.Errorf("parse deployment order for workloads: %w", err)
}
prioritiesMap[parts[0]] = order
} else {
return fmt.Errorf("invalid deployment order for workload %s", wkldName)
}
}
o.deploymentOrderMap = prioritiesMap
return nil
}
// getDeploymentOrder parses names and tags from the --name flag, respecting "all".
// It returns an ordered list of which workloads should be deployed and when.
// For example, when a customer specifies "fe/2,be/1,worker,job" and the --all flag
// in a workspace where there also exists a `db` service, this function will return
//
// [][]string{ {"be"}, {"fe"}, {"worker", "job", "db"} }.
//
// TODO: when there's a dependsOn field in the manifest, we should modify this function to respect it.
func (o *deployOpts) getDeploymentOrder() ([][]string, error) {
// Get a map from workload name to deployment priority
if err := o.parseDeploymentOrderTags(o.workloadNames); err != nil {
return nil, err
}
// Iterate over priority map to invert it and get groups of workloads with the same priority.
groupsMap := make(map[int][]string)
for k, v := range o.deploymentOrderMap {
groupsMap[v] = append(groupsMap[v], k)
}
// If --all is specified, we need to add the remainder of workloads with math.MaxInt priority according to whether or not --init-wkld is specified.
if o.deployAllWorkloads {
specifiedWorkloadList := make([]string, 0, len(o.deploymentOrderMap))
for k := range o.deploymentOrderMap {
specifiedWorkloadList = append(specifiedWorkloadList, k)
}
if o.yesInitWkld {
// Add all unspecified local workloads to the list of workloads to be deployed.
localWorkloads, err := o.listLocalWorkloads()
if err != nil {
return nil, err
}
workloadsToAppend := slices.DeleteFunc(slices.Clone(localWorkloads), func(s string) bool { return slices.Contains(specifiedWorkloadList, s) })
if len(workloadsToAppend) != 0 {
groupsMap[math.MaxInt] = append(groupsMap[math.MaxInt], workloadsToAppend...)
}
} else {
// Otherwise (--init-wkld is false): get only get initialized local workloads.
initializedWorkloads, err := o.listInitializedLocalWorkloads()
if err != nil {
return nil, err
}
workloadsToAppend := slices.DeleteFunc(slices.Clone(initializedWorkloads), func(s string) bool { return slices.Contains(specifiedWorkloadList, s) })
if len(workloadsToAppend) != 0 {
groupsMap[math.MaxInt] = append(groupsMap[math.MaxInt], workloadsToAppend...)
}
}
}
if len(groupsMap) == 0 {
log.Infoln("No workloads to deploy. Did you mean to specify --init-wkld with --all?")
return nil, errors.New("generate deployment groups: no workloads were specified")
}
deploymentGroups := queue.NewPriorityQueue[workloadPriority]()
for k, v := range groupsMap {
deploymentGroups.Push(workloadPriority{
priority: k,
workloads: v,
})
}
res := make([][]string, 0, deploymentGroups.Len())
for deploymentGroups.Len() > 0 {
v, ok := deploymentGroups.Pop()
if !ok || v == nil {
continue
}
res = append(res, v.workloads)
}
return res, nil
}
func (o *deployOpts) listStoreWorkloads() ([]*config.Workload, error) {
if o.storeWorkloads != nil {
return o.storeWorkloads, nil
}
wls, err := o.store.ListWorkloads(o.appName)
if err != nil {
return nil, fmt.Errorf("retrieve store workloads: %w", err)
}
o.storeWorkloads = wls
return o.storeWorkloads, nil
}
func (o *deployOpts) listLocalWorkloads() ([]string, error) {
if o.wsWorkloads != nil {
return o.wsWorkloads, nil
}
localWorkloads, err := o.ws.ListWorkloads()
if err != nil {
return nil, fmt.Errorf("retrieve workspace workloads: %w", err)
}
o.wsWorkloads = localWorkloads
return o.wsWorkloads, nil
}
func (o *deployOpts) listInitializedLocalWorkloads() ([]string, error) {
if o.initializedWsWorkloads != nil {
return o.initializedWsWorkloads, nil
}
storeWls, err := o.listStoreWorkloads()
if err != nil {
return nil, err
}
localWorkloads, err := o.listLocalWorkloads()
if err != nil {
return nil, err
}
o.initializedWsWorkloads = filterItemsByStrings(localWorkloads, storeWls, func(workload *config.Workload) string { return workload.Name })
return o.initializedWsWorkloads, nil
}
type workloadCommand struct {
actionCommand
name string
}
func getTotalNumberOfWorkloads(deploymentGroups [][]workloadCommand) int {
var count int
for i := 0; i < len(deploymentGroups); i++ {
count += len(deploymentGroups[i])
}
return count
}
func (o *deployOpts) Run() error {
if err := o.askNames(); err != nil {
return err
}
if err := o.askEnv(); err != nil {
return err
}
if err := o.checkEnvExists(); err != nil {
return err
}
if err := o.maybeInitEnv(); err != nil {
return err
}
if err := o.maybeDeployEnv(); err != nil {
return err
}
deploymentOrderGroups, err := o.getDeploymentOrder()
if err != nil {
return err
}
cmds := make([][]workloadCommand, len(deploymentOrderGroups))
// Do all our asking before executing deploy commands.
// Also initialize workloads that need initialization (if they're specified by name and un-initialized,
// it means the customer probably wants to init them).
log.Infoln("Checking for all required information. We may ask you some questions.")
for order, deploymentGroup := range deploymentOrderGroups {
for _, workload := range deploymentGroup {
// 1. Decide whether the current workload needs initialization.
if err := o.maybeInitWkld(workload); err != nil {
return err
}
// 2. Set up workload command.
deployCmd, err := o.loadWkldCmd(workload)
if err != nil {
return err
}
cmds[order] = append(cmds[order], workloadCommand{
name: workload,
actionCommand: deployCmd,
})
// 3. Ask() and Validate() for required info.
if err := deployCmd.Ask(); err != nil {
return fmt.Errorf("ask %s deploy: %w", o.wlType, err)
}
if err := deployCmd.Validate(); err != nil {
return fmt.Errorf("validate %s deploy: %w", o.wlType, err)
}
}
}
if count := getTotalNumberOfWorkloads(cmds); count > 1 {
logDeploymentOrderInfo(cmds, count)
}
for g, deploymentGroup := range cmds {
// TODO parallelize this. Steps involve:
// 1. Modify the cmd to optionally disable progress tracker
// 2. Modify labeledSyncBuffer so it can display a spinner.
// 3. Wrap Execute() in a goroutine with ErrorGroup and context
for i, cmd := range deploymentGroup {
if err := cmd.Execute(); err != nil {
var errNoInfraChanges *errNoInfrastructureChanges
if !errors.As(err, &errNoInfraChanges) {
return fmt.Errorf("execute deployment %d of %d in group %d: %w", i+1, len(deploymentGroup), g+1, err)
}
// Don't run recommended actions if there is an infra error. It's possible for RecommendActions to panic
// when it returns an error (the actionRecommender return is nil in error cases and the struct member
// is never set.
continue
}
if err := cmd.RecommendActions(); err != nil {
return err
}
}
}
return nil
}
func logDeploymentOrderInfo(cmds [][]workloadCommand, totalCount int) {
log.Infof("Will deploy %d %s in the following order.\n", totalCount, english.PluralWord(totalCount, "workload", ""))
for i := 0; i < len(cmds); i++ {
names := make([]string, 0, len(cmds))
for _, cmd := range cmds[i] {
names = append(names, cmd.name)
}
log.Infof("%d. %s\n", i+1, strings.Join(names, ", "))
}
}
func (o *deployOpts) askNames() error {
if o.workloadNames != nil || len(o.workloadNames) != 0 {
return nil
}
if o.deployAllWorkloads {
// --all and --init-wkld=true, means we should use ALL local workloads as our list of names.
if o.yesInitWkld {
o.workloadNames = o.wsWorkloads
return nil
}
// --all and --init-wkld=false means we should only use the initialized local workloads.
o.workloadNames = o.initializedWsWorkloads
return nil
}
names, err := o.sel.Workloads("Select one or more services or jobs in your workspace.", "")
if err != nil {
return fmt.Errorf("select service or job: %w", err)
}
if len(names) == 1 {
o.workloadNames = names
return nil
}
// Select workload priority by repeatedly prompting with a multiselect until the list of options is depleted.
options := names
taggedOptions := make([]string, 0, len(options))
currentPriority := 1
for len(options) > 0 {
selectedNames, err := o.prompt.MultiSelect(
fmt.Sprintf("Which workload(s) would you like to deploy in your %s deployment group?", humanize.Ordinal(currentPriority)),
"These workloads will be deployed together.",
append(options, optionAllRemainingWorkloads),
prompt.RequireMinItems(1),
prompt.WithFinalMessage(fmt.Sprintf("Group %d", currentPriority)),
)
if err != nil {
return fmt.Errorf("select workloads for deployment group: %w", err)
}
if slices.Contains(selectedNames, optionAllRemainingWorkloads) {
selectedNames = options
}
// Tag all selected workloads with the given priority.
for _, name := range selectedNames {
taggedOptions = append(taggedOptions, fmt.Sprintf("%s/%d", name, currentPriority))
}
options = slices.DeleteFunc(options, func(s string) bool { return slices.Contains(selectedNames, s) })
currentPriority++
}
if err := o.parseDeploymentOrderTags(taggedOptions); err != nil {
return err
}
return nil
}
func (o *deployOpts) listWsEnvironments() ([]string, error) {
if o.wsEnvironments == nil {
envs, err := o.ws.ListEnvironments()
if err != nil {
return nil, err
}
if len(envs) == 0 {
envs = []string{}
}
o.wsEnvironments = envs
return o.wsEnvironments, nil
}
return o.wsEnvironments, nil
}
func (o *deployOpts) askEnv() error {
if o.envName != "" {
return nil
}
localEnvs, err := o.listWsEnvironments()
if err != nil {
return fmt.Errorf("get workspace environments: %w", err)
}
initializedEnvs, err := o.store.ListEnvironments(o.appName)
if err != nil {
return fmt.Errorf("get initialized environments: %w", err)
}
// Get uninitialized local environments and append them to the env selector call.
var extraOptions []prompt.Option
for _, localEnv := range localEnvs {
var envIsInitted bool
for _, inittedEnv := range initializedEnvs {
if inittedEnv.Name == localEnv {
envIsInitted = true
break
}
}
if envIsInitted {
continue
}
extraOptions = append(extraOptions, prompt.Option{Value: localEnv, Hint: "uninitialized"})
}
o.envName, err = o.sel.Environment("Select an environment to deploy to", "", o.appName, extraOptions...)
if err != nil {
return fmt.Errorf("get environment name: %w", err)
}
return nil
}
// checkEnvExists checks whether the environment is initialized and has a local manifest.
func (o *deployOpts) checkEnvExists() error {
o.envExistsInApp = true
_, err := o.store.GetEnvironment(o.appName, o.envName)
if err != nil {
var errNotFound *config.ErrNoSuchEnvironment
if !errors.As(err, &errNotFound) {
return fmt.Errorf("get environment from config store: %w", err)
}
o.envExistsInApp = false
}
envs, err := o.listWsEnvironments()
if err != nil {
return fmt.Errorf("list environments in workspace: %w", err)
}
o.envExistsInWs = slices.Contains(envs, o.envName)
// the desired environment doesn't actually exist.
if !o.envExistsInApp && !o.envExistsInWs {
log.Errorf("Environment %q does not exist in the current application or workspace. Please initialize it by running %s.\n", o.envName, color.HighlightCode("copilot env init"))
return fmt.Errorf("environment %q does not exist in the workspace", o.envName)
}
if o.envExistsInApp && !o.envExistsInWs {
log.Infof("Manifest for environment %q does not exist in the current workspace. To deploy this environment, generate a manifest with %s", o.envName, color.HighlightCode("copilot env show --manifest"))
}
return nil
}
func (o *deployOpts) maybeInitEnv() error {
if o.envExistsInApp {
return nil
}
// If no initialization flags were specified and the env wasn't initialized, ask to confirm.
if !o.envExistsInApp && o.yesInitEnv == nil {
v, err := o.prompt.Confirm(fmt.Sprintf("Environment %q does not exist in app %q. Initialize it?", o.envName, o.appName), "")
if err != nil {
return fmt.Errorf("confirm env init: %w", err)
}
o.yesInitEnv = aws.Bool(v)
}
if aws.BoolValue(o.yesInitEnv) {
cmd, err := o.newInitEnvCmd(o)
if err != nil {
return fmt.Errorf("load env init command : %w", err)
}
if err = cmd.Validate(); err != nil {
return err
}
if err = cmd.Ask(); err != nil {
return err
}
if err = cmd.Execute(); err != nil {
return err
}
if o.deployEnv == nil {
log.Infof("Environment %q was just initialized. We'll deploy it now.\n", o.envName)
o.deployEnv = aws.Bool(true)
} else if !aws.BoolValue(o.deployEnv) {
log.Errorf("Environment is not deployed but --%s=false was specified. Deploy the environment with %s in order to deploy a workload to it.\n", deployEnvFlag, color.HighlightCode("copilot env deploy"))
return fmt.Errorf("environment %s was initialized but has not been deployed", o.envName)
}
return nil
}
log.Errorf("Environment %q does not exist in application %q and was not initialized after prompting.\n", o.envName, o.appName)
return fmt.Errorf("env %s does not exist in app %s", o.envName, o.appName)
}
func (o *deployOpts) maybeDeployEnv() error {
if !o.envExistsInWs {
return nil
}
if aws.BoolValue(o.deployEnv) {
cmd, err := o.newDeployEnvCmd(o)
if err != nil {
return fmt.Errorf("set up env deploy command: %w", err)
}
if err = cmd.Validate(); err != nil {
return err
}
if err = cmd.Ask(); err != nil {
return err
}
return cmd.Execute()
}
return nil
}
func (o *deployOpts) loadWkldCmd(name string) (actionCommand, error) {
wl, err := o.store.GetWorkload(o.appName, name)
if err != nil {
return nil, fmt.Errorf("retrieve %s from application %s: %w", o.appName, name, err)
}
cmd, err := o.setupDeployCmd(o, name, wl.Type)
if err != nil {
return nil, err
}
if slices.Contains(manifestinfo.JobTypes(), wl.Type) {
o.wlType = jobWkldType
return cmd, nil
}
o.wlType = svcWkldType
return cmd, nil
}
// BuildDeployCmd is the deploy command.
func BuildDeployCmd() *cobra.Command {
vars := deployVars{}
var initEnvironment bool
var deployEnvironment bool
cmd := &cobra.Command{
Use: "deploy",
Short: "Deploy one or more Copilot jobs or services.",
Long: "Deploy one or more Copilot jobs or services.",
Example: `
Deploys a service named "frontend" to a "test" environment.
/code $ copilot deploy --name frontend --env test
Deploys a job named "mailer" with additional resource tags to a "prod" environment.
/code $ copilot deploy -n mailer -e prod --resource-tags source/revision=bb133e7,deployment/initiator=manual
Initializes and deploys an environment named "test" in us-west-2 under the "default" profile with local manifest,
then deploys a service named "api"
/code $ copilot deploy --init-env --deploy-env --env test --name api --profile default --region us-west-2
Initializes and deploys a service named "backend" to a "prod" environment.
/code $ copilot deploy --init-wkld --env prod --name backend
Deploys all local, initialized workloads in no particular order.
/code $ copilot deploy --all --env prod --name backend
Deploys multiple workloads in a prescribed order (fe and worker, then be).
/code $ copilot deploy -n fe/1 -n be/2 -n worker/1
Initializes and deploys all local workloads after deploying environment changes.
/code $ copilot deploy --all --init-wkld --deploy-env -e prod`,
RunE: runCmdE(func(cmd *cobra.Command, args []string) error {
opts, err := newDeployOpts(vars)
if err != nil {
return err
}
if cmd.Flags().Changed(yesInitEnvFlag) {
opts.yesInitEnv = aws.Bool(false)
if initEnvironment {
opts.yesInitEnv = aws.Bool(true)
}
}
if cmd.Flags().Changed(deployEnvFlag) {
opts.deployEnv = aws.Bool(false)
if deployEnvironment {
opts.deployEnv = aws.Bool(true)
}
}
if err := opts.Run(); err != nil {
return err
}
return nil
}),
}
cmd.Flags().StringVarP(&vars.appName, appFlag, appFlagShort, tryReadingAppName(), appFlagDescription)
cmd.Flags().StringSliceVarP(&vars.workloadNames, nameFlag, nameFlagShort, nil, workloadsFlagDescription)
cmd.Flags().StringVarP(&vars.envName, envFlag, envFlagShort, "", envFlagDescription)
cmd.Flags().StringVar(&vars.imageTag, imageTagFlag, "", imageTagFlagDescription)
cmd.Flags().StringToStringVar(&vars.resourceTags, resourceTagsFlag, nil, resourceTagsFlagDescription)
cmd.Flags().BoolVar(&vars.forceNewUpdate, forceFlag, false, forceFlagDescription)
cmd.Flags().BoolVar(&vars.disableRollback, noRollbackFlag, false, noRollbackFlagDescription)
cmd.Flags().BoolVar(&vars.allowWkldDowngrade, allowDowngradeFlag, false, allowDowngradeFlagDescription)
cmd.Flags().BoolVar(&vars.detach, detachFlag, false, detachFlagDescription)
cmd.Flags().BoolVar(&deployEnvironment, deployEnvFlag, false, deployEnvFlagDescription)
cmd.Flags().BoolVar(&initEnvironment, yesInitEnvFlag, false, yesInitEnvFlagDescription)
cmd.Flags().BoolVar(&vars.yesInitWkld, yesInitWorkloadFlag, false, yesInitWorkloadFlagDescription)
cmd.Flags().BoolVar(&vars.deployAllWorkloads, allFlag, false, allWorkloadsFlagDescription)
cmd.Flags().StringVar(&vars.profile, profileFlag, "", profileFlagDescription)
cmd.Flags().StringVar(&vars.tempCreds.AccessKeyID, accessKeyIDFlag, "", accessKeyIDFlagDescription)
cmd.Flags().StringVar(&vars.tempCreds.SecretAccessKey, secretAccessKeyFlag, "", secretAccessKeyFlagDescription)
cmd.Flags().StringVar(&vars.tempCreds.SessionToken, sessionTokenFlag, "", sessionTokenFlagDescription)
cmd.Flags().StringVar(&vars.region, regionFlag, "", envRegionTokenFlagDescription)
cmd.SetUsageTemplate(template.Usage)
cmd.Annotations = map[string]string{
"group": group.Release,
}
return cmd
}