func TestRackUpdateStable(t *testing.T) { versions, err := version.All() require.Nil(t, err) stable, err := versions.Resolve("stable") require.Nil(t, err) ts := testServer(t, test.Http{Method: "PUT", Body: fmt.Sprintf("version=%s", stable.Version), Path: "/system", Code: 200, Response: client.System{ Name: "mysystem", Version: "ver", Count: 1, Type: "type", }}, ) defer ts.Close() test.Runs(t, test.ExecRun{ Command: "convox rack update", Exit: 0, Stdout: fmt.Sprintf("Name mysystem\nStatus \nVersion ver\nCount 1\nType type\n\nUpdating to version: %s\n", stable.Version), }, ) }
func cmdRackUpdate(c *cli.Context) error { versions, err := version.All() if err != nil { return stdcli.ExitError(err) } specified := "stable" if len(c.Args()) > 0 { specified = c.Args()[0] } version, err := versions.Resolve(specified) if err != nil { return stdcli.ExitError(err) } system, err := rackClient(c).UpdateSystem(version.Version) if err != nil { return stdcli.ExitError(err) } fmt.Printf("Name %s\n", system.Name) fmt.Printf("Status %s\n", system.Status) fmt.Printf("Version %s\n", system.Version) fmt.Printf("Count %d\n", system.Count) fmt.Printf("Type %s\n", system.Type) fmt.Println() fmt.Printf("Updating to version: %s\n", version.Version) return nil }
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 cmdInstall(c *cli.Context) error { ep := stdcli.QOSEventProperties{Start: time.Now()} region := c.String("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 { msg := fmt.Errorf("Stack name '%s' is invalid, must match [a-z0-9-]*", stackName) stdcli.QOSEventSend("cli-install", distinctID, stdcli.QOSEventProperties{ValidationError: msg}) return stdcli.Error(msg) } } tenancy := "default" instanceType := c.String("instance-type") if c.Bool("dedicated") { tenancy = "dedicated" if strings.HasPrefix(instanceType, "t2") { stdcli.QOSEventSend("cli-install", distinctID, stdcli.QOSEventProperties{ValidationError: fmt.Errorf("t2 instance types aren't supported in dedicated tenancy, please set --instance-type.")}) return stdcli.Error(fmt.Errorf("t2 instance types aren't supported in dedicated tenancy, please set --instance-type.")) } } numInstances := c.Int("instance-count") instanceCount := fmt.Sprintf("%d", numInstances) if numInstances <= 2 { stdcli.QOSEventSend("cli-install", distinctID, stdcli.QOSEventProperties{ValidationError: fmt.Errorf("instance-count must be greater than 2")}) return stdcli.Error(fmt.Errorf("instance-count must be greater than 2")) } var subnet0CIDR, subnet1CIDR, subnet2CIDR string if cidrs := c.String("subnet-cidrs"); cidrs != "" { parts := strings.SplitN(cidrs, ",", 3) if len(parts) < 3 { stdcli.QOSEventSend("cli-install", distinctID, stdcli.QOSEventProperties{ValidationError: fmt.Errorf("subnet-cidrs must have 3 values")}) return stdcli.Error(fmt.Errorf("subnet-cidrs must have 3 values")) } subnet0CIDR = parts[0] subnet1CIDR = parts[1] subnet2CIDR = parts[2] } var subnetPrivate0CIDR, subnetPrivate1CIDR, subnetPrivate2CIDR string if cidrs := c.String("private-cidrs"); cidrs != "" { parts := strings.SplitN(cidrs, ",", 3) if len(parts) < 3 { stdcli.QOSEventSend("cli-install", distinctID, stdcli.QOSEventProperties{ValidationError: fmt.Errorf("private-cidrs must have 3 values")}) return stdcli.Error(fmt.Errorf("private-cidrs must have 3 values")) } subnetPrivate0CIDR = parts[0] subnetPrivate1CIDR = parts[1] subnetPrivate2CIDR = parts[2] } var existingVPC string if vpc := c.String("existing-vpc"); vpc != "" { existingVPC = vpc } internetGateway := c.String("internet-gateway") if (existingVPC != "") && (internetGateway == "") { return stdcli.Error(fmt.Errorf("must specify valid Internet Gateway for existing VPC")) } private := "No" if c.Bool("private") || strings.ToLower(os.Getenv("RACK_PRIVATE")) == "yes" || strings.ToLower(os.Getenv("RACK_PRIVATE")) == "true" { private = "Yes" } ami := c.String("ami") key := c.String("key") vpcCIDR := c.String("vpc-cidr") versions, err := version.All() if err != nil { stdcli.QOSEventSend("cli-install", distinctID, stdcli.QOSEventProperties{Error: fmt.Errorf("error getting versions: %s", err)}) return stdcli.Error(err) } version, err := versions.Resolve(c.String("version")) if err != nil { stdcli.QOSEventSend("cli-install", distinctID, stdcli.QOSEventProperties{Error: fmt.Errorf("error resolving version: %s", err)}) return stdcli.Error(err) } versionName := version.Version furl := fmt.Sprintf(formationURL, versionName) fmt.Println(Banner) if err != nil { stdcli.QOSEventSend("cli-install", distinctID, stdcli.QOSEventProperties{Error: err}) return stdcli.Error(err) } fmt.Printf("Installing Convox (%s)...\n", versionName) if private == "Yes" { fmt.Println("(Private Network Edition)") } 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 { stdcli.QOSEventSend("cli-install", distinctID, stdcli.QOSEventProperties{Error: err}) return stdcli.Error(err) } if strings.TrimSpace(email) != "" { distinctID = email updateId(email) } } credentialsFile := "" if len(c.Args()) >= 1 { credentialsFile = c.Args()[0] } creds, err := readCredentials(credentialsFile) if err != nil { stdcli.QOSEventSend("cli-install", distinctID, stdcli.QOSEventProperties{Error: fmt.Errorf("error: %s", err)}) return stdcli.Error(err) } if creds == nil { stdcli.QOSEventSend("cli-install", distinctID, stdcli.QOSEventProperties{Error: fmt.Errorf("error reading credentials")}) return stdcli.Error(err) } err = validateUserAccess(region, creds) if err != nil { stdcli.Error(err) } 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{ {ParameterKey: aws.String("Ami"), ParameterValue: aws.String(ami)}, {ParameterKey: aws.String("ClientId"), ParameterValue: aws.String(distinctID)}, {ParameterKey: aws.String("ExistingVpc"), ParameterValue: aws.String(existingVPC)}, {ParameterKey: aws.String("InstanceCount"), ParameterValue: aws.String(instanceCount)}, {ParameterKey: aws.String("InstanceType"), ParameterValue: aws.String(instanceType)}, {ParameterKey: aws.String("InternetGateway"), ParameterValue: aws.String(internetGateway)}, {ParameterKey: aws.String("Key"), ParameterValue: aws.String(key)}, {ParameterKey: aws.String("Password"), ParameterValue: aws.String(password)}, {ParameterKey: aws.String("Private"), ParameterValue: aws.String(private)}, {ParameterKey: aws.String("Tenancy"), ParameterValue: aws.String(tenancy)}, {ParameterKey: aws.String("Version"), ParameterValue: aws.String(versionName)}, {ParameterKey: aws.String("Subnet0CIDR"), ParameterValue: aws.String(subnet0CIDR)}, {ParameterKey: aws.String("Subnet1CIDR"), ParameterValue: aws.String(subnet1CIDR)}, {ParameterKey: aws.String("Subnet2CIDR"), ParameterValue: aws.String(subnet2CIDR)}, {ParameterKey: aws.String("SubnetPrivate0CIDR"), ParameterValue: aws.String(subnetPrivate0CIDR)}, {ParameterKey: aws.String("SubnetPrivate1CIDR"), ParameterValue: aws.String(subnetPrivate1CIDR)}, {ParameterKey: aws.String("SubnetPrivate2CIDR"), ParameterValue: aws.String(subnetPrivate2CIDR)}, {ParameterKey: aws.String("VPCCIDR"), ParameterValue: aws.String(vpcCIDR)}, }, StackName: aws.String(stackName), TemplateURL: aws.String(furl), } if tf := os.Getenv("TEMPLATE_FILE"); tf != "" { dat, err := ioutil.ReadFile(tf) if err != nil { stdcli.QOSEventSend("cli-install", distinctID, stdcli.QOSEventProperties{Error: fmt.Errorf("error reading template file: %s", tf)}) return stdcli.Error(err) } t := new(bytes.Buffer) if err := json.Compact(t, dat); err != nil { stdcli.QOSEventSend("cli-install", distinctID, stdcli.QOSEventProperties{Error: err}) return stdcli.Error(err) } req.TemplateURL = nil req.TemplateBody = aws.String(t.String()) } res, err := CloudFormation.CreateStack(req) if err != nil { if awsErr, ok := err.(awserr.Error); ok { if awsErr.Code() == "AlreadyExistsException" { stdcli.QOSEventSend("cli-install", distinctID, stdcli.QOSEventProperties{Error: err}) return stdcli.Error(fmt.Errorf("Stack %q already exists. Run `convox uninstall` then try again", stackName)) } } stdcli.QOSEventSend("cli-install", distinctID, stdcli.QOSEventProperties{Error: err}) return stdcli.Error(err) } // 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 nil } host, err := waitForCompletion(*res.StackId, CloudFormation, false) if err != nil { stdcli.QOSEventSend("cli-install", distinctID, stdcli.QOSEventProperties{Error: err}) return stdcli.Error(err) } fmt.Println("Waiting for load balancer...") if err := waitForAvailability(fmt.Sprintf("http://%s/", host)); err != nil { stdcli.QOSEventSend("cli-install", distinctID, stdcli.QOSEventProperties{Error: err}) return stdcli.Error(err) } fmt.Println("Logging in...") err = addLogin(host, password) if err != nil { stdcli.QOSEventSend("cli-install", distinctID, stdcli.QOSEventProperties{Error: err}) return stdcli.Error(err) } err = switchHost(host) if err != nil { stdcli.QOSEventSend("cli-install", distinctID, stdcli.QOSEventProperties{Error: err}) return stdcli.Error(err) } fmt.Println("Success, try `convox apps`") return stdcli.QOSEventSend("cli-install", distinctID, ep) }
func cmdRackUpdate(c *cli.Context) error { vs, err := version.All() if err != nil { return stdcli.Error(err) } target, err := vs.Latest() if err != nil { return stdcli.Error(err) } if len(c.Args()) > 0 { t, err := vs.Find(c.Args()[0]) if err != nil { return stdcli.Error(err) } target = t } system, err := rackClient(c).GetSystem() if err != nil { return stdcli.Error(err) } nv, err := vs.Next(system.Version) if err != nil && strings.HasSuffix(err.Error(), "is latest") { nv = target.Version } else if err != nil { return stdcli.Error(err) } next, err := vs.Find(nv) if err != nil { return stdcli.Error(err) } // stop at a required release if necessary if next.Version < target.Version { stdcli.Writef("WARNING: Required update found.\nPlease run `convox rack update` again once this update completes.\n") target = next } stdcli.Startf("Updating to <release>%s</release>", target.Version) _, err = rackClient(c).UpdateSystem(target.Version) if err != nil { return stdcli.Error(err) } stdcli.Wait("UPDATING") if c.Bool("wait") { stdcli.Startf("Waiting for completion") // give the rack a few seconds to start updating time.Sleep(5 * time.Second) if err := waitForRackRunning(c); err != nil { return stdcli.Error(err) } stdcli.OK() } return nil }