Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 63 additions & 11 deletions internal/pkg/cli/app_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ type initAppOpts struct {
prompt prompter
prog progress
iam policyLister
iamRoleManager roleManager
isSessionFromEnvVars func() (bool, error)

existingWorkspace func() (wsAppManager, error)
Expand All @@ -70,6 +71,7 @@ func newInitAppOpts(vars initAppVars) (*initAppOpts, error) {
}
fs := afero.NewOsFs()
identity := identity.New(sess)
iam := iam.New(sess)
return &initAppOpts{
initAppVars: vars,
identity: identity,
Expand All @@ -79,7 +81,8 @@ func newInitAppOpts(vars initAppVars) (*initAppOpts, error) {
cfn: cloudformation.New(sess, cloudformation.WithProgressTracker(os.Stderr)),
prompt: prompt.New(),
prog: termprogress.NewSpinner(log.DiagnosticWriter),
iam: iam.New(sess),
iam: iam,
iamRoleManager: iam,
Comment thread
dannyrandall marked this conversation as resolved.
isSessionFromEnvVars: func() (bool, error) {
return sessions.AreCredsFromEnvVars(sess)
},
Expand Down Expand Up @@ -144,6 +147,9 @@ https://aws.github.io/copilot-cli/docs/credentials/`)
log.Infoln(fmt.Sprintf(
"Your workspace is registered to application %s.",
color.HighlightUserInput(summary.Application)))
if err := o.validateAppName(summary.Application); err != nil {
return err
}
o.name = summary.Application
return nil
}
Expand Down Expand Up @@ -243,21 +249,36 @@ func (o *initAppOpts) Execute() error {
}

func (o *initAppOpts) validateAppName(name string) error {
if err := validateAppName(name); err != nil {
if err := validateAppNameString(name); err != nil {
return err
}
app, err := o.store.GetApplication(name)
if err != nil {
var noSuchAppErr *config.ErrNoSuchApplication
if errors.As(err, &noSuchAppErr) {
return nil
if err == nil {
if o.domainName != "" && app.Domain != o.domainName {
return fmt.Errorf("application named %s already exists with a different domain name %s", name, app.Domain)
}
return fmt.Errorf("get application %s: %w", name, err)
return nil
}
if o.domainName != "" && app.Domain != o.domainName {
return fmt.Errorf("application named %s already exists with a different domain name %s", name, app.Domain)
var noSuchAppErr *config.ErrNoSuchApplication
if errors.As(err, &noSuchAppErr) {
roleName := fmt.Sprintf("%s-adminrole", name)
tags, err := o.iamRoleManager.ListRoleTags(roleName)
// NOTE: This is a best-effort attempt to check if the app exists in other regions.
// The error either indicates that the role does not exist, or not.
// In the first case, it means that this is a valid app name, hence we don't error out.
// In the second case, since this is a best-effort, we don't need to surface the error either.
if err != nil {
return nil
}
if _, hasTag := tags[deploy.AppTagKey]; hasTag {
return &errAppAlreadyExistsInAccount{appName: name}
}
return &errStackSetAdminRoleExistsInAccount{
appName: name,
roleName: roleName,
}
}
return nil
return fmt.Errorf("get application %s: %w", name, err)
}

func (o *initAppOpts) validatePermBound(policyName string) error {
Expand Down Expand Up @@ -315,11 +336,14 @@ func (o *initAppOpts) askAppName(formatMsg string) error {
appName, err := o.prompt.Get(
fmt.Sprintf(formatMsg, color.Emphasize("name")),
appInitNameHelpPrompt,
validateAppName,
validateAppNameString,
prompt.WithFinalMessage("Application name:"))
if err != nil {
return fmt.Errorf("prompt get application name: %w", err)
}
if err := o.validateAppName(appName); err != nil {
Comment thread
KollaAdithya marked this conversation as resolved.
return err
}
o.name = appName
return nil
}
Expand All @@ -341,6 +365,34 @@ func (o *initAppOpts) askSelectExistingAppName(existingApps []*config.Applicatio
return nil
}

type errAppAlreadyExistsInAccount struct {
appName string
}

type errStackSetAdminRoleExistsInAccount struct {
appName string
roleName string
}

func (e *errAppAlreadyExistsInAccount) Error() string {
return fmt.Sprintf("application named %q already exists in another region", e.appName)
}

func (e *errAppAlreadyExistsInAccount) RecommendActions() string {
return fmt.Sprintf(`If you want to create a new workspace reusing the existing application %s, please switch to the region where you created the application, and run %s.
If you'd like to recreate the application and all of its resources, please switch to the region where you created the application, and run %s.`, e.appName, color.HighlightCode("copilot app init"), color.HighlightCode("copilot app delete"))
}

func (e *errStackSetAdminRoleExistsInAccount) Error() string {
return fmt.Sprintf("IAM admin role %q already exists in this account", e.roleName)
}

func (e *errStackSetAdminRoleExistsInAccount) RecommendActions() string {
return fmt.Sprintf(`Copilot will create an IAM admin role named %s to manage the stack set of the application %s.
You have an existing role with the exact same name in your account, which will collide with the role that Copilot creates.
Please create the application with a different name, so that the IAM role name does not collide.`, e.roleName, e.appName)
}

// buildAppInitCommand builds the command for creating a new application.
func buildAppInitCommand() *cobra.Command {
vars := initAppVars{}
Expand Down
Loading