Codegolf: Build a container in Cloud Build

Katie McLaughlin - Mar 27 - - Dev Community

Banner Photo by Viktor Kiryanov on Unsplash

I read Adam Ross's Modernizing cloudbuild.yaml for Container Builds and saw all his mentions of "character counts" and it got me thinking:

How codegolf can we get with this?


Context: like regular golf, codegolf asks you to implement something in the least amount of character possible.

Starting with Adam's finishing iteration:

steps:
- id: 'Build Container Image'
  name: 'gcr.io/cloud-builders/docker:latest'
  script: docker build . --tag "${_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${_REPO}/${_IMAGE}:latest"

images:
- "${_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${_REPO}/${_IMAGE}:latest"

options:
  automapSubstitutions: true

substitutions:
  _IMAGE: service
  _LOCATION: us
  _REPO: container
Enter fullscreen mode Exit fullscreen mode

Initial character count: 358

It appears his character count doesn't inline new lines, which is effectively the same as the wc -c minus the wc -l. I'll be using the following for my character counts:
expr $(wc -c cloudbuild.yaml | cut -d' ' -f1) - $(wc -l cloudbuild.yaml | cut -d ' ' -f1)

He notes that the introduction of Artifact Registry make this a bit longer than it would be in Container Registry, but we'll sit with that since it's the supported registry.

We'll also keep with this last iteration's requirements: a project might have more than one registry, so we'll keep some parameterization.

Each iteration I'll make sure is a valid build. For testing, I'll ensure gcloud builds submit runs clean in a folder containing just this cloudbuild.yaml file and my go-to codegolf Dockerfile (that's also a valid Cloud Run service, just for fun).

What's in an ID?

To start, Cloud Build steps don't require an ID, so we can remove that line entirely. Without an ID, the Cloud Build build details in the Google Cloud console will use the image name as the step details, so there is a slight usability disadvantage to this change, but nothing that affects functionality.

-- id: 'Build Container Image'
-  name: 'gcr.io/cloud-builders/docker:latest'
+- name: 'gcr.io/cloud-builders/docker:latest'
Enter fullscreen mode Exit fullscreen mode

Character count: 329.

Deduplicate with dynamics

We've also got the same image ID used multiple times. We want to keep the dynamic nature of this, but we also want to use substitutions in the value itself. We can use dynamicSubstitutions to allow use of substitutions within other substitutions, but adding this adds ~27 characters to our file (and we're removing 45x2 characters so this is a win!)

steps:
- name: 'gcr.io/cloud-builders/docker:latest'
  script: docker build . --tag "$_IMAGE_URI"

images:
- "$_IMAGE_URI"

options:
  automapSubstitutions: true
  dynamicSubstitutions: true

substitutions:
  _IMAGE_URI: ${_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${_REPO}/${_IMAGE}:latest
  _IMAGE: service
  _LOCATION: us
  _REPO: container
Enter fullscreen mode Exit fullscreen mode

Character count: 326.

At this point we can also remove the superfluous quotation marks, saving 6 characters, bringing us down to 320.

Shortest values

Now we get to the part where we work out: What's the minimum value for the variables we're using?

The $PROJECT_ID is a default substitution, so we should keep that as is, and it will be dynamic for our project.

We're already using the shortest Artifact Registry region, the US multi-region (the longest Google Cloud region could be up to 24 characters, so 2 is great!)

But we can possibly do better with the Artifact Registry name and the Container Name.

d (for "Docker") is a valid Artifact Registry repository name, and i (for "image") are valid, so we can save ~15 characters there:

...
-  _IMAGE: service
+  _IMAGE: i
   _LOCATION: us
-  _REPO: container
+  _REPO: c
Enter fullscreen mode Exit fullscreen mode

Character count: 306.

Minimal variables

Any of our underscore substitutions we can reduce down to two letters, the underscore and some useful character. (We're replacing "Image URL", "Image", "Repo", and "Location", respectively.)

steps:
- name: gcr.io/cloud-builders/docker:latest
  script: docker build . --tag $_U

images:
- $_U

options:
  automapSubstitutions: true
  dynamicSubstitutions: true

substitutions:
  _U: ${_L}-docker.pkg.dev/${PROJECT_ID}/${_R}/${_I}:latest
  _I: i
  _L: us
  _R: d
Enter fullscreen mode Exit fullscreen mode

Character count: 254.

(We're now down below what Adam was before migrating to Artifact Registry! ๐Ÿ˜…)


Remove tags

Another reduction we can make is removing the :latest tags. "Latest" is used in Docker where there is a lack of tag, so this is implicit in our call (and it just so happens that the Cloud Builder we are referencing has a "latest" tag. (You can check this by going to gcr.io/cloud-builders/docker in the browser, which will show you information about this image in its registry. This image in particular will direct you to its Artifact Registry information, because this registry has been automatically redirected.)

steps:
- name: gcr.io/cloud-builders/docker
  script: docker build . --tag $_U

images:
- $_U

options:
  automapSubstitutions: true
  dynamicSubstitutions: true

substitutions:
  _U: ${_L}-docker.pkg.dev/${PROJECT_ID}/${_R}/${_I}
  _I: i
  _L: us
  _R: d
Enter fullscreen mode Exit fullscreen mode

Character count: 240.

Putting the args back

The last step we can do to save space is to actually re-introduce the args parameter, rather than using script. In this case, we will be adding some characters for the separation of the arguments, but the biggest win is being able to remove the ~27 characters that the automapSubstitutions config setting adds.

steps:
- name: gcr.io/cloud-builders/docker
  args: ['build','.','--tag','$_U']

images:
- $_U

options:
  dynamicSubstitutions: true

substitutions:
  _U: ${_L}-docker.pkg.dev/${PROJECT_ID}/${_R}/${_I}
  _I: i
  _L: us
  _R: d
Enter fullscreen mode Exit fullscreen mode

Character count: 213.

Tip: Make sure you check what your default entrypoint is for images! In this case, I have also removed docker, as my args are appended to the default entrypoint. The script needs the full command (I may have spent too long debugging this issue ๐Ÿ˜…)

Twist

Actually you only need 22 characters:

gcloud run deploy a
[ENTER]
[ENTER]
Enter fullscreen mode Exit fullscreen mode

First time you run this you'll need 1 extra character (ENTER) to enable APIs, and also 1 extra character (ENTER) to create the Cloud Run Source Deploy image registry. (You can also use this method to automatically create the Artifact Registry for you, read more about this on my blog post: "Auto-provisioning Artifact Registry though Cloud Run Source Deploys (glasnt.com)").

Summary

You can make a very short Cloud Build configuration to build images, but you should consider the readability of your scripts as you go. Addressing any cruft and modernizing your scripts can be an interesting exercise for you and your infrastructure.

With thanks to Adam Ross for the review!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .