// Delete the provided serviceName. Failing to delete a non-existent // service is not considered an error. Note that the delete does func Delete(serviceName string, logger *logrus.Logger) error { session := spartaAWS.NewSession(logger) awsCloudFormation := cloudformation.New(session) exists, err := spartaCF.StackExists(serviceName, session, logger) if nil != err { return err } logger.WithFields(logrus.Fields{ "Exists": exists, "Name": serviceName, }).Info("Stack existence check") if exists { params := &cloudformation.DeleteStackInput{ StackName: aws.String(serviceName), } resp, err := awsCloudFormation.DeleteStack(params) if nil != resp { logger.WithFields(logrus.Fields{ "Response": resp, }).Info("Delete request submitted") } return err } logger.Info("Stack does not exist") return nil }
// TODO cache the data somewhere other than querying CF func initializeDiscovery(serviceName string, lambdaAWSInfos []*LambdaAWSInfo, logger *logrus.Logger) { // Setup the discoveryImpl reference discoveryCache = make(map[string]*DiscoveryInfo, 0) discoverImpl = func(golangFuncName string) (*DiscoveryInfo, error) { // Find the LambdaAWSInfo that has this golang function // as its target lambdaCFResource := "" for _, eachLambda := range lambdaAWSInfos { if eachLambda.lambdaFunctionName() == golangFuncName { lambdaCFResource = eachLambda.logicalName() } } logger.WithFields(logrus.Fields{ "CallerName": golangFuncName, "CFResourceName": lambdaCFResource, "ServiceName": serviceName, }).Debug("Discovery Info") if "" == lambdaCFResource { return nil, fmt.Errorf("Unsupported call site for sparta.Discover(): %s", golangFuncName) } emptyConfiguration := &DiscoveryInfo{} if "" != lambdaCFResource { cachedConfig, exists := discoveryCache[lambdaCFResource] if exists { return cachedConfig, nil } // Look it up awsCloudFormation := cloudformation.New(spartaAWS.NewSession(logger)) params := &cloudformation.DescribeStackResourceInput{ LogicalResourceId: aws.String(lambdaCFResource), StackName: aws.String(serviceName), } result, err := awsCloudFormation.DescribeStackResource(params) if nil != err { // TODO - retry/cache expiry discoveryCache[lambdaCFResource] = emptyConfiguration return nil, err } metadata := result.StackResourceDetail.Metadata if nil == metadata { metadata = aws.String("{}") } // Transform this into a map logger.WithFields(logrus.Fields{ "Metadata": metadata, }).Debug("DiscoveryInfo Metadata") var discoveryInfo DiscoveryInfo err = json.Unmarshal([]byte(*metadata), &discoveryInfo) if err != nil { logger.WithFields(logrus.Fields{ "Metadata": *metadata, "Error": err, }).Error("Failed to unmarshal discovery info") } discoveryCache[lambdaCFResource] = &discoveryInfo return &discoveryInfo, err } return emptyConfiguration, nil } }
// Provision compiles, packages, and provisions (either via create or update) a Sparta application. // The serviceName is the service's logical // identify and is used to determine create vs update operations. The compilation options/flags are: // // TAGS: -tags lambdabinary // ENVIRONMENT: GOOS=linux GOARCH=amd64 // // The compiled binary is packaged with a NodeJS proxy shim to manage AWS Lambda setup & invocation per // http://docs.aws.amazon.com/lambda/latest/dg/authoring-function-in-nodejs.html // // The two files are ZIP'd, posted to S3 and used as an input to a dynamically generated CloudFormation // template (http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html) // which creates or updates the service state. // // More information on golang 1.5's support for vendor'd resources is documented at // // https://docs.google.com/document/d/1Bz5-UB7g2uPBdOx-rw5t9MxJwkfpx90cqG9AFL0JAYo/edit // https://medium.com/@freeformz/go-1-5-s-vendor-experiment-fd3e830f52c3#.voiicue1j // // type Configuration struct { // Val string // Proxy struct { // Address string // Port string // } // } func Provision(noop bool, serviceName string, serviceDescription string, lambdaAWSInfos []*LambdaAWSInfo, api *API, site *S3Site, s3Bucket string, buildID string, codePipelineTrigger string, buildTags string, linkerFlags string, templateWriter io.Writer, workflowHooks *WorkflowHooks, logger *logrus.Logger) error { err := validateSpartaPreconditions(lambdaAWSInfos, logger) if nil != err { return err } startTime := time.Now() ctx := &workflowContext{ noop: noop, serviceName: serviceName, serviceDescription: serviceDescription, lambdaAWSInfos: lambdaAWSInfos, api: api, s3SiteContext: &s3SiteContext{ s3Site: site, }, cfTemplate: gocf.NewTemplate(), s3Bucket: s3Bucket, s3BucketVersioningEnabled: false, buildID: buildID, codePipelineTrigger: codePipelineTrigger, buildTags: buildTags, linkFlags: linkerFlags, buildTime: time.Now(), awsSession: spartaAWS.NewSession(logger), templateWriter: templateWriter, workflowHooks: workflowHooks, workflowHooksContext: make(map[string]interface{}, 0), logger: logger, } ctx.cfTemplate.Description = serviceDescription // Update the context iff it exists if nil != workflowHooks && nil != workflowHooks.Context { for eachKey, eachValue := range workflowHooks.Context { ctx.workflowHooksContext[eachKey] = eachValue } } ctx.logger.WithFields(logrus.Fields{ "BuildID": buildID, "NOOP": noop, "Tags": ctx.buildTags, "CodePipelineTrigger": ctx.codePipelineTrigger, }).Info("Provisioning service") if len(lambdaAWSInfos) <= 0 { return errors.New("No lambda functions provided to Sparta.Provision()") } // Start the workflow for step := verifyIAMRoles; step != nil; { next, err := step(ctx) if err != nil { ctx.rollback() // Workflow step? ctx.logger.Error(err) return err } if next == nil { elapsed := time.Since(startTime) ctx.logger.WithFields(logrus.Fields{ "Seconds": fmt.Sprintf("%.f", elapsed.Seconds()), }).Info("Elapsed time") break } else { step = next } } // When we're done, execute any finalizers if nil != ctx.finalizerFunctions { ctx.logger.WithFields(logrus.Fields{ "FinalizerCount": len(ctx.finalizerFunctions), }).Debug("Invoking finalizer functions") for _, eachFinalizer := range ctx.finalizerFunctions { eachFinalizer(ctx.logger) } } return nil }