Skip to content
This repository has been archived by the owner on Dec 12, 2022. It is now read-only.

chore: automate Docker build & push #36

Merged
merged 5 commits into from
Feb 4, 2021

Conversation

theneva
Copy link
Contributor

@theneva theneva commented Feb 2, 2021

Hi! 👋

This is a big step towards closing #35.

This introduces some npm scripts to automate Docker-related stuff in the repo: npm run docker:build-default and npm run docker:publish-default.

Using npm run docker:publish-default as an example, the scripts invoke a CLI app (./tools/release.js) that…

  1. gathers relevant information
  2. generates Dockerfiles with the specified base images (12-alpine and 14-alpine by default) & writes them to a temporary directory
  3. builds Docker images for each specified base image, and tags them on the form unleashorg/unleash-server:3.10.1-node14-alpine
  4. tags the image with unleash-server's default Node base image (currently node:12-alpine) as latest and without the -node<version> suffix (so for example, unleashorg/unleash-server:3.10.1)
  5. asks if you want to push the new tags
  6. if approved, pushes the new tags

npm run docker:build-default does the same thing, but stops after step 4.

Here's a quick demo:

unleashdemo

I hope this makes sense! It should be relatively easy to run this code on any kind of CI by doing echo 'y' | npm run publish-default, assuming dev dependencies get installed first.

I'll leave some comments in the code 😄

I don't know how the GitHub Actions and Docker Hub flows work in this project. How should I document this in the readme?

Edit: By the way, I've formatted all the code in ./tools/ using $ npx prettier --single-quote --trailing-comma all --write ./tools

@@ -0,0 +1,2 @@
tools/
node_modules/
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the build process runs npm ci anyways, so there's no real point to copying over 50 MB of node modules on every build

@theneva theneva force-pushed the automate-docker-build branch from 18f9fb4 to 2889f77 Compare February 3, 2021 00:01
@@ -1,5 +1,7 @@
FROM node:12-alpine

ENV NODE_ENV production
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this prevents dev deps from getting installed 😄

README.md Show resolved Hide resolved
"docker:build": "./tools/release.js build",
"docker:build-default": "./tools/release.js build --node-docker-versions 12-alpine,14-alpine --default-node-docker-version 12-alpine",
"docker:publish": "./tools/release.js publish",
"docker:publish-default": "./tools/release.js publish --node-docker-versions 12-alpine,14-alpine --default-node-docker-version 12-alpine"
Copy link
Contributor Author

@theneva theneva Feb 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here are the scripts. the CLI will yell at you if you invoke it would without the proper arguments:

image

"devDependencies": {
"execa": "^5.0.0",
"tempy": "^1.0.0",
"yargs": "^16.2.0"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this introduces some dev dependencies. this functionality is obviously doable without them, but it would require a fair bit more code. is introducing dev deps a problem?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm ok with dev-deps here.


const tempDirPath = tempy.directory();

/** @type {string} */
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IntelliJ yells at me because it thinks this is a Buffer (which is not true, as I pass { encoding: 'utf8' })

const readline = require('readline').createInterface({
input: process.stdin,
output: process.stdout,
});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

readline is used to prompt the user for confirmation about pushing the tags later on

async function main() {
const argv = yargs(hideBin(process.argv))
.option('defaultNodeDockerVersion', {
alias: 'default-node-docker-version',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I strongly dislike having to use --camelCasedNames on the command line, so I figured I might as well allow both camel and kebab case

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really mind either way, now if you started debating spaces vs tabs, we'd have to have a serious conversation :sarcasm:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😄


throw new Error(
`defaultNodeDockerVersion "${defaultNodeDockerVersion}" was not in list of versions "${nodeDockerVersions}"`,
);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started writing code to trigger a build if the defaultNodeDockerVersion wasn't present in the nodeDockerVersions, but it turned into a big mess so I decided to fail early instead

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Failing early seems logical :)

nodeDockerVersions,
defaultNodeDockerVersion,
}) {
const unleashServerVersion = await getUnleashServerVersion();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it doesn't really make sense for a "docker util" do get information about an npm package, but I couldn't find a nice way to pass it in as context without a bunch of duplicated code.

);
const versionLine = npmListOutput.split('\n')[1];
return versionLine.split('@')[1].trim();
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is to include the unleash-server version in the Docker tag. It's not pretty, but I don't know a better way to find the installed version. Do you?

(the .trim() gets rid of a trailing space)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

require('unleash-server/package.json').version

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep that's approximately 78 times better. b6e864c

tools/docker-utils.js Outdated Show resolved Hide resolved

const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');
const readline = require('readline').createInterface({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer prompts over readline, but no biggie of course

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is fine. I tried to keep the dependencies to a minimum, although I guess it doesn't really matter since the jump from zero to one dependency is way greater than the jump from 3 to 4

.demandCommand(1, 'You have to specify a command.')
.strict(true)
.version(false)
.wrap(120)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it defaults to 80 characters which causes some dumb wrapping since the option names are pretty long

);
const versionLine = npmListOutput.split('\n')[1];
return versionLine.split('@')[1].trim();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

require('unleash-server/package.json').version

Copy link
Member

@chriswk chriswk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this, good job, particularly doing it in an additive manner, so the rest of our build process is undisturbed.

Be aware though, for now, the Unleash team is not running the server on node14 for any customers so it does not receive the same amount of production experience as the default (node-12) build does.

@ivarconr
Copy link
Member

ivarconr commented Feb 3, 2021

Be aware though, for now, the Unleash team is not running the server on node14 for any customers so it does not receive the same amount of production experience as the default (node-12) build does.

But we do run all our unit and integration tests on both node-12 and node-14, so should work in theory. Also our OSS demo instance is using node-14.

@theneva
Copy link
Contributor Author

theneva commented Feb 3, 2021

Cool! I'm still unsure about if & how to document the release flow in the readme, and I don't think I'm the right person to configure github actions. I've also not written anything changelog-related.

We already run a Node 14-based image because I had to override the logger configuration, so I guess we're voluntary testers, too 😄

@chriswk
Copy link
Member

chriswk commented Feb 4, 2021

For now, I'd say it's fine running the release flow manually, once we get to automating as much of our release flows as possible, we'll revisit a GHA configuration

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants