Example #1
0
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),
		},
	)
}
Example #2
0
File: rack.go Project: gmelika/rack
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
}
Example #3
0
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", "")
}
Example #4
0
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)
}
Example #5
0
File: rack.go Project: convox/rack
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
}