// Explore supports interactive command line invocation of the previously // provisioned Sparta service func Explore(lambdaAWSInfos []*LambdaAWSInfo, port int, logger *logrus.Logger) error { if 0 == port { port = 9999 } urlHost := fmt.Sprintf("http://*****:*****@testEvent.json %s", functionURL) } } logger.Info("Functions can be invoked via application/json over POST") logger.Info(msgText) logger.Info("Where @testEvent.json is a local file with top level `context` and `event` properties:") logger.Info("\t{context: {}, event: {}}") // Start up the localhost server and publish the info return Execute(lambdaAWSInfos, port, 0, logger) }
// 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 := awsSession(logger) awsCloudFormation := cloudformation.New(session) exists, err := stackExists(serviceName, awsCloudFormation, 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 }
// 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 (command HelloWorldResource) create(session *session.Session, logger *logrus.Logger) (map[string]interface{}, error) { logger.Info("create: Hello ", command.Message) return map[string]interface{}{ "Resource": "Created message: " + command.Message, }, nil }
func s3LambdaProcessor(event *json.RawMessage, context *LambdaContext, w http.ResponseWriter, logger *logrus.Logger) { logger.WithFields(logrus.Fields{ "RequestID": context.AWSRequestID, }).Info("S3Event") logger.Info("Event data: ", string(*event)) }
func exploreTestHelloWorld(event *json.RawMessage, context *LambdaContext, w http.ResponseWriter, logger *logrus.Logger) { logger.Info("Hello World: ", string(*event)) fmt.Fprint(w, string(*event)) }
// NOTE: your application MUST use `package main` and define a `main()` function. The // example text is to make the documentation compatible with godoc. func echoS3SiteAPIGatewayEvent(event *json.RawMessage, context *LambdaContext, w http.ResponseWriter, logger *logrus.Logger) { logger.Info("Hello World: ", string(*event)) fmt.Fprint(w, string(*event)) }
// Return a string representation of a JS function call that can be exposed // to AWS Lambda func createNewNodeJSProxyEntry(lambdaInfo *LambdaAWSInfo, logger *logrus.Logger) string { // Create an entry of the form: logger.Info("Creating NodeJS proxy entry: " + lambdaInfo.jsHandlerName()) primaryEntry := fmt.Sprintf("exports[\"%s\"] = createForwarder(\"/%s\");\n", lambdaInfo.jsHandlerName(), lambdaInfo.lambdaFnName) return primaryEntry }
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) } } }
func cloudWatchEventProcessor(event *json.RawMessage, context *LambdaContext, w http.ResponseWriter, logger *logrus.Logger) { logger.WithFields(logrus.Fields{ "RequestID": context.AWSRequestID, }).Info("Request received") logger.Info("CloudWatch Event data: ", string(*event)) }
func ensureConfiguratorLambdaResource(awsPrincipalName string, sourceArn string, resources ArbitraryJSONObject, S3Bucket string, S3Key string, logger *logrus.Logger) (string, error) { // AWS service basename awsServiceName := awsPrincipalToService(awsPrincipalName) configuratorExportName := strings.ToLower(awsServiceName) ////////////////////////////////////////////////////////////////////////////// // IAM Role definition // TODO - Check sourceArn for equivalence iamResourceName, err := ensureIAMRoleResource(awsPrincipalName, sourceArn, resources, logger) if nil != err { return "", err } iamRoleRef := ArbitraryJSONObject{ "Fn::GetAtt": []string{iamResourceName, "Arn"}, } // Custom handler resource for this service type subscriberHandlerName := fmt.Sprintf("%sSubscriber", awsServiceName) _, exists := resources[subscriberHandlerName] if !exists { logger.Info("Creating Subscription Lambda Resource for AWS 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 := fmt.Sprintf("index.%sConfiguration", 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 archiveHook(context map[string]interface{}, serviceName string, zipWriter *zip.Writer, awsSession *session.Session, noop bool, logger *logrus.Logger) error { logger.Info("Adding userResource") resourceFileName := "userResource.json" binaryWriter, binaryWriterErr := zipWriter.Create(resourceFileName) if nil != binaryWriterErr { return binaryWriterErr } userdataReader := strings.NewReader(userdataResourceContents) _, copyErr := io.Copy(binaryWriter, userdataReader) return copyErr }
// WaitForStackOperationComplete is a blocking, polling based call that // periodically fetches the stackID set of events and uses the state value // to determine if an operation is complete func WaitForStackOperationComplete(stackID string, pollingMessage string, awsCloudFormation *cloudformation.CloudFormation, logger *logrus.Logger) (*WaitForStackOperationCompleteResult, error) { result := &WaitForStackOperationCompleteResult{} // Poll for the current stackID state, and describeStacksInput := &cloudformation.DescribeStacksInput{ StackName: aws.String(stackID), } for waitComplete := false; !waitComplete; { sleepDuration := time.Duration(11+rand.Int31n(13)) * time.Second time.Sleep(sleepDuration) describeStacksOutput, err := awsCloudFormation.DescribeStacks(describeStacksInput) if nil != err { // TODO - add retry iff we're RateExceeded due to collective access return nil, err } if len(describeStacksOutput.Stacks) <= 0 { return nil, fmt.Errorf("Failed to enumerate stack info: %v", *describeStacksInput.StackName) } result.stackInfo = describeStacksOutput.Stacks[0] switch *(result.stackInfo).StackStatus { case cloudformation.StackStatusCreateComplete, cloudformation.StackStatusUpdateComplete: result.operationSuccessful = true waitComplete = true case // Include DeleteComplete as new provisions will automatically rollback cloudformation.StackStatusDeleteComplete, cloudformation.StackStatusCreateFailed, cloudformation.StackStatusDeleteFailed, cloudformation.StackStatusRollbackFailed, cloudformation.StackStatusRollbackComplete, cloudformation.StackStatusUpdateRollbackComplete: result.operationSuccessful = false waitComplete = true default: logger.Info(pollingMessage) } } return result, nil }
// Does a given stack exist? func stackExists(stackNameOrID string, cf *cloudformation.CloudFormation, logger *logrus.Logger) (bool, error) { describeStacksInput := &cloudformation.DescribeStacksInput{ StackName: aws.String(stackNameOrID), } describeStacksOutput, err := cf.DescribeStacks(describeStacksInput) logger.Debug("DescribeStackOutput: ", describeStacksOutput) exists := false if err != nil { logger.Info("DescribeStackOutputError: ", err) // If the stack doesn't exist, then no worries if strings.Contains(err.Error(), "does not exist") { exists = false } else { return false, err } } else { exists = true } return exists, nil }
// Explore supports interactive command line invocation of the previously // provisioned Sparta service func Explore(serviceName string, logger *logrus.Logger) error { session := awsSession(logger) awsCloudFormation := cloudformation.New(session) exists, err := stackExists(serviceName, awsCloudFormation, logger) if nil != err { return err } else if !exists { logger.Info("Stack does not exist: ", serviceName) return nil } else { resources, err := stackLambdaResources(serviceName, awsCloudFormation, logger) if nil != err { return nil } selected := promptForSelection(resources) if nil != selected { logger.Info("TODO: Invoke", selected) } } return nil }
// MigratorMain is the entry point for the "migrate" cli command func MigratorMain(log *logrus.Logger) { opts := NewOptions() if opts.Debug { log.Level = logrus.DebugLevel } log.Debug("spinning up database") db, err := metadata.NewDatabase(opts.DatabaseURL, log) if err != nil { log.Fatal(err) } log.Debug("migrating") err = db.Migrate(log) if err != nil { log.Fatal(err) } log.Info("database migration complete") }
func buildGoBinary(executableOutput string, buildTags string, linkFlags string, logger *logrus.Logger) error { // Go generate cmd := exec.Command("go", "generate") if logger.Level == logrus.DebugLevel { cmd = exec.Command("go", "generate", "-v", "-x") } cmd.Env = os.Environ() commandString := fmt.Sprintf("%s", cmd.Args) logger.Info(fmt.Sprintf("Running `%s`", strings.Trim(commandString, "[]"))) goGenerateErr := runOSCommand(cmd, logger) if nil != goGenerateErr { return goGenerateErr } // TODO: Smaller binaries via linker flags // Ref: https://blog.filippo.io/shrink-your-go-binaries-with-this-one-weird-trick/ allBuildTags := fmt.Sprintf("lambdabinary %s", buildTags) buildArgs := []string{ "build", "-o", executableOutput, "-tags", allBuildTags, } // Append all the linker flags if len(linkFlags) != 0 { buildArgs = append(buildArgs, "-ldflags", linkFlags) } buildArgs = append(buildArgs, ".") cmd = exec.Command("go", buildArgs...) cmd.Env = os.Environ() cmd.Env = append(cmd.Env, "GOOS=linux", "GOARCH=amd64") logger.WithFields(logrus.Fields{ "Name": executableOutput, }).Info("Compiling binary") return runOSCommand(cmd, logger) }
func stampImage(bucket string, key string, logger *logrus.Logger) error { // Only transform if the key doesn't have the _xformed part if !strings.Contains(key, transformPrefix) { awsSession := awsSession(logger) svc := s3.New(awsSession) result, err := svc.GetObject(&s3.GetObjectInput{ Bucket: aws.String(bucket), Key: aws.String(key), }) if nil != err { return err } defer result.Body.Close() transformed, err := transforms.StampImage(result.Body, logger) if err != nil { return err } // Put the encoded image to a byte buffer, then wrap a reader around it. uploadResult, err := svc.PutObject(&s3.PutObjectInput{ Body: transformed, Bucket: aws.String(bucket), Key: aws.String(fmt.Sprintf("%s%s", transformPrefix, key)), }) if err != nil { return err } logger.WithFields(logrus.Fields{ "Transformed": uploadResult, }).Info("Image transformed") } else { logger.Info("File already transformed") } return nil }
// 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 := awsSession(logger) awsCloudFormation := cloudformation.New(session) exists, err := stackExists(serviceName, awsCloudFormation, logger) if nil != err { return err } if exists { logger.Info("Stack exists: ", serviceName) params := &cloudformation.DeleteStackInput{ StackName: aws.String(serviceName), } resp, err := awsCloudFormation.DeleteStack(params) if nil != resp { logger.Info("Stack delete issued: ", resp) } return err } logger.Info("Stack does not exist: ", serviceName) return nil }
func (command HelloWorldResource) delete(session *session.Session, logger *logrus.Logger) (map[string]interface{}, error) { logger.Info("delete: ", command.Message) return nil, nil }
func StampImage(reader io.Reader, logger *logrus.Logger) (io.ReadSeeker, error) { target, imageType, err := image.Decode(reader) if err != nil { logger.WithFields(logrus.Fields{ "Error": err, }).Info("Failed to decode image") return nil, err } // Pick the longer edge and a reasonably sized stamp maxEdge := math.Max(float64(target.Bounds().Max.X), float64(target.Bounds().Max.Y)) edgeLog := int(math.Floor(math.Log2(maxEdge))) - 1 logger.WithFields(logrus.Fields{ "ImageType": imageType, "MaxEdge": maxEdge, "EdgeLog": edgeLog, "TargetBounds": target.Bounds(), }).Info("Target Dimensions") watermarkSuffix := int(math.Max(32, math.Pow(2, math.Min(float64(8), float64(edgeLog))))) resourceName := watermarkName(watermarkSuffix) logger.Info("Watermark resource: ", resourceName) byteSource, err := assets.FSByte(false, resourceName) if err != nil { logger.WithFields(logrus.Fields{ "Error": err, "Name": resourceName, "TargetBounds": target.Bounds(), }).Warn("Failed to load computed watermark. Falling to default.") byteSource = assets.FSMustByte(false, watermarkName(16)) } stampReader := bytes.NewReader(byteSource) stamp, _, err := image.Decode(stampReader) if err != nil { logger.WithFields(logrus.Fields{ "Error": err, }).Info("Failed to load stamp image") return nil, err } // Save it... compositedImage := image.NewRGBA(image.Rect(0, 0, target.Bounds().Max.X, target.Bounds().Max.Y)) draw.Draw(compositedImage, compositedImage.Bounds(), target, image.Point{0, 0}, draw.Src) // Bottom right corner targetRect := target.Bounds() targetRect.Min.X = (targetRect.Max.X - stamp.Bounds().Max.X) targetRect.Min.Y = (targetRect.Max.Y - stamp.Bounds().Max.Y) logger.WithFields(logrus.Fields{ "TargetBounds": target.Bounds(), "StampBounds": stamp.Bounds(), "TargetRect": targetRect, }).Info("Drawing") logger.Debug("Composing image") draw.Draw(compositedImage, targetRect, stamp, image.Point{0, 0}, draw.Over) buf := new(bytes.Buffer) err = png.Encode(buf, compositedImage) return bytes.NewReader(buf.Bytes()), nil }
func apiStageInfo(apiName string, stageName string, session *session.Session, noop bool, logger *logrus.Logger) (*apigateway.Stage, error) { logger.WithFields(logrus.Fields{ "APIName": apiName, "StageName": stageName, }).Info("Checking current APIGateway stage status") if noop { logger.Info("Bypassing APIGateway check to -n/-noop command line argument") return nil, nil } svc := apigateway.New(session) restApisInput := &apigateway.GetRestApisInput{ Limit: aws.Int64(500), } restApisOutput, restApisOutputErr := svc.GetRestApis(restApisInput) if nil != restApisOutputErr { return nil, restApisOutputErr } // Find the entry that has this name restAPIID := "" for _, eachRestAPI := range restApisOutput.Items { if *eachRestAPI.Name == apiName { if restAPIID != "" { return nil, fmt.Errorf("Multiple RestAPI matches for API Name: %s", apiName) } restAPIID = *eachRestAPI.Id } } if "" == restAPIID { return nil, nil } // API exists...does the stage name exist? stagesInput := &apigateway.GetStagesInput{ RestApiId: aws.String(restAPIID), } stagesOutput, stagesOutputErr := svc.GetStages(stagesInput) if nil != stagesOutputErr { return nil, stagesOutputErr } // Find this stage name... var matchingStageOutput *apigateway.Stage for _, eachStage := range stagesOutput.Item { if *eachStage.StageName == stageName { if nil != matchingStageOutput { return nil, fmt.Errorf("Multiple stage matches for name: %s", stageName) } matchingStageOutput = eachStage } } if nil != matchingStageOutput { logger.WithFields(logrus.Fields{ "DeploymentId": *matchingStageOutput.DeploymentId, "LastUpdated": matchingStageOutput.LastUpdatedDate, "CreatedDate": matchingStageOutput.CreatedDate, }).Info("Checking current APIGateway stage status") } else { logger.Info("APIGateway stage has not been deployed") } return matchingStageOutput, nil }