Example #1
0
// Deploy creates or updates the specified CloudFormation template to AWS.
//
// If you secify nil for awsConfigProvider, a default one will be used.
//
// This is a handy function to call from your main, i.e.:
//
//   func main() {
//   	dnsName := flag.String("name", "example.com", "the DNS name")
//   	flag.Parse()
//
//   	template := makeTemplate(*dnsName)
//   	if err := deploycfn.Deploy(nil, "Example", template); err != nil {
//   		log.Fatalf("deploy: %s", err)
//   	}
//   }
//
func Deploy(input DeployInput) error {
	templateBody, err := json.Marshal(input.Template)
	if err != nil {
		return err
	}

	if input.Session == nil {
		awsSession := session.New()
		awsregion.GuessRegion(awsSession.Config)
		input.Session = awsSession
	}
	cfnSvc := cloudformation.New(input.Session)

	describeStacksResponse, err := cfnSvc.DescribeStacks(&cloudformation.DescribeStacksInput{
		StackName: aws.String(input.StackName),
	})
	doCreate := err != nil || len(describeStacksResponse.Stacks) == 0

	watcher := &StackEventWatcher{
		Service:   cfnSvc,
		StackName: input.StackName,
	}
	if !doCreate {
		watcher, err = NewStackEventWatcher(input.Session, input.StackName)
		if err != nil {
			return err
		}
	}

	needCapabilityIam := false
	for _, resource := range input.Template.Resources {
		switch resource.Properties.CfnResourceType() {
		case "AWS::IAM::AccessKey":
			needCapabilityIam = true
		case "AWS::IAM::Group":
			needCapabilityIam = true
		case "AWS::IAM::InstanceProfile":
			needCapabilityIam = true
		case "AWS::IAM::Policy":
			needCapabilityIam = true
		case "AWS::IAM::Role":
			needCapabilityIam = true
		case "AWS::IAM::User":
			needCapabilityIam = true
		case "AWS::IAM::UserToGroupAddition":
			needCapabilityIam = true
		}
	}

	capabilities := []*string{}
	if needCapabilityIam {
		capabilities = append(capabilities, aws.String(cloudformation.CapabilityCapabilityIam))
	}

	parameters := []*cloudformation.Parameter{}
	for key, value := range input.Parameters {
		parameters = append(parameters, &cloudformation.Parameter{
			ParameterKey:   aws.String(key),
			ParameterValue: aws.String(value),
		})
	}
	if !doCreate {
		for key := range input.Template.Parameters {
			if _, ok := input.Parameters[key]; ok {
				continue
			}
			parameters = append(parameters, &cloudformation.Parameter{
				ParameterKey:     aws.String(key),
				UsePreviousValue: aws.Bool(true),
			})
		}
	}

	if doCreate {
		_, err = cfnSvc.CreateStack(&cloudformation.CreateStackInput{
			StackName:    aws.String(input.StackName),
			TemplateBody: aws.String(string(templateBody)),
			Capabilities: capabilities,
			Parameters:   parameters,
		})
		if err != nil {
			return err
		}
	} else {
		_, err = cfnSvc.UpdateStack(&cloudformation.UpdateStackInput{
			StackName:    aws.String(input.StackName),
			TemplateBody: aws.String(string(templateBody)),
			Capabilities: capabilities,
			Parameters:   parameters,
		})
		if err != nil {
			return err
		}
	}

	if err := watcher.Watch(); err != nil {
		return err
	}

	return nil
}
Example #2
0
// Deploy creates or updates the specified CloudFormation template to AWS.
//
// If you secify nil for awsConfigProvider, a default one will be used.
//
// This is a handy function to call from your main, i.e.:
//
//   func main() {
//   	dnsName := flag.String("name", "example.com", "the DNS name")
//   	flag.Parse()
//
//   	template := makeTemplate(*dnsName)
//   	if err := deploycfn.Deploy(nil, "Example", template); err != nil {
//   		log.Fatalf("deploy: %s", err)
//   	}
//   }
//
func Deploy(awsConfigProvider client.ConfigProvider, stackName string, template *cf.Template) error {
	templateBody, err := json.Marshal(template)
	if err != nil {
		return err
	}

	if awsConfigProvider == nil {
		awsSession := session.New()
		awsregion.GuessRegion(awsSession.Config)
		awsConfigProvider = awsSession
	}
	cfn := cloudformation.New(awsConfigProvider)

	describeStacksResponse, err := cfn.DescribeStacks(&cloudformation.DescribeStacksInput{
		StackName: aws.String(stackName),
	})
	doCreate := err != nil || len(describeStacksResponse.Stacks) == 0

	// seenEvents maps the events that have already been printed. If the stack
	// already exists, then we want to get all the existing events into this
	// set so we don't re-print old events.
	seenEvents := map[string]struct{}{}
	if !doCreate {
		cfn.DescribeStackEventsPages(&cloudformation.DescribeStackEventsInput{
			StackName: aws.String(stackName),
		}, func(p *cloudformation.DescribeStackEventsOutput, _ bool) bool {
			for _, stackEvent := range p.StackEvents {
				seenEvents[*stackEvent.EventId] = struct{}{}
			}
			return true
		})
	}

	if doCreate {
		_, err = cfn.CreateStack(&cloudformation.CreateStackInput{
			StackName:    aws.String(stackName),
			TemplateBody: aws.String(string(templateBody)),
		})
		if err != nil {
			return err
		}
	} else {
		_, err = cfn.UpdateStack(&cloudformation.UpdateStackInput{
			StackName:    aws.String(stackName),
			TemplateBody: aws.String(string(templateBody)),
		})
		if err != nil {
			return err
		}
	}

	lastStackStatus := ""
	for {
		// print the events for the stack
		cfn.DescribeStackEventsPages(&cloudformation.DescribeStackEventsInput{
			StackName: aws.String(stackName),
		}, func(p *cloudformation.DescribeStackEventsOutput, _ bool) bool {
			for _, stackEvent := range p.StackEvents {
				if _, ok := seenEvents[*stackEvent.EventId]; ok {
					continue
				}
				wrapStrPtr := func(s *string) string {
					if s == nil {
						return ""
					}
					return *s
				}
				log.Printf("%s\t%s\t%s\t%s\t%s\n",
					wrapStrPtr(stackEvent.ResourceStatus),
					wrapStrPtr(stackEvent.ResourceType),
					wrapStrPtr(stackEvent.PhysicalResourceId),
					wrapStrPtr(stackEvent.LogicalResourceId),
					wrapStrPtr(stackEvent.ResourceStatusReason))
				seenEvents[*stackEvent.EventId] = struct{}{}
			}
			return true
		})

		// monitor the status of the stack
		describeStacksResponse, err := cfn.DescribeStacks(&cloudformation.DescribeStacksInput{
			StackName: aws.String(stackName),
		})
		if err != nil {
			// the stack might not exist yet
			log.Printf("DescribeStacks: %s", err)
			time.Sleep(time.Second)
			continue
		}

		stackStatus := *describeStacksResponse.Stacks[0].StackStatus
		if stackStatus != lastStackStatus {
			log.Printf("Stack: %s\n", stackStatus)
			lastStackStatus = stackStatus
		}
		switch stackStatus {
		case cloudformation.StackStatusCreateComplete:
			return nil
		case cloudformation.StackStatusCreateFailed:
			return fmt.Errorf("%s", stackStatus)
		case cloudformation.StackStatusRollbackComplete:
			return fmt.Errorf("%s", stackStatus)
		case cloudformation.StackStatusUpdateRollbackComplete:
			return fmt.Errorf("%s", stackStatus)
		case cloudformation.StackStatusRollbackFailed:
			return fmt.Errorf("%s", stackStatus)
		case cloudformation.StackStatusUpdateComplete:
			return nil
		case cloudformation.StackStatusUpdateRollbackFailed:
			return fmt.Errorf("%s", stackStatus)
		default:
			time.Sleep(time.Second * 5)
			continue
		}
	}
}
Example #3
0
func main() {
	instanceID := flag.String("instance", "",
		"The instance ID of the cluster member. If not supplied, then the instance ID is determined from EC2 metadata")
	clusterTagName := flag.String("tag", "aws:autoscaling:groupName",
		"The instance tag that is common to all members of the cluster")
	clusterTagValue := flag.String("tag-value", "",
		"The value of the tag used to describe cluster members. Default is the value of the tag in the current instance")
	queueURL := flag.String("watch-queue", "",
		"Monitor autoscaling lifecycle events for the current autoscaling group and print them to stdout as they occur")
	flag.Parse()

	if *instanceID == "" {
		var err error
		*instanceID, err = ec2cluster.DiscoverInstanceID()
		if err != nil {
			log.Fatalf("ERROR: %s", err)
		}
	}

	s := &ec2cluster.Cluster{
		InstanceID: *instanceID,
		TagName:    *clusterTagName,
		TagValue:   *clusterTagValue,
	}

	s.AwsSession = session.New()
	if region := os.Getenv("AWS_REGION"); region != "" {
		s.AwsSession.Config.WithRegion(region)
	}
	awsregion.GuessRegion(s.AwsSession.Config)

	if *queueURL != "" {
		err := s.WatchLifecycleEvents(*queueURL, func(m *ec2cluster.LifecycleMessage) (bool, error) {
			fmt.Printf("%s\t%s\n", m.LifecycleTransition, m.EC2InstanceID)
			return true, nil
		})
		if err != nil {
			log.Fatalf("ERROR: %s", err)
		}
		return
	}

	instance, err := s.Instance()
	if err != nil {
		log.Fatalf("ERROR: failed to inspect instance: %s", err)
	}

	availabilityZone := *instance.Placement.AvailabilityZone
	fmt.Printf("AVAILABILITY_ZONE=\"%s\"\n", availabilityZone)
	fmt.Printf("REGION=\"%s\"\n", availabilityZone[:len(availabilityZone)-1])
	if instance.PrivateIpAddress != nil {
		fmt.Printf("ADVERTISE_ADDRESS=\"%s\"\n", *instance.PrivateIpAddress)
	}
	for _, tag := range instance.Tags {
		fmt.Printf("TAG_%s=\"%s\"\n", strings.ToUpper(
			regexp.MustCompile("[^A-Za-z0-9]").ReplaceAllString(*tag.Key, "_")),
			strings.Replace(*tag.Value, "\"", "\\\"", -1))
	}

	asg, err := s.AutoscalingGroup()
	if err != nil {
		log.Fatalf("ERROR: %s", err)
	}
	if asg != nil {
		fmt.Printf("ASG_DESIRED_CAPACITY=%d\n", asg.DesiredCapacity)
		fmt.Printf("ASG_MIN_SIZE=%d\n", asg.MinSize)
		fmt.Printf("ASG_MAX_SIZE=%d\n", asg.MaxSize)
	}

	clusterInstances, err := s.Members()
	if err != nil {
		log.Fatalf("ERROR: %s", err)
	}

	clusterVar := []string{}
	for _, instance := range clusterInstances {
		if instance.PrivateIpAddress != nil {
			clusterVar = append(clusterVar, *instance.PrivateIpAddress)
		}
	}
	fmt.Printf("CLUSTER=\"%s\"\n", strings.Join(clusterVar, " "))
}