func cmdPs(c *cli.Context) { _, app, err := stdcli.DirApp(c, ".") if err != nil { stdcli.Error(err) return } ps, err := rackClient(c).GetProcesses(app, c.Bool("stats")) if err != nil { stdcli.Error(err) return } if c.Bool("stats") { t := stdcli.NewTable("ID", "NAME", "RELEASE", "SIZE", "CPU", "MEM", "STARTED", "COMMAND") for _, p := range ps { t.AddRow(prettyId(p), p.Name, p.Release, fmt.Sprintf("%d", p.Size), fmt.Sprintf("%0.2f%%", p.Cpu), fmt.Sprintf("%0.2f%%", p.Memory*100), humanizeTime(p.Started), p.Command) } t.Print() } else { t := stdcli.NewTable("ID", "NAME", "RELEASE", "SIZE", "STARTED", "COMMAND") for _, p := range ps { t.AddRow(prettyId(p), p.Name, p.Release, fmt.Sprintf("%d", p.Size), humanizeTime(p.Started), p.Command) } t.Print() } }
func cmdRun(c *cli.Context) { if c.Bool("detach") { cmdRunDetached(c) return } _, app, err := stdcli.DirApp(c, ".") if err != nil { stdcli.Error(err) return } if len(c.Args()) < 2 { stdcli.Usage(c, "run") return } ps := c.Args()[0] args := strings.Join(c.Args()[1:], " ") code, err := runAttached(c, app, ps, args) if err != nil { stdcli.Error(err) return } os.Exit(code) }
func cmdRackReleases(c *cli.Context) { vs, err := version.All() if err != nil { return } selected := version.Versions{} for _, v := range vs { switch { case !v.Published && c.Bool("unpublished"): selected = append(selected, v) case v.Published: selected = append(selected, v) } } sort.Sort(sort.Reverse(selected)) if len(selected) > 20 { selected = selected[0:20] } for _, v := range selected { fmt.Println(v.Version) } }
func executeBuildDir(c *cli.Context, dir, app, manifest, description string) (string, error) { dir, err := filepath.Abs(dir) if err != nil { return "", err } fmt.Print("Creating tarball... ") tar, err := createTarball(dir) if err != nil { return "", err } fmt.Println("OK") cache := !c.Bool("no-cache") fmt.Print("Uploading... ") build, err := rackClient(c).CreateBuildSource(app, tar, cache, manifest, description) if err != nil { return "", err } fmt.Println("OK") return finishBuild(c, app, build) }
func executeBuildUrl(c *cli.Context, url string, app string, config string) (string, error) { cache := !c.Bool("no-cache") build, err := rackClient(c).CreateBuildUrl(app, url, cache, config) if err != nil { return "", err } return finishBuild(c, app, build) }
func executeBuildUrl(c *cli.Context, url, app, manifest, description string) (string, error) { cache := !c.Bool("no-cache") build, err := rackClient(c).CreateBuildUrl(app, url, cache, manifest, description) if err != nil { return "", err } return finishBuild(c, app, build) }
func executeBuildDirIncremental(c *cli.Context, dir, app, manifest, description string) (string, error) { system, err := rackClient(c).GetSystem() if err != nil { return "", err } // if the rack doesnt support incremental builds then fall back if system.Version < "20160226234213" { return executeBuildDir(c, dir, app, manifest, description) } cache := !c.Bool("no-cache") dir, err = filepath.Abs(dir) if err != nil { return "", err } fmt.Printf("Analyzing source... ") index, err := createIndex(dir) if err != nil { return "", err } fmt.Println("OK") fmt.Printf("Uploading changes... ") err = uploadIndex(c, index) if err != nil { return "", err } fmt.Printf("Starting build... ") build, err := rackClient(c).CreateBuildIndex(app, index, cache, manifest, description) if err != nil { return "", err } fmt.Println("OK") return finishBuild(c, app, build) }
func cmdEnvUnset(c *cli.Context) { _, app, err := stdcli.DirApp(c, ".") if err != nil { stdcli.Error(err) return } if len(c.Args()) == 0 { stdcli.Error(errors.New("No variable specified")) return } if len(c.Args()) > 1 { stdcli.Error(errors.New("Only 1 variable can be unset at a time")) return } key := c.Args()[0] fmt.Print("Updating environment... ") _, releaseId, err := rackClient(c).DeleteEnvironment(app, key) if err != nil { stdcli.Error(err) return } fmt.Println("OK") if releaseId != "" { if c.Bool("promote") { fmt.Printf("Promoting %s... ", releaseId) _, err = rackClient(c).PromoteRelease(app, releaseId) if err != nil { stdcli.Error(err) return } fmt.Println("OK") } else { fmt.Printf("To deploy these changes run `convox releases promote %s`\n", releaseId) } } }
func executeBuild(c *cli.Context, source, app, manifest, description string) (string, error) { u, _ := url.Parse(source) switch u.Scheme { case "http", "https": return executeBuildUrl(c, source, app, manifest, description) default: if c.Bool("incremental") { return executeBuildDirIncremental(c, source, app, manifest, description) } else { return executeBuildDir(c, source, app, manifest, description) } } return "", fmt.Errorf("unreachable") }
func cmdBuildsCopy(c *cli.Context) { _, app, err := stdcli.DirApp(c, ".") if err != nil { stdcli.Error(err) return } if len(c.Args()) != 2 { stdcli.Usage(c, "copy") return } build := c.Args()[0] destApp := c.Args()[1] fmt.Print("Copying build... ") b, err := rackClient(c).CopyBuild(app, build, destApp) if err != nil { stdcli.Error(err) return } fmt.Println("OK") if b.Release != "" { if c.Bool("promote") { fmt.Printf("Promoting %s... ", b.Release) _, err = rackClient(c).PromoteRelease(destApp, b.Release) if err != nil { stdcli.Error(err) return } fmt.Println("OK") } else { fmt.Printf("To deploy this copy run `convox releases promote %s --app %s`\n", b.Release, destApp) } } }
func cmdInstall(c *cli.Context) { region := c.String("region") if !lambdaRegions[region] { stdcli.Error(fmt.Errorf("Convox is not currently supported in %s", region)) } stackName := c.String("stack-name") awsRegexRules := []string{ //ecr: http://docs.aws.amazon.com/AmazonECR/latest/APIReference/API_CreateRepository.html "(?:[a-z0-9]+(?:[._-][a-z0-9]+)*/)*[a-z0-9]+(?:[._-][a-z0-9]+)*", //cloud formation: https://forums.aws.amazon.com/thread.jspa?threadID=118427 "[a-zA-Z][-a-zA-Z0-9]*", } for _, r := range awsRegexRules { rp := regexp.MustCompile(r) matchedStr := rp.FindString(stackName) match := len(matchedStr) == len(stackName) if !match { stdcli.Error(fmt.Errorf("Stack name is invalid, must match [a-z0-9-]*")) } } tenancy := "default" instanceType := c.String("instance-type") if c.Bool("dedicated") { tenancy = "dedicated" if strings.HasPrefix(instanceType, "t2") { stdcli.Error(fmt.Errorf("t2 instance types aren't supported in dedicated tenancy, please set --instance-type.")) } } fmt.Println(Banner) distinctId, err := currentId() creds, err := readCredentials(c) if err != nil { handleError("install", distinctId, err) return } if creds == nil { err = fmt.Errorf("error reading credentials") handleError("install", distinctId, err) return } reader := bufio.NewReader(os.Stdin) if email := c.String("email"); email != "" { distinctId = email updateId(distinctId) } else if terminal.IsTerminal(int(os.Stdin.Fd())) { fmt.Print("Email Address (optional, to receive project updates): ") email, err := reader.ReadString('\n') if err != nil { handleError("install", distinctId, err) return } if strings.TrimSpace(email) != "" { distinctId = email updateId(email) } } development := "No" if c.Bool("development") { isDevelopment = true development = "Yes" } private := "No" if c.Bool("private") { private = "Yes" } privateApi := "No" if c.Bool("private-api") { private = "Yes" privateApi = "Yes" } ami := c.String("ami") key := c.String("key") vpcCIDR := c.String("vpc-cidr") subnet0CIDR := c.String("subnet0-cidr") subnet1CIDR := c.String("subnet1-cidr") subnet2CIDR := c.String("subnet2-cidr") subnetPrivate0CIDR := c.String("subnet-private0-cidr") subnetPrivate1CIDR := c.String("subnet-private1-cidr") subnetPrivate2CIDR := c.String("subnet-private2-cidr") versions, err := version.All() if err != nil { handleError("install", distinctId, err) return } version, err := versions.Resolve(c.String("version")) if err != nil { handleError("install", distinctId, err) return } versionName := version.Version formationUrl := fmt.Sprintf(FormationUrl, versionName) instanceCount := fmt.Sprintf("%d", c.Int("instance-count")) fmt.Printf("Installing Convox (%s)...\n", versionName) if isDevelopment { fmt.Println("(Development Mode)") } if private == "Yes" { fmt.Println("(Private Network Edition)") } password := c.String("password") if password == "" { password = randomString(30) } CloudFormation := cloudformation.New(session.New(), awsConfig(region, creds)) req := &cloudformation.CreateStackInput{ Capabilities: []*string{aws.String("CAPABILITY_IAM")}, Parameters: []*cloudformation.Parameter{ &cloudformation.Parameter{ParameterKey: aws.String("Ami"), ParameterValue: aws.String(ami)}, &cloudformation.Parameter{ParameterKey: aws.String("ClientId"), ParameterValue: aws.String(distinctId)}, &cloudformation.Parameter{ParameterKey: aws.String("Development"), ParameterValue: aws.String(development)}, &cloudformation.Parameter{ParameterKey: aws.String("InstanceCount"), ParameterValue: aws.String(instanceCount)}, &cloudformation.Parameter{ParameterKey: aws.String("InstanceType"), ParameterValue: aws.String(instanceType)}, &cloudformation.Parameter{ParameterKey: aws.String("Key"), ParameterValue: aws.String(key)}, &cloudformation.Parameter{ParameterKey: aws.String("Password"), ParameterValue: aws.String(password)}, &cloudformation.Parameter{ParameterKey: aws.String("Private"), ParameterValue: aws.String(private)}, &cloudformation.Parameter{ParameterKey: aws.String("PrivateApi"), ParameterValue: aws.String(privateApi)}, &cloudformation.Parameter{ParameterKey: aws.String("Tenancy"), ParameterValue: aws.String(tenancy)}, &cloudformation.Parameter{ParameterKey: aws.String("Version"), ParameterValue: aws.String(versionName)}, &cloudformation.Parameter{ParameterKey: aws.String("Subnet0CIDR"), ParameterValue: aws.String(subnet0CIDR)}, &cloudformation.Parameter{ParameterKey: aws.String("Subnet1CIDR"), ParameterValue: aws.String(subnet1CIDR)}, &cloudformation.Parameter{ParameterKey: aws.String("Subnet2CIDR"), ParameterValue: aws.String(subnet2CIDR)}, &cloudformation.Parameter{ParameterKey: aws.String("SubnetPrivate0CIDR"), ParameterValue: aws.String(subnetPrivate0CIDR)}, &cloudformation.Parameter{ParameterKey: aws.String("SubnetPrivate1CIDR"), ParameterValue: aws.String(subnetPrivate1CIDR)}, &cloudformation.Parameter{ParameterKey: aws.String("SubnetPrivate2CIDR"), ParameterValue: aws.String(subnetPrivate2CIDR)}, &cloudformation.Parameter{ParameterKey: aws.String("VPCCIDR"), ParameterValue: aws.String(vpcCIDR)}, }, StackName: aws.String(stackName), TemplateURL: aws.String(formationUrl), } if tf := os.Getenv("TEMPLATE_FILE"); tf != "" { dat, err := ioutil.ReadFile(tf) if err != nil { handleError("install", distinctId, err) } req.TemplateURL = nil req.TemplateBody = aws.String(string(dat)) } res, err := CloudFormation.CreateStack(req) // NOTE: we start making lots of network requests here // so we're just going to return for testability if os.Getenv("AWS_REGION") == "test" { fmt.Println(*res.StackId) return } if err != nil { sendMixpanelEvent(fmt.Sprintf("convox-install-error"), err.Error()) if awsErr, ok := err.(awserr.Error); ok { if awsErr.Code() == "AlreadyExistsException" { stdcli.Error(fmt.Errorf("Stack %q already exists. Run `convox uninstall` then try again.", stackName)) } } stdcli.Error(err) } sendMixpanelEvent("convox-install-start", "") host, err := waitForCompletion(*res.StackId, CloudFormation, false) if err != nil { handleError("install", distinctId, err) return } if privateApi == "Yes" { fmt.Println("Success. See http://convox.com/docs/private-api/ for instructions to log into the private Rack API.") } else { fmt.Println("Waiting for load balancer...") waitForAvailability(fmt.Sprintf("http://%s/", host)) fmt.Println("Logging in...") addLogin(host, password) switchHost(host) fmt.Println("Success, try `convox apps`") } sendMixpanelEvent("convox-install-success", "") }
func cmdEnvSet(c *cli.Context) { _, app, err := stdcli.DirApp(c, ".") if err != nil { stdcli.Error(err) return } env, err := rackClient(c).GetEnvironment(app) if err != nil { stdcli.Error(err) return } data := "" for key, value := range env { data += fmt.Sprintf("%s=%s\n", key, value) } stat, err := os.Stdin.Stat() if err != nil { stdcli.Error(err) return } if (stat.Mode() & os.ModeCharDevice) == 0 { in, err := ioutil.ReadAll(os.Stdin) if err != nil { stdcli.Error(err) return } data += string(in) } for _, value := range c.Args() { data += fmt.Sprintf("%s\n", value) } fmt.Print("Updating environment... ") _, releaseId, err := rackClient(c).SetEnvironment(app, strings.NewReader(data)) if err != nil { stdcli.Error(err) return } fmt.Println("OK") if releaseId != "" { if c.Bool("promote") { fmt.Printf("Promoting %s... ", releaseId) _, err = rackClient(c).PromoteRelease(app, releaseId) if err != nil { stdcli.Error(err) return } fmt.Println("OK") } else { fmt.Printf("To deploy these changes run `convox releases promote %s`\n", releaseId) } } }
func cmdSSLCreate(c *cli.Context) { _, app, err := stdcli.DirApp(c, ".") if err != nil { stdcli.Error(err) return } if len(c.Args()) < 1 { stdcli.Usage(c, "create") return } target := c.Args()[0] parts := strings.Split(target, ":") if len(parts) != 2 { stdcli.Error(fmt.Errorf("target must be process:port")) return } var pub []byte var key []byte switch len(c.Args()) { case 1: if c.Bool("self-signed") { formation, err := rackClient(c).ListFormation(app) if err != nil { stdcli.Error(err) return } host := "" for _, entry := range formation { if entry.Name == parts[0] { host = entry.Balancer } } if host == "" { stdcli.Error(fmt.Errorf("no balancer for process: %s", parts[0])) return } pub, key, err = generateSelfSignedCertificate(app, host) if err != nil { stdcli.Error(err) return } } else { stdcli.Usage(c, "create") return } case 3: pub, err = ioutil.ReadFile(c.Args()[1]) if err != nil { stdcli.Error(err) return } key, err = ioutil.ReadFile(c.Args()[2]) if err != nil { stdcli.Error(err) return } default: stdcli.Usage(c, "create") return } chain := "" if chainFile := c.String("chain"); chainFile != "" { data, err := ioutil.ReadFile(chainFile) if err != nil { stdcli.Error(err) return } chain = string(data) } fmt.Printf("Creating SSL listener %s... ", target) _, err = rackClient(c).CreateSSL(app, parts[0], parts[1], string(pub), string(key), chain, c.Bool("secure")) if err != nil { stdcli.Error(err) return } fmt.Println("Done.") }
func cmdUninstall(c *cli.Context) { if !c.Bool("force") { apps, err := rackClient(c).GetApps() if err != nil { stdcli.Error(err) return } if len(apps) != 0 { stdcli.Error(fmt.Errorf("Please delete all apps before uninstalling.")) } services, err := rackClient(c).GetServices() if err != nil { stdcli.Error(err) return } if len(services) != 0 { stdcli.Error(fmt.Errorf("Please delete all services before uninstalling.")) } } fmt.Println(Banner) creds, err := readCredentials(c) if err != nil { stdcli.Error(err) return } if creds == nil { stdcli.Error(fmt.Errorf("error reading credentials")) return } region := c.String("region") stackName := c.String("stack-name") fmt.Println("") fmt.Println("Uninstalling Convox...") // CF Stack Delete and Retry could take 30+ minutes. Periodically generate more progress output. go func() { t := time.Tick(2 * time.Minute) for range t { fmt.Println("Uninstalling Convox...") } }() distinctId := randomString(10) CloudFormation := cloudformation.New(session.New(), awsConfig(region, creds)) res, err := CloudFormation.DescribeStacks(&cloudformation.DescribeStacksInput{ StackName: aws.String(stackName), }) if err != nil { sendMixpanelEvent(fmt.Sprintf("convox-uninstall-error"), err.Error()) if awsErr, ok := err.(awserr.Error); ok { if awsErr.Code() == "ValidationError" { stdcli.Error(fmt.Errorf("Stack %q does not exist.", stackName)) } } stdcli.Error(err) } stackId := *res.Stacks[0].StackId _, err = CloudFormation.DeleteStack(&cloudformation.DeleteStackInput{ StackName: aws.String(stackId), }) if err != nil { handleError("uninstall", distinctId, err) return } sendMixpanelEvent("convox-uninstall-start", "") _, err = waitForCompletion(stackId, CloudFormation, true) if err != nil { sendMixpanelEvent("convox-uninstall-retry", "") _, err = CloudFormation.DeleteStack(&cloudformation.DeleteStackInput{ StackName: aws.String(stackId), }) if err != nil { handleError("uninstall", distinctId, err) return } _, err = waitForCompletion(stackId, CloudFormation, true) if err != nil { handleError("uninstall", distinctId, err) return } } host := "" for _, o := range res.Stacks[0].Outputs { if *o.OutputKey == "Dashboard" { host = *o.OutputValue break } } if configuredHost, _ := currentHost(); configuredHost == host { removeHost() } removeLogin(host) fmt.Println("Successfully uninstalled.") sendMixpanelEvent("convox-uninstall-success", "") }
func cmdInstall(c *cli.Context) { region := c.String("region") if !lambdaRegions[region] { stdcli.Error(fmt.Errorf("Convox is not currently supported in %s", region)) } tenancy := "default" instanceType := c.String("instance-type") if c.Bool("dedicated") { tenancy = "dedicated" if strings.HasPrefix(instanceType, "t2") { stdcli.Error(fmt.Errorf("t2 instance types aren't supported in dedicated tenancy, please set --instance-type.")) } } fmt.Println(Banner) distinctId, err := currentId() creds, err := readCredentials(c) if err != nil { handleError("install", distinctId, err) return } if creds == nil { err = fmt.Errorf("error reading credentials") handleError("install", distinctId, err) return } reader := bufio.NewReader(os.Stdin) if email := c.String("email"); email != "" { distinctId = email updateId(distinctId) } else if terminal.IsTerminal(int(os.Stdin.Fd())) { fmt.Print("Email Address (optional, to receive project updates): ") email, err := reader.ReadString('\n') if err != nil { handleError("install", distinctId, err) return } if strings.TrimSpace(email) != "" { distinctId = email updateId(email) } } development := "No" if c.Bool("development") { isDevelopment = true development = "Yes" } encryption := "Yes" if c.Bool("disable-encryption") { encryption = "No" } ami := c.String("ami") key := c.String("key") stackName := c.String("stack-name") vpcCIDR := c.String("vpc-cidr") subnet0CIDR := c.String("subnet0-cidr") subnet1CIDR := c.String("subnet1-cidr") subnet2CIDR := c.String("subnet2-cidr") versions, err := version.All() if err != nil { handleError("install", distinctId, err) return } version, err := versions.Resolve(c.String("version")) if err != nil { handleError("install", distinctId, err) return } versionName := version.Version formationUrl := fmt.Sprintf(FormationUrl, versionName) instanceCount := fmt.Sprintf("%d", c.Int("instance-count")) fmt.Printf("Installing Convox (%s)...\n", versionName) if isDevelopment { fmt.Println("(Development Mode)") } password := randomString(30) CloudFormation := cloudformation.New(session.New(), awsConfig(region, creds)) req := &cloudformation.CreateStackInput{ Capabilities: []*string{aws.String("CAPABILITY_IAM")}, Parameters: []*cloudformation.Parameter{ &cloudformation.Parameter{ParameterKey: aws.String("Ami"), ParameterValue: aws.String(ami)}, &cloudformation.Parameter{ParameterKey: aws.String("ClientId"), ParameterValue: aws.String(distinctId)}, &cloudformation.Parameter{ParameterKey: aws.String("Development"), ParameterValue: aws.String(development)}, &cloudformation.Parameter{ParameterKey: aws.String("Encryption"), ParameterValue: aws.String(encryption)}, &cloudformation.Parameter{ParameterKey: aws.String("InstanceCount"), ParameterValue: aws.String(instanceCount)}, &cloudformation.Parameter{ParameterKey: aws.String("InstanceType"), ParameterValue: aws.String(instanceType)}, &cloudformation.Parameter{ParameterKey: aws.String("Key"), ParameterValue: aws.String(key)}, &cloudformation.Parameter{ParameterKey: aws.String("Password"), ParameterValue: aws.String(password)}, &cloudformation.Parameter{ParameterKey: aws.String("Tenancy"), ParameterValue: aws.String(tenancy)}, &cloudformation.Parameter{ParameterKey: aws.String("Version"), ParameterValue: aws.String(versionName)}, &cloudformation.Parameter{ParameterKey: aws.String("Subnet0CIDR"), ParameterValue: aws.String(subnet0CIDR)}, &cloudformation.Parameter{ParameterKey: aws.String("Subnet1CIDR"), ParameterValue: aws.String(subnet1CIDR)}, &cloudformation.Parameter{ParameterKey: aws.String("Subnet2CIDR"), ParameterValue: aws.String(subnet2CIDR)}, &cloudformation.Parameter{ParameterKey: aws.String("VPCCIDR"), ParameterValue: aws.String(vpcCIDR)}, }, StackName: aws.String(stackName), TemplateURL: aws.String(formationUrl), } if tf := os.Getenv("TEMPLATE_FILE"); tf != "" { dat, err := ioutil.ReadFile(tf) if err != nil { handleError("install", distinctId, err) } req.TemplateURL = nil req.TemplateBody = aws.String(string(dat)) } res, err := CloudFormation.CreateStack(req) // NOTE: we start making lots of network requests here // so we're just going to return for testability if os.Getenv("AWS_REGION") == "test" { fmt.Println(*res.StackId) return } if err != nil { sendMixpanelEvent(fmt.Sprintf("convox-install-error"), err.Error()) if awsErr, ok := err.(awserr.Error); ok { if awsErr.Code() == "AlreadyExistsException" { stdcli.Error(fmt.Errorf("Stack %q already exists. Run `convox uninstall` then try again.", stackName)) } } stdcli.Error(err) } sendMixpanelEvent("convox-install-start", "") host, err := waitForCompletion(*res.StackId, CloudFormation, false) if err != nil { handleError("install", distinctId, err) return } fmt.Println("Waiting for load balancer...") waitForAvailability(fmt.Sprintf("http://%s/", host)) fmt.Println("Logging in...") addLogin(host, password) switchHost(host) fmt.Println("Success, try `convox apps`") sendMixpanelEvent("convox-install-success", "") }
func cmdStart(c *cli.Context) { wd := "." if len(c.Args()) > 0 { wd = c.Args()[0] } dir, app, err := stdcli.DirApp(c, wd) if err != nil { stdcli.Error(err) return } file := c.String("file") m, err := manifest.Read(dir, file) if err != nil { changes, err := manifest.Init(dir) if err != nil { stdcli.Error(err) return } fmt.Printf("Generated: %s\n", strings.Join(changes, ", ")) m, err = manifest.Read(dir, file) if err != nil { stdcli.Error(err) return } } conflicts, err := m.PortConflicts() if err != nil { stdcli.Error(err) return } if len(conflicts) > 0 { stdcli.Error(fmt.Errorf("ports in use: %s", strings.Join(conflicts, ", "))) return } missing, err := m.MissingEnvironment() if err != nil { stdcli.Error(err) return } if len(missing) > 0 { stdcli.Error(fmt.Errorf("env expected: %s", strings.Join(missing, ", "))) return } errors := m.Build(app, dir, !c.Bool("no-cache")) if len(errors) != 0 { fmt.Printf("errors: %+v\n", errors) return } errors = m.Run(app) if len(errors) != 0 { // TODO figure out what to do here // fmt.Printf("errors: %+v\n", errors) return } }
func cmdUninstall(c *cli.Context) { if !c.Bool("force") { apps, err := rackClient(c).GetApps() if err != nil { stdcli.Error(err) return } if len(apps) != 0 { stdcli.Error(fmt.Errorf("Please delete all apps before uninstalling.")) } } fmt.Println(Banner) creds, err := readCredentials(c) if err != nil { stdcli.Error(err) return } if creds == nil { stdcli.Error(fmt.Errorf("error reading credentials")) return } region := c.String("region") stackName := c.String("stack-name") fmt.Println("") fmt.Println("Uninstalling Convox...") distinctId := randomString(10) CloudFormation := cloudformation.New(&aws.Config{ Region: aws.String(region), Credentials: credentials.NewStaticCredentials(creds.Access, creds.Secret, creds.Session), }) res, err := CloudFormation.DescribeStacks(&cloudformation.DescribeStacksInput{ StackName: aws.String(stackName), }) if err != nil { sendMixpanelEvent(fmt.Sprintf("convox-uninstall-error"), err.Error()) if awsErr, ok := err.(awserr.Error); ok { if awsErr.Code() == "ValidationError" { stdcli.Error(fmt.Errorf("Stack %q does not exist.", stackName)) } } stdcli.Error(err) } stackId := *res.Stacks[0].StackId _, err = CloudFormation.DeleteStack(&cloudformation.DeleteStackInput{ StackName: aws.String(stackId), }) if err != nil { handleError("uninstall", distinctId, err) return } sendMixpanelEvent("convox-uninstall-start", "") _, err = waitForCompletion(stackId, CloudFormation, true) if err != nil { handleError("uninstall", distinctId, err) return } host := "" for _, o := range res.Stacks[0].Outputs { if *o.OutputKey == "Dashboard" { host = *o.OutputValue break } } if configuredHost, _ := currentHost(); configuredHost == host { removeHost() } removeLogin(host) fmt.Println("Successfully uninstalled.") sendMixpanelEvent("convox-uninstall-success", "") }