Example #1
0
func (c *AWSCluster) fetchStack() error {
	stackID := aws.String(c.StackID)

	c.base.SendLog("Fetching stack")
	var res *cloudformation.DescribeStacksResult
	err := c.wrapRequest(func() error {
		var err error
		res, err = c.cf.DescribeStacks(&cloudformation.DescribeStacksInput{
			StackName: stackID,
		})
		return err
	})
	if err != nil {
		return err
	}
	if len(res.Stacks) == 0 {
		return StackNotFoundError
	}
	stack := &res.Stacks[0]
	if strings.HasPrefix(*stack.StackStatus, "DELETE_") {
		return StackNotFoundError
	}
	c.stack = stack
	return nil
}
Example #2
0
func (c *AWSCluster) loadKeyPair(name string) error {
	keypair, err := loadSSHKey(name)
	if err != nil {
		return err
	}
	fingerprint, err := awsutil.FingerprintImportedKey(keypair.PrivateKey)
	if err != nil {
		return err
	}
	res, err := c.ec2.DescribeKeyPairs(&ec2.DescribeKeyPairsRequest{
		Filters: []ec2.Filter{
			{
				Name:   aws.String("fingerprint"),
				Values: []string{fingerprint},
			},
		},
	})
	if err != nil {
		return err
	}
	if len(res.KeyPairs) == 0 {
		return errors.New("No matching key found")
	}
	c.base.SSHKey = keypair
	for _, p := range res.KeyPairs {
		if *p.KeyName == name {
			c.base.SSHKeyName = name
			return nil
		}
	}
	c.base.SSHKeyName = *res.KeyPairs[0].KeyName
	return saveSSHKey(c.base.SSHKeyName, keypair)
}
Example #3
0
func (c *AWSCluster) Delete() {
	c.cf = cloudformation.New(c.creds, c.Region, nil)
	stackEventsSince := time.Now()
	c.base.setState("deleting")
	if err := c.fetchStack(); err != StackNotFoundError {
		if err := c.wrapRequest(func() error {
			return c.cf.DeleteStack(&cloudformation.DeleteStackInput{
				StackName: aws.String(c.StackName),
			})
		}); err != nil {
			err = fmt.Errorf("Unable to delete stack %s: %s", c.StackName, err)
			c.base.SendError(err)
			if !c.base.YesNoPrompt(fmt.Sprintf("%s\nWould you like to remove it from the installer?", err.Error())) {
				c.base.setState("error")
				return
			}
		} else if err := c.waitForStackCompletion("DELETE", stackEventsSince); err != nil {
			c.base.SendError(err)
		}
	}
	if err := c.base.MarkDeleted(); err != nil {
		c.base.SendError(err)
	}
	c.base.sendEvent(&Event{
		ClusterID:   c.base.ID,
		Type:        "cluster_state",
		Description: "deleted",
	})
}
Example #4
0
func (c *AWSCluster) Delete() {
	c.cf = cloudformation.New(c.creds, c.Region, nil)
	stackEventsSince := time.Now()
	c.base.setState("deleting")
	if err := c.fetchStack(); err != StackNotFoundError {
		if err := c.wrapRequest(func() error {
			return c.cf.DeleteStack(&cloudformation.DeleteStackInput{
				StackName: aws.String(c.StackName),
			})
		}); err != nil {
			c.base.setState("error")
			c.base.SendError(fmt.Errorf("Unable to delete stack %s: %s", c.StackName, err))
		} else {
			if err := c.waitForStackCompletion("DELETE", stackEventsSince); err != nil {
				c.base.SendError(err)
			}
		}
	}
	if err := c.base.MarkDeleted(); err != nil {
		c.base.SendError(err)
	}
	c.base.sendEvent(&Event{
		ClusterID:   c.base.ID,
		Type:        "cluster_state",
		Description: "deleted",
	})
}
Example #5
0
func (c *AWSCluster) configureDNS() error {
	// TODO(jvatic): Run directly after receiving zone create complete stack event
	c.base.SendLog("Configuring DNS")

	// Set region to us-east-1, since any other region will fail for global services like Route53
	r53 := route53.New(c.creds, "us-east-1", nil)
	res, err := r53.GetHostedZone(&route53.GetHostedZoneRequest{ID: aws.String(c.DNSZoneID)})
	if err != nil {
		return err
	}
	if err := c.base.Domain.Configure(res.DelegationSet.NameServers); err != nil {
		return err
	}
	return nil
}
Example #6
0
func (c *AWSCluster) waitForStackCompletion(action string, after time.Time) error {
	stackID := aws.String(c.StackID)

	const actionCompleteSuffix = "_COMPLETE"
	const actionFailureSuffix = "_FAILED"
	const actionInProgressSuffix = "_IN_PROGRESS"
	isComplete := false
	isFailed := false

	var stackEvents []cloudformation.StackEvent
	var nextToken aws.StringValue

	var fetchStackEvents func() error
	fetchStackEvents = func() error {
		res, err := c.cf.DescribeStackEvents(&cloudformation.DescribeStackEventsInput{
			NextToken: nextToken,
			StackName: stackID,
		})
		if err != nil {
			switch err.(type) {
			case *url.Error:
				return nil
			default:
				return err
			}
		}

		// some events are not returned in order
		sort.Sort(StackEventSort(res.StackEvents))

		for _, se := range res.StackEvents {
			if !se.Timestamp.After(after) {
				continue
			}
			stackEventExists := false
			for _, e := range stackEvents {
				if *e.EventID == *se.EventID {
					stackEventExists = true
					break
				}
			}
			if stackEventExists {
				continue
			}
			stackEvents = append(stackEvents, se)
			if se.ResourceType != nil && se.ResourceStatus != nil {
				if *se.ResourceType == "AWS::CloudFormation::Stack" {
					if strings.HasSuffix(*se.ResourceStatus, actionInProgressSuffix) && !strings.HasPrefix(*se.ResourceStatus, action) {
						isFailed = true
						break
					}
					if strings.HasSuffix(*se.ResourceStatus, actionCompleteSuffix) {
						if strings.HasPrefix(*se.ResourceStatus, action) {
							isComplete = true
						} else {
							isFailed = true
						}
					} else if strings.HasSuffix(*se.ResourceStatus, actionFailureSuffix) {
						isFailed = true
					}
				}
				var desc string
				if se.ResourceStatusReason != nil {
					desc = fmt.Sprintf(" (%s)", *se.ResourceStatusReason)
				}
				name := *se.ResourceType
				if se.LogicalResourceID != nil {
					name = fmt.Sprintf("%s (%s)", name, *se.LogicalResourceID)
				}
				c.base.SendLog(fmt.Sprintf("%s\t%s%s", name, *se.ResourceStatus, desc))
			}
		}
		if res.NextToken != nil {
			nextToken = res.NextToken
			fetchStackEvents()
		}

		return nil
	}

	for {
		if err := c.wrapRequest(fetchStackEvents); err != nil {
			return err
		}
		if isComplete {
			break
		}
		if isFailed {
			return fmt.Errorf("Failed to create stack %s", c.StackName)
		}
		time.Sleep(1 * time.Second)
	}

	return nil
}
Example #7
0
func (c *AWSCluster) createStack() error {
	c.base.SendLog("Generating start script")
	startScript, discoveryToken, err := c.base.genStartScript(c.base.NumInstances, "/dev/xvdb")
	if err != nil {
		return err
	}
	c.base.DiscoveryToken = discoveryToken
	if err := c.base.saveField("DiscoveryToken", discoveryToken); err != nil {
		return err
	}

	var stackTemplateBuffer bytes.Buffer
	err = stackTemplate.Execute(&stackTemplateBuffer, &stackTemplateData{
		Instances:           make([]struct{}, c.base.NumInstances),
		DefaultInstanceType: DefaultInstanceType,
	})
	if err != nil {
		return err
	}
	stackTemplateString := stackTemplateBuffer.String()

	parameters := []cloudformation.Parameter{
		{
			ParameterKey:   aws.String("ImageId"),
			ParameterValue: aws.String(c.ImageID),
		},
		{
			ParameterKey:   aws.String("ClusterDomain"),
			ParameterValue: aws.String(c.base.Domain.Name),
		},
		{
			ParameterKey:   aws.String("KeyName"),
			ParameterValue: aws.String(c.base.SSHKeyName),
		},
		{
			ParameterKey:   aws.String("UserData"),
			ParameterValue: aws.String(startScript),
		},
		{
			ParameterKey:   aws.String("InstanceType"),
			ParameterValue: aws.String(c.InstanceType),
		},
		{
			ParameterKey:   aws.String("VpcCidrBlock"),
			ParameterValue: aws.String(c.VpcCIDR),
		},
		{
			ParameterKey:   aws.String("SubnetCidrBlock"),
			ParameterValue: aws.String(c.SubnetCIDR),
		},
	}

	stackEventsSince := time.Now()

	if c.StackID != "" && c.StackName != "" {
		if err := c.fetchStack(); err == nil && !strings.HasPrefix(*c.stack.StackStatus, "DELETE") {
			if c.base.YesNoPrompt(fmt.Sprintf("Stack found from previous installation (%s), would you like to delete it? (a new one will be created either way)", c.StackName)) {
				c.base.SendLog(fmt.Sprintf("Deleting stack %s", c.StackName))
				if err := c.wrapRequest(func() error {
					return c.cf.DeleteStack(&cloudformation.DeleteStackInput{
						StackName: aws.String(c.StackName),
					})
				}); err != nil {
					c.base.SendLog(fmt.Sprintf("Unable to delete stack %s: %s", c.StackName, err))
				}
			}
		}
	}

	c.base.SendLog("Creating stack")
	var res *cloudformation.CreateStackResult
	err = c.wrapRequest(func() error {
		var err error
		res, err = c.cf.CreateStack(&cloudformation.CreateStackInput{
			OnFailure:        aws.String("DELETE"),
			StackName:        aws.String(c.StackName),
			Tags:             []cloudformation.Tag{},
			TemplateBody:     aws.String(stackTemplateString),
			TimeoutInMinutes: aws.Integer(10),
			Parameters:       parameters,
		})
		return err
	})
	if err != nil {
		return err
	}
	c.StackID = *res.StackID

	if err := c.saveField("StackID", c.StackID); err != nil {
		return err
	}
	return c.waitForStackCompletion("CREATE", stackEventsSince)
}
Example #8
0
func (c *AWSCluster) createKeyPair() error {
	keypairNames := listSSHKeyNames()
	if c.base.SSHKeyName != "" {
		newKeypairNames := make([]string, len(keypairNames)+1)
		newKeypairNames[0] = c.base.SSHKeyName
		for i, name := range keypairNames {
			newKeypairNames[i+1] = name
		}
		keypairNames = newKeypairNames
	}
	for _, name := range keypairNames {
		if err := c.loadKeyPair(name); err == nil {
			c.base.SendLog(fmt.Sprintf("Using saved key pair (%s)", c.base.SSHKeyName))
			return nil
		}
	}

	keypairName := "flynn"
	if c.base.SSHKeyName != "" {
		keypairName = c.base.SSHKeyName
	}

	keypair, err := loadSSHKey(keypairName)
	if err == nil {
		c.base.SendLog("Importing key pair")
	} else {
		c.base.SendLog("Creating key pair")
		keypair, err = sshkeygen.Generate()
		if err != nil {
			return err
		}
	}

	enc := base64.StdEncoding
	publicKeyBytes := make([]byte, enc.EncodedLen(len(keypair.PublicKey)))
	enc.Encode(publicKeyBytes, keypair.PublicKey)

	var res *ec2.ImportKeyPairResult
	err = c.wrapRequest(func() error {
		var err error
		res, err = c.ec2.ImportKeyPair(&ec2.ImportKeyPairRequest{
			KeyName:           aws.String(keypairName),
			PublicKeyMaterial: publicKeyBytes,
		})
		return err
	})
	if apiErr, ok := err.(aws.APIError); ok && apiErr.Code == "InvalidKeyPair.Duplicate" {
		if c.base.YesNoPrompt(fmt.Sprintf("Key pair %s already exists, would you like to delete it?", keypairName)) {
			c.base.SendLog("Deleting key pair")
			if err := c.wrapRequest(func() error {
				return c.ec2.DeleteKeyPair(&ec2.DeleteKeyPairRequest{
					KeyName: aws.String(keypairName),
				})
			}); err != nil {
				return err
			}
			return c.createKeyPair()
		}
		for {
			keypairName = c.base.PromptInput("Please enter a new key pair name")
			if keypairName != "" {
				c.base.SSHKeyName = keypairName
				return c.createKeyPair()
			}
		}
	}
	if err != nil {
		return err
	}

	c.base.SSHKey = keypair
	c.base.SSHKeyName = *res.KeyName

	err = saveSSHKey(keypairName, keypair)
	if err != nil {
		return err
	}
	return nil
}
Example #9
0
func TestEC2Request(t *testing.T) {
	var m sync.Mutex
	var httpReq *http.Request
	var form url.Values

	server := httptest.NewServer(http.HandlerFunc(
		func(w http.ResponseWriter, r *http.Request) {
			m.Lock()
			defer m.Unlock()

			httpReq = r

			if err := r.ParseForm(); err != nil {
				t.Fatal(err)
			}
			form = r.Form

			fmt.Fprintln(w, `<Thing><IpAddress>woo</IpAddress></Thing>`)
		},
	))
	defer server.Close()

	client := aws.EC2Client{
		Context: aws.Context{
			Service: "animals",
			Region:  "us-west-2",
			Credentials: aws.Creds(
				"accessKeyID",
				"secretAccessKey",
				"securityToken",
			),
		},
		Client:     http.DefaultClient,
		Endpoint:   server.URL,
		APIVersion: "1.1",
	}

	req := fakeEC2Request{
		PresentString:  aws.String("string"),
		PresentBoolean: aws.True(),
		PresentInteger: aws.Integer(1),
		PresentLong:    aws.Long(2),
		PresentDouble:  aws.Double(1.2),
		PresentFloat:   aws.Float(2.3),
		PresentTime:    time.Date(2001, 1, 1, 2, 1, 1, 0, time.FixedZone("UTC+1", 3600)),
		PresentSlice:   []string{"one", "two"},
		PresentStruct:  &EmbeddedStruct{Value: aws.String("v")},
		PresentStructSlice: []EmbeddedStruct{
			{Value: aws.String("p")},
			{Value: aws.String("q")},
		},
	}
	var resp fakeEC2Response
	if err := client.Do("GetIP", "POST", "/", &req, &resp); err != nil {
		t.Fatal(err)
	}

	m.Lock()
	defer m.Unlock()

	if v, want := httpReq.Method, "POST"; v != want {
		t.Errorf("Method was %v but expected %v", v, want)
	}

	if httpReq.Header.Get("Authorization") == "" {
		t.Error("Authorization header is missing")
	}

	if v, want := httpReq.Header.Get("Content-Type"), "application/x-www-form-urlencoded"; v != want {
		t.Errorf("Content-Type was %v but expected %v", v, want)
	}

	if v, want := httpReq.Header.Get("User-Agent"), "aws-go"; v != want {
		t.Errorf("User-Agent was %v but expected %v", v, want)
	}

	if err := httpReq.ParseForm(); err != nil {
		t.Fatal(err)
	}

	expectedForm := url.Values{
		"Action":                     []string{"GetIP"},
		"Version":                    []string{"1.1"},
		"PresentString":              []string{"string"},
		"PresentBoolean":             []string{"true"},
		"PresentInteger":             []string{"1"},
		"PresentLong":                []string{"2"},
		"PresentDouble":              []string{"1.2"},
		"PresentFloat":               []string{"2.3"},
		"PresentTime":                []string{"2001-01-01T01:01:01Z"},
		"PresentSlice.1":             []string{"one"},
		"PresentSlice.2":             []string{"two"},
		"PresentStruct.Value":        []string{"v"},
		"PresentStructSlice.1.Value": []string{"p"},
		"PresentStructSlice.2.Value": []string{"q"},
	}

	if !reflect.DeepEqual(form, expectedForm) {
		t.Errorf("Post body was \n%s\n but expected \n%s", form.Encode(), expectedForm.Encode())
	}

	if want := (fakeEC2Response{IPAddress: "woo"}); want != resp {
		t.Errorf("Response was %#v, but expected %#v", resp, want)
	}
}