func stackLambdaResources(serviceName string, cf *cloudformation.CloudFormation, logger *logrus.Logger) (provisionedResources, error) { resources := make(provisionedResources, 0) nextToken := "" for { params := &cloudformation.ListStackResourcesInput{ StackName: aws.String(serviceName), } if "" != nextToken { params.NextToken = aws.String(nextToken) } resp, err := cf.ListStackResources(params) if err != nil { logger.Error(err.Error()) return nil, err } for _, eachSummary := range resp.StackResourceSummaries { if *eachSummary.ResourceType == "AWS::Lambda::Function" { resources = append(resources, eachSummary) } } if nil != resp.NextToken { nextToken = *resp.NextToken } else { break } } return resources, nil }
// Execute creates an HTTP listener to dispatch execution. Typically // called via Main() via command line arguments. func Execute(lambdaAWSInfos []*LambdaAWSInfo, port int, parentProcessPID int, logger *logrus.Logger) error { if port <= 0 { port = defaultHTTPPort } logger.Info("Execute!") lookupMap := make(dispatchMap, 0) for _, eachLambdaInfo := range lambdaAWSInfos { lookupMap[eachLambdaInfo.lambdaFnName] = eachLambdaInfo } server := &http.Server{ Addr: fmt.Sprintf(":%d", port), Handler: &lambdaHandler{lookupMap, logger}, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, } if 0 != parentProcessPID { logger.Debug("Sending SIGUSR2 to parent process: ", parentProcessPID) syscall.Kill(parentProcessPID, syscall.SIGUSR2) } logger.Debug("Binding to port: ", port) err := server.ListenAndServe() if err != nil { logger.Error("FAILURE: " + err.Error()) return err } logger.Debug("Server available at: ", port) return nil }
func safeMergeTemplates(sourceTemplate *gocf.Template, destTemplate *gocf.Template, logger *logrus.Logger) error { var mergeErrors []string // Append the custom resources for eachKey, eachLambdaResource := range sourceTemplate.Resources { _, exists := destTemplate.Resources[eachKey] if exists { errorMsg := fmt.Sprintf("Duplicate CloudFormation resource name: %s", eachKey) mergeErrors = append(mergeErrors, errorMsg) } else { destTemplate.Resources[eachKey] = eachLambdaResource } } // Append the custom outputs for eachKey, eachLambdaOutput := range sourceTemplate.Outputs { _, exists := destTemplate.Outputs[eachKey] if exists { errorMsg := fmt.Sprintf("Duplicate CloudFormation output key name: %s", eachKey) mergeErrors = append(mergeErrors, errorMsg) } else { destTemplate.Outputs[eachKey] = eachLambdaOutput } } if len(mergeErrors) > 0 { logger.Error("Failed to update template. The following collisions were found:") for _, eachError := range mergeErrors { logger.Error("\t" + eachError) } return errors.New("Template merge failed") } return nil }
//////////////////////////////////////////////////////////////////////////////// // SES handler // func echoSESEvent(event *json.RawMessage, context *sparta.LambdaContext, w http.ResponseWriter, logger *logrus.Logger) { logger.WithFields(logrus.Fields{ "RequestID": context.AWSRequestID, }).Info("Request received") configuration, configErr := sparta.Discover() logger.WithFields(logrus.Fields{ "Error": configErr, "Configuration": configuration, }).Info("Discovery results") // The message bucket is an explicit `DependsOn` relationship, so it'll be in the // resources map. We'll find it by looking for the dependent resource with the "AWS::S3::Bucket" type bucketName := "" for _, eachResource := range configuration.Resources { if eachResource.Properties[sparta.TagResourceType] == "AWS::S3::Bucket" { bucketName = eachResource.Properties["Ref"] } } if "" == bucketName { logger.Error("Failed to discover SES bucket from sparta.Discovery") http.Error(w, "Failed to discovery SES MessageBodyBucket", http.StatusInternalServerError) } // The bucket is in the configuration map, prefixed by // SESMessageStoreBucket var lambdaEvent spartaSES.Event err := json.Unmarshal([]byte(*event), &lambdaEvent) if err != nil { logger.Error("Failed to unmarshal event data: ", err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) } // Get the metdata about the item... svc := s3.New(session.New()) for _, eachRecord := range lambdaEvent.Records { logger.WithFields(logrus.Fields{ "Source": eachRecord.SES.Mail.Source, "MessageID": eachRecord.SES.Mail.MessageID, "BucketName": bucketName, }).Info("SES Event") if "" != bucketName { params := &s3.HeadObjectInput{ Bucket: aws.String(bucketName), Key: aws.String(eachRecord.SES.Mail.MessageID), } resp, err := svc.HeadObject(params) logger.WithFields(logrus.Fields{ "Error": err, "Metadata": resp, }).Info("SES MessageBody") } } }
func transformImage(event *json.RawMessage, context *sparta.LambdaContext, w http.ResponseWriter, logger *logrus.Logger) { logger.WithFields(logrus.Fields{ "RequestID": context.AWSRequestID, "Event": string(*event), }).Info("Request received :)") var lambdaEvent spartaS3.Event err := json.Unmarshal([]byte(*event), &lambdaEvent) if err != nil { logger.Error("Failed to unmarshal event data: ", err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) } logger.WithFields(logrus.Fields{ "S3Event": lambdaEvent, }).Info("S3 Notification") for _, eachRecord := range lambdaEvent.Records { err = nil // What happened? switch eachRecord.EventName { case "ObjectCreated:Put": { err = stampImage(eachRecord.S3.Bucket.Name, eachRecord.S3.Object.Key, logger) } case "s3:ObjectRemoved:Delete": { deleteKey := fmt.Sprintf("%s%s", transformPrefix, eachRecord.S3.Object.Key) awsSession := awsSession(logger) svc := s3.New(awsSession) params := &s3.DeleteObjectInput{ Bucket: aws.String(eachRecord.S3.Bucket.Name), Key: aws.String(deleteKey), } resp, err := svc.DeleteObject(params) logger.WithFields(logrus.Fields{ "Response": resp, "Error": err, }).Info("Deleted object") } default: { logger.Info("Unsupported event: ", eachRecord.EventName) } } // if err != nil { logger.Error("Failed to process event: ", err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) } } }
// Describe is not available in the AWS Lambda binary func Describe(serviceName string, serviceDescription string, lambdaAWSInfos []*LambdaAWSInfo, api *API, site *S3Site, s3BucketName string, buildTags string, linkerFlags string, outputWriter io.Writer, workflowHooks *WorkflowHooks, logger *logrus.Logger) error { logger.Error("Describe() not supported in AWS Lambda binary") return errors.New("Describe not supported for this binary") }
// Provision is not available in the AWS Lambda binary func Provision(noop bool, serviceName string, serviceDescription string, lambdaAWSInfos []*LambdaAWSInfo, api *API, site *S3Site, s3Bucket string, buildID string, codePipelineTrigger string, buildTags string, linkerFlags string, writer io.Writer, workflowHooks *WorkflowHooks, logger *logrus.Logger) error { logger.Error("Deploy() not supported in AWS Lambda binary") return errors.New("Deploy not supported for this binary") }
//////////////////////////////////////////////////////////////////////////////// // Kinesis handler // func echoKinesisEvent(event *json.RawMessage, context *sparta.LambdaContext, w http.ResponseWriter, logger *logrus.Logger) { logger.WithFields(logrus.Fields{ "RequestID": context.AWSRequestID, "Event": string(*event), }).Info("Request received") var lambdaEvent spartaKinesis.Event err := json.Unmarshal([]byte(*event), &lambdaEvent) if err != nil { logger.Error("Failed to unmarshal event data: ", err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) } for _, eachRecord := range lambdaEvent.Records { logger.WithFields(logrus.Fields{ "EventID": eachRecord.EventID, }).Info("Kinesis Event") } }
// UploadTemplate marshals the given cfTemplate and uploads it to the // supplied bucket using the given KeyName func UploadTemplate(serviceName string, cfTemplate *gocf.Template, s3Bucket string, s3KeyName string, awsSession *session.Session, logger *logrus.Logger) (string, error) { logger.WithFields(logrus.Fields{ "Key": s3KeyName, "Bucket": s3Bucket, }).Info("Uploading CloudFormation template") s3Uploader := s3manager.NewUploader(awsSession) // Serialize the template and upload it cfTemplateJSON, err := json.Marshal(cfTemplate) if err != nil { logger.Error("Failed to Marshal CloudFormation template: ", err.Error()) return "", err } // Upload the actual CloudFormation template to S3 to maximize the template // size limit // Ref: http://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_CreateStack.html contentBody := string(cfTemplateJSON) uploadInput := &s3manager.UploadInput{ Bucket: &s3Bucket, Key: &s3KeyName, ContentType: aws.String("application/json"), Body: strings.NewReader(contentBody), } templateUploadResult, templateUploadResultErr := s3Uploader.Upload(uploadInput) if nil != templateUploadResultErr { return "", templateUploadResultErr } // Be transparent logger.WithFields(logrus.Fields{ "URL": templateUploadResult.Location, }).Info("Template uploaded") return templateUploadResult.Location, nil }
func echoAPIGatewayHTTPEvent(event *json.RawMessage, context *LambdaContext, w http.ResponseWriter, logger *logrus.Logger) { var lambdaEvent APIGatewayLambdaJSONEvent err := json.Unmarshal([]byte(*event), &lambdaEvent) if err != nil { logger.Error("Failed to unmarshal event data: ", err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) return } responseBody, err := json.Marshal(lambdaEvent) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } else { fmt.Fprint(w, string(responseBody)) } }
//////////////////////////////////////////////////////////////////////////////// // DynamoDB handler // func echoDynamoDBEvent(event *json.RawMessage, context *sparta.LambdaContext, w http.ResponseWriter, logger *logrus.Logger) { logger.WithFields(logrus.Fields{ "RequestID": context.AWSRequestID, "Event": string(*event), }).Info("Request received") var lambdaEvent spartaDynamoDB.Event err := json.Unmarshal([]byte(*event), &lambdaEvent) if err != nil { logger.Error("Failed to unmarshal event data: ", err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) } for _, eachRecord := range lambdaEvent.Records { logger.WithFields(logrus.Fields{ "Keys": eachRecord.DynamoDB.Keys, "NewImage": eachRecord.DynamoDB.NewImage, }).Info("DynamoDb Event") } fmt.Fprintf(w, "Done!") }
func OnFile(e dispatcher.Event, p dispatcher.Path, log *logrus.Logger) { u, err := New(e, p) if err != nil { log.Error(err) return } if exists, total := u.FileCount(); exists != total { log.WithFields(logrus.Fields{"path": u.Event.Dir()}).Infof("%d/%d files", exists, total) return } // Verify if err := u.Verify(); err != nil { log.WithError(err).Warn("Verification failed") return } log.WithFields(logrus.Fields{"path": u.Event.Dir()}).Info("Verified successfully") // Unpack if err := u.Run(); err != nil { log.WithError(err).Error("Unpacking failed") return } log.WithFields(logrus.Fields{"path": u.Event.Dir()}).Info("Unpacked successfully") // Clean up if ok, err := u.Remove(); err != nil { log.WithError(err).Error("Failed to delete files") } else if ok { log.WithFields(logrus.Fields{"path": u.Event.Dir()}).Info("Cleaned up") } // Run post-command if cmd, err := u.PostRun(); err != nil { log.WithFields(logrus.Fields{"command": cmd}).WithError(err).Warn("Failed to run post-command") } else if cmd != "" { log.WithFields(logrus.Fields{"command": cmd}).Info("Executed post-command") } }
func (action *SESLambdaEventSourceResourceAction) toReceiptAction(logger *logrus.Logger) *ses.ReceiptAction { actionProperties := action.ActionProperties switch action.ActionType.Literal { case "LambdaAction": action := &ses.ReceiptAction{ LambdaAction: &ses.LambdaAction{ FunctionArn: aws.String(actionProperties["FunctionArn"].(string)), InvocationType: aws.String("Event"), }, } if val, exists := actionProperties["InvocationType"]; exists { action.LambdaAction.InvocationType = aws.String(val.(string)) } if val, exists := actionProperties["TopicArn"]; exists { action.LambdaAction.TopicArn = aws.String(val.(string)) } return action case "S3Action": action := &ses.ReceiptAction{ S3Action: &ses.S3Action{ BucketName: aws.String(actionProperties["BucketName"].(string)), }, } if val, exists := actionProperties["KmsKeyArn"]; exists { action.S3Action.KmsKeyArn = aws.String(val.(string)) } if val, exists := actionProperties["ObjectKeyPrefix"]; exists { action.S3Action.ObjectKeyPrefix = aws.String(val.(string)) } if val, exists := actionProperties["TopicArn"]; exists { action.S3Action.TopicArn = aws.String(val.(string)) } return action default: logger.Error("No SESLmabdaEventSourceResourceAction marshaler found for action: " + action.ActionType.Literal) } return nil }
// Execute creates an HTTP listener to dispatch execution. Typically // called via Main() via command line arguments. func Execute(lambdaAWSInfos []*LambdaAWSInfo, port int, parentProcessPID int, logger *logrus.Logger) error { if port <= 0 { port = defaultHTTPPort } server := &http.Server{ Addr: fmt.Sprintf(":%d", port), Handler: NewLambdaHTTPHandler(lambdaAWSInfos, logger), ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, } if 0 != parentProcessPID { logger.Debug("Sending SIGUSR2 to parent process: ", parentProcessPID) syscall.Kill(parentProcessPID, syscall.SIGUSR2) } logger.Debug("Binding to port: ", port) err := server.ListenAndServe() if err != nil { logger.Error("FAILURE: " + err.Error()) return err } logger.Debug("Server available at: ", port) return nil }
func s3ItemInfo(event *json.RawMessage, context *sparta.LambdaContext, w http.ResponseWriter, logger *logrus.Logger) { logger.WithFields(logrus.Fields{ "RequestID": context.AWSRequestID, "Event": string(*event), }).Info("Request received") var lambdaEvent sparta.APIGatewayLambdaJSONEvent err := json.Unmarshal([]byte(*event), &lambdaEvent) if err != nil { logger.Error("Failed to unmarshal event data: ", err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) return } getObjectInput := &s3.GetObjectInput{ Bucket: aws.String(lambdaEvent.QueryParams["bucketName"]), Key: aws.String(lambdaEvent.QueryParams["keyName"]), } awsSession := awsSession(logger) svc := s3.New(awsSession) result, err := svc.GetObject(getObjectInput) if nil != err { logger.Error("Failed to process event: ", err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) return } presignedReq, _ := svc.GetObjectRequest(getObjectInput) url, err := presignedReq.Presign(5 * time.Minute) if nil != err { logger.Error("Failed to process event: ", err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) return } httpResponse := map[string]interface{}{ "S3": result, "URL": url, } responseBody, err := json.Marshal(httpResponse) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } else { w.Header().Set("Content-Type", "application/json") fmt.Fprint(w, string(responseBody)) } }
// TODO - Refactor ensure Lambdaconfigurator, then finish // implementing the CloudWatchEvents Principal type. func ensureConfiguratorLambdaResource(awsPrincipalName string, sourceArn *gocf.StringExpr, dependsOn []string, template *gocf.Template, S3Bucket string, S3Key string, logger *logrus.Logger) (string, error) { // AWS service basename awsServiceName := awsPrincipalToService(awsPrincipalName) configuratorExportName := strings.ToLower(awsServiceName) logger.WithFields(logrus.Fields{ "ServiceName": awsServiceName, "NodeJSExportName": configuratorExportName, }).Debug("Ensuring AWS push service configurator CustomResource") // Use a stable resource CloudFormation resource name to represent // the single CustomResource that can configure the different // PushSource's for the given principal. keyName, err := json.Marshal(ArbitraryJSONObject{ "Principal": awsPrincipalName, "ServiceName": awsServiceName, }) if err != nil { logger.Error("Failed to create configurator resource name: ", err.Error()) return "", err } subscriberHandlerName := CloudFormationResourceName(fmt.Sprintf("%sCustomResource", awsServiceName), string(keyName)) ////////////////////////////////////////////////////////////////////////////// // IAM Role definition iamResourceName, err := ensureIAMRoleForCustomResource(awsPrincipalName, sourceArn, template, logger) if nil != err { return "", err } iamRoleRef := gocf.GetAtt(iamResourceName, "Arn") _, exists := template.Resources[subscriberHandlerName] if !exists { logger.WithFields(logrus.Fields{ "Service": awsServiceName, }).Debug("Including Lambda CustomResource for AWS Service") configuratorDescription := fmt.Sprintf("Sparta created Lambda CustomResource to configure %s service", awsServiceName) ////////////////////////////////////////////////////////////////////////////// // Custom Resource Lambda Handler // NOTE: This brittle function name has an analog in ./resources/index.js b/c the // AWS Lamba execution treats the entire ZIP file as a module. So all module exports // need to be forwarded through the module's index.js file. handlerName := nodeJSHandlerName(configuratorExportName) logger.Debug("Lambda Configuration handler: ", handlerName) customResourceHandlerDef := gocf.LambdaFunction{ Code: &gocf.LambdaFunctionCode{ S3Bucket: gocf.String(S3Bucket), S3Key: gocf.String(S3Key), }, Description: gocf.String(configuratorDescription), Handler: gocf.String(handlerName), Role: iamRoleRef, Runtime: gocf.String("nodejs"), Timeout: gocf.Integer(30), } cfResource := template.AddResource(subscriberHandlerName, customResourceHandlerDef) if nil != dependsOn && (len(dependsOn) > 0) { cfResource.DependsOn = append(cfResource.DependsOn, dependsOn...) } } return subscriberHandlerName, nil }
func ensureCustomResourceHandler(serviceName string, customResourceTypeName string, sourceArn *gocf.StringExpr, dependsOn []string, template *gocf.Template, S3Bucket string, S3Key string, logger *logrus.Logger) (string, error) { // AWS service basename awsServiceName := awsPrincipalToService(customResourceTypeName) // Use a stable resource CloudFormation resource name to represent // the single CustomResource that can configure the different // PushSource's for the given principal. keyName, err := json.Marshal(ArbitraryJSONObject{ "Principal": customResourceTypeName, "ServiceName": awsServiceName, }) if err != nil { logger.Error("Failed to create configurator resource name: ", err.Error()) return "", err } subscriberHandlerName := CloudFormationResourceName(fmt.Sprintf("%sCustomResource", awsServiceName), string(keyName)) ////////////////////////////////////////////////////////////////////////////// // IAM Role definition iamResourceName, err := ensureIAMRoleForCustomResource(customResourceTypeName, sourceArn, template, logger) if nil != err { return "", err } iamRoleRef := gocf.GetAtt(iamResourceName, "Arn") _, exists := template.Resources[subscriberHandlerName] if !exists { logger.WithFields(logrus.Fields{ "Service": customResourceTypeName, }).Debug("Including Lambda CustomResource for AWS Service") configuratorDescription := customResourceDescription(serviceName, customResourceTypeName) ////////////////////////////////////////////////////////////////////////////// // Custom Resource Lambda Handler // The export name MUST correspond to the createForwarder entry that is dynamically // written into the index.js file during compile in createNewSpartaCustomResourceEntry handlerName := lambdaExportNameForCustomResourceType(customResourceTypeName) logger.WithFields(logrus.Fields{ "CustomResourceType": customResourceTypeName, "NodeJSExport": handlerName, }).Debug("Sparta CloudFormation custom resource handler info") customResourceHandlerDef := gocf.LambdaFunction{ Code: &gocf.LambdaFunctionCode{ S3Bucket: gocf.String(S3Bucket), S3Key: gocf.String(S3Key), }, Description: gocf.String(configuratorDescription), Handler: gocf.String(handlerName), Role: iamRoleRef, Runtime: gocf.String(NodeJSVersion), Timeout: gocf.Integer(30), } cfResource := template.AddResource(subscriberHandlerName, customResourceHandlerDef) if nil != dependsOn && (len(dependsOn) > 0) { cfResource.DependsOn = append(cfResource.DependsOn, dependsOn...) } } return subscriberHandlerName, nil }
// ConvergeStackState ensures that the serviceName converges to the template // state defined by cfTemplate. This function establishes a polling loop to determine // when the stack operation has completed. func ConvergeStackState(serviceName string, cfTemplate *gocf.Template, templateURL string, tags map[string]string, startTime time.Time, awsSession *session.Session, logger *logrus.Logger) (*cloudformation.Stack, error) { awsCloudFormation := cloudformation.New(awsSession) // Update the tags awsTags := make([]*cloudformation.Tag, 0) if nil != tags { for eachKey, eachValue := range tags { awsTags = append(awsTags, &cloudformation.Tag{ Key: aws.String(eachKey), Value: aws.String(eachValue), }) } } exists, existsErr := StackExists(serviceName, awsSession, logger) if nil != existsErr { return nil, existsErr } stackID := "" if exists { updateErr := updateStackViaChangeSet(serviceName, templateURL, stackCapabilities(cfTemplate), awsTags, awsCloudFormation, logger) if nil != updateErr { return nil, updateErr } stackID = serviceName } else { // Create stack createStackInput := &cloudformation.CreateStackInput{ StackName: aws.String(serviceName), TemplateURL: aws.String(templateURL), TimeoutInMinutes: aws.Int64(20), OnFailure: aws.String(cloudformation.OnFailureDelete), Capabilities: stackCapabilities(cfTemplate), } if len(awsTags) != 0 { createStackInput.Tags = awsTags } createStackResponse, createStackResponseErr := awsCloudFormation.CreateStack(createStackInput) if nil != createStackResponseErr { return nil, createStackResponseErr } logger.WithFields(logrus.Fields{ "StackID": *createStackResponse.StackId, }).Info("Creating stack") stackID = *createStackResponse.StackId } // Wait for the operation to succeeed pollingMessage := "Waiting for CloudFormation operation to complete" convergeResult, convergeErr := WaitForStackOperationComplete(stackID, pollingMessage, awsCloudFormation, logger) if nil != convergeErr { return nil, convergeErr } // If it didn't work, then output some failure information if !convergeResult.operationSuccessful { // Get the stack events and find the ones that failed. events, err := StackEvents(stackID, startTime, awsSession) if nil != err { return nil, err } logger.Error("Stack provisioning error") for _, eachEvent := range events { switch *eachEvent.ResourceStatus { case cloudformation.ResourceStatusCreateFailed, cloudformation.ResourceStatusDeleteFailed, cloudformation.ResourceStatusUpdateFailed: errMsg := fmt.Sprintf("\tError ensuring %s (%s): %s", *eachEvent.ResourceType, *eachEvent.LogicalResourceId, *eachEvent.ResourceStatusReason) logger.Error(errMsg) default: // NOP } } return nil, fmt.Errorf("Failed to provision: %s", serviceName) } else if nil != convergeResult.stackInfo.Outputs { for _, eachOutput := range convergeResult.stackInfo.Outputs { logger.WithFields(logrus.Fields{ "Key": *eachOutput.OutputKey, "Value": *eachOutput.OutputValue, "Description": *eachOutput.Description, }).Info("Stack output") } } return convergeResult.stackInfo, nil }
func ensureConfiguratorLambdaResource(awsPrincipalName string, sourceArn interface{}, resources ArbitraryJSONObject, S3Bucket string, S3Key string, logger *logrus.Logger) (string, error) { // AWS service basename awsServiceName := awsPrincipalToService(awsPrincipalName) configuratorExportName := strings.ToLower(awsServiceName) // Create a unique name that we can use for the configuration info keyName, err := json.Marshal(ArbitraryJSONObject{ "Principal": awsPrincipalName, "Arn": sourceArn, }) if err != nil { logger.Error("Failed to create configurator resource name: ", err.Error()) return "", err } subscriberHandlerName := CloudFormationResourceName(fmt.Sprintf("%sSubscriber", awsServiceName), string(keyName)) ////////////////////////////////////////////////////////////////////////////// // IAM Role definition principalActions, exists := PushSourceConfigurationActions[awsPrincipalName] if !exists { return "", fmt.Errorf("Unsupported principal for IAM role creation: %s", awsPrincipalName) } // Create a Role that enables this resource management iamResourceName, err := ensureIAMRoleResource(principalActions, sourceArn, resources, logger) if nil != err { return "", err } iamRoleRef := ArbitraryJSONObject{ "Fn::GetAtt": []string{iamResourceName, "Arn"}, } _, exists = resources[subscriberHandlerName] if !exists { logger.WithFields(logrus.Fields{ "Service": awsServiceName, }).Info("Creating configuration Lambda for AWS service") ////////////////////////////////////////////////////////////////////////////// // Custom Resource Lambda Handler // NOTE: This brittle function name has an analog in ./resources/index.js b/c the // AWS Lamba execution treats the entire ZIP file as a module. So all module exports // need to be forwarded through the module's index.js file. handlerName := nodeJSHandlerName(configuratorExportName) logger.Debug("Lambda Configuration handler: ", handlerName) customResourceHandlerDef := ArbitraryJSONObject{ "Type": "AWS::Lambda::Function", "Properties": ArbitraryJSONObject{ "Code": ArbitraryJSONObject{ "S3Bucket": S3Bucket, "S3Key": S3Key, }, "Role": iamRoleRef, "Handler": handlerName, "Runtime": "nodejs", "Timeout": "30", }, } resources[subscriberHandlerName] = customResourceHandlerDef } return subscriberHandlerName, nil }
func Delete(serviceName string, logger *logrus.Logger) error { logger.Error("Delete() not supported in AWS Lambda binary") return errors.New("Delete not supported for this binary") }
func Provision(noop bool, serviceName string, serviceDescription string, lambdaAWSInfos []*LambdaAWSInfo, api *API, site *S3Site, s3Bucket string, writer io.Writer, logger *logrus.Logger) error { logger.Error("Deploy() not supported in AWS Lambda binary") return errors.New("Deploy not supported for this binary") }
func Describe(serviceName string, serviceDescription string, lambdaAWSInfos []*LambdaAWSInfo, api *API, site *S3Site, outputWriter io.Writer, logger *logrus.Logger) error { logger.Error("Describe() not supported in AWS Lambda binary") return errors.New("Describe not supported for this binary") }
func Explore(lambdaAWSInfos []*LambdaAWSInfo, port int, logger *logrus.Logger) error { logger.Error("Explore() not supported in AWS Lambda binary") return errors.New("Explore not supported for this binary") }
func initializeDiscovery(serviceName string, lambdaAWSInfos []*LambdaAWSInfo, logger *logrus.Logger) { // Setup the discoveryImpl reference discoveryCache = make(map[string]*DiscoveryInfo, 0) discoverImpl = func() (*DiscoveryInfo, error) { pc := make([]uintptr, 2) entriesWritten := runtime.Callers(2, pc) if entriesWritten != 2 { return nil, fmt.Errorf("Unsupported call site for sparta.Discover()") } // The actual caller is sparta.Discover() f := runtime.FuncForPC(pc[1]) golangFuncName := f.Name() // Find the LambdaAWSInfo that has this golang function // as its target lambdaCFResource := "" for _, eachLambda := range lambdaAWSInfos { if eachLambda.lambdaFnName == 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(awsSession(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.Error("Failed to unmarshal discovery info") } discoveryCache[lambdaCFResource] = &discoveryInfo return &discoveryInfo, err } return emptyConfiguration, nil } }