// 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 }
// 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 } } }
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, " ")) }