How I Built a Resume API w/ Go, Terraform, and AWS
Tags: resume, go, aws, terraform
Published: 2020-06-27 22:18:32

## Background

I've been inspired by [Sara Vieira](https://twitter.com/NikkitaFTW) to [build more dumb stuff on the Internet](https://egghead.io/podcasts/making-dumb-stuff-that-makes-strangers-on-the-internet-smile-with-sara-vieira) to recharge my soul and learn while I'm doing it. [I wanted to learn Go](https://github.com/cujarrett/personal-goals#things-ill-do-in-2020) this year and after taking [Stephen Grider's](https://twitter.com/ste_grider) awesome [Go: The Complete Developer's Guide (Golang)](https://www.udemy.com/course/go-the-complete-developers-guide/) I wanted to build something simple to continue my Go journey. I decided to build an open source REST API for my [JSON-based standard format resume](https://jsonresume.org/). I'm using [Go](https://golang.org/) for my app, [Terraform](https://www.terraform.io/) for my [Infrastructure as Code (IaC)](https://en.wikipedia.org/wiki/Infrastructure_as_code) with [Terraform Cloud](https://www.terraform.io/docs/cloud/overview.html) to provision my infrastructure, either on demand or in response to various events. I'm deploying the app to [AWS Lambda](https://aws.amazon.com/lambda/) with a [Circle CI] (https://circleci.com/) CI/CD pipeline.

All of the code for this can be found in [my GitHub](https://github.com/cujarrett/resume-api).

## Demo
You can curl the REST API to view my resume

```sh
curl -L https://resume.mattjarrett.dev
```

### Setup
You'll need to set up Go and Terraform locally as well as have an account on [Terraform Cloud](https://www.terraform.io/docs/cloud/index.html) and [AWS](https://aws.amazon.com/) if you want to deploy your code.

### Creating your Resume in JSON
I copied the [JSON-based standard format resume schema](https://jsonresume.org/schema/) that I would edit in my info. After I was done editing that JSON, I ended up with this.

```json
{
  "basics": {
    "name": "Matt Jarrett",
    "label": "Full Stack Developer",
    "picture": "https://mattjarrett.dev/img/header.jpg",
    "email": "",
    "phone": "",
    "website": "https://mattjarrett.dev/",
    "summary": "Dad. Husband. Full Stack Developer. JavaScript Enthusiast. Aspiring Designer. Cyclist. I'm a passionate and detail oriented full stack software developer based in Dallas, Texas. Experienced leader where I enjoy design, clean code, and building positive team environments and strong developer experiences. I enjoy turning problems into solutions that make people smile. I enjoy continuous learning and exploring new technologies. When I'm not coding at home or work, I enjoy time with my family and photography.",
    "location": {
      "address": "",
      "postalCode": "",
      "city": "Dallas",
      "countryCode": "US",
      "region": "Texas"
    },
    "profiles": [
      {
        "network": "Linkedin",
        "username": "Matt Jarrett",
        "url": "https://www.linkedin.com/in/matt-jarrett-303a75144/"
      },
      {
        "network": "GitHub",
        "username": "cujarrett",
        "url": "https://github.com/cujarrett"
      },
      {
        "network": "Twitter",
        "username": "cujarrett",
        "url": "https://twitter.com/home"
      }
    ]
  },
  "work": [
    {
      "company": "State Farm",
      "position": "Technology Engineer",
      "website": "https://www.statefarm.com",
      "startDate": "2011-11-01",
      "endDate": "N/A",
      "summary": "State Farm is the largest property and casualty insurance provider in the United States. It is also the largest auto insurance provider in the United States. State Farm is ranked 36th in the 2019 Fortune 500, which lists American companies by revenue.",
      "highlights": [
        "Advancing Cloud Native and DevOps transformations in the telematics platform space. Working with an exciting mix of technology including public cloud, mobile, and legacy systems. Working daily on not only technology but also culture.",
        "Built new solutions using React, Node, NOSQL, REST, Kubernetes, GitLab, CI/CD, Grafana, and Prometheus for enablement of large high priority enterprise initiatives. Mentored several team members in modern technology. Designed solutions for implementation. Taught classes on modern JavaScript.",
        "Designed and implemented many successful web services allowing developers on demand access to safe fabricated data for a variety of needs and automation. It's since been used millions of times across the enterprise, enabling development teams to focus on improving customer experience.",
        "Designed and implemented an automated infrastructure solution offering a complete stand up and tear down process accomplished in minutes compared to days.",
        "Taught Java in Enterprise Java classes to spread knowledge. Active member of the college recruitment team including creation of campus events, hacks, and interviews. Mentored many college interns through a successful internships resulting in all receiving and accepting full time positions. Designed and implemented multiple solutions in Java."
      ]
    }
  ],
  "volunteer": [
    {
      "organization": "Tech Titans",
      "position": "Speaker",
      "website": "https://techtitans.org/",
      "startDate": "2017-01-01",
      "endDate": "N/A",
      "summary": "Tech Titans is a forum that leverages the regional technology community to collaborate, share and inspire creative thinking that fuels tomorrow’s innovations.",
      "highlights": [
        "I Spoke at many events over the years with audiences including teachers and students."
      ]
    }
  ],
  "education": [
    {
      "institution": "Illinois State University",
      "area": "Information Technology",
      "studyType": "Bachelor",
      "startDate": "2009-06-01",
      "endDate": "2011-01-01",
      "gpa": "3.87/4.0",
      "courses": []
    }
  ],
  "awards": [],
  "publications": [],
  "skills": [
    {
      "name": "AWS",
      "level": "Intermediate",
      "keywords": [
        "Cloud",
        "Lambda",
        "S3",
        "CloudFront"
      ]
    },
    {
      "name": "JavaScript",
      "level": "Expert",
      "keywords": [
        "ECMAScript",
        "ES6",
        "Node.js",
        "Web",
        "Front End"
      ]
    },
    {
      "name": "React",
      "level": "Pro",
      "keywords": [
        "SPA",
        "Web",
        "Front End"
      ]
    },
    {
      "name": "Vue",
      "level": "Intermediate",
      "keywords": [
        "SPA",
        "Web",
        "Front End"
      ]
    },
    {
      "name": "Angular",
      "level": "Intermediate",
      "keywords": [
        "SPA",
        "Web",
        "Front End"
      ]
    },
    {
      "name": "Go",
      "level": "Intermediate",
      "keywords": [
        "Golang"
      ]
    },
    {
      "name": "Kubernetes",
      "level": "Pro",
      "keywords": [
        "K8s",
        "CKAD"
      ]
    },
    {
      "name": "Docker",
      "level": "Intermediate",
      "keywords": [
        "Containers"
      ]
    },
    {
      "name": "Design",
      "level": "Intermediate",
      "keywords": [
        "Web",
        "Front End"
      ]
    },
    {
      "name": "Linux",
      "level": "Intermediate",
      "keywords": [
        "Bash",
        "Shell"
      ]
    },
    {
      "name": "NOSQL",
      "level": "Pro",
      "keywords": [
        "Couch",
        "Dynamo"
      ]
    },
    {
      "name": "SQL",
      "level": "Pro",
      "keywords": [
        "PostgreSQL",
        "Aurora"
      ]
    },
    {
      "name": "GitHub",
      "level": "Expert",
      "keywords": [
        "SDLC",
        "OSS",
        "CI/CD"
      ]
    },
    {
      "name": "GitLab",
      "level": "Expert",
      "keywords": [
        "SDLC",
        "OSS",
        "CI/CD"
      ]
    },
    {
      "name": "Pipelines",
      "level": "Expert",
      "keywords": [
        "DevOps"
      ]
    },
    {
      "name": "Grafana",
      "level": "Expert",
      "keywords": [
        "DevOps",
        "Observability",
        "Dashboards",
        "SRE",
        "SLO"
      ]
    },
    {
      "name": "Prometheus",
      "level": "Expert",
      "keywords": [
        "DevOps",
        "Observability",
        "Dashboards",
        "SRE",
        "SLO"
      ]
    },
    {
      "name": "Java",
      "level": "Pro",
      "keywords": []
    },
    {
      "name": "Photoshop",
      "level": "Expert",
      "keywords": []
    },
    {
      "name": "Python",
      "level": "Intermediate",
      "keywords": []
    }
  ],
  "languages": [
    {
      "language": "English",
      "fluency": "Native speaker"
    }
  ],
  "interests": [
    {
      "name": "Cycling",
      "keywords": [
        "MTB",
        "Gravel",
        "Road"
      ]
    },
    {
      "name": "Photography",
      "keywords": [
        "Weddings",
        "Landscape",
        "Senior"
      ]
    }
  ],
  "references": [
    {
      "name": "Caleb Lemoine",
      "reference": "Matt is one of the best engineers I've had the pleasure of working with and knowing personally. He would be a highly valued asset anywhere."
    },
    {
      "name": "Lucas Reardon",
      "reference": "Matt Jarrett's passion for technology is infectious. His brand as a forward-thinking leader and influencer raises the performance of those around him as well as the products and capabilities he touches. If you're building a team, you want Matt. Full stop."
    }
  ]
}
```

### Creating the Structs

Once I was done with my edits I used [this rad JSON to Go struct app](https://mholt.github.io/json-to-go/) created by [Matt Holt](https://matt.life/) to convert my JSON to to Go's equivalent of JSON, the `struct`.

After I made `struct`'s for each section.

```go
type Resume struct {
	Basics       Basics      `json:"basics"`
	Work         []Work      `json:"work"`
	Volunteer    []Volunteer `json:"volunteer"`
	Education    []Education `json:"education"`
	Awards       []string    `json:"awards"`
	Publications []string    `json:"publications"`
	Skills       []Skill     `json:"skills"`
	Languages    []Language  `json:"languages"`
	Interests    []Interest  `json:"interests"`
	References   []Reference `json:"references"`
}

type Basics struct {
	Name     string    `json:"name"`
	Label    string    `json:"label"`
	Picture  string    `json:"picture"`
	Email    string    `json:"email"`
	Phone    string    `json:"phone"`
	Website  string    `json:"website"`
	Summary  string    `json:"summary"`
	Location Location  `json:"location"`
	Profiles []Profile `json:"profiles"`
}

type Location struct {
	Address     string `json:"address"`
	PostalCode  string `json:"postalCode"`
	City        string `json:"city"`
	CountryCode string `json:"countryCode"`
	Region      string `json:"region"`
}

type Profile struct {
	Network  string `json:"network"`
	Username string `json:"username"`
	URL      string `json:"url"`
}

type Work struct {
	Company    string   `json:"company"`
	Position   string   `json:"position"`
	Website    string   `json:"website"`
	StartDate  string   `json:"startDate"`
	EndDate    string   `json:"endDate"`
	Summary    string   `json:"summary"`
	Highlights []string `json:"highlights"`
}

type Volunteer struct {
	Organization string   `json:"organization"`
	Position     string   `json:"position"`
	Website      string   `json:"website"`
	StartDate    string   `json:"startDate"`
	EndDate      string   `json:"endDate"`
	Summary      string   `json:"summary"`
	Highlights   []string `json:"highlights"`
}

type Education struct {
	Institution string   `json:"institution"`
	Area        string   `json:"area"`
	StudyType   string   `json:"studyType"`
	StartDate   string   `json:"startDate"`
	EndDate     string   `json:"endDate"`
	Gpa         string   `json:"gpa"`
	Courses     []string `json:"courses"`
}

type Skill struct {
	Name     string   `json:"name"`
	Level    string   `json:"level"`
	Keywords []string `json:"keywords"`
}

type Language struct {
	Language string `json:"language"`
	Fluency  string `json:"fluency"`
}

type Interest struct {
	Name     string   `json:"name"`
	Keywords []string `json:"keywords"`
}

type Reference struct {
	Name      string `json:"name"`
	Reference string `json:"reference"`
}
```

Next I made a `func` that filled the Structs

```go
func getResume() *Resume {
	return &Resume{
		Basics: Basics{
			Name:    "Matt Jarrett",
			Label:   "Full Stack Developer",
			Picture: "https://mattjarrett.dev/img/header.jpg",
			Email:   "",
			Phone:   "",
			Website: "https://mattjarrett.dev/",
			Summary: "Dad. Husband. Full Stack Developer. JavaScript Enthusiast. Aspiring Designer. Cyclist. I'm a passionate and detail oriented full stack software developer based in Dallas, Texas. Experienced leader where I enjoy design, clean code, and building positive team environments and strong developer experiences. I enjoy turning problems into solutions that make people smile. I enjoy continuous learning and exploring new technologies. When I'm not coding at home or work, I enjoy time with my family and photography.",
			Location: Location{
				Address:     "",
				PostalCode:  "",
				City:        "Dallas",
				CountryCode: "US",
				Region:      "Texas",
			},
			Profiles: []Profile{
				{
					Network:  "Linkedin",
					Username: "Matt Jarrett",
					URL:      "https://www.linkedin.com/in/matt-jarrett-303a75144/",
				}, {
					Network:  "GitHub",
					Username: "cujarrett",
					URL:      "https://github.com/cujarrett",
				}, {
					Network:  "Twitter",
					Username: "cujarrett",
					URL:      "https://twitter.com/home",
				},
			},
		},
		Work: []Work{
			{
				Company:   "State Farm",
				Position:  "Technology Engineer",
				Website:   "https://www.statefarm.com",
				StartDate: "2011-11-01",
				EndDate:   "N/A",
				Summary:   "State Farm is the largest property and casualty insurance provider in the United States. It is also the largest auto insurance provider in the United States. State Farm is ranked 36th in the 2019 Fortune 500, which lists American companies by revenue.",
				Highlights: []string{
					"Advancing Cloud Native and DevOps transformations in the telematics platform space. Working with an exciting mix of technology including public cloud, mobile, and legacy systems. Working daily on not only technology but also culture.",
					"Built new solutions using React, Node, NOSQL, REST, Kubernetes, GitLab, CI/CD, Grafana, and Prometheus for enablement of large high priority enterprise initiatives. Mentored several team members in modern technology. Designed solutions for implementation. Taught classes on modern JavaScript.",
					"Designed and implemented many successful web services allowing developers on demand access to safe fabricated data for a variety of needs and automation. It's since been used millions of times across the enterprise, enabling development teams to focus on improving customer experience.",
					"Designed and implemented an automated infrastructure solution offering a complete stand up and tear down process accomplished in minutes compared to days.",
					"Taught Java in Enterprise Java classes to spread knowledge. Active member of the college recruitment team including creation of campus events, hacks, and interviews. Mentored many college interns through a successful internships resulting in all receiving and accepting full time positions. Designed and implemented multiple solutions in Java."},
			},
		},
		Volunteer: []Volunteer{
			{
				Organization: "Tech Titans",
				Position:     "Speaker",
				Website:      "https://techtitans.org/",
				StartDate:    "2017-01-01",
				EndDate:      "N/A",
				Summary:      "Tech Titans is a forum that leverages the regional technology community to collaborate, share and inspire creative thinking that fuels tomorrow’s innovations.",
				Highlights:   []string{"I Spoke at many events over the years with audiences including teachers and students."},
			},
		},
		Education: []Education{
			{
				Institution: "Illinois State University",
				Area:        "Information Technology",
				StudyType:   "Bachelor",
				StartDate:   "2009-06-01",
				EndDate:     "2011-01-01",
				Gpa:         "3.87/4.0",
				Courses:     []string{},
			},
		},
		Awards:       []string{},
		Publications: []string{},
		Skills: []Skill{
			{
				Name:  "AWS",
				Level: "Intermediate",
				Keywords: []string{
					"Cloud", "Lambda", "S3", "CloudFront",
				},
			}, {
				Name:  "JavaScript",
				Level: "Expert",
				Keywords: []string{
					"ECMAScript", "ES6", "Node.js", "Web", "Front End",
				},
			}, {
				Name:  "React",
				Level: "Pro",
				Keywords: []string{
					"SPA", "Web", "Front End",
				},
			}, {
				Name:  "Vue",
				Level: "Intermediate",
				Keywords: []string{
					"SPA", "Web", "Front End",
				},
			}, {
				Name:  "Angular",
				Level: "Intermediate",
				Keywords: []string{
					"SPA", "Web", "Front End",
				},
			}, {
				Name:  "Go",
				Level: "Intermediate",
				Keywords: []string{
					"Golang",
				},
			}, {
				Name:  "Kubernetes",
				Level: "Pro",
				Keywords: []string{
					"K8s", "CKAD",
				},
			}, {
				Name:  "Docker",
				Level: "Intermediate",
				Keywords: []string{
					"Containers",
				},
			}, {
				Name:  "Design",
				Level: "Intermediate",
				Keywords: []string{
					"Web", "Front End",
				},
			}, {
				Name:  "Linux",
				Level: "Intermediate",
				Keywords: []string{
					"Bash", "Shell",
				},
			}, {
				Name:  "NOSQL",
				Level: "Pro",
				Keywords: []string{
					"Couch", "Dynamo",
				},
			}, {
				Name:  "SQL",
				Level: "Pro",
				Keywords: []string{
					"PostgreSQL", "Aurora",
				},
			}, {
				Name:  "GitHub",
				Level: "Expert",
				Keywords: []string{
					"SDLC", "OSS", "CI/CD",
				},
			}, {
				Name:  "GitLab",
				Level: "Expert",
				Keywords: []string{
					"SDLC", "OSS", "CI/CD",
				},
			}, {
				Name:  "Pipelines",
				Level: "Expert",
				Keywords: []string{
					"DevOps",
				},
			}, {
				Name:  "Grafana",
				Level: "Expert",
				Keywords: []string{
					"DevOps", "Observability", "Dashboards", "SRE", "SLO",
				},
			}, {
				Name:  "Prometheus",
				Level: "Expert",
				Keywords: []string{
					"DevOps", "Observability", "Dashboards", "SRE", "SLO",
				},
			}, {
				Name:     "Java",
				Level:    "Pro",
				Keywords: []string{},
			}, {
				Name:     "Photoshop",
				Level:    "Expert",
				Keywords: []string{},
			}, {
				Name:     "Python",
				Level:    "Intermediate",
				Keywords: []string{},
			},
		},
		Languages: []Language{
			{
				Language: "English",
				Fluency:  "Native speaker",
			},
		},
		Interests: []Interest{
			{
				Name: "Cycling",
				Keywords: []string{
					"MTB", "Gravel", "Road",
				},
			}, {
				Name: "Photography",
				Keywords: []string{
					"Weddings", "Landscape", "Senior",
				},
			},
		},
		References: []Reference{
			{
				Name:      "Caleb Lemoine",
				Reference: "Matt is one of the best engineers I've had the pleasure of working with and knowing personally. He would be a highly valued asset anywhere.",
			},
			{
				Name:      "Lucas Reardon",
				Reference: "Matt Jarrett's passion for technology is infectious. His brand as a forward-thinking leader and influencer raises the performance of those around him as well as the products and capabilities he touches. If you're building a team, you want Matt. Full stop.",
			},
		},
	}
}
```

### Formatting the response JSON

I wanted to return a formatted JSON for readability. I wrote this `func` to do so.

```go
func (input Resume) formatResume() string {
	bytesBuffer := new(bytes.Buffer)
	json.NewEncoder(bytesBuffer).Encode(&input)

	responseBytes := bytesBuffer.Bytes()

	var prettyJSON bytes.Buffer
	error := json.Indent(&prettyJSON, responseBytes, "", "  ")
	if error != nil {
		log.Println("JSON parse error: ", error)
	}
	formattedResume := string(prettyJSON.Bytes())
	return formattedResume
}
```

### Serving the API
I used AWS's API Gateway to handle requests via Lambda with this `func`.

```go
func handleRequest(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
	resume := getResume()
	formattedResume := resume.formatResume()

	response := events.APIGatewayProxyResponse{
		StatusCode: http.StatusOK,
		Headers:    map[string]string{"Content-Type": "application/json"},
		Body:       formattedResume,
	}
	return response, nil
}
```

### Main
I set up my main to handle requests by calling `handleRequest`.

```go
func main() {
	lambda.Start(handleRequest)
}
```

## Provisioning the Infrastructure with Terraform

I used [Terraform](https://www.terraform.io/) for my [Infrastructure as Code (IaC)](https://en.wikipedia.org/wiki/Infrastructure_as_code). I'm using [AWS Lambda](https://aws.amazon.com/lambda/) for compute and serving that compute via [AWS API Gateway](https://docs.aws.amazon.com/apigateway/index.html).

As I journey into the cloud and Terraform use I sometimes find the line blurred between provisioning infrastructure vs applications. My mind prefers separation between provisioning my infrastructure, either on demand or in response to various events and my CI/CD pipeline for running continuous integration and continuous deployments. To accomplish this for my `resume-api`, I had to work around AWS Lambda wanting the compute code at time of creation. I choose to handle this by using a placeholder at infrastructure provisioning and then using a [Circle CI] (https://circleci.com/) CI/CD pipeline for building and updating the Lambda with the actual code as changes occur with pull requests from GitHub.

I am using [Terraform Cloud](https://www.terraform.io/docs/cloud/overview.html) to provision my infrastructure, either on demand or in response to various events. After setting up my Terraform Cloud project workspace I updated my `resume-api` workspace to have all applies be done manually so I still get automated plans on each change, but infrastructure is only updated when I manually click `apply` in Terraform Cloud.

![Setting Terraform Cloud to manual apply](https://dev-to-uploads.s3.amazonaws.com/i/5eymiduo5zptfkzm1hm1.png)

This is what the Terraform Cloud runs look like.
![Terraform Cloud](https://dev-to-uploads.s3.amazonaws.com/i/10eqemafxniev2otqlsz.png)

```terraform
variable "aws_region" {
  description = "AWS region for the infrastructure"
  type = string
  default = "us-east-1"
}

data "archive_file" "placeholder" {
  type = "zip"
  output_path = "${path.module}/lambda-function-payload.zip"

  source {
    content = "placeholder"
    filename = "placeholder.txt"
  }
}

provider "aws" {
  region = var.aws_region
}

# Define a Lambda function.
#
# The handler is the name of the executable for go1.x runtime.
resource "aws_lambda_function" "resume-api" {
  filename = data.archive_file.placeholder.output_path
  function_name = "resume-api"
  handler       = "resume-api"
  role          = aws_iam_role.resume-api.arn
  runtime       = "go1.x"
  memory_size   = 128
  timeout       = 1
}

# A Lambda function may access to other AWS resources such as S3 bucket. So an
# IAM role needs to be defined. This example does not access to any resource,
# so the role is empty.
#
# The date 2012-10-17 is just the version of the policy language used here [1].
#
# [1]: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_version.html
resource "aws_iam_role" "resume-api" {
  name               = "resume-api"
  assume_role_policy = <<POLICY
{
  "Version": "2012-10-17",
  "Statement": {
    "Action": "sts:AssumeRole",
    "Principal": {
      "Service": "lambda.amazonaws.com"
    },
    "Effect": "Allow"
  }
}
POLICY
}

# Allow API gateway to invoke the resume-api Lambda function.
resource "aws_lambda_permission" "resume-api" {
  statement_id  = "AllowAPIGatewayInvoke"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.resume-api.arn
  principal     = "apigateway.amazonaws.com"
}

# A Lambda function is not a usual public REST API. We need to use AWS API
# Gateway to map a Lambda function to an HTTP endpoint.
resource "aws_api_gateway_resource" "resume-api" {
  rest_api_id = aws_api_gateway_rest_api.resume-api.id
  parent_id   = aws_api_gateway_rest_api.resume-api.root_resource_id
  path_part   = "resume-api"
}

resource "aws_api_gateway_rest_api" "resume-api" {
  name = "resume-api"
}

#           GET
# Internet -----> API Gateway
resource "aws_api_gateway_method" "resume-api" {
  rest_api_id   = aws_api_gateway_rest_api.resume-api.id
  resource_id   = aws_api_gateway_resource.resume-api.id
  http_method   = "GET"
  authorization = "NONE"
}

#              POST
# API Gateway ------> Lambda
# For Lambda the method is always POST and the type is always AWS_PROXY.
#
# The date 2015-03-31 in the URI is just the version of AWS Lambda.
resource "aws_api_gateway_integration" "resume-api" {
  rest_api_id             = aws_api_gateway_rest_api.resume-api.id
  resource_id             = aws_api_gateway_resource.resume-api.id
  http_method             = aws_api_gateway_method.resume-api.http_method
  integration_http_method = "POST"
  type                    = "AWS_PROXY"
  uri                     = "arn:aws:apigateway:${var.aws_region}:lambda:path/2015-03-31/functions/${aws_lambda_function.resume-api.arn}/invocations"
}

# This resource defines the URL of the API Gateway.
resource "aws_api_gateway_deployment" "resume-api_v1" {
  depends_on = [
    aws_api_gateway_integration.resume-api
  ]
  rest_api_id = aws_api_gateway_rest_api.resume-api.id
  stage_name  = "v1"
}

# Set the generated URL as an output. Run `terraform output url` to get this.
output "url" {
  value = "${aws_api_gateway_deployment.resume-api_v1.invoke_url}${aws_api_gateway_resource.resume-api.path}"
}
```

### Makefile

I made a `Makefile` for running project related tasks.

```makefile
clean:
	go clean

install:
	go get -v -t -d ./...

test:
	go test -v ./...

compile:
	GOOS=linux go build -o resume-api main.go
```

### CI/CD With Circle CI
After I've provisioned the infrastructure I am free to update the Lambda code with a Circle CI/CD pipeline after automated quality tests pass.

```yml
# Golang CircleCI 2.0 configuration file
# Check https://circleci.com/docs/2.0/language-go/ for more details
version: 2
jobs:

  test:
    docker:
      - image: circleci/golang:1.14
    working_directory: /go/src/github.com/cujarrett/resume-api
    steps:
      - checkout
      - run:
          name: Install dependencies
          command: make install
      - run:
          name: Run tests
          command:  make test

  build:
    docker:
      - image: circleci/golang:1.14
    working_directory: ~/temp
    steps:
      - checkout
      - run:
          name: Install dependencies
          command: make install
      - run:
          name: Build executable
          command: make compile
      - run:
          name: Make temp build folder
          command: mkdir build
      - run:
          name: Zip executable
          command: zip build/resume-api.zip -q resume-api
      - persist_to_workspace:
          root: .
          paths:
            - build/resume-api.zip

  deploy:
    docker:
      - image: 'circleci/python:3.7.6'
    working_directory: ~/temp
    steps:
      - attach_workspace:
          at: ~/temp
      - run:
          name: Install AWS CLI
          command: sudo pip install awscli
      - deploy:
          name: Deploy to AWS S3
          command: |
            aws lambda update-function-code \
              --function-name=resume-api \
              --zip-file=fileb://build/resume-api.zip 1> /dev/null \
              --region=us-east-1

workflows:
  version: 2
  cicd:
    jobs:
      - test
      - build:
          requires:
            - test
      - deploy:
          requires:
            - build
          filters:
            branches:
              only: master
```

Now every branch's pull request triggers a `terraform plan` and all quality tests as well as deploying the app out to AWS when it is merged.

## Conclusion
I had fun. I learned a little more Go, Terraform, and Terraform Cloud than I knew before. If I made any mistakes I can learn from I'm happy to learn of those

### Links

- [Spacelift’s Infrastructure as Code : Best Practices, Benefits & Examples](https://spacelift.io/blog/infrastructure-as-code)