From 6afadcd21777cedb36925087f1510f519fd8cf79 Mon Sep 17 00:00:00 2001 From: Peter Baumgartner Date: Fri, 12 Feb 2021 16:43:34 -0700 Subject: [PATCH] Update CLI for new Cfn database template (#12) * Update CLI for new Cfn database template * Trigger initial build * Check error on confirm delete * Fix CI * Fix aurora flag handling * Handle RDS instance or cluster * Test Redis too * Only build databases nightly and on-demand --- .github/workflows/databases.yml | 241 ++++++++++++++++++++++++++++++++ cmd/create.go | 5 +- cmd/createDatabase.go | 157 +++++++++++++++------ cmd/destroy.go | 44 ++++-- cmd/upgrade.go | 27 +--- 5 files changed, 392 insertions(+), 82 deletions(-) create mode 100644 .github/workflows/databases.yml diff --git a/.github/workflows/databases.yml b/.github/workflows/databases.yml new file mode 100644 index 0000000..33506c2 --- /dev/null +++ b/.github/workflows/databases.yml @@ -0,0 +1,241 @@ +name: databases + +on: + workflow_dispatch: {} + schedule: + - cron: '0 1 * * *' + +jobs: + init: + name: init + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.15 + - + name: Build + run: go build -o bin/apppack + - + name: AppPack Init + run: | + ./bin/apppack create region --region us-east-2 \ + --dockerhub-username $DOCKERHUB_USERNAME \ + --dockerhub-access-token $DOCKERHUB_ACCESS_TOKEN + ./bin/apppack create cluster --region us-east-2 \ + --domain testclusters.apppack.io \ + --instance-class t3.micro + timeout-minutes: 9 + env: + DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKERHUB_ACCESS_TOKEN: ${{ secrets.DOCKERHUB_ACCESS_TOKEN }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + - + uses: actions/upload-artifact@master + with: + name: apppack + path: bin + standard-mysql: + runs-on: ubuntu-latest + needs: ["init"] + steps: + - + uses: actions/download-artifact@master + with: + name: apppack + path: bin + - + name: Create standard MySQL + run: | + chmod +x ./bin/apppack + ./bin/apppack create database --region us-east-2 \ + --non-interactive \ + --instance-class db.t3.micro \ + --engine mysql \ + --allocated-storage 10 \ + --max-allocated-storage 20 \ + standard-mysql + timeout-minutes: 25 + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + - + name: Destroy standard MySQL + run: | + yes yes | ./bin/apppack destroy database standard-mysql \ + --region us-east-2 + if: always() + timeout-minutes: 15 + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + standard-postgres: + runs-on: ubuntu-latest + needs: ["init"] + steps: + - + uses: actions/download-artifact@master + with: + name: apppack + path: bin + - + name: Create standard Postgres + run: | + chmod +x ./bin/apppack + ./bin/apppack create database --region us-east-2 \ + --non-interactive \ + --instance-class db.t3.micro \ + --engine postgres \ + --allocated-storage 10 \ + --max-allocated-storage 20 \ + standard-postgres + timeout-minutes: 25 + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + - + name: Destroy standard Postgres + run: | + yes yes | ./bin/apppack destroy database standard-postgres \ + --region us-east-2 + if: always() + timeout-minutes: 15 + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aurora-mysql: + runs-on: ubuntu-latest + needs: ["init"] + steps: + - + uses: actions/download-artifact@master + with: + name: apppack + path: bin + - + name: Create Aurora MySQL + run: | + chmod +x ./bin/apppack + ./bin/apppack create database --region us-east-2 \ + --non-interactive \ + --instance-class db.t3.small \ + --aurora \ + --engine mysql \ + --allocated-storage 10 \ + --max-allocated-storage 20 \ + aurora-mysql + timeout-minutes: 25 + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + - + name: Destroy Aurora MySQL + run: | + yes yes | ./bin/apppack destroy database aurora-mysql \ + --region us-east-2 + if: always() + timeout-minutes: 15 + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aurora-postgres: + runs-on: ubuntu-latest + needs: ["init"] + steps: + - + uses: actions/download-artifact@master + with: + name: apppack + path: bin + - + name: Create Aurora Postgres + run: | + chmod +x ./bin/apppack + ./bin/apppack create database --region us-east-2 \ + --non-interactive \ + --instance-class db.t3.medium \ + --aurora \ + --engine postgres \ + aurora-postgres + timeout-minutes: 25 + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + - + name: Destroy Aurora Postgres + run: | + yes yes | ./bin/apppack destroy database aurora-postgres \ + --region us-east-2 + if: always() + timeout-minutes: 15 + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + redis: + runs-on: ubuntu-latest + needs: ["init"] + steps: + - + uses: actions/download-artifact@master + with: + name: apppack + path: bin + - + name: Create Redis + run: | + chmod +x ./bin/apppack + ./bin/apppack create redis --region us-east-2 \ + --non-interactive + timeout-minutes: 25 + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + - + name: Destroy Redis + run: | + yes yes | ./bin/apppack destroy redis apppack \ + --region us-east-2 + if: always() + timeout-minutes: 15 + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + destroy: + runs-on: ubuntu-latest + if: always() + needs: + - standard-mysql + - standard-postgres + - aurora-mysql + - aurora-postgres + - redis + steps: + - + uses: actions/download-artifact@master + with: + name: apppack + path: bin + - + name: Destroy cluster + run: | + chmod +x ./bin/apppack + yes yes | ./bin/apppack destroy cluster apppack --region us-east-2 + if: always() + timeout-minutes: 8 + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + - + name: Destroy region + run: yes yes | ./bin/apppack destroy region --region us-east-2 + if: always() + timeout-minutes: 3 + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} diff --git a/cmd/create.go b/cmd/create.go index 9b4f007..2b48867 100644 --- a/cmd/create.go +++ b/cmd/create.go @@ -51,8 +51,7 @@ const ( clusterFormationURL = "https://s3.amazonaws.com/apppack-cloudformations/latest/cluster.json" accountFormationURL = "https://s3.amazonaws.com/apppack-cloudformations/latest/account.json" regionFormationURL = "https://s3.amazonaws.com/apppack-cloudformations/latest/region.json" - postgresFormationURL = "https://s3.amazonaws.com/apppack-cloudformations/latest/postgres.json" - mysqlFormationURL = "https://s3.amazonaws.com/apppack-cloudformations/latest/mysql.json" + databaseFormationURL = "https://s3.amazonaws.com/apppack-cloudformations/latest/database.json" redisFormationURL = "https://s3.amazonaws.com/apppack-cloudformations/latest/redis.json" customDomainFormationURL = "https://s3.amazonaws.com/apppack-cloudformations/latest/custom-domain.json" redisStackNameTmpl = "apppack-redis-%s" @@ -740,7 +739,7 @@ var createRedisCmd = &cobra.Command{ clusterStack, err := stackFromDDBItem(sess, fmt.Sprintf("CLUSTER#%s", *cluster)) checkErr(err) var multiAZParameter string - if *(getArgValue(cmd, &answers, "multi-az", false)) == "true" { + if isTruthy((getArgValue(cmd, &answers, "multi-az", false))) { multiAZParameter = "yes" } else { multiAZParameter = "no" diff --git a/cmd/createDatabase.go b/cmd/createDatabase.go index 33c52e0..98cf356 100644 --- a/cmd/createDatabase.go +++ b/cmd/createDatabase.go @@ -57,6 +57,23 @@ var classOrder = []struct { {"metal", 9999}, } +var previousGenerations = []string{ + "db.t2.", + "db.m3.", + "db.m4.", + "db.r3.", + "db.r4.", +} + +func isPreviousGeneration(instanceClass *string) bool { + for _, p := range previousGenerations { + if strings.HasPrefix(*instanceClass, p) { + return true + } + } + return false +} + // instanceNameWeight creates a sortable string for instance classes func instanceNameWeight(name string) string { parts := strings.Split(name, ".") @@ -90,11 +107,14 @@ func instanceNameWeight(name string) string { return name } -func listRDSInstanceClasses(sess *session.Session, engine *string) ([]string, error) { +func listRDSInstanceClasses(sess *session.Session, engine *string, version *string) ([]string, error) { rdsSvc := rds.New(sess) var instanceClassResults []*rds.OrderableDBInstanceOption - err := rdsSvc.DescribeOrderableDBInstanceOptionsPages(&rds.DescribeOrderableDBInstanceOptionsInput{Engine: engine}, func(resp *rds.DescribeOrderableDBInstanceOptionsOutput, lastPage bool) bool { + err := rdsSvc.DescribeOrderableDBInstanceOptionsPages(&rds.DescribeOrderableDBInstanceOptionsInput{ + Engine: engine, + EngineVersion: version, + }, func(resp *rds.DescribeOrderableDBInstanceOptionsOutput, lastPage bool) bool { for _, instanceOption := range resp.OrderableDBInstanceOptions { if instanceOption == nil { continue @@ -109,7 +129,9 @@ func listRDSInstanceClasses(sess *session.Session, engine *string) ([]string, er } var instanceClasses []string for _, opt := range instanceClassResults { - instanceClasses = append(instanceClasses, *opt.DBInstanceClass) + if !isPreviousGeneration(opt.DBInstanceClass) { + instanceClasses = append(instanceClasses, *opt.DBInstanceClass) + } } instanceClasses = dedupe(instanceClasses) sort.Slice(instanceClasses, func(i int, j int) bool { @@ -118,6 +140,32 @@ func listRDSInstanceClasses(sess *session.Session, engine *string) ([]string, er return instanceClasses, nil } +func engineName(engine *string, aurora bool) (*string, error) { + if aurora { + if *engine == "mysql" { + return aws.String("aurora-mysql"), nil + } else if *engine == "postgres" { + return aws.String("aurora-postgresql"), nil + } else { + return nil, fmt.Errorf("unrecognized databae engine. valid options are 'mysql' or 'postgres'") + } + } + if *engine == "mysql" || *engine == "postgres" { + return engine, nil + } else { + return nil, fmt.Errorf("unrecognized databae engine. valid options are 'mysql' or 'postgres'") + } +} + +func getLatestRdsVersion(sess *session.Session, engine *string) (*string, error) { + rdsSvc := rds.New(sess) + resp, err := rdsSvc.DescribeDBEngineVersions(&rds.DescribeDBEngineVersionsInput{Engine: engine}) + if err != nil { + return nil, err + } + return resp.DBEngineVersions[len(resp.DBEngineVersions)-1].EngineVersion, nil +} + // createDatabaseCmd represents the create database command var createDatabaseCmd = &cobra.Command{ Use: "database []", @@ -135,6 +183,8 @@ var createDatabaseCmd = &cobra.Command{ sess, err := awsSession() checkErr(err) var engine *string + var isAurora bool + var version *string answers := make(map[string]interface{}) if !nonInteractive { questions := []*survey.Question{} @@ -143,26 +193,40 @@ var createDatabaseCmd = &cobra.Command{ questions = append(questions, clusterQuestion) addQuestionFromFlag(cmd.Flags().Lookup("engine"), &questions, &survey.Question{ Name: "engine", - Prompt: &survey.Select{Message: "select the database engine", Options: []string{"aurora-mysql", "aurora-postgresql"}, FilterMessage: "", Default: "aurora-postgresql"}, + Prompt: &survey.Select{Message: "select the database engine", Options: []string{"mysql", "postgres"}, FilterMessage: "", Default: "postgres"}, }) + addQuestionFromFlag(cmd.Flags().Lookup("aurora"), &questions, nil) err = survey.Ask(questions, &answers) checkErr(err) engine = getArgValue(cmd, &answers, "engine", false) + isAurora = isTruthy(getArgValue(cmd, &answers, "aurora", false)) + engine, err = engineName(engine, isAurora) + checkErr(err) questions = []*survey.Question{} startSpinner() Spinner.Suffix = fmt.Sprintf(" retrieving instance classes for %s", *engine) - instanceClasses, err := listRDSInstanceClasses(sess, engine) + version, err = getLatestRdsVersion(sess, engine) + checkErr(err) + instanceClasses, err := listRDSInstanceClasses(sess, engine, version) checkErr(err) + Spinner.Stop() addQuestionFromFlag(cmd.Flags().Lookup("instance-class"), &questions, &survey.Question{ Name: "instance-class", Prompt: &survey.Select{Message: "select the instance class", Options: instanceClasses, FilterMessage: "", Default: "db.t3.medium"}, }) - Spinner.Stop() addQuestionFromFlag(cmd.Flags().Lookup("multi-az"), &questions, nil) + if !isAurora { + addQuestionFromFlag(cmd.Flags().Lookup("allocated-storage"), &questions, nil) + addQuestionFromFlag(cmd.Flags().Lookup("max-allocated-storage"), &questions, nil) + } err = survey.Ask(questions, &answers) checkErr(err) } else { engine = getArgValue(cmd, &answers, "engine", false) + isAurora = isTruthy(getArgValue(cmd, &answers, "aurora", false)) + engine, err = engineName(engine, isAurora) + version, err = getLatestRdsVersion(sess, engine) + checkErr(err) } cluster := getArgValue(cmd, &answers, "cluster", true) // check if a database already exists on the cluster @@ -173,20 +237,12 @@ var createDatabaseCmd = &cobra.Command{ clusterStack, err := stackFromDDBItem(sess, fmt.Sprintf("CLUSTER#%s", *cluster)) checkErr(err) var multiAZParameter string - if *(getArgValue(cmd, &answers, "multi-az", false)) == "true" { + if isTruthy((getArgValue(cmd, &answers, "multi-az", false))) { multiAZParameter = "yes" } else { multiAZParameter = "no" } - var formationURL string - if *engine == "aurora-mysql" { - formationURL = mysqlFormationURL - } else if *engine == "aurora-postgresql" { - formationURL = postgresFormationURL - } else { - checkErr(fmt.Errorf("unrecognized databae engine. valid options are 'aurora-mysql' or 'aurora-postgresql'")) - } if createChangeSet { fmt.Println("Creating Cloudformation Change Set for database resources...") } else { @@ -198,32 +254,49 @@ var createDatabaseCmd = &cobra.Command{ {Key: aws.String("apppack:cluster"), Value: cluster}, {Key: aws.String("apppack"), Value: aws.String("true")}, } + parameters := []*cloudformation.Parameter{ + { + ParameterKey: aws.String("Name"), + ParameterValue: &name, + }, + { + ParameterKey: aws.String("ClusterStackName"), + ParameterValue: clusterStack.StackName, + }, + { + ParameterKey: aws.String("Engine"), + ParameterValue: engine, + }, + { + ParameterKey: aws.String("Version"), + ParameterValue: version, + }, + { + ParameterKey: aws.String("OneTimePassword"), + ParameterValue: aws.String(generatePassword()), + }, + { + ParameterKey: aws.String("InstanceClass"), + ParameterValue: getArgValue(cmd, &answers, "instance-class", true), + }, + { + ParameterKey: aws.String("MultiAZ"), + ParameterValue: &multiAZParameter, + }, + { + ParameterKey: aws.String("AllocatedStorage"), + ParameterValue: getArgValue(cmd, &answers, "allocated-storage", false), + }, + { + ParameterKey: aws.String("MaxAllocatedStorage"), + ParameterValue: getArgValue(cmd, &answers, "max-allocated-storage", false), + }, + } input := cloudformation.CreateStackInput{ - StackName: aws.String(fmt.Sprintf(databaseStackNameTmpl, name)), - TemplateURL: aws.String(formationURL), - Parameters: []*cloudformation.Parameter{ - { - ParameterKey: aws.String("Name"), - ParameterValue: &name, - }, - { - ParameterKey: aws.String("ClusterStackName"), - ParameterValue: clusterStack.StackName, - }, - { - ParameterKey: aws.String("OneTimePassword"), - ParameterValue: aws.String(generatePassword()), - }, - { - ParameterKey: aws.String("InstanceClass"), - ParameterValue: getArgValue(cmd, &answers, "instance-class", true), - }, - { - ParameterKey: aws.String("MultiAZ"), - ParameterValue: &multiAZParameter, - }, - }, + StackName: aws.String(fmt.Sprintf(databaseStackNameTmpl, name)), + TemplateURL: aws.String(databaseFormationURL), + Parameters: parameters, Capabilities: []*string{aws.String("CAPABILITY_IAM")}, Tags: cfnTags, } @@ -248,7 +321,9 @@ func init() { createCmd.AddCommand(createDatabaseCmd) createDatabaseCmd.Flags().StringP("cluster", "c", "apppack", "cluster name") createDatabaseCmd.Flags().StringP("instance-class", "i", "db.t3.medium", "instance class -- see https://aws.amazon.com/rds/postgresql/pricing/?pg=pr&loc=3") - createDatabaseCmd.Flags().StringP("engine", "e", "aurora-postgresql", "engine [aurora-mysql,aurora-postgresql]") + createDatabaseCmd.Flags().BoolP("aurora", "a", false, "use Aurora -- see https://aws.amazon.com/rds/aurora/") + createDatabaseCmd.Flags().StringP("engine", "e", "postgresql", "engine [mysql,postgres") createDatabaseCmd.Flags().Bool("multi-az", false, "enable multi-AZ -- see https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.MultiAZ.html") - + createDatabaseCmd.Flags().Int("allocated-storage", 50, "initial storage allocated in GB (does not apply to Aurora engines)") + createDatabaseCmd.Flags().Int("max-allocated-storage", 500, "maximum storage allocated on-demand in GB (does not apply to Aurora engines)") } diff --git a/cmd/destroy.go b/cmd/destroy.go index e1355ac..f70ede9 100644 --- a/cmd/destroy.go +++ b/cmd/destroy.go @@ -33,22 +33,36 @@ type accountDetails struct { func setRdsDeletionProtection(sess *session.Session, stack *cloudformation.Stack, protected bool) error { rdsSvc := rds.New(sess) - clusterID := "" + DBID := "" + DBType := "" for _, output := range stack.Outputs { - if *output.OutputKey == "DBClusterId" { - clusterID = *output.OutputValue - break + if *output.OutputKey == "DBId" { + DBID = *output.OutputValue + } + if *output.OutputKey == "DBType" { + DBType = *output.OutputValue } } - if clusterID == "" { - return fmt.Errorf("unable to retrieve DBClusterID from %s", *stack.StackId) + if DBID == "" || DBType == "" { + return fmt.Errorf("unable to retrieve Database ID from %s", *stack.StackId) } - _, err := rdsSvc.ModifyDBCluster(&rds.ModifyDBClusterInput{ - DBClusterIdentifier: &clusterID, - DeletionProtection: &protected, - ApplyImmediately: aws.Bool(true), - }) - return err + if DBType == "instance" { + _, err := rdsSvc.ModifyDBInstance(&rds.ModifyDBInstanceInput{ + DBInstanceIdentifier: &DBID, + DeletionProtection: &protected, + ApplyImmediately: aws.Bool(true), + }) + return err + } + if DBType == "cluster" { + _, err := rdsSvc.ModifyDBCluster(&rds.ModifyDBClusterInput{ + DBClusterIdentifier: &DBID, + DeletionProtection: &protected, + ApplyImmediately: aws.Bool(true), + }) + return err + } + return fmt.Errorf("unexpected DB type %s", DBType) } // confirmDeleteStack will prompt the user to confirm stack deletion and return a Stack object @@ -116,6 +130,7 @@ var destroyAccountCmd = &cobra.Command{ cfnSvc := cloudformation.New(sess) friendlyName := "AppPack account" stack, err := confirmDeleteStack(cfnSvc, stackName, friendlyName) + checkErr(err) err = deleteStack(cfnSvc, *stack.StackId, friendlyName, false) checkErr(err) }, @@ -135,6 +150,7 @@ var destroyRegionCmd = &cobra.Command{ cfnSvc := cloudformation.New(sess) friendlyName := fmt.Sprintf("region %s", *sess.Config.Region) stack, err := confirmDeleteStack(cfnSvc, stackName, friendlyName) + checkErr(err) _, err = ssmSvc.DeleteParameter(&ssm.DeleteParameterInput{ Name: aws.String("/apppack/account/dockerhub-access-token"), }) @@ -160,6 +176,7 @@ var destroyRedisCmd = &cobra.Command{ cfnSvc := cloudformation.New(sess) friendlyName := fmt.Sprintf("app %s", args[0]) stack, err := confirmDeleteStack(cfnSvc, stackName, friendlyName) + checkErr(err) err = deleteStack(cfnSvc, *stack.StackId, friendlyName, false) if err != nil { printError(fmt.Sprintf("%v", err)) @@ -210,6 +227,7 @@ var destroyClusterCmd = &cobra.Command{ friendlyName := fmt.Sprintf("cluster %s", clusterName) stackName := fmt.Sprintf("apppack-cluster-%s", clusterName) stack, err := confirmDeleteStack(cfnSvc, stackName, friendlyName) + checkErr(err) // Weird circular dependency causes this https://github.com/aws/containers-roadmap/issues/631 // Cluster depends on ASG for creation, but ASG must be deleted before the Cluster // retrying works around this for now @@ -232,6 +250,7 @@ var destroyAppCmd = &cobra.Command{ cfnSvc := cloudformation.New(sess) friendlyName := fmt.Sprintf("app %s", appName) stack, err := confirmDeleteStack(cfnSvc, appStackName(appName), friendlyName) + checkErr(err) err = deleteStack(cfnSvc, *stack.StackId, friendlyName, false) checkErr(err) }, @@ -251,6 +270,7 @@ var destroyCustomDomainCmd = &cobra.Command{ checkErr(err) cfnSvc := cloudformation.New(sess) stack, err := confirmDeleteStack(cfnSvc, customDomainStackName(primaryDomain), friendlyName) + checkErr(err) err = deleteStack(cfnSvc, *stack.StackId, friendlyName, false) checkErr(err) }, diff --git a/cmd/upgrade.go b/cmd/upgrade.go index 4a061b9..1cc9088 100644 --- a/cmd/upgrade.go +++ b/cmd/upgrade.go @@ -153,32 +153,7 @@ var upgradeDatabaseCmd = &cobra.Command{ Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { stackName := fmt.Sprintf("apppack-database-%s", args[0]) - startSpinner() - sess, err := awsSession() - checkErr(err) - cfnSvc := cloudformation.New(sess) - stackOutput, err := cfnSvc.DescribeStacks(&cloudformation.DescribeStacksInput{ - StackName: &stackName, - }) - checkErr(err) - engine := aws.String("") - for _, out := range stackOutput.Stacks[0].Outputs { - if *out.OutputKey == "Engine" { - engine = out.OutputValue - break - } - } - var formationURL string - if *engine == "mysql" { - formationURL = mysqlFormationURL - } else if *engine == "postgres" { - formationURL = postgresFormationURL - } else { - Spinner.Stop() - checkErr(fmt.Errorf("unexpected database engine, %s", *engine)) - } - - err = upgradeStack(stackName, formationURL) + err := upgradeStack(stackName, databaseFormationURL) checkErr(err) }, }