{"id":557,"date":"2018-04-11T00:28:43","date_gmt":"2018-04-10T16:28:43","guid":{"rendered":"https:\/\/vinta.ws\/code\/?p=557"},"modified":"2026-03-17T00:17:24","modified_gmt":"2026-03-16T16:17:24","slug":"apex-and-terraform-the-easiest-way-to-manage-aws-lambda-functions","status":"publish","type":"post","link":"https:\/\/vinta.ws\/code\/apex-and-terraform-the-easiest-way-to-manage-aws-lambda-functions.html","title":{"rendered":"Apex and Terraform: The easiest way to manage AWS Lambda functions"},"content":{"rendered":"<p>AWS Lambda lets you run code without provisioning or managing servers, which is so-called Serverless or Function as a Service (FaaS).<\/p>\n<p>Apex is a Go command-line tool to manage and deploy your serverless functions on AWS Lambda. Apex is also integrated with Terraform to provide cloud infrastructure management, for instance, configuring your AWS Lambda functions with Amazon API Gateway.<\/p>\n<p>ref:<br \/>\n<a href=\"https:\/\/aws.amazon.com\/lambda\/\">https:\/\/aws.amazon.com\/lambda\/<\/a><br \/>\n<a href=\"https:\/\/aws.amazon.com\/api-gateway\/\">https:\/\/aws.amazon.com\/api-gateway\/<\/a><br \/>\n<a href=\"https:\/\/github.com\/apex\/apex\">https:\/\/github.com\/apex\/apex<\/a><\/p>\n<p>You could browse projects created in this post on GitHub:<br \/>\n<a href=\"https:\/\/github.com\/vinta\/pangu.space\">https:\/\/github.com\/vinta\/pangu.space<\/a><br \/>\n<a href=\"https:\/\/github.com\/CodeTengu\/LambdaBaku\">https:\/\/github.com\/CodeTengu\/LambdaBaku<\/a><\/p>\n<h2>Install<\/h2>\n<pre class=\"line-numbers\"><code class=\"language-console\">$ curl https:\/\/raw.githubusercontent.com\/apex\/apex\/master\/install.sh | sh<\/code><\/pre>\n<p>ref:<br \/>\n<a href=\"https:\/\/apex.run\/#installation\">https:\/\/apex.run\/#installation<\/a><\/p>\n<h2>Initialize<\/h2>\n<p>It is recommended to configure your AWS credentials with <code>awscli<\/code>.<\/p>\n<pre class=\"line-numbers\"><code class=\"language-console\">$ pip install awscli\n$ aws configure<\/code><\/pre>\n<p>ref:<br \/>\n<a href=\"https:\/\/docs.aws.amazon.com\/cli\/latest\/userguide\/cli-chap-getting-started.html\">https:\/\/docs.aws.amazon.com\/cli\/latest\/userguide\/cli-chap-getting-started.html<\/a><\/p>\n<p>To use Apex to manage Lambda functions, you have to make sure your AWS credential has minimum IAM permissions:<\/p>\n<pre class=\"line-numbers\"><code class=\"language-json\">{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Action\": [\n        \"iam:CreateRole\",\n        \"iam:CreatePolicy\",\n        \"iam:AttachRolePolicy\",\n        \"iam:PassRole\",\n        \"lambda:GetFunction\",\n        \"lambda:ListFunctions\",\n        \"lambda:CreateFunction\",\n        \"lambda:DeleteFunction\",\n        \"lambda:InvokeFunction\",\n        \"lambda:GetFunctionConfiguration\",\n        \"lambda:UpdateFunctionConfiguration\",\n        \"lambda:UpdateFunctionCode\",\n        \"lambda:CreateAlias\",\n        \"lambda:UpdateAlias\",\n        \"lambda:GetAlias\",\n        \"lambda:ListAliases\",\n        \"lambda:ListVersionsByFunction\",\n        \"logs:FilterLogEvents\",\n        \"cloudwatch:GetMetricStatistics\"\n      ],\n      \"Effect\": \"Allow\",\n      \"Resource\": \"*\"\n    }\n  ]\n}<\/code><\/pre>\n<pre class=\"line-numbers\"><code class=\"language-console\">$ apex init<\/code><\/pre>\n<p>ref:<br \/>\n<a href=\"https:\/\/apex.run\/#getting-started\">https:\/\/apex.run\/#getting-started<\/a><\/p>\n<p>After running <code>apex init<\/code>, Apex creates a Role and a Policy. You should be able to find them on AWS IAM Management Console. If you want to access other AWS resources, for instance, S3 buckets, DynamoDB tables, SNS,  in your Lambda functions, you must create a new Policy which grants appropriate permissions and attaches itself to the Role that Apex created.<\/p>\n<p>Here is a Policy example of operating certain DynamoDB tables:<\/p>\n<pre class=\"line-numbers\"><code class=\"language-json\">{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Sid\": \"Stmt123456789\",\n            \"Effect\": \"Allow\",\n            \"Action\": [\n                \"dynamodb:*\"\n            ],\n            \"Resource\": [\n                \"arn:aws:dynamodb:ap-northeast-1:123456789:table\/CodeTengu_Preference\",\n                \"arn:aws:dynamodb:ap-northeast-1:123456789:table\/CodeTengu_Preference\/*\",\n                \"arn:aws:dynamodb:ap-northeast-1:123456789:table\/CodeTengu_WeeklyIssue\",\n                \"arn:aws:dynamodb:ap-northeast-1:123456789:table\/CodeTengu_WeeklyIssue\/*\",\n                \"arn:aws:dynamodb:ap-northeast-1:123456789:table\/CodeTengu_WeeklyPost\",\n                \"arn:aws:dynamodb:ap-northeast-1:123456789:table\/CodeTengu_WeeklyPost\/*\"\n            ]\n        }\n    ]\n}<\/code><\/pre>\n<h2>Write Lambda Functions<\/h2>\n<p>ref:<br \/>\n<a href=\"https:\/\/docs.aws.amazon.com\/lambda\/latest\/dg\/current-supported-versions.html\">https:\/\/docs.aws.amazon.com\/lambda\/latest\/dg\/current-supported-versions.html<\/a><br \/>\n<a href=\"https:\/\/docs.aws.amazon.com\/lambda\/latest\/dg\/best-practices.html\">https:\/\/docs.aws.amazon.com\/lambda\/latest\/dg\/best-practices.html<\/a><\/p>\n<h3>Node.js<\/h3>\n<p>The simplest handler:<\/p>\n<pre class=\"line-numbers\"><code class=\"language-js\">const aws = require('aws-sdk');\n\nexports.handle = (event, context, callback) =&gt; {\n  doYourShit();\n  callback(null, 'DONE');\n};<\/code><\/pre>\n<p>ref:<br \/>\n<a href=\"https:\/\/docs.aws.amazon.com\/lambda\/latest\/dg\/programming-model.html\">https:\/\/docs.aws.amazon.com\/lambda\/latest\/dg\/programming-model.html<\/a><\/p>\n<p>Call another Lambda function in a Lambda function:<\/p>\n<p>You must make sure your Lambda role has the permission of invoking other Lambda functions.<\/p>\n<pre class=\"line-numbers\"><code class=\"language-js\">const util = require('util');\n\nconst aws = require('aws-sdk');\n\nconst params = {\n  FunctionName: 'LambdaBaku_syncIssue',\n  InvocationType: 'Event', \/\/ means asynchronous execution\n  Payload: JSON.stringify({ issue_number: curatedIssue.number }),\n};\n\nlambda.invoke(params, (err, data) =&gt; {\n  if (err) {\n    console.log('FAIL', params);\n    console.log(util.inspect(err));\n  } else {\n    console.log(data);\n  }\n});<\/code><\/pre>\n<p>ref:<br \/>\n<a href=\"https:\/\/docs.aws.amazon.com\/AWSJavaScriptSDK\/latest\/AWS\/Lambda.html\">https:\/\/docs.aws.amazon.com\/AWSJavaScriptSDK\/latest\/AWS\/Lambda.html<\/a><br \/>\n<a href=\"https:\/\/stackoverflow.com\/questions\/31714788\/can-an-aws-lambda-function-call-another\">https:\/\/stackoverflow.com\/questions\/31714788\/can-an-aws-lambda-function-call-another<\/a><\/p>\n<h3>Go<\/h3>\n<p>Write a Lambda function triggered by Amazon API Gateway:<\/p>\n<pre class=\"line-numbers\"><code class=\"language-go\">package main\n\nimport (\n    \"encoding\/json\"\n    \"errors\"\n    \"log\"\n\n    \"github.com\/aws\/aws-lambda-go\/events\"\n    \"github.com\/aws\/aws-lambda-go\/lambda\"\n    \"github.com\/vinta\/pangu\"\n)\n\nvar (\n    \/\/ ErrTextNotProvided is thrown when text is not provided in HTTP query string\n    ErrTextNotProvided = errors.New(\"No text was provided in HTTP query string\")\n)\n\n\/\/ Handler is the AWS Lambda function handler\nfunc Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {\n    log.Printf(\"request id: %sn\", request.RequestContext.RequestID)\n\n    text, ok := request.QueryStringParameters[\"t\"]\n    if !ok {\n        errMap := map[string]string{\n            \"message\": ErrTextNotProvided.Error(),\n        }\n        errMapJSON, _ := json.MarshalIndent(errMap, \"\", \" \")\n\n        return events.APIGatewayProxyResponse{\n            Body: string(errMapJSON),\n            StatusCode: 400,\n        }, nil\n    }\n\n    log.Printf(\"text: %sn\", text)\n\n    textPlainHeaders := map[string]string{\n        \"content-type\": \"text\/plain; charset=utf-8\",\n    }\n\n    return events.APIGatewayProxyResponse{\n        Body: pangu.SpacingText(text),\n        Headers: textPlainHeaders,\n        StatusCode: 200,\n    }, nil\n}\n\nfunc main() {\n    lambda.Start(Handler)\n}<\/code><\/pre>\n<p>ref:<br \/>\n<a href=\"https:\/\/aws.amazon.com\/blogs\/compute\/announcing-go-support-for-aws-lambda\/\">https:\/\/aws.amazon.com\/blogs\/compute\/announcing-go-support-for-aws-lambda\/<\/a><br \/>\n<a href=\"https:\/\/docs.aws.amazon.com\/lambda\/latest\/dg\/go-programming-model-handler-types.html\">https:\/\/docs.aws.amazon.com\/lambda\/latest\/dg\/go-programming-model-handler-types.html<\/a><br \/>\n<a href=\"https:\/\/docs.aws.amazon.com\/lambda\/latest\/dg\/go-programming-model-errors.html\">https:\/\/docs.aws.amazon.com\/lambda\/latest\/dg\/go-programming-model-errors.html<\/a><\/p>\n<p>Your &quot;Integration Request&quot; configurations in API Gateway should be like:<\/p>\n<ul>\n<li>Integration type: <code>Lambda Function<\/code><\/li>\n<li>Use Lambda Proxy integration: <code>Yes<\/code><\/li>\n<li>Lambda Region: <code>ap-northeast-1<\/code><\/li>\n<li>Lambda Function: <code>panguspace_spacing_text<\/code><\/li>\n<li>Invoke with caller credentials: <code>No<\/code><\/li>\n<li>Credentials cache: <code>Do not add caller credentials to cache key<\/code><\/li>\n<li>Use Default Timeout: <code>Yes<\/code><\/li>\n<\/ul>\n<p>It's also worth noting that the API response is mainly defined by <code>APIGatewayProxyResponse<\/code> in Lambda function code. Configurations in API Gateway, i.e., &quot;Integration Response&quot; and &quot;Method Response&quot; do not matter.<\/p>\n<p>ref:<br \/>\n<a href=\"https:\/\/docs.aws.amazon.com\/apigateway\/latest\/developerguide\/getting-started-with-lambda-integration.html\">https:\/\/docs.aws.amazon.com\/apigateway\/latest\/developerguide\/getting-started-with-lambda-integration.html<\/a><\/p>\n<h2>Usage<\/h2>\n<p>Deploy all functions:<\/p>\n<pre class=\"line-numbers\"><code class=\"language-console\">$ apex deploy<\/code><\/pre>\n<p>ref:<br \/>\n<a href=\"https:\/\/apex.run\/#deploying-functions\">https:\/\/apex.run\/#deploying-functions<\/a><\/p>\n<p>Invoke a function:<\/p>\n<pre class=\"line-numbers\"><code class=\"language-console\"># invoke a function directly\n$ apex invoke spacing_text --logs\n{\n    \"statusCode\": 400,\n    \"headers\": null,\n    \"body\":\"{\"message\": \"No text was provided in the HTTP query string\"}\"\n}\n\n# invoke a function with an API Gateway event\n$ cat fixtures\/spacing_text_event.json\n{\n    \"queryStringParameters\": {\"t\": \"\u8207PM\u6230\u9b25\u7684\u4eba\uff0c\u61c9\u7576\u5c0f\u5fc3\u81ea\u5df1\u4e0d\u8981\u6210\u70baPM\"}\n}\n$ apex invoke spacing_text --logs &lt; fixtures\/spacing_text_event.json\n{\n    \"statusCode\": 200,\n    \"headers\": {\"content-type\": \"text\/plain; charset=utf-8\"},\n    \"body\": \"\u8207 PM \u6230\u9b25\u7684\u4eba\uff0c\u61c9\u7576\u5c0f\u5fc3\u81ea\u5df1\u4e0d\u8981\u6210\u70ba PM\"\n}<\/code><\/pre>\n<p>ref:<br \/>\n<a href=\"https:\/\/apex.run\/#invoking-functions\">https:\/\/apex.run\/#invoking-functions<\/a><\/p>\n<p>View logs which might delay several seconds:<\/p>\n<pre class=\"line-numbers\"><code class=\"language-console\">$ apex logs -f<\/code><\/pre>\n<p>Pack a function:<\/p>\n<pre class=\"line-numbers\"><code class=\"language-console\">$ apex build spacing_text &gt; spacing_text.zip<\/code><\/pre>\n<h2>Configure API Gateway<\/h2>\n<h3>Create API Keys<\/h3>\n<p>To setup API keys, do the following:<\/p>\n<ol>\n<li>Configure your API methods to require an API key<\/li>\n<li>Deploy your API<\/li>\n<li>Create an API key for the API in a region<\/li>\n<li>Create an Usage Plan and assign an API key with a certain Stage<\/li>\n<\/ol>\n<p>In step 1, your &quot;Method Request&quot; configurations in API Gateway should be like:<\/p>\n<ul>\n<li>Authorization: <code>NONE<\/code><\/li>\n<li>Request Validator: <code>NONE<\/code><\/li>\n<li>API Key Required: <code>true<\/code><\/li>\n<\/ul>\n<p>Now you are able to call the API with a <code>x-api-key<\/code> header:<\/p>\n<pre class=\"line-numbers\"><code class=\"language-console\">$ curl -H \"x-api-key: YOUR-API-KEY\" https:\/\/xxx.execute-api.ap-northeast-1.amazonaws.com\/v1\/your-endpoint\/<\/code><\/pre>\n<p>ref:<br \/>\n<a href=\"https:\/\/docs.aws.amazon.com\/apigateway\/latest\/developerguide\/api-gateway-create-usage-plans-with-rest-api.html\">https:\/\/docs.aws.amazon.com\/apigateway\/latest\/developerguide\/api-gateway-create-usage-plans-with-rest-api.html<\/a><br \/>\n<a href=\"https:\/\/docs.aws.amazon.com\/apigateway\/latest\/developerguide\/how-to-use-postman-to-call-api.html\">https:\/\/docs.aws.amazon.com\/apigateway\/latest\/developerguide\/how-to-use-postman-to-call-api.html<\/a><\/p>\n<p>Actually, you could release your APIs without API keys if you like.<\/p>\n<h3>Setup a Custom Domain<\/h3>\n<p>To setup a custom domain which managed by Cloudflare, see the following link:<br \/>\n<a href=\"https:\/\/stackoverflow.com\/a\/46061708\/885524\">https:\/\/stackoverflow.com\/a\/46061708\/885524<\/a><\/p>\n<p>It is worth noting that even the Stack Overflow answer said using <code>Full (Strict)<\/code> SSL mode but actually <code>Full<\/code> also works.<\/p>\n<p>Moreover, it might take a long time to generate &quot;Target Domain Name&quot; (<code>xxx.cloudfront.net<\/code>).<\/p>\n<p>Don't forget to add &quot;Base Path Mappings&quot; in API Gateway Custom Domain Names:<\/p>\n<ul>\n<li><code>api.pangu.space<\/code>\n<ul>\n<li>Target Domain Name: <code>xxx.cloudfront.net<\/code><\/li>\n<li>ACM Certificate: <code>*.pangu.space<\/code><\/li>\n<li>Base Path Mappings:\n<ul>\n<li>Path: <code>\/v1<\/code><\/li>\n<li>Destination: <code>Pangu:v1<\/code><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<h2>Manage Infrastructures with Terraform<\/h2>\n<p>Terraform is a tool to manage your cloud infrastructures as code.<\/p>\n<pre class=\"line-numbers\"><code class=\"language-console\">$ brew install terraform\n\n$ tree .\n.\n\u251c\u2500\u2500 functions\n\u2502   \u251c\u2500\u2500 introduce\n\u2502   \u2502   \u2514\u2500\u2500 main.go\n\u2502   \u2514\u2500\u2500 spacing_text\n\u2502       \u2514\u2500\u2500 main.go\n\u2514\u2500\u2500 infrastructure\n    \u251c\u2500\u2500 main.tf\n    \u2514\u2500\u2500 variables.tf<\/code><\/pre>\n<p>Define variables and data sources:<\/p>\n<pre class=\"line-numbers\"><code class=\"language-go\"># infrastructure\/variables.tf\ndata \"aws_caller_identity\" \"current\" {}\n\nvariable \"aws_region\" {}\nvariable \"apex_environment\" {}\nvariable \"apex_function_role\" {}\n\nvariable \"apex_function_arns\" {\n  type = \"map\"\n}\n\nvariable \"apex_function_names\" {\n  type = \"map\"\n}\n\nvariable \"apex_function_introduce\" {}\nvariable \"apex_function_spacing_text\" {}<\/code><\/pre>\n<p>ref:<br \/>\n<a href=\"https:\/\/www.terraform.io\/docs\/providers\/aws\/d\/caller_identity.html\">https:\/\/www.terraform.io\/docs\/providers\/aws\/d\/caller_identity.html<\/a><\/p>\n<p>Define AWS resources:<\/p>\n<pre class=\"line-numbers\"><code class=\"language-go\"># infrastructure\/main.tf\nresource \"aws_api_gateway_rest_api\" \"pangu\" {\n  name = \"Pangu\"\n}\n\nresource \"aws_api_gateway_method\" \"pangu_root\" {\n  rest_api_id   = \"${aws_api_gateway_rest_api.pangu.id}\"\n  resource_id   = \"${aws_api_gateway_rest_api.pangu.root_resource_id}\"\n  http_method   = \"GET\"\n  authorization = \"NONE\"\n}\n\nresource \"aws_api_gateway_integration\" \"pangu_root_get\" {\n  rest_api_id             = \"${aws_api_gateway_rest_api.pangu.id}\"\n  resource_id             = \"${aws_api_gateway_rest_api.pangu.root_resource_id}\"\n  http_method             = \"${aws_api_gateway_method.pangu_root.http_method}\"\n  integration_http_method = \"POST\"\n  type                    = \"AWS_PROXY\"\n  uri                     = \"arn:aws:apigateway:${var.aws_region}:lambda:path\/2015-03-31\/functions\/${var.apex_function_introduce}\/invocations\"\n}\n\nresource \"aws_api_gateway_method_response\" \"pangu_root_get_200\" {\n  rest_api_id = \"${aws_api_gateway_rest_api.pangu.id}\"\n  resource_id = \"${aws_api_gateway_rest_api.pangu.root_resource_id}\"\n  http_method = \"${aws_api_gateway_method.pangu_root.http_method}\"\n  status_code = \"200\"\n\n  response_models = {\n    \"application\/json\" = \"Empty\"\n  }\n\n  response_parameters = {\n    \"method.response.header.Access-Control-Allow-Origin\" = true\n  }\n}\n\nresource \"aws_api_gateway_resource\" \"pangu_spacing_text\" {\n  rest_api_id = \"${aws_api_gateway_rest_api.pangu.id}\"\n  parent_id   = \"${aws_api_gateway_rest_api.pangu.root_resource_id}\"\n  path_part   = \"spacing-text\"\n}\n\nresource \"aws_api_gateway_method\" \"pangu_spacing_text_get\" {\n  rest_api_id      = \"${aws_api_gateway_rest_api.pangu.id}\"\n  resource_id      = \"${aws_api_gateway_resource.pangu_spacing_text.id}\"\n  http_method      = \"GET\"\n  authorization    = \"NONE\"\n  api_key_required = true\n}\n\nresource \"aws_api_gateway_integration\" \"pangu_spacing_text_get\" {\n  rest_api_id             = \"${aws_api_gateway_rest_api.pangu.id}\"\n  resource_id             = \"${aws_api_gateway_resource.pangu_spacing_text.id}\"\n  http_method             = \"${aws_api_gateway_method.pangu_spacing_text_get.http_method}\"\n  integration_http_method = \"POST\"\n  type                    = \"AWS_PROXY\"\n  uri                     = \"arn:aws:apigateway:${var.aws_region}:lambda:path\/2015-03-31\/functions\/${var.apex_function_spacing_text}\/invocations\"\n}\n\nresource \"aws_api_gateway_method_response\" \"pangu_spacing_text_get_200\" {\n  rest_api_id = \"${aws_api_gateway_rest_api.pangu.id}\"\n  resource_id = \"${aws_api_gateway_resource.pangu_spacing_text.id}\"\n  http_method = \"${aws_api_gateway_method.pangu_spacing_text_get.http_method}\"\n  status_code = \"200\"\n\n  response_models = {\n    \"application\/json\" = \"Empty\"\n  }\n\n  response_parameters = {\n    \"method.response.header.Access-Control-Allow-Origin\" = true\n  }\n}\n\nresource \"aws_api_gateway_deployment\" \"pangu\" {\n  depends_on = [\n    \"aws_api_gateway_method.pangu_root\",\n    \"aws_api_gateway_integration.pangu_root_get\",\n    \"aws_api_gateway_method_response.pangu_root_get_200\",\n    \"aws_api_gateway_resource.pangu_spacing_text\",\n    \"aws_api_gateway_method.pangu_spacing_text_get\",\n    \"aws_api_gateway_integration.pangu_spacing_text_get\",\n    \"aws_api_gateway_method_response.pangu_spacing_text_get_200\",\n  ]\n\n  rest_api_id = \"${aws_api_gateway_rest_api.pangu.id}\"\n  stage_name  = \"v1\"\n}\n\nresource \"aws_lambda_permission\" \"pangu_root_get\" {\n  statement_id  = \"AllowInvokeFromAPIGateway\"\n  action        = \"lambda:InvokeFunction\"\n  function_name = \"${var.apex_function_introduce}\"\n  principal     = \"apigateway.amazonaws.com\"\n\n  source_arn = \"arn:aws:execute-api:${var.aws_region}:${data.aws_caller_identity.current.account_id}:${aws_api_gateway_rest_api.pangu.id}\/*\/${aws_api_gateway_integration.pangu_root_get.http_method}\/\"\n}\n\nresource \"aws_lambda_permission\" \"pangu_spacing_text\" {\n  statement_id  = \"AllowInvokeFromAPIGateway\"\n  action        = \"lambda:InvokeFunction\"\n  function_name = \"${var.apex_function_spacing_text}\"\n  principal     = \"apigateway.amazonaws.com\"\n\n  source_arn = \"arn:aws:execute-api:${var.aws_region}:${data.aws_caller_identity.current.account_id}:${aws_api_gateway_rest_api.pangu.id}\/*\/${aws_api_gateway_integration.pangu_spacing_text_get.http_method}${aws_api_gateway_resource.pangu_spacing_text.path}\"\n}<\/code><\/pre>\n<p>ref:<br \/>\n<a href=\"https:\/\/www.terraform.io\/docs\/providers\/aws\/guides\/serverless-with-aws-lambda-and-api-gateway.html\">https:\/\/www.terraform.io\/docs\/providers\/aws\/guides\/serverless-with-aws-lambda-and-api-gateway.html<\/a><\/p>\n<pre class=\"line-numbers\"><code class=\"language-console\"># download provider plugins\n$ apex infra init\n\n# view the generated execution plan\n$ apex infra plan\n\n# deploy your infrastructures\n$ apex infra apply\n$ apex infra apply -auto-approve<\/code><\/pre>\n<p>ref:<br \/>\n<a href=\"https:\/\/apex.run\/#managing-infrastructure\">https:\/\/apex.run\/#managing-infrastructure<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Apex is a Go command-line tool to manage and deploy your serverless functions on AWS Lambda. Apex is also integrated with Terraform to provide cloud infrastructure management, for instance, configuring your AWS Lambda functions with Amazon API Gateway.<\/p>\n","protected":false},"author":1,"featured_media":558,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[38,116],"tags":[16,124,66,11],"class_list":["post-557","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-about-devops","category-about-web-development","tag-amazon-web-services","tag-aws-lambda","tag-go","tag-javascript"],"_links":{"self":[{"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/posts\/557","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/comments?post=557"}],"version-history":[{"count":0,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/posts\/557\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/media\/558"}],"wp:attachment":[{"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/media?parent=557"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/categories?post=557"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/tags?post=557"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}