Skip to content

Commit

Permalink
feat(image): add support for cache-from build flag
Browse files Browse the repository at this point in the history
includes support for the build flags cache-from and cache-to

fixes: #35
  • Loading branch information
esatterwhite committed Jul 11, 2023
1 parent dc9c577 commit f2c566f
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 28 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ module.exports = {
]
, extends: 'codedependant'
, parserOptions: {
ecmaVersion: 2020
ecmaVersion: 2022
, type: 'script'
}
, rules: {
Expand Down
80 changes: 65 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,22 @@ omitted, it is assumed the docker daemon is already authenticated with the targe

### Options

| Option | Description | Type | Default |
|-------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|---------------------------------------------------------------|
| `dockerTags` | _Optional_. An array of strings allowing to specify additional tags to apply to the image. Supports templating | [Array][]<[String][]> | [`latest`, `{{major}}-latest`, `{{version}}`] |
| `dockerImage` | _Optional_. The name of the image to release. | [String][] | Parsed from package.json `name` property |
| `dockerRegistry` | _Optional_. The hostname and port used by the the registry in format `hostname[:port]`. Omit the port if the registry uses the default port | [String][] | `null` (dockerhub) |
| `dockerProject` | _Optional_. The project or repository name to publish the image to | [String][] | For scoped packages, the scope will be used, otherwise `null` |
| `dockerFile` | _Optional_. The path, relative to `$PWD` to a Docker file to build the target image with | [String][] | `Dockerfile` |
| `dockerContext` | _Optional_. A path, relative to `$PWD` to use as the build context A | [String][] | `.` |
| `dockerLogin` | _Optional_. Set to false it by pass docker login if the docker daemon is already authorized | [String][] | `true` |
| `dockerArgs` | _Optional_. Include additional values for docker's `build-arg`. Supports templating | [Object][] | |
| `dockerPublish` | _Optional_. Automatically push image tags during the publish phase. | [Boolean][] | `true` |
| `dockerVerifyCmd` | _Optional_. If specified, during the verify stage, the specified command will execute in a container of the build image. If the command errors, the release will fail. | [String][] | `false` |
| `dockerNetwork` | _Optional_. Specify the Docker network to use while the image is building. | [String][] | `default` |
| `dockerAutoClean` | _Optional_. If set to true | [Boolean][] | `true` |
| Option | Description | Type | Default |
|------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------|---------------------------------------------------------------|
| `dockerTags` | _Optional_. An array of strings allowing to specify additional tags to apply to the image. Supports templating | [Array][]<[String][]> | [`latest`, `{{major}}-latest`, `{{version}}`] |
| `dockerImage` | _Optional_. The name of the image to release. | [String][] | Parsed from package.json `name` property |
| `dockerRegistry` | _Optional_. The hostname and port used by the the registry in format `hostname[:port]`. Omit the port if the registry uses the default port | [String][] | `null` (dockerhub) |
| `dockerProject` | _Optional_. The project or repository name to publish the image to | [String][] | For scoped packages, the scope will be used, otherwise `null` |
| `dockerFile` | _Optional_. The path, relative to `$PWD` to a Docker file to build the target image with | [String][] | `Dockerfile` |
| `dockerContext` | _Optional_. A path, relative to `$PWD` to use as the build context A | [String][] | `.` |
| `dockerLogin` | _Optional_. Set to false it by pass docker login if the docker daemon is already authorized | [String][] | `true` |
| `dockerArgs` | _Optional_. Include additional values for docker's `build-arg`. Supports templating | [Object][] | |
| `dockerPublish` | _Optional_. Automatically push image tags during the publish phase. | [Boolean][] | `true` |
| `dockerVerifyCmd` | _Optional_. If specified, during the verify stage, the specified command will execute in a container of the build image. If the command errors, the release will fail. | [String][] | `false` |
| `dockerNetwork` | _Optional_. Specify the Docker network to use while the image is building. | [String][] | `default` |
| `dockerAutoClean` | _Optional_. If set to true | [Boolean][] | `true` |
| `dockerBuildFlags` | _Optional_. An object containing additional flags to the `docker build` command. Values can be strings or an array of strings | [Object][] | `{}` |
| `dockerBuildCacheFrom` | _Optional_. A list of external cache sources. See [--cache-from][] | [String][] | [Array][]<[String][]> | |

### Build Arguments

Expand Down Expand Up @@ -117,7 +119,50 @@ The following handlebars template helpers are pre installed
| `startswith` | returns true if a string starts with another | [Boolean][] | <pre lang="hbs">{{#if (starts myvar 'foo')}}{{ othervar }}{{/if}}</pre> |
| `upper` | returns the upper cased varient of the input string | [String][] | <pre lang="hbs">{{upper my_var}}</pre> |

### Build Flags

Using the `dockerBuildFlags` option allows you to pass arbitrary flags to the build command.
If the standardized options are not sufficient, `dockerBuildFlags` is a perfect workaround
until first class support can be added. This is considered and advanced feature, and you should
know what you intend to do before using. There is no validation, and any configuration of the
docker daemon required is expected to be done before hand.

Keys found in `dockerBuildFlags` are normalized as command line flags in the following manner:

* If the key does not start with a `-` it will be prepended
* all occurences of `_` will be re-written as `-`
* Single letter keys are considered shorthands e.g. `p` becomes `-p`
* Multi letter keys are considered long form e.g. `foo_bar` becomes `--foo-bar`
* If the value is an array, the flag is repeated for each occurance
* A `null` value may be used to omit the value and only inject the flag itself

#### Example

```javascript
{
plugins: [
['@codedependant/semantic-release-docker', {
dockerImage: 'my-image',
dockerRegistry: 'quay.io',
dockerProject: 'codedependant',
dockerCacheFrom: 'myname/myapp'
dockerBuildFlags: {
pull: null
, target: 'release'
},
dockerArgs: {
GITHUB_TOKEN: null
}
}]
]
}
```

This configuration, will generate the following build command

```bash
> docker build --network=default --quiet --tag quay.io/codedependant/my-image:abc123 --cache-from myname/myapp --build-arg GITHUB_TOKEN --pull --target release -f path/to/repo/Dockerfile /path/to/repo
```

## Usage

Expand All @@ -135,8 +180,12 @@ module.exports = {
dockerFile: 'Dockerfile',
dockerRegistry: 'quay.io',
dockerProject: 'codedependant',
dockerBuildFlags: {
pull: null
, target: 'release'
},
dockerArgs: {
API_TOKEN: true
API_TOKEN: null
, RELEASE_DATE: new Date().toISOString()
, RELEASE_VERSION: '{{next.version}}'
}
Expand Down Expand Up @@ -233,3 +282,4 @@ $ openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout server.key -o
[Array]: https://mdn.io/array
[Object]: https://mdn.io/object
[Number]: https://mdn.io/number
[--cache-from]: https://docs.docker.com/engine/reference/commandline/build/#cache-from
4 changes: 4 additions & 0 deletions lib/build-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ async function buildConfig(build_id, config, context) {
, dockerTags: tags = ['latest', '{{major}}-latest', '{{version}}']
, dockerArgs: args = {}
, dockerBuildFlags: build_flags = {}
, dockerBuildCacheFrom: cache_from
, dockerRegistry: registry = null
, dockerLogin: login = true
, dockerImage: image
Expand All @@ -41,6 +42,9 @@ async function buildConfig(build_id, config, context) {
const root = object.get(context, 'options.root')
const target = path.relative(root || context.cwd, context.cwd) || PWD
const {nextRelease = {}} = context

if (cache_from) build_flags.cache_from = array.toArray(cache_from)

return {
registry
, dockerfile
Expand Down
32 changes: 23 additions & 9 deletions lib/docker/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ class Image {
this.sha = sha
this.opts = {
build_id: build_id
, args: new Map()
, context: context
, cwd: cwd
, dockerfile: dockerfile
, flags: []
, flags: new Map()
, name: name
, network: network
, project: project
Expand Down Expand Up @@ -75,16 +76,22 @@ class Image {
get flags() {
const output = []

for (const [key, value] of this.opts.flags) {
for (const [key, value] of this.opts.flags.entries()) {
let normalized = key
if (!key.startsWith('-')) {
normalized = (key.length === 1 ? `-${key}` : `--${key}`)
.toLowerCase()
.replace(/_/g, '-')
}

output.push(normalized)
if (value !== null) output.push(value)
if (value === null) {
output.push(normalized)
continue
}

for (const item of value) {
output.push(normalized, item)
}
}

return output
Expand All @@ -96,18 +103,25 @@ class Image {
} else {
this.flag('build-arg', `${key}=${val}`)
}
this.opts.args.set(key, val)
return this
}

flag(key, val) {
if (Array.isArray(val)) {
for (const item of val) {
this.opts.flags.push([key, item])
}
if (val === null) {
this.opts.flags.set(key, val)
return this
}

this.opts.flags.push([key, val])
let value = this.opts.flags.get(key) || []

if (Array.isArray(val)) {
value = value.concat(val)
} else {
value.push(val)
}

this.opts.flags.set(key, value)
return this
}

Expand Down
12 changes: 11 additions & 1 deletion lib/prepare.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,22 @@ async function dockerPrepare(opts, context) {
})

const vars = buildTemplateVars(opts, context)

function render(item, vars) {
if (Array.isArray(item)) {
return item.map((element) => {
return string.template(element)(vars)
})
}
return string.template(item)(vars)
}

for (const [key, value] of Object.entries(opts.args)) {
image.arg(key, string.template(value)(vars))
}

for (const [key, value] of Object.entries(opts.build_flags)) {
image.flag(key, string.template(value)(vars))
image.flag(key, render(value, vars))
}

context.logger.info('building image', image.name)
Expand Down
7 changes: 7 additions & 0 deletions test/integration/prepare.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@ test('steps::prepare', async (t) => {
}
}


const config = await buildConfig(build_id, {
dockerRegistry: DOCKER_REGISTRY_HOST
, dockerProject: 'docker-prepare'
, dockerImage: 'fake'
, dockerVerifyCmd: ['date', '+\'%x\'']
, dockerBuildCacheFrom: 'test'
, dockerArgs: {
MY_VARIABLE: '1'
, TAG_TEMPLATE: '{{git_tag}}'
Expand Down Expand Up @@ -70,6 +72,11 @@ test('steps::prepare', async (t) => {
tt.equal(image.opts.args.get('MAJOR_TEMPLATE'), '2', 'MAJOR_TEMPLATE value')
tt.equal(image.opts.args.get('GIT_REF'), 'abacadaba', 'GIT_REF value')
tt.match(image.opts.args.get('BUILD_DATE'), DATE_REGEX, 'BUILD_DATE value')
tt.match(
image.opts.flags.get('TAG_TEMPLATE')
, ['v2.1.2']
, 'TAG_TEMPLATE stored as a flag'
)
tt.equal(image.context, path.join(context.cwd, config.context), 'docker context path')

const {stdout} = await execa('docker', [
Expand Down
6 changes: 4 additions & 2 deletions test/unit/docker/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ test('Image', async (t) => {
, name: 'test'
, dockerfile: 'Dockerfile'
, cwd: process.cwd()
, flags: Array
, flags: Map
, args: Map
}, 'default image options')
})

Expand All @@ -58,7 +59,8 @@ test('Image', async (t) => {
, name: 'test'
, dockerfile: 'Dockerfile.test'
, cwd: path.join(__dirname, 'build')
, flags: Array
, flags: Map
, args: Map
, network: 'custom-network'
}, 'default image options')

Expand Down

0 comments on commit f2c566f

Please sign in to comment.