From 44ab03ab14e8d8745f70845e776575df2978800e Mon Sep 17 00:00:00 2001 From: Sanyam Khurana <8039608+CuriousLearner@users.noreply.github.com> Date: Wed, 29 May 2024 21:23:32 +0530 Subject: [PATCH 01/15] fix(cmd/ps): resize raises warning for non-existent service (#60) --- CHANGELOG.md | 6 ++++++ cmd/ps.go | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a26be55..372efb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [4.5.1] - [TBD] + +### Changed + +* `ps resize` raises a warning for non-existent service. + ## [4.5.0] - 2024-05-16 ### Changed diff --git a/cmd/ps.go b/cmd/ps.go index 1ec57bd..2df0943 100644 --- a/cmd/ps.go +++ b/cmd/ps.go @@ -166,6 +166,11 @@ var psResizeCmd = &cobra.Command{ processType := args[0] a, err := app.Init(AppName, UseAWSCredentials, SessionDurationSeconds) checkErr(err) + a.LoadDeployStatus() + _, err = a.DeployStatus.FindProcess(processType) + if err != nil { + printWarning(fmt.Sprintf("Service %q does not exist. Settings will be used if the service is created later.\n", processType)) + } size, err := humanToECSSizeConfiguration(scaleCPU, scaleMemory) checkErr(err) checkErr(a.ValidateECSTaskSize(*size)) From 2eef5cd636f5460d185960940ef4e23cf8199477 Mon Sep 17 00:00:00 2001 From: Sanyam Khurana <8039608+CuriousLearner@users.noreply.github.com> Date: Thu, 30 May 2024 02:22:12 +0530 Subject: [PATCH 02/15] fix: Validate max 4 custom domains (#62) --- stacks/app_pipeline.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/stacks/app_pipeline.go b/stacks/app_pipeline.go index 0c92db0..5fc5e4b 100644 --- a/stacks/app_pipeline.go +++ b/stacks/app_pipeline.go @@ -1,6 +1,7 @@ package stacks import ( + "errors" "fmt" "math/rand" "os" @@ -455,7 +456,7 @@ func (a *AppStack) CanChangeParameter(name string) (bool, error) { return *currentVal == "", nil } -func (a *AppStack) AskQuestions(sess *session.Session) error { +func (a *AppStack) AskQuestions(sess *session.Session) error { // skipcq: GO-R1005 var questions []*ui.QuestionExtra var err error if a.Stack == nil { @@ -509,6 +510,13 @@ func (a *AppStack) AskQuestions(sess *session.Session) error { Message: "Custom Domain(s)", Default: strings.Join(a.Parameters.Domains, "\n"), }, + Validate: func(val interface{}) error { + domains := strings.Split(val.(string), "\n") + if len(domains) > 4 { + return errors.New("limit of 4 custom domains exceeded") + } + return nil + }, }, }, }...) From fad96a6a11da6514db5e7d495a9fd78d5fb45c6b Mon Sep 17 00:00:00 2001 From: Sanyam Khurana Date: Thu, 30 May 2024 02:38:40 +0530 Subject: [PATCH 03/15] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 372efb1..0dbf29a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +* Limits the number of custom domains to 4. * `ps resize` raises a warning for non-existent service. ## [4.5.0] - 2024-05-16 From d99e82ac9a873fa70de0e0120cdc739567281ec8 Mon Sep 17 00:00:00 2001 From: Sanyam Khurana <8039608+CuriousLearner@users.noreply.github.com> Date: Tue, 4 Jun 2024 21:28:55 +0530 Subject: [PATCH 04/15] fix(reviewapp): Prevent non-existent app build (#61) --- app/app.go | 19 +++++++++++++++++++ app/utils.go | 15 +++++++++++++++ cmd/build.go | 6 ++++++ 3 files changed, 40 insertions(+) diff --git a/app/app.go b/app/app.go index 6a567ad..dcba065 100644 --- a/app/app.go +++ b/app/app.go @@ -394,6 +394,25 @@ func (a *App) GetReviewApps() ([]*ReviewApp, error) { return reviewApps, nil } +func (a *App) ReviewAppExists() (bool, error) { + if !a.Pipeline { + return false, fmt.Errorf("%s is not a pipeline and cannot have review apps", a.Name) + } + parameter, err := SsmParameter(a.Session, fmt.Sprintf("/apppack/pipelines/%s/review-apps/pr/%s", a.Name, *a.ReviewApp)) + if err != nil { + return false, fmt.Errorf("ReviewApp named %s:%s does not exist", a.Name, *a.ReviewApp) + } + r := ReviewApp{} + err = json.Unmarshal([]byte(*parameter.Value), &r) + if err != nil { + return false, err + } + if r.Status != "created" { + return false, fmt.Errorf("ReviewApp isn't created") + } + return true, nil +} + func (a *App) ddbItem(key string) (*map[string]*dynamodb.AttributeValue, error) { if !a.IsReviewApp() { return ddbItem(a.Session, fmt.Sprintf("APP#%s", a.Name), key) diff --git a/app/utils.go b/app/utils.go index 155890a..ec8bb94 100644 --- a/app/utils.go +++ b/app/utils.go @@ -63,6 +63,21 @@ func SsmParameters(sess *session.Session, path string) ([]*ssm.Parameter, error) return parameters, nil } +func SsmParameter(sess *session.Session, name string) (*ssm.Parameter, error) { + ssmSvc := ssm.New(sess) + input := &ssm.GetParameterInput{ + Name: aws.String(name), + WithDecryption: aws.Bool(true), + } + + result, err := ssmSvc.GetParameter(input) + if err != nil { + return nil, err + } + + return result.Parameter, nil +} + func S3FromURL(sess *session.Session, logURL string) (*strings.Builder, error) { s3Svc := s3.New(sess) parts := strings.Split(strings.TrimPrefix(logURL, "s3://"), "/") diff --git a/cmd/build.go b/cmd/build.go index d7d3086..58b2b71 100644 --- a/cmd/build.go +++ b/cmd/build.go @@ -627,6 +627,12 @@ var buildStartCmd = &cobra.Command{ } a, err := app.Init(AppName, UseAWSCredentials, duration) checkErr(err) + if a.Pipeline && a.ReviewApp == nil { + err := fmt.Errorf("%q is a pipeline. You can build ReviewApps within a pipeline", a.Name) + checkErr(err) + } + _, err = a.ReviewAppExists() + checkErr(err) build, err := a.StartBuild(false) checkErr(err) ui.Spinner.Stop() From b1c030b421650ba939edc46b0faa3519ba6d6f29 Mon Sep 17 00:00:00 2001 From: Sanyam Khurana <8039608+CuriousLearner@users.noreply.github.com> Date: Wed, 28 Aug 2024 10:48:51 -0400 Subject: [PATCH 05/15] fix: Prevent Ctrl+C from exiting remote shell (#71) --- CHANGELOG.md | 4 ++++ app/app.go | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dbf29a..6b17c04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [4.5.1] - [TBD] +### Fixed + +* Prevent `Ctrl+C` from exiting the remote shell session prematurely. + ### Changed * Limits the number of custom domains to 4. diff --git a/app/app.go b/app/app.go index dcba065..c0dd67a 100644 --- a/app/app.go +++ b/app/app.go @@ -5,6 +5,8 @@ import ( "errors" "fmt" "os" + "os/signal" + "syscall" "strconv" "strings" "time" @@ -649,7 +651,11 @@ func (a *App) ConnectToEcsSession(ecsSession *ecs.Session) error { *region, "StartSession", } - + // Ignore Ctrl+C to keep the session active; + // reset the signal afterward so the main function + // can handle interrupts during the rest of the program's execution. + signal.Ignore(syscall.SIGINT) + defer signal.Reset(syscall.SIGINT) sessionManagerPluginSession.ValidateInputAndStartSession(args, os.Stdout) return nil } From cf06763a166c7133b0e6cdcb2d645f66d1f5d995 Mon Sep 17 00:00:00 2001 From: Sanyam Khurana <8039608+CuriousLearner@users.noreply.github.com> Date: Wed, 28 Aug 2024 19:42:56 -0400 Subject: [PATCH 06/15] feat: Add APPPACK_ACCOUNT env variable (#69) --- cmd/access.go | 9 +++++---- cmd/admins.go | 9 +++++---- cmd/create.go | 5 +++-- cmd/destroy.go | 19 ++++++++++--------- cmd/modify.go | 2 +- cmd/root.go | 19 ++++++++++++++++++- cmd/stacks.go | 5 +++-- cmd/upgrade.go | 17 +++++++++-------- utils/utils.go | 3 +++ 9 files changed, 57 insertions(+), 31 deletions(-) create mode 100644 utils/utils.go diff --git a/cmd/access.go b/cmd/access.go index 16b6988..57b5e61 100644 --- a/cmd/access.go +++ b/cmd/access.go @@ -24,6 +24,7 @@ import ( "github.com/apppackio/apppack/stacks" "github.com/apppackio/apppack/stringslice" "github.com/apppackio/apppack/ui" + "github.com/apppackio/apppack/utils" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/spf13/cobra" @@ -107,7 +108,7 @@ var accessCmd = &cobra.Command{ Short: "list users with access to the app", Args: cobra.NoArgs, DisableFlagsInUseLine: true, - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { ui.StartSpinner() var err error sess, err := adminSession(SessionDurationSeconds) @@ -131,7 +132,7 @@ Updates the application Cloudformation stack to add access for the user.`, DisableFlagsInUseLine: true, Args: cobra.MinimumNArgs(1), Example: "apppack -a my-app access add user1@example.com user2@example.com", - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, args []string) { for _, email := range args { if !validateEmail(email) { checkErr(fmt.Errorf("%s does not appear to be a valid email address", email)) @@ -163,7 +164,7 @@ Updates the application Cloudformation stack to remove access for the user.`, DisableFlagsInUseLine: true, Args: cobra.MinimumNArgs(1), Example: "apppack -a my-app access remove user1@example.com user2@example.com", - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, args []string) { for _, email := range args { if !validateEmail(email) { checkErr(fmt.Errorf("%s does not appear to be a valid email address", email)) @@ -195,7 +196,7 @@ func init() { "account", "c", "", - "AWS account ID or alias (not needed if you are only the administrator of one account)", + utils.AccountFlagHelpText, ) accessCmd.PersistentFlags().BoolVar( &UseAWSCredentials, diff --git a/cmd/admins.go b/cmd/admins.go index 8c3d8f3..2214751 100644 --- a/cmd/admins.go +++ b/cmd/admins.go @@ -22,6 +22,7 @@ import ( "github.com/apppackio/apppack/stacks" "github.com/apppackio/apppack/stringslice" "github.com/apppackio/apppack/ui" + "github.com/apppackio/apppack/utils" "github.com/aws/aws-sdk-go/aws/session" "github.com/spf13/cobra" ) @@ -57,7 +58,7 @@ var adminsCmd = &cobra.Command{ Long: "*Requires admin permissions.*", Args: cobra.NoArgs, DisableFlagsInUseLine: true, - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { ui.StartSpinner() sess, err := adminSession(SessionDurationSeconds) checkErr(err) @@ -78,7 +79,7 @@ var adminsAddCmd = &cobra.Command{ DisableFlagsInUseLine: true, Args: cobra.MinimumNArgs(1), Example: "apppack admins add user1@example.com user2@example.com", - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, args []string) { for _, email := range args { if !validateEmail(email) { checkErr(fmt.Errorf("%s does not appear to be a valid email address", email)) @@ -108,7 +109,7 @@ var adminsRemoveCmd = &cobra.Command{ Updates the application Cloudformation stack to remove an administrators.`, DisableFlagsInUseLine: true, Args: cobra.MinimumNArgs(1), - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, args []string) { for _, email := range args { if !validateEmail(email) { checkErr(fmt.Errorf("%s does not appear to be a valid email address", email)) @@ -137,7 +138,7 @@ func init() { "account", "c", "", - "AWS account ID or alias (not needed if you are only the administrator of one account)", + utils.AccountFlagHelpText, ) adminsCmd.PersistentFlags().BoolVar( &UseAWSCredentials, diff --git a/cmd/create.go b/cmd/create.go index 08ffe28..6a3c7bd 100644 --- a/cmd/create.go +++ b/cmd/create.go @@ -22,6 +22,7 @@ import ( "github.com/apppackio/apppack/bridge" "github.com/apppackio/apppack/stacks" "github.com/apppackio/apppack/ui" + "github.com/apppackio/apppack/utils" "github.com/aws/aws-sdk-go/aws/session" "github.com/logrusorgru/aurora" "github.com/spf13/cobra" @@ -293,7 +294,7 @@ var createRegionCmd = &cobra.Command{ Short: "setup AppPack resources for an AWS region", Long: "*Requires admin permissions.*", DisableFlagsInUseLine: true, - Run: func(cmd *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, _ []string) { sess, err := adminSession(MaxSessionDurationSeconds) checkErr(err) region := sess.Config.Region @@ -310,7 +311,7 @@ var createRegionCmd = &cobra.Command{ func init() { rootCmd.AddCommand(createCmd) - createCmd.PersistentFlags().StringVarP(&AccountIDorAlias, "account", "c", "", "AWS account ID or alias (not needed if you are only the administrator of one account)") + createCmd.PersistentFlags().StringVarP(&AccountIDorAlias, "account", "c", "", utils.AccountFlagHelpText) createCmd.PersistentFlags().BoolVar(&UseAWSCredentials, "aws-credentials", false, "use AWS credentials instead of AppPack.io federation") createCmd.PersistentFlags().BoolVar(&createChangeSet, "check", false, "check stack in Cloudformation before creating") createCmd.PersistentFlags().BoolVar(&nonInteractive, "non-interactive", false, "do not prompt for missing flags") diff --git a/cmd/destroy.go b/cmd/destroy.go index f85d4db..8871d65 100644 --- a/cmd/destroy.go +++ b/cmd/destroy.go @@ -20,6 +20,7 @@ import ( "github.com/apppackio/apppack/stacks" "github.com/apppackio/apppack/ui" + "github.com/apppackio/apppack/utils" "github.com/aws/aws-sdk-go/aws/session" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -80,7 +81,7 @@ var destroyAccountCmd = &cobra.Command{ Short: "destroy AWS resources used by your AppPack account", Long: "*Requires admin permissions.*", DisableFlagsInUseLine: true, - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { ui.StartSpinner() sess, err := adminSession(MaxSessionDurationSeconds) checkErr(err) @@ -94,7 +95,7 @@ var destroyRegionCmd = &cobra.Command{ Short: "destroy AWS resources used by an AppPack region", Long: "*Requires admin permissions.*", DisableFlagsInUseLine: true, - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { ui.StartSpinner() sess, err := adminSession(MaxSessionDurationSeconds) checkErr(err) @@ -109,7 +110,7 @@ var destroyRedisCmd = &cobra.Command{ Long: "*Requires admin permissions.*", Args: cobra.ExactArgs(1), DisableFlagsInUseLine: true, - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, args []string) { ui.StartSpinner() sess, err := adminSession(MaxSessionDurationSeconds) checkErr(err) @@ -124,7 +125,7 @@ var destroyDatabaseCmd = &cobra.Command{ Long: "*Requires admin permissions.*", Args: cobra.ExactArgs(1), DisableFlagsInUseLine: true, - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, args []string) { ui.StartSpinner() sess, err := adminSession(MaxSessionDurationSeconds) checkErr(err) @@ -139,7 +140,7 @@ var destroyClusterCmd = &cobra.Command{ Long: "*Requires admin permissions.*", DisableFlagsInUseLine: true, Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, args []string) { ui.StartSpinner() sess, err := adminSession(MaxSessionDurationSeconds) checkErr(err) @@ -154,7 +155,7 @@ var destroyAppCmd = &cobra.Command{ Long: "*Requires admin permissions.*", DisableFlagsInUseLine: true, Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, args []string) { ui.StartSpinner() sess, err := adminSession(MaxSessionDurationSeconds) checkErr(err) @@ -169,7 +170,7 @@ var destroyPipelineCmd = &cobra.Command{ Long: "*Requires admin permissions.*", DisableFlagsInUseLine: true, Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, args []string) { ui.StartSpinner() sess, err := adminSession(MaxSessionDurationSeconds) checkErr(err) @@ -184,7 +185,7 @@ var destroyCustomDomainCmd = &cobra.Command{ Long: "*Requires admin permissions.*", DisableFlagsInUseLine: true, Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, args []string) { ui.StartSpinner() sess, err := adminSession(MaxSessionDurationSeconds) checkErr(err) @@ -194,7 +195,7 @@ var destroyCustomDomainCmd = &cobra.Command{ func init() { rootCmd.AddCommand(destroyCmd) - destroyCmd.PersistentFlags().StringVarP(&AccountIDorAlias, "account", "c", "", "AWS account ID or alias (not needed if you are only the administrator of one account)") + destroyCmd.PersistentFlags().StringVarP(&AccountIDorAlias, "account", "c", "", utils.AccountFlagHelpText) destroyCmd.PersistentFlags().BoolVar(&UseAWSCredentials, "aws-credentials", false, "use AWS credentials instead of AppPack.io federation") destroyCmd.PersistentFlags().StringVar(®ion, "region", "", "AWS region to destroy resources in") diff --git a/cmd/modify.go b/cmd/modify.go index 5f04611..a6730a1 100644 --- a/cmd/modify.go +++ b/cmd/modify.go @@ -90,7 +90,7 @@ package cmd // func init() { // rootCmd.AddCommand(modifyCmd) -// modifyCmd.PersistentFlags().StringVarP(&AccountIDorAlias, "account", "c", "", "AWS account ID or alias (not needed if you are only the administrator of one account)") +// modifyCmd.PersistentFlags().StringVarP(&AccountIDorAlias, "account", "c", "", utils.AccountFlagHelpText) // modifyCmd.PersistentFlags().BoolVar(&UseAWSCredentials, "aws-credentials", false, "use AWS credentials instead of AppPack.io federation") // modifyCmd.PersistentFlags().BoolVar(&createChangeSet, "check", false, "check stack in Cloudformation before creating") // modifyCmd.PersistentFlags().BoolVar(&nonInteractive, "non-interactive", false, "do not prompt for user input") diff --git a/cmd/root.go b/cmd/root.go index 3f11394..c065119 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -50,6 +50,14 @@ var ( MaxSessionDurationSeconds = 3600 ) +func userHasMultipleAccounts() bool { + ui.StartSpinner() + admins, err := auth.AdminList() + checkErr(err) + ui.Spinner.Stop() + return len(admins) > 1 +} + // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "apppack", @@ -57,13 +65,22 @@ var rootCmd = &cobra.Command{ Long: `AppPack is a tool to manage applications deployed on AWS via AppPack.io`, DisableAutoGenTag: true, DisableFlagsInUseLine: true, - PersistentPreRun: func(cmd *cobra.Command, args []string) { + PersistentPreRun: func(_ *cobra.Command, _ []string) { if debug { logrus.SetOutput(os.Stdout) logrus.SetLevel(logrus.DebugLevel) } else { logrus.SetLevel(logrus.ErrorLevel) } + // Check for account flag or environment variable + if AccountIDorAlias == "" { + AccountIDorAlias = os.Getenv("APPPACK_ACCOUNT") + } + + // If neither is set and the user has multiple accounts, throw an error + if AccountIDorAlias == "" && userHasMultipleAccounts() { + checkErr(fmt.Errorf("you must specify an account using the -c flag or the APPPACK_ACCOUNT environment variable")) + } }, } diff --git a/cmd/stacks.go b/cmd/stacks.go index 787972f..f42a93d 100644 --- a/cmd/stacks.go +++ b/cmd/stacks.go @@ -23,6 +23,7 @@ import ( "github.com/apppackio/apppack/bridge" "github.com/apppackio/apppack/ui" + "github.com/apppackio/apppack/utils" "github.com/aws/aws-sdk-go/service/cloudformation" "github.com/juju/ansiterm/tabwriter" "github.com/logrusorgru/aurora" @@ -67,7 +68,7 @@ var stacksCmd = &cobra.Command{ Long: "*Requires admin permissions.*", DisableFlagsInUseLine: true, Args: cobra.NoArgs, - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { ui.StartSpinner() sess, err := adminSession(SessionDurationSeconds) checkErr(err) @@ -113,6 +114,6 @@ var stacksCmd = &cobra.Command{ func init() { rootCmd.AddCommand(stacksCmd) - stacksCmd.PersistentFlags().StringVarP(&AccountIDorAlias, "account", "c", "", "AWS account ID or alias (not needed if you are only the administrator of one account)") + stacksCmd.PersistentFlags().StringVarP(&AccountIDorAlias, "account", "c", "", utils.AccountFlagHelpText) stacksCmd.PersistentFlags().BoolVar(&UseAWSCredentials, "aws-credentials", false, "use AWS credentials instead of AppPack.io federation") } diff --git a/cmd/upgrade.go b/cmd/upgrade.go index 703973f..24d317a 100644 --- a/cmd/upgrade.go +++ b/cmd/upgrade.go @@ -20,6 +20,7 @@ import ( "github.com/apppackio/apppack/stacks" "github.com/apppackio/apppack/ui" + "github.com/apppackio/apppack/utils" "github.com/aws/aws-sdk-go/aws/session" "github.com/logrusorgru/aurora" "github.com/spf13/cobra" @@ -75,7 +76,7 @@ var upgradeAccountCmd = &cobra.Command{ Short: "upgrade your AppPack account stack", Long: "*Requires admin permissions.*", DisableFlagsInUseLine: true, - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { ui.StartSpinner() sess, err := adminSession(MaxSessionDurationSeconds) checkErr(err) @@ -89,7 +90,7 @@ var upgradeRegionCmd = &cobra.Command{ Short: "upgrade your AppPack region stack", Long: "*Requires admin permissions.*", DisableFlagsInUseLine: true, - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, _ []string) { ui.StartSpinner() sess, err := adminSession(MaxSessionDurationSeconds) checkErr(err) @@ -104,7 +105,7 @@ var upgradeAppCmd = &cobra.Command{ Long: "*Requires admin permissions.*", DisableFlagsInUseLine: true, Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, args []string) { ui.StartSpinner() sess, err := adminSession(MaxSessionDurationSeconds) checkErr(err) @@ -119,7 +120,7 @@ var upgradePipelineCmd = &cobra.Command{ Long: "*Requires admin permissions.*", DisableFlagsInUseLine: true, Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, args []string) { ui.StartSpinner() sess, err := adminSession(MaxSessionDurationSeconds) checkErr(err) @@ -134,7 +135,7 @@ var upgradeClusterCmd = &cobra.Command{ Long: "*Requires admin permissions.*", DisableFlagsInUseLine: true, Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, args []string) { ui.StartSpinner() sess, err := adminSession(MaxSessionDurationSeconds) checkErr(err) @@ -149,7 +150,7 @@ var upgradeRedisCmd = &cobra.Command{ Long: "*Requires admin permissions.*", DisableFlagsInUseLine: true, Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, args []string) { ui.StartSpinner() sess, err := adminSession(MaxSessionDurationSeconds) checkErr(err) @@ -164,7 +165,7 @@ var upgradeDatabaseCmd = &cobra.Command{ Long: "*Requires admin permissions.*", DisableFlagsInUseLine: true, Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, args []string) { ui.StartSpinner() sess, err := adminSession(MaxSessionDurationSeconds) checkErr(err) @@ -174,7 +175,7 @@ var upgradeDatabaseCmd = &cobra.Command{ func init() { rootCmd.AddCommand(upgradeCmd) - upgradeCmd.PersistentFlags().StringVarP(&AccountIDorAlias, "account", "c", "", "AWS account ID or alias (not needed if you are only the administrator of one account)") + upgradeCmd.PersistentFlags().StringVarP(&AccountIDorAlias, "account", "c", "", utils.AccountFlagHelpText) upgradeCmd.PersistentFlags().BoolVar(&UseAWSCredentials, "aws-credentials", false, "use AWS credentials instead of AppPack.io federation") upgradeCmd.PersistentFlags().BoolVar(&createChangeSet, "check", false, "check stack in Cloudformation before creating") upgradeCmd.PersistentFlags().StringVar(®ion, "region", "", "AWS region to upgrade resources in") diff --git a/utils/utils.go b/utils/utils.go new file mode 100644 index 0000000..b0f250a --- /dev/null +++ b/utils/utils.go @@ -0,0 +1,3 @@ +package utils + +var AccountFlagHelpText string = "AWS account ID or alias. Use this flag to override the APPPACK_ACCOUNT environment variable (not needed if you are the administrator of only one account)." From e8915a2a50f471b7408921d4136677a7d6350570 Mon Sep 17 00:00:00 2001 From: Sanyam Khurana <8039608+CuriousLearner@users.noreply.github.com> Date: Wed, 28 Aug 2024 19:43:20 -0400 Subject: [PATCH 07/15] fix(reviewapp): Don't fail reviewapp commands if account flag is passed (#68) --- CHANGELOG.md | 1 + cmd/reviewapps.go | 24 +++++++++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b17c04..860c8d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Limits the number of custom domains to 4. * `ps resize` raises a warning for non-existent service. +* `reviewapps` cmd optionally accepts `-c`/ `account` flag. ## [4.5.0] - 2024-05-16 diff --git a/cmd/reviewapps.go b/cmd/reviewapps.go index 81aea1d..a901fd4 100644 --- a/cmd/reviewapps.go +++ b/cmd/reviewapps.go @@ -28,14 +28,21 @@ import ( "github.com/spf13/cobra" ) +func accountFlagIgnoredWarning() { + if AccountIDorAlias != "" { + fmt.Println(aurora.Yellow("Warning: The 'account' flag is ignored for reviewapps and all its subcommands. Reviewapps depend on access to the pipeline rather than access to the account.")) + } +} + // reviewappsCmd represents the reviewapps command var reviewappsCmd = &cobra.Command{ Use: "reviewapps ", Short: "list deployed review apps", Args: cobra.ExactArgs(1), DisableFlagsInUseLine: true, - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, args []string) { ui.StartSpinner() + accountFlagIgnoredWarning() a, err := app.Init(args[0], UseAWSCredentials, SessionDurationSeconds) checkErr(err) reviewApps, err := a.GetReviewApps() @@ -62,8 +69,9 @@ var reviewappsCreateCmd = &cobra.Command{ Long: `Creates a review app from a pull request on the pipeline repository and triggers the iniital build`, DisableFlagsInUseLine: true, Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, args []string) { ui.StartSpinner() + accountFlagIgnoredWarning() name := args[0] if len(strings.Split(name, ":")) != 2 { checkErr(fmt.Errorf("invalid review app name -- must be in format :")) @@ -122,8 +130,9 @@ var reviewappsDestroyCmd = &cobra.Command{ Short: "destroys the review app", DisableFlagsInUseLine: true, Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, args []string) { ui.StartSpinner() + accountFlagIgnoredWarning() a, err := app.Init(args[0], UseAWSCredentials, SessionDurationSeconds) checkErr(err) if !a.IsReviewApp() { // TODO: validate @@ -140,4 +149,13 @@ func init() { reviewappsCreateCmd.Flags().StringVar(&release, "release", "", "Specify a specific pre-release stack") upgradeCmd.PersistentFlags().MarkHidden("release") reviewappsCmd.AddCommand(reviewappsDestroyCmd) + + reviewappsCmd.PersistentFlags().StringVarP( + &AccountIDorAlias, + "account", + "c", + "", + "AWS account ID or alias (not needed if you are only the administrator of one account)", + ) + reviewappsCmd.PersistentFlags().MarkHidden("account") } From c676d6c086a58e63ba431e79c909951d3c5f4453 Mon Sep 17 00:00:00 2001 From: Sanyam Khurana Date: Thu, 29 Aug 2024 05:21:06 +0530 Subject: [PATCH 08/15] chore: Release 4.6.0 --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 860c8d9..3625db2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [4.5.1] - [TBD] +## [4.6.0] - 2024-08-28 ### Fixed @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Limits the number of custom domains to 4. * `ps resize` raises a warning for non-existent service. * `reviewapps` cmd optionally accepts `-c`/ `account` flag. +* Implemented a check that throws an error if neither the `-c` flag nor the `APPPACK_ACCOUNT` environment variable is set and the user has multiple accounts. This ensures that users specify an account explicitly to avoid ambiguity. ## [4.5.0] - 2024-05-16 From 57c2d5928eb32e7d9a1a291f4e46da81479bbee5 Mon Sep 17 00:00:00 2001 From: Sanyam Khurana <8039608+CuriousLearner@users.noreply.github.com> Date: Thu, 29 Aug 2024 14:00:41 -0400 Subject: [PATCH 09/15] chore: Release 4.6.1; revert APPPACK_ACCOUNT (#74) --- CHANGELOG.md | 6 ++++++ cmd/root.go | 17 ----------------- utils/utils.go | 2 +- 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3625db2..429a0ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [4.6.1] - 2024-08-28 + +### Fixed + +* Revert the ability to provide `APPPACK_ACCOUNT` for multiple accounts. + ## [4.6.0] - 2024-08-28 ### Fixed diff --git a/cmd/root.go b/cmd/root.go index c065119..8e432a9 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -50,14 +50,6 @@ var ( MaxSessionDurationSeconds = 3600 ) -func userHasMultipleAccounts() bool { - ui.StartSpinner() - admins, err := auth.AdminList() - checkErr(err) - ui.Spinner.Stop() - return len(admins) > 1 -} - // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "apppack", @@ -72,15 +64,6 @@ var rootCmd = &cobra.Command{ } else { logrus.SetLevel(logrus.ErrorLevel) } - // Check for account flag or environment variable - if AccountIDorAlias == "" { - AccountIDorAlias = os.Getenv("APPPACK_ACCOUNT") - } - - // If neither is set and the user has multiple accounts, throw an error - if AccountIDorAlias == "" && userHasMultipleAccounts() { - checkErr(fmt.Errorf("you must specify an account using the -c flag or the APPPACK_ACCOUNT environment variable")) - } }, } diff --git a/utils/utils.go b/utils/utils.go index b0f250a..a2ad5cf 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1,3 +1,3 @@ package utils -var AccountFlagHelpText string = "AWS account ID or alias. Use this flag to override the APPPACK_ACCOUNT environment variable (not needed if you are the administrator of only one account)." +var AccountFlagHelpText string = "AWS account ID or alias (not needed if you are only the administrator of one account)" From 4d91b6a50c95ec780822b1496d3b4130eb2d9f1b Mon Sep 17 00:00:00 2001 From: Sanyam Khurana <8039608+CuriousLearner@users.noreply.github.com> Date: Tue, 3 Sep 2024 15:17:07 -0400 Subject: [PATCH 10/15] fix(build): Allow non-pipeline apps to be built (#75) --- CHANGELOG.md | 6 ++++++ cmd/build.go | 10 ++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 429a0ed..4a63b25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [4.6.2] - 2024-09-03 + +### Fixed + +* Allow `apppack build` commands to work for non-pipeline apps. + ## [4.6.1] - 2024-08-28 ### Fixed diff --git a/cmd/build.go b/cmd/build.go index 58b2b71..5ebca7d 100644 --- a/cmd/build.go +++ b/cmd/build.go @@ -627,12 +627,14 @@ var buildStartCmd = &cobra.Command{ } a, err := app.Init(AppName, UseAWSCredentials, duration) checkErr(err) - if a.Pipeline && a.ReviewApp == nil { - err := fmt.Errorf("%q is a pipeline. You can build ReviewApps within a pipeline", a.Name) + if a.Pipeline { + if a.ReviewApp == nil { + err := fmt.Errorf("%q is a pipeline. You can build ReviewApps within a pipeline", a.Name) + checkErr(err) + } + _, err = a.ReviewAppExists() checkErr(err) } - _, err = a.ReviewAppExists() - checkErr(err) build, err := a.StartBuild(false) checkErr(err) ui.Spinner.Stop() From cb92f21097b0e2a0899041f12f556c78f830738b Mon Sep 17 00:00:00 2001 From: Sanyam Khurana <8039608+CuriousLearner@users.noreply.github.com> Date: Fri, 13 Sep 2024 13:54:10 -0400 Subject: [PATCH 11/15] chore: Delete orphan R53 record in functional-tests workflow (#78) --- .github/workflows/functional_tests.yml | 45 ++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/.github/workflows/functional_tests.yml b/.github/workflows/functional_tests.yml index 890aef5..76be3ab 100644 --- a/.github/workflows/functional_tests.yml +++ b/.github/workflows/functional_tests.yml @@ -23,6 +23,7 @@ jobs: - name: Build run: go build + - name: AppPack Account run: | @@ -113,6 +114,50 @@ jobs: env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + - name: Check and Delete CNAME Record + if: always() + run: | + hosted_zone_id="Z05906472T84V7X7Q6UDY" + record_name="_5b8b09a8917e1fbb8c6aead5121fb550.testclusters.apppack.io" + + echo "Checking if CNAME record exists ..." + # Check if the CNAME record exists + record_value=$(aws route53 list-resource-record-sets --hosted-zone-id $hosted_zone_id \ + --query "ResourceRecordSets[?Name == '$record_name.']" \ + --output text) + + if [[ -n "$record_value" ]]; then + echo "CNAME record exists, deleting..." + + # Extract the actual value of the CNAME record to use in the deletion + cname_value=$(aws route53 list-resource-record-sets --hosted-zone-id $hosted_zone_id \ + --query "ResourceRecordSets[?Name == '$record_name.'].ResourceRecords[0].Value" \ + --output text) + + # Delete the CNAME record + aws route53 change-resource-record-sets --hosted-zone-id "$hosted_zone_id" \ + --change-batch "{ + \"Changes\": [{ + \"Action\": \"DELETE\", + \"ResourceRecordSet\": { + \"Name\": \"$record_name\", + \"Type\": \"CNAME\", + \"TTL\": 300, + \"ResourceRecords\": [{\"Value\": \"$cname_value\"}] + } + }] + }" + + echo "CNAME record deleted successfully." + else + echo "CNAME record does not exist, skipping deletion." + fi + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: us-east-1 + - name: Destroy region run: | From 4514e99d0da74b63f841aeba1854f56c67c711f0 Mon Sep 17 00:00:00 2001 From: Sanyam Khurana <8039608+CuriousLearner@users.noreply.github.com> Date: Mon, 16 Sep 2024 17:29:28 -0400 Subject: [PATCH 12/15] fix: Delete both CNAME/A record for cleanup (#79) --- .github/workflows/functional_tests.yml | 71 +++++++++++++++----------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/.github/workflows/functional_tests.yml b/.github/workflows/functional_tests.yml index 76be3ab..4476d1c 100644 --- a/.github/workflows/functional_tests.yml +++ b/.github/workflows/functional_tests.yml @@ -115,44 +115,55 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - - name: Check and Delete CNAME Record + - name: Check and Delete CNAME and A Records if: always() run: | hosted_zone_id="Z05906472T84V7X7Q6UDY" - record_name="_5b8b09a8917e1fbb8c6aead5121fb550.testclusters.apppack.io" - echo "Checking if CNAME record exists ..." - # Check if the CNAME record exists - record_value=$(aws route53 list-resource-record-sets --hosted-zone-id $hosted_zone_id \ - --query "ResourceRecordSets[?Name == '$record_name.']" \ - --output text) + # Record details + cname_record_name="_5b8b09a8917e1fbb8c6aead5121fb550.testclusters.apppack.io" + a_record_name="*.testcluster.apppack.io" - if [[ -n "$record_value" ]]; then - echo "CNAME record exists, deleting..." + # Function to check and delete a record + delete_record() { + record_name=$1 + record_type=$2 - # Extract the actual value of the CNAME record to use in the deletion - cname_value=$(aws route53 list-resource-record-sets --hosted-zone-id $hosted_zone_id \ - --query "ResourceRecordSets[?Name == '$record_name.'].ResourceRecords[0].Value" \ - --output text) + echo "Checking if $record_type record '$record_name' exists ..." + record_value=$(aws route53 list-resource-record-sets --hosted-zone-id $hosted_zone_id \ + --query "ResourceRecordSets[?Name == '$record_name.']" \ + --output text) + if [[ -n "$record_value" ]]; then + echo "$record_type record exists, deleting..." + # Extract the actual value of the record to use in the deletion + record_actual_value=$(aws route53 list-resource-record-sets --hosted-zone-id $hosted_zone_id \ + --query "ResourceRecordSets[?Name == '$record_name.'].ResourceRecords[0].Value" \ + --output text) + # Delete the record + aws route53 change-resource-record-sets --hosted-zone-id "$hosted_zone_id" \ + --change-batch "{ + \"Changes\": [{ + \"Action\": \"DELETE\", + \"ResourceRecordSet\": { + \"Name\": \"$record_name\", + \"Type\": \"$record_type\", + \"TTL\": 300, + \"ResourceRecords\": [{\"Value\": \"$record_actual_value\"}] + } + }] + }" + echo "$record_type record '$record_name' deleted successfully." + else + echo "$record_type record '$record_name' does not exist, skipping deletion." + fi + } - # Delete the CNAME record - aws route53 change-resource-record-sets --hosted-zone-id "$hosted_zone_id" \ - --change-batch "{ - \"Changes\": [{ - \"Action\": \"DELETE\", - \"ResourceRecordSet\": { - \"Name\": \"$record_name\", - \"Type\": \"CNAME\", - \"TTL\": 300, - \"ResourceRecords\": [{\"Value\": \"$cname_value\"}] - } - }] - }" + # Delete CNAME record + delete_record "$cname_record_name" "CNAME" + + # Delete A record + delete_record "$a_record_name" "A" - echo "CNAME record deleted successfully." - else - echo "CNAME record does not exist, skipping deletion." - fi env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} From 78afd9a54bb1f544b95927333006667065580c36 Mon Sep 17 00:00:00 2001 From: Sanyam Khurana Date: Thu, 19 Sep 2024 20:39:50 +0530 Subject: [PATCH 13/15] chore(functional-tests): Cron to run at 2:00 UTC --- .github/workflows/functional_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/functional_tests.yml b/.github/workflows/functional_tests.yml index 4476d1c..e30c80c 100644 --- a/.github/workflows/functional_tests.yml +++ b/.github/workflows/functional_tests.yml @@ -4,7 +4,7 @@ name: functional-tests on: workflow_dispatch: {} schedule: - - cron: '0 1 * * *' + - cron: '0 2 * * *' jobs: functional-tests: From 6ebbdeac4f16e32e0f6e2799aefd99c49def2021 Mon Sep 17 00:00:00 2001 From: Sanyam Khurana Date: Fri, 20 Sep 2024 19:00:02 +0530 Subject: [PATCH 14/15] chore: Update cron timing for database/functional_tests --- .github/workflows/databases.yml | 2 +- .github/workflows/functional_tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/databases.yml b/.github/workflows/databases.yml index fcb3077..69c778b 100644 --- a/.github/workflows/databases.yml +++ b/.github/workflows/databases.yml @@ -4,7 +4,7 @@ name: databases on: workflow_dispatch: {} schedule: - - cron: '0 1 * * *' + - cron: '0 2 * * *' jobs: init: diff --git a/.github/workflows/functional_tests.yml b/.github/workflows/functional_tests.yml index e30c80c..434dc4b 100644 --- a/.github/workflows/functional_tests.yml +++ b/.github/workflows/functional_tests.yml @@ -4,7 +4,7 @@ name: functional-tests on: workflow_dispatch: {} schedule: - - cron: '0 2 * * *' + - cron: '0 0 * * *' jobs: functional-tests: From 2b2da0d1d0d86f02e3412cff9a80fc7232dad01b Mon Sep 17 00:00:00 2001 From: Sanyam Khurana <8039608+CuriousLearner@users.noreply.github.com> Date: Wed, 9 Oct 2024 11:14:48 -0400 Subject: [PATCH 15/15] fix(app): Verify existence of review app before resizing process (#77) --- CHANGELOG.md | 6 ++++++ app/app.go | 2 +- cmd/ps.go | 4 ++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a63b25..4c7459a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] - 2024-10-XX + +### Fixed + +* Verify existence of review app before resizing process with `ps resize`. + ## [4.6.2] - 2024-09-03 ### Fixed diff --git a/app/app.go b/app/app.go index c0dd67a..bacda88 100644 --- a/app/app.go +++ b/app/app.go @@ -6,9 +6,9 @@ import ( "fmt" "os" "os/signal" - "syscall" "strconv" "strings" + "syscall" "time" "github.com/apppackio/apppack/auth" diff --git a/cmd/ps.go b/cmd/ps.go index 2df0943..74deaa3 100644 --- a/cmd/ps.go +++ b/cmd/ps.go @@ -167,6 +167,10 @@ var psResizeCmd = &cobra.Command{ a, err := app.Init(AppName, UseAWSCredentials, SessionDurationSeconds) checkErr(err) a.LoadDeployStatus() + if a.IsReviewApp() { + _, err = a.ReviewAppExists() + checkErr(err) + } _, err = a.DeployStatus.FindProcess(processType) if err != nil { printWarning(fmt.Sprintf("Service %q does not exist. Settings will be used if the service is created later.\n", processType))