Skip to content

Commit

Permalink
feat: use channel or latest as release image tag
Browse files Browse the repository at this point in the history
BREAKING CHANGE: now use default image tag as branch channel name or latest

close #481
  • Loading branch information
lgaticaq committed Mar 22, 2023
1 parent 1ac37d8 commit 8469ec0
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 121 deletions.
24 changes: 13 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,13 @@ The plugin can be configured in the [**semantic-release** configuration file](ht

#### Config

| Variable | Description |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `baseImageName` | Name of the previously constructed docker image. Required. |
| `baseImageTag` | Name of the previously constructed docker image tag. Optional. Default `"latest"` |
| `registries` | Array of [Registry](#registry) objects. Required. Example: {"user": "DOCKER_USER", "password": "DOCKER_PASSWORD", "url": "docker.pkg.github.com", "imageName": "docker.pkg.github.com/myuser/myrepo/myapp"} |
| `additionalTags` | Array of additional tags to push. Optional. Example: `["beta", "next"]` |
| Variable | Description |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `baseImageName` | Name of the previously constructed docker image. Required. |
| `baseImageTag` | Name of the previously constructed docker image tag. Optional. Default `"latest"` |
| `releaseImageTag` | Name of the docker image tag for new. Optional. Default `"latest"` or `channel` if use a custom branches or prerelase |
| `registries` | Array of [Registry](#registry) objects. Required. Example: {"user": "DOCKER_USER", "password": "DOCKER_PASSWORD", "url": "docker.pkg.github.com", "imageName": "docker.pkg.github.com/myuser/myrepo/myapp"} |
| `additionalTags` | Array of additional tags to push. Optional. Example: `["beta", "next"]` |

#### Registry

Expand All @@ -65,11 +66,12 @@ The plugin can be configured in the [**semantic-release** configuration file](ht

Environment variables are variables. Depends of `registries` option.

| Variable | Description |
| ----------------------- | ---------------------------------------------------------------------------------- |
| `DOCKER_USER` | username for docker registry. |
| `DOCKER_PASSWORD` | password for docker registry. |
| `DOCKER_BASE_IMAGE_TAG` | Name of the previously constructed docker image tag. Optional. Default `"latest"`. |
| Variable | Description |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `DOCKER_USER` | username for docker registry. |
| `DOCKER_PASSWORD` | password for docker registry. |
| `DOCKER_BASE_IMAGE_TAG` | Name of the previously constructed docker image tag. Optional. Default `"latest"`. |
| `DOCKER_RELEASE_IMAGE_TAG` | Name of the docker image tag for new release. Optional. Default `"latest"` or `channel` if use a custom branches or prerelase |

### Examples

Expand Down
7 changes: 6 additions & 1 deletion src/prepare.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ module.exports = async (pluginConfig, ctx) => {
const docker = new Dockerode()
const image = docker.getImage(pluginConfig.baseImageName)
const tags = [ctx.nextRelease.version]
const channel = ctx.nextRelease.channel || 'latest'
const releaseImageTag =
ctx.env.DOCKER_RELEASE_IMAGE_TAG ||
pluginConfig.releaseImageTag ||
channel
if (pluginConfig.additionalTags && pluginConfig.additionalTags.length > 0) {
tags.push(...pluginConfig.additionalTags)
}
Expand All @@ -31,7 +36,7 @@ module.exports = async (pluginConfig, ctx) => {
await image.tag({ repo: pluginConfig.baseImageName, tag })
}
for (const { imageName } of pluginConfig.registries) {
for (const tag of [...tags, baseImageTag]) {
for (const tag of [...tags, releaseImageTag]) {
ctx.logger.log(
`Tagging docker image ${pluginConfig.baseImageName}:${baseImageTag} to ${imageName}:${tag}`,
)
Expand Down
9 changes: 6 additions & 3 deletions src/publish.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,12 @@ const pushImage = (response) => {
module.exports = async (pluginConfig, ctx) => {
try {
const docker = new Dockerode()
const baseImageTag =
ctx.env.DOCKER_BASE_IMAGE_TAG || pluginConfig.baseImageTag || 'latest'
const tags = [baseImageTag, ctx.nextRelease.version]
const channel = ctx.nextRelease.channel || 'latest'
const releaseImageTag =
ctx.env.DOCKER_RELEASE_IMAGE_TAG ||
pluginConfig.releaseImageTag ||
channel
const tags = [releaseImageTag, ctx.nextRelease.version]
if (pluginConfig.additionalTags && pluginConfig.additionalTags.length > 0) {
tags.push(...pluginConfig.additionalTags)
}
Expand Down
1 change: 1 addition & 0 deletions src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export interface Config extends SemanticReleaseConfig {
registries?: Registry[]
baseImageName?: string
baseImageTag?: string
releaseImageTag?: string
}

export interface ExecOptions {
Expand Down
57 changes: 22 additions & 35 deletions test/prepare.test.js
Original file line number Diff line number Diff line change
@@ -1,50 +1,25 @@
/* eslint-disable require-jsdoc */
const { describe, it, before, after } = require('mocha')
const { describe, it, before, beforeEach, after } = require('mocha')
const { expect } = require('chai')
const mock = require('mock-require')
const { createContext, createConfig, DockerMock, tags } = require('./shared')

describe('Prepare', () => {
const ctx = {
env: { DOCKER_USER: 'user', DOCKER_PASSWORD: 'password' },
nextRelease: { version: '1.0.0' },
logger: { log: () => ({}), error: () => ({}) },
}
let prepare
/** @type {import('../src/types').Config} */
const pluginConfig = {
baseImageName: 'ci.example.com/myapp',
registries: [
{
user: 'DOCKER_USER',
password: 'DOCKER_PASSWORD',
url: 'registry.example.com',
imageName: 'registry.example.com/error',
},
],
}

before(() => {
class DockerImage {
tag({ repo, tag }) {
return new Promise((resolve, reject) => {
if (/error/.test(repo)) {
return reject(new Error('invalid image'))
}
resolve()
})
}
}
class DockerMock {
getImage(imageName) {
return new DockerImage()
}
}
tags.splice(0, tags.length)
mock('dockerode', DockerMock)
prepare = require('../src/prepare')
})

beforeEach(() => {
tags.splice(0, tags.length)
})

it('expect a EDOCKERIMAGETAG error', async () => {
try {
const pluginConfig = createConfig()
const ctx = createContext()
await prepare(pluginConfig, ctx)
} catch (errs) {
const err = errs._errors[0]
Expand All @@ -54,13 +29,25 @@ describe('Prepare', () => {
})

it('expect success prepare', async () => {
const pluginConfig = createConfig()
const ctx = createContext()
pluginConfig.registries[0].imageName = 'registry.example.com/myapp'
pluginConfig.additionalTags = ['beta']
expect(await prepare(pluginConfig, ctx)).to.be.a('undefined')
expect(tags).include('beta')
expect(tags).include('latest')
})

it('prepare with custom branch', async () => {
const pluginConfig = createConfig()
const ctx = createContext()
pluginConfig.registries[0].imageName = 'registry.example.com/myapp'
ctx.nextRelease.channel = 'staging'
expect(await prepare(pluginConfig, ctx)).to.be.a('undefined')
expect(tags).include('staging')
})

after(() => {
mock.stopAll()
})
})
/* eslint-enable require-jsdoc */
85 changes: 24 additions & 61 deletions test/publish.test.js
Original file line number Diff line number Diff line change
@@ -1,68 +1,25 @@
/* eslint-disable require-jsdoc */
const EventEmitter = require('events')
const { describe, it, before, after } = require('mocha')
/* eslint-disable sonarjs/no-duplicate-string */
const { describe, it, before, beforeEach, after } = require('mocha')
const { expect } = require('chai')
const mock = require('mock-require')
const { createContext, createConfig, DockerMock, tags } = require('./shared')

describe('Publish', () => {
// @ts-ignore
class MyEmitter extends EventEmitter {}
const ctx = {
env: { DOCKER_USER: 'user', DOCKER_PASSWORD: 'password' },
nextRelease: { version: '1.0.0' },
logger: { log: () => ({}), error: () => ({}) },
}
let publish
/** @type {import('../src/types').Config} */
const pluginConfig = {
baseImageName: 'ci.example.com/myapp',
registries: [
{
user: 'DOCKER_USER',
password: 'DOCKER_PASSWORD',
url: 'registry.error.com',
imageName: 'registry.example.com/myapp',
},
],
}
let dockerPushArgs

before(() => {
class DockerImage {
push({ tag, password, serveraddress, username }) {
dockerPushArgs.push({ tag, password, serveraddress, username })

return new Promise((resolve, reject) => {
const response = new MyEmitter()
setTimeout(() => {
if (/error/.test(serveraddress)) {
// @ts-ignore
response.emit('error', new Error('remote error'))
} else {
// @ts-ignore
response.emit('end')
}
}, 500)
resolve(response)
})
}
}
class DockerMock {
getImage(imageName) {
return new DockerImage()
}
}
tags.splice(0, tags.length)
mock('dockerode', DockerMock)
publish = require('../src/publish')
})

// eslint-disable-next-line no-undef
beforeEach(() => {
dockerPushArgs = []
tags.splice(0, tags.length)
})

it('expect a EDOCKERIMAGEPUSH error', async () => {
try {
const pluginConfig = createConfig()
const ctx = createContext()
await publish(pluginConfig, ctx)
} catch (errs) {
const err = errs._errors[0]
Expand All @@ -72,29 +29,35 @@ describe('Publish', () => {
})

it('expect success publish', async () => {
const pluginConfig = createConfig()
const ctx = createContext()
pluginConfig.registries[0].url = 'registry.example.com'
pluginConfig.additionalTags = ['beta']
expect(await publish(pluginConfig, ctx)).to.be.a('undefined')
// eslint-disable-next-line no-unused-expressions
expect(isTagPublished('latest')).to.be.true
// eslint-disable-next-line no-unused-expressions
expect(isTagPublished('beta')).to.be.true
expect(tags).include('latest')
expect(tags).include('beta')
})

it('expect skip "latest" publish', async () => {
const pluginConfig = createConfig()
const ctx = createContext()
pluginConfig.registries[0].url = 'registry.example.com'
pluginConfig.registries[0].skipTags = ['latest']
expect(await publish(pluginConfig, ctx)).to.be.a('undefined')
// eslint-disable-next-line no-unused-expressions
expect(isTagPublished('latest')).to.be.false
expect(tags).not.include('latest')
})

it('expect publish branch channel as image tag', async () => {
const pluginConfig = createConfig()
const ctx = createContext()
pluginConfig.registries[0].url = 'registry.example.com'
ctx.nextRelease.channel = 'staging'
expect(await publish(pluginConfig, ctx)).to.be.a('undefined')
expect(tags).include('staging')
})

after(() => {
mock.stopAll()
})

const isTagPublished = (tag) => {
return dockerPushArgs.some((arg) => arg.tag === tag)
}
})
/* eslint-enable require-jsdoc */
/* eslint-enable sonarjs/no-duplicate-string */
Loading

0 comments on commit 8469ec0

Please sign in to comment.