Background
I've been inspired by Sara Vieira to build more dumb stuff on the Internet to recharge my soul and learn while I'm doing it. I wanted to learn Go this year and after taking Stephen Grider's awesome Go: The Complete Developer's Guide (Golang) 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. I'm using Go for my app, Terraform for my Infrastructure as Code (IaC) with Terraform Cloud to provision my infrastructure, either on demand or in response to various events. I'm deploying the app to AWS Lambda with a Circle CI CI/CD pipeline.
All of the code for this can be found in my GitHub.
Demo
You can curl the REST API to view my resume
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 and AWS if you want to deploy your code.
Creating your Resume in JSON
I copied the JSON-based standard format resume schema that I would edit in my info. After I was done editing that JSON, I ended up with this.
{
"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 created by Matt Holt to convert my JSON to to Go's equivalent of JSON, the struct
.
After I made struct
's for each section.
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
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.
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
.
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
.
func main() {
lambda.Start(handleRequest)
}
Provisioning the Infrastructure with Terraform
I used Terraform for my Infrastructure as Code (IaC). I'm using AWS Lambda for compute and serving that compute via AWS API Gateway.
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 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 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.
This is what the Terraform Cloud runs look like.
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.
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.
# 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
Top comments (5)
Looks great! I started my Go learning with Stephen Grider's course too. I came across an interesting talk about Code generation and Json unmarshalling recently which I think would tie in nicely with this Go API, pasted the link below if you're interested 😊
m.youtube.com/watch?v=3llI65DQB_w
Man, Terraform is great! When I discovered it, I spent the next few days messing with it. There's a lot of potential to save time/money if a dev team decides to actually go ahead and implement it into their workflow
Spot on! The only addition I might suggest is assigning an Alias to the Lambda function. Using an Alias you can upload new code, publish a new version of a function, and then use CodeDeploy to canary the new function with automatic rollback if there are errors. Not essential though, and what’s you’ve built looks awesome!
Why not use LaTex? I use Google docs mostly for my CV, but the export takes forever, so I wrote a UI only shell to create an illusion of work happening.
nishantarora.in/CV
I’ve got my resume on mattjarrett.dev but wanted to build something last week with Go to learn.