Example #1
0
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
}
Example #2
0
// 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
}
Example #4
0
////////////////////////////////////////////////////////////////////////////////
// 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")
		}
	}
}
Example #5
0
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)
		}
	}
}
Example #6
0
// 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")
}
Example #7
0
// 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")
}
Example #8
0
////////////////////////////////////////////////////////////////////////////////
// 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")
	}
}
Example #9
0
// 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))
	}
}
Example #11
0
////////////////////////////////////////////////////////////////////////////////
// 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!")
}
Example #12
0
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
}
Example #14
0
// 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
}
Example #15
0
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))
	}
}
Example #16
0
// 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
}
Example #17
0
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
}
Example #18
0
// 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
}
Example #19
0
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
}
Example #20
0
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")
}
Example #21
0
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")

}
Example #22
0
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")
}
Example #23
0
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")
}
Example #24
0
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
	}
}