Custom Go Vanity URL for Cheap Using Amazon Lambda

go’s CLI tool provides a very powerful way of accessing your code, specifically the subcommand go get. Typical people use go get for it’s resolving and adding dependencies to their package but it can also be used as a proxy for git clone. It can also resolve these dependencies from any domain that returns the right html.

If you have ever worked with kubernetes, etcd or even any package from gopkg.in you’ll know that you can use a custom domain or “vanity url” to load your code from, this comes in handy when you’re unsure of the eventual location of the code or you want to shorten the import path for your code. If you have never looked into go’s import management this mechanism might seem a little strange but it’s relatively easy to reverse engineer.

How go imports work

When you set up an import in a go package or use the go cli to get or install a new package it will first start by issuing an http request to the url, with this request it appends a query param ?go-get=1. When it issues this request it is expecting the server to return back a response with html.meta tags that help to point to where the code actually lives.

Example Server

$ curl https://go.hein.dev/go-version?go-get=1
<html>
        <head>
                <meta name="go-import"
                      content="go.hein.dev/go-version
                   git https://github.com/christopherhein/go-version">
                <meta name="go-source"
                      content="go.hein.dev/go-version
                   https://github.com/christopherhein/go-version
                   https://github.com/christopherhein/go-version/tree/master{/dir}
                   https://github.com/christopherhein/go-version/blob/master{/dir}/{file}#L{line}">
                <meta http-equiv="refresh" content="0; url=https://godoc.org/go.hein.dev/go-version/">
        </head>
        <body>
                Nothing to see here; <a href="https://godoc.org/go.hein.dev/go-version/">see the package on godoc</a>.
        </body>
</html

If you take a look at the above example you will see that in the <html><head> there a two important <meta> tags, namely go-source and go-import those take the import path and tell the go cli tooling where to load to get the code from. Notice this returns https://github.com/christopherhein/go-version

If you make any changes to the URL you’ll also notice that parts of the document end up being slightly dynamic. For example if you added cmd to the URL path you can see it changes the go-import and go-source paths.

$ curl https://go.hein.dev/go-version/cmd?go-get=1
<html>
        <head>
                <meta name="go-import"
                      content="go.hein.dev/go-version/cmd
                   git https://github.com/christopherhein/go-version/cmd">
                <meta name="go-source"
                      content="go.hein.dev/go-version/cmd
                   https://github.com/christopherhein/go-version/cmd
                   https://github.com/christopherhein/go-version/cmd/tree/master{/dir}
                   https://github.com/christopherhein/go-version/cmd/blob/master{/dir}/{file}#L{line}">
                <meta http-equiv="refresh" content="0; url=https://godoc.org/go.hein.dev/go-version/cmd/">
        </head>
        <body>
                Nothing to see here; <a href="https://godoc.org/go.hein.dev/go-version/cmd/">see the package on godoc</a>.
        </body>
</html>

This makes using a static hosting likg Amazon S3 somewhat difficult. So looking around I found some example of how to do this using Google App Engine. Given I like to tinker and have been using AWS for almost all my projects for the last couple years I wanted to replicate this using Amazon Lambda and API Gateway.

Making this cheap and easy

Lambda will give you 1,000,000 requests per month for free, this mean we can build this relatively quick, and unless you have a very popular project, it should be nearly free. To get up and running I started the project using Serverless Application Model (SAM) and built my own webserver to host to serve the html page.

Deploying your own

First you can start by cloning the go-path-router project to you’re local machine.

git clone git@github.com:christopherhein/go-path-router.git

Once you have the project cloned you need to set a handful of environment variables. These are used by the Makefile to configure your own vanity url router.

export S3_BUCKET=chrishein-website-assets
export STACK_NAME=hein-dev-code
# Domain you want to act as your proxy
export DOMAIN=go.hein.dev
export AWS_REGION=us-east-1
# For API Gateway you want to provision your cert in us-east-1 even if your deploy in another region.
export ACM_CERT=arn:aws:acm:us-east-1:XXXXXXXXXXXX:certificate/2de3a8a4-b1c8-11e9-98b9-2b84193dfe73
# Code Path represents the Github organization to proxy to
export CODE_PATH=github.com/christopherhein
# Route 53 hosted zone, make sure to include a period at the end.
export HOSTED_ZONE=hein.dev.

Once you have set these environment variables you can deploy the go server to Lambda and API Gateway via AWS CloudFormation. To do this we can use the make target to build_and_deploy.

$ make build_and_deploy 
GOOS=linux GOARCH=amd64 go build -o go-path-router/go-path-router ./go-path-router
sam package --output-template-file packaged.yaml --s3-bucket chrishein-website-assets
Uploading to 60c22d6e360750a9d0c72289eec843c6  4893297 / 4893297.0  (100.00%)
Successfully packaged artifacts and wrote output template to file packaged.yaml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file /Users/user/Code/src/go.hein.dev/go-path-router/packaged.yaml --stack-name <YOUR STACK NAME>
sam deploy --template-file packaged.yaml --stack-name hein-dev-code --capabilities CAPABILITY_IAM --region us-west-2 --parameter-overrides CertificateArn=arn:aws:acm:us-east-1:XXXXXXXXXXXX:certificate/2de3a8a4-b1c8-11e9-98b9-2b84193dfe73 CodePath=github.com/christopherhein Domain= HostedZoneName=hein.dev.

Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - hein-dev-code

Once this is done you should be able to route to your domain and see the html page that will tell go how to find your own go packages from a shorter custom vanity domain.

Hopefully you find this useful for your own projects. I’m started to use this for all my golang packages making it shorter and easier to install. So if you use my go-version project that will allow you to setup version subcommands for your cobra projects as I explained in Go Version for Cobra Projects you’ll be able import version "go.hein.dev/go-version" and that’s all you need.

Want to read more from me? Browse my blogs here or reach out @christopherhein.


See also

comments powered by Disqus