func waitForCompletion(stack string, CloudFormation *cloudformation.CloudFormation, isDeleting bool) (string, error) { for { dres, err := CloudFormation.DescribeStacks(&cloudformation.DescribeStacksInput{ StackName: aws.String(stack), }) if err != nil { stdcli.Error(err) } err = displayProgress(stack, CloudFormation, isDeleting) if err != nil { stdcli.Error(err) } if len(dres.Stacks) != 1 { stdcli.Error(fmt.Errorf("could not read stack status")) } switch *dres.Stacks[0].StackStatus { case "CREATE_COMPLETE": // Dump .env if DEVELOPMENT if isDevelopment { fmt.Printf("Development .env:\n") // convert Port5432TcpAddr to PORT_5432_TCP_ADDR re := regexp.MustCompile("([a-z])([A-Z0-9])") // lower case letter followed by upper case or number, i.e. Port5432 re2 := regexp.MustCompile("([0-9])([A-Z])") // number followed by upper case letter, i.e. 5432Tcp for _, o := range dres.Stacks[0].Outputs { k := re.ReplaceAllString(*o.OutputKey, "${1}_${2}") k = re2.ReplaceAllString(k, "${1}_${2}") k = strings.ToUpper(k) fmt.Printf("%v=%v\n", k, *o.OutputValue) } } for _, o := range dres.Stacks[0].Outputs { if *o.OutputKey == "Dashboard" { return *o.OutputValue, nil } } return "", fmt.Errorf("could not install stack, contact [email protected] for assistance") case "CREATE_FAILED": return "", fmt.Errorf("stack creation failed, contact [email protected] for assistance") case "ROLLBACK_COMPLETE": return "", fmt.Errorf("stack creation failed, contact [email protected] for assistance") case "DELETE_COMPLETE": return "", nil case "DELETE_FAILED": return "", fmt.Errorf("stack deletion failed, contact [email protected] for assistance") } time.Sleep(2 * time.Second) } }
func displayProgress(stack string, CloudFormation *cloudformation.CloudFormation, isDeleting bool) error { res, err := CloudFormation.DescribeStackEvents(&cloudformation.DescribeStackEventsInput{ StackName: aws.String(stack), }) if err != nil { return err } for _, event := range res.StackEvents { if events[*event.EventId] == true { continue } events[*event.EventId] = true // Log all CREATE_FAILED to display and MixPanel if !isDeleting && *event.ResourceStatus == "CREATE_FAILED" { msg := fmt.Sprintf("Failed %s: %s", *event.ResourceType, *event.ResourceStatusReason) fmt.Println(msg) sendMixpanelEvent("convox-install-error", msg) } name := friendlyName(*event.ResourceType) if name == "" { continue } switch *event.ResourceStatus { case "CREATE_IN_PROGRESS": case "CREATE_COMPLETE": if !isDeleting { id := *event.PhysicalResourceId if strings.HasPrefix(id, "arn:") { id = *event.LogicalResourceId } fmt.Printf("Created %s: %s\n", name, id) } case "CREATE_FAILED": case "DELETE_IN_PROGRESS": case "DELETE_COMPLETE": id := *event.PhysicalResourceId if strings.HasPrefix(id, "arn:") { id = *event.LogicalResourceId } fmt.Printf("Deleted %s: %s\n", name, id) case "DELETE_SKIPPED": id := *event.PhysicalResourceId if strings.HasPrefix(id, "arn:") { id = *event.LogicalResourceId } fmt.Printf("Skipped %s: %s\n", name, id) case "DELETE_FAILED": id := *event.PhysicalResourceId if strings.HasPrefix(id, "arn:") { id = *event.LogicalResourceId } fmt.Printf("Failed to delete %s: %s\n", name, id) case "ROLLBACK_IN_PROGRESS", "ROLLBACK_COMPLETE": case "UPDATE_IN_PROGRESS", "UPDATE_COMPLETE", "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS", "UPDATE_FAILED", "UPDATE_ROLLBACK_IN_PROGRESS", "UPDATE_ROLLBACK_COMPLETE", "UPDATE_ROLLBACK_FAILED": default: return fmt.Errorf("Unhandled status: %s\n", *event.ResourceStatus) } } return nil }