Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Server: buildx for server images #11582

Open
wants to merge 12 commits into
base: dev
Choose a base branch
from
4 changes: 2 additions & 2 deletions .github/workflows/github-actions-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ jobs:
"deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update || true
sudo apt-get install -y docker-ce docker-ce-cli containerd.io
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin

- uses: actions/checkout@v4
- uses: olegtarasov/[email protected]
Expand Down Expand Up @@ -165,7 +165,7 @@ jobs:
"deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update || true
sudo apt-get install -y docker-ce docker-ce-cli containerd.io
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin

- uses: actions/checkout@v4

Expand Down
14 changes: 9 additions & 5 deletions Dockerfile.server
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ FROM node:18 AS builder
RUN apt-get update \
&& apt-get install -y \
python3 tini \
# needed for node-canvas for ARM32 platform.
# See also https://github.com/Automattic/node-canvas/wiki/Installation:-Ubuntu-and-other-Debian-based-systems
libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev \
&& rm -rf /var/lib/apt/lists/*

# Enables Yarn
Expand Down Expand Up @@ -47,9 +50,9 @@ RUN sed --in-place '/onenote-converter/d' ./packages/lib/package.json
# Note that `yarn install` ignores `NODE_ENV=production` and will install dev
# dependencies too, but this is fine because we need them to build the app.

RUN BUILD_SEQUENCIAL=1 yarn install --inline-builds \
&& yarn cache clean \
&& rm -rf .yarn/berry
RUN --mount=type=cache,target=/build/.yarn/cache --mount=type=cache,target=/build/.yarn/berry/cache\
BUILD_SEQUENCIAL=1 yarn config set cacheFolder /build/.yarn/cache \
&& yarn install --inline-builds

# =============================================================================
# Final stage - we copy only the relevant files from the build stage and start
Expand Down Expand Up @@ -81,10 +84,11 @@ CMD ["yarn", "start-prod"]
ARG BUILD_DATE
ARG REVISION
ARG VERSION
ARG SOURCE
LABEL org.opencontainers.image.created="$BUILD_DATE" \
org.opencontainers.image.title="Joplin Server" \
org.opencontainers.image.description="Docker image for Joplin Server" \
org.opencontainers.image.url="https://joplinapp.org/" \
org.opencontainers.image.revision="$REVISION" \
org.opencontainers.image.source="https://github.com/laurent22/joplin.git" \
org.opencontainers.image.version="${VERSION}"
org.opencontainers.image.source="$SOURCE" \
org.opencontainers.image.version="$VERSION"
11 changes: 9 additions & 2 deletions packages/tools/buildServerDocker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,15 @@ describe('buildServerDocker', () => {
type TestCase = [string, boolean, string];

const testCases: TestCase[] = [
['server-v1.1.2-beta', true, '1.1.2-beta'],
['server-v1.1.2', false, '1.1.2'],
['server-v1.2.3-beta', true, '1.2.3-beta'],
['server-v1.2.3-beta', false, '1.2.3'],
['server-v1.2.3', false, '1.2.3'],
['server-v1.2.3-zxc', true, '1.2.3-beta.zxc'],
['server-v1.2.3-zxc', false, '1.2.3'],
['server-v1.2.3-4-zxc', true, '1.2.3-beta.4.zxc'],
['server-v1.2.3-4-zxc', false, '1.2.3'],
['server-1.2.3-4-zxc', true, '1.2.3-beta.4.zxc'],
['server-1.2.3-4-zxc', false, '1.2.3'],
];

for (const testCase of testCases) {
Expand Down
121 changes: 100 additions & 21 deletions packages/tools/buildServerDocker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,66 @@ interface Argv {
pushImages?: boolean;
repository?: string;
tagName?: string;
platform?: string;
source?: string;
addLatestTag?: boolean;
}

function parseArgv(): Argv {
return require('yargs')
.scriptName('yarn buildServerDocker')
.usage('$0 --repository OWNER/IMAGE [args]')
.option('r', {
alias: 'repository',
describe: 'Target image repository. Usually in format `OWNER/NAME`',
demandOption: true,
type: 'string',
})
.option('t', {
alias: 'tagName',
describe: 'Base image tag. Usually should be in format `server-v1.2.3` or `server-v1.2.3-beta`. The latest `server-v*` git tag will be used by default.',
type: 'string',
})
.option('l', {
alias: 'addLatestTag',
describe: 'Add `latest` tag even for pre-release images.',
type: 'boolean',
default: false,
})
.option('platform', {
describe: 'Comma separated list of target image platforms. E.g. `linux/amd64` or `linux/amd64,linux/arm64`',
type: 'string',
default: 'linux/amd64',
})
.option('source', {
describe: 'Source Git repository for the images.',
type: 'string',
default: 'https://github.com/laurent22/joplin.git',
})
.option('p', {
alias: 'pushImages',
describe: 'Publish images to target repository.',
type: 'boolean',
default: false,
})
.option('dryRun', {
alias: 'dryRun',
describe: 'Do not call docker, just show command instead.',
type: 'boolean',
default: false,
})
.help()
.argv as Argv;
}

export function getVersionFromTag(tagName: string, isPreRelease: boolean): string {
const s = tagName.split('-');
const suffix = isPreRelease ? '-beta' : '';
return s[1].substr(1) + suffix;
const mainVersion = s[1].replace(/^(v)/, '');
const metaComponents = s.slice(2).filter(item => item !== 'beta');

// Append `git describe` components for pre release images. Mostly for case without `tagName` arg
const suffix = isPreRelease ? `-beta${metaComponents.length > 0 ? `.${metaComponents.join('.')}` : ''}` : '';
return mainVersion + suffix;
}

export function getIsPreRelease(_tagName: string): boolean {
Expand All @@ -23,14 +77,17 @@ export function getIsPreRelease(_tagName: string): boolean {
}

async function main() {
const argv = require('yargs').argv as Argv;
if (!argv.tagName) throw new Error('--tag-name not provided');
if (!argv.repository) throw new Error('--repository not provided');
const argv = parseArgv();
if (!argv.tagName) console.info('No `--tag-name` was specified. A latest git tag will be used instead.');

const dryRun = !!argv.dryRun;
const pushImages = !!argv.pushImages;
const dryRun = argv.dryRun;
const addLatestTag = argv.addLatestTag;
const pushImages = argv.pushImages;
const repository = argv.repository;
const tagName = argv.tagName;
const tagName = argv.tagName || `server-${await execCommand('git describe --tags --match v*', { showStdout: false })}`;
const platform = argv.platform;
const source = argv.source;

const isPreRelease = getIsPreRelease(tagName);
const imageVersion = getVersionFromTag(tagName, isPreRelease);
const buildDate = moment(new Date().getTime()).format('YYYY-MM-DDTHH:mm:ssZ');
Expand All @@ -40,35 +97,57 @@ async function main() {
} catch (error) {
console.info('Could not get git commit: metadata revision field will be empty');
}
const buildArgs = `--build-arg BUILD_DATE="${buildDate}" --build-arg REVISION="${revision}" --build-arg VERSION="${imageVersion}"`;

const buildArgs = [];
buildArgs.push(`BUILD_DATE="${buildDate}"`);
buildArgs.push(`REVISION="${revision}"`);
buildArgs.push(`VERSION="${imageVersion}"`);
buildArgs.push(`SOURCE="${source}"`);

const dockerTags: string[] = [];
const versionPart = imageVersion.split('.');
dockerTags.push(isPreRelease ? 'beta' : 'latest');
dockerTags.push(versionPart[0] + (isPreRelease ? '-beta' : ''));
dockerTags.push(`${versionPart[0]}.${versionPart[1]}${isPreRelease ? '-beta' : ''}`);
dockerTags.push(imageVersion);
const versionParts = imageVersion.split('.');
const patchVersionPart = versionParts[2].split('-')[0];
dockerTags.push(isPreRelease ? 'latest-beta' : 'latest');
dockerTags.push(versionParts[0] + (isPreRelease ? '-beta' : ''));
dockerTags.push(`${versionParts[0]}.${versionParts[1]}${isPreRelease ? '-beta' : ''}`);
dockerTags.push(`${versionParts[0]}.${versionParts[1]}.${patchVersionPart}${isPreRelease ? '-beta' : ''}`);
if (dockerTags.indexOf(imageVersion) < 0) {
dockerTags.push(imageVersion);
}
if (addLatestTag && dockerTags.indexOf('latest') < 0) {
dockerTags.push('latest');
}


process.chdir(rootDir);
console.info(`Running from: ${process.cwd()}`);

console.info('repository:', repository);
console.info('tagName:', tagName);
console.info('platform:', platform);
console.info('pushImages:', pushImages);
console.info('imageVersion:', imageVersion);
console.info('isPreRelease:', isPreRelease);
console.info('Docker tags:', dockerTags.join(', '));

const dockerCommand = `docker build --progress=plain -t "${repository}:${imageVersion}" ${buildArgs} -f Dockerfile.server .`;
const cliArgs = ['--progress=plain'];
cliArgs.push(`--platform ${platform}`);
cliArgs.push(...dockerTags.map(tag => `--tag "${repository}:${tag}"`));
cliArgs.push(...buildArgs.map(arg => `--build-arg ${arg}`));
if (pushImages) {
cliArgs.push('--push');
}
cliArgs.push('-f Dockerfile.server');
cliArgs.push('.');

const dockerCommand = `docker buildx build ${cliArgs.join(' ')}`;

console.info('exec:', dockerCommand);
if (dryRun) {
console.info(dockerCommand);
return;
}

await execCommand(dockerCommand);

for (const tag of dockerTags) {
await execCommand(`docker tag "${repository}:${imageVersion}" "${repository}:${tag}"`);
if (pushImages) await execCommand(`docker push ${repository}:${tag}`);
}
}

if (require.main === module) {
Expand Down
1 change: 1 addition & 0 deletions packages/tools/cspell/dictionary4.txt
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,4 @@ unwatcher
pedr
Slotozilla
keyshortcuts
buildx