first commit
Some checks failed
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Some checks failed
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
177
.github/scripts/docker/docker-config.mjs
vendored
Normal file
177
.github/scripts/docker/docker-config.mjs
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { appendFileSync } from 'node:fs';
|
||||
|
||||
class BuildContext {
|
||||
constructor() {
|
||||
this.githubOutput = process.env.GITHUB_OUTPUT || null;
|
||||
}
|
||||
|
||||
determine({ event, pr, branch, version, releaseType, pushEnabled }) {
|
||||
let context = {
|
||||
version: '',
|
||||
release_type: '',
|
||||
platforms: ['linux/amd64', 'linux/arm64'],
|
||||
push_to_ghcr: true,
|
||||
push_to_docker: false,
|
||||
};
|
||||
|
||||
if (version && releaseType) {
|
||||
context.version = version;
|
||||
context.release_type = releaseType;
|
||||
context.push_to_docker = true;
|
||||
} else {
|
||||
switch (event) {
|
||||
case 'schedule':
|
||||
context.version = 'nightly';
|
||||
context.release_type = 'nightly';
|
||||
context.push_to_docker = true;
|
||||
break;
|
||||
|
||||
case 'pull_request':
|
||||
context.version = `pr-${pr}`;
|
||||
context.release_type = 'dev';
|
||||
context.platforms = ['linux/amd64'];
|
||||
break;
|
||||
|
||||
case 'workflow_dispatch':
|
||||
context.version = `branch-${this.sanitizeBranch(branch)}`;
|
||||
context.release_type = 'branch';
|
||||
context.platforms = ['linux/amd64'];
|
||||
break;
|
||||
|
||||
case 'push':
|
||||
if (branch === 'master') {
|
||||
context.version = 'dev';
|
||||
context.release_type = 'dev';
|
||||
context.push_to_docker = true;
|
||||
} else {
|
||||
context.version = `branch-${this.sanitizeBranch(branch)}`;
|
||||
context.release_type = 'branch';
|
||||
context.platforms = ['linux/amd64'];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'workflow_call':
|
||||
case 'release':
|
||||
if (!version) throw new Error('Version required for release');
|
||||
context.version = version;
|
||||
context.release_type = releaseType || 'stable';
|
||||
context.push_to_docker = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown event: ${event}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle push_enabled override
|
||||
if (pushEnabled !== undefined) {
|
||||
context.push_enabled = pushEnabled;
|
||||
} else {
|
||||
context.push_enabled = context.push_to_ghcr;
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
sanitizeBranch(branch) {
|
||||
if (!branch) return 'unknown';
|
||||
return branch
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9._-]/g, '-')
|
||||
.replace(/^[.-]/, '')
|
||||
.replace(/[.-]$/, '')
|
||||
.substring(0, 128);
|
||||
}
|
||||
|
||||
buildMatrix(platforms) {
|
||||
const runners = {
|
||||
'linux/amd64': 'blacksmith-4vcpu-ubuntu-2204',
|
||||
'linux/arm64': 'blacksmith-4vcpu-ubuntu-2204-arm',
|
||||
};
|
||||
|
||||
const matrix = {
|
||||
platform: [],
|
||||
include: [],
|
||||
};
|
||||
|
||||
for (const platform of platforms) {
|
||||
const shortName = platform.split('/').pop(); // amd64 or arm64
|
||||
matrix.platform.push(shortName);
|
||||
matrix.include.push({
|
||||
platform: shortName,
|
||||
runner: runners[platform],
|
||||
docker_platform: platform,
|
||||
});
|
||||
}
|
||||
|
||||
return matrix;
|
||||
}
|
||||
|
||||
output(context, matrix = null) {
|
||||
const buildMatrix = matrix || this.buildMatrix(context.platforms);
|
||||
|
||||
if (this.githubOutput) {
|
||||
const outputs = [
|
||||
`version=${context.version}`,
|
||||
`release_type=${context.release_type}`,
|
||||
`platforms=${JSON.stringify(context.platforms)}`,
|
||||
`push_to_ghcr=${context.push_to_ghcr}`,
|
||||
`push_to_docker=${context.push_to_docker}`,
|
||||
`push_enabled=${context.push_enabled}`,
|
||||
`build_matrix=${JSON.stringify(buildMatrix)}`,
|
||||
];
|
||||
appendFileSync(this.githubOutput, outputs.join('\n') + '\n');
|
||||
} else {
|
||||
console.log(JSON.stringify({ ...context, build_matrix: buildMatrix }, null, 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CLI - Simple argument parsing
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
const args = process.argv.slice(2);
|
||||
const getArg = (name) => {
|
||||
const index = args.indexOf(`--${name}`);
|
||||
if (index === -1 || !args[index + 1]) return undefined;
|
||||
const value = args[index + 1];
|
||||
// Handle empty strings and 'null' as undefined
|
||||
return value === '' || value === 'null' ? undefined : value;
|
||||
};
|
||||
|
||||
try {
|
||||
const context = new BuildContext();
|
||||
const pushEnabledArg = getArg('push-enabled');
|
||||
const result = context.determine({
|
||||
event: getArg('event') || process.env.GITHUB_EVENT_NAME,
|
||||
pr: getArg('pr') || process.env.GITHUB_PR_NUMBER,
|
||||
branch: getArg('branch') || process.env.GITHUB_REF_NAME,
|
||||
version: getArg('version'),
|
||||
releaseType: getArg('release-type'),
|
||||
pushEnabled:
|
||||
pushEnabledArg === 'true' ? true : pushEnabledArg === 'false' ? false : undefined,
|
||||
});
|
||||
|
||||
const matrix = context.buildMatrix(result.platforms);
|
||||
|
||||
// Debug output when GITHUB_OUTPUT is set (running in Actions)
|
||||
if (context.githubOutput) {
|
||||
console.log('=== Build Context ===');
|
||||
console.log(`version: ${result.version}`);
|
||||
console.log(`release_type: ${result.release_type}`);
|
||||
console.log(`platforms: ${JSON.stringify(result.platforms, null, 2)}`);
|
||||
console.log(`push_to_ghcr: ${result.push_to_ghcr}`);
|
||||
console.log(`push_to_docker: ${result.push_to_docker}`);
|
||||
console.log(`push_enabled: ${result.push_enabled}`);
|
||||
console.log('build_matrix:', JSON.stringify(matrix, null, 2));
|
||||
}
|
||||
|
||||
context.output(result, matrix);
|
||||
} catch (error) {
|
||||
console.error(`Error: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
export default BuildContext;
|
||||
113
.github/scripts/docker/docker-tags.mjs
vendored
Normal file
113
.github/scripts/docker/docker-tags.mjs
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { appendFileSync } from 'node:fs';
|
||||
|
||||
class TagGenerator {
|
||||
constructor() {
|
||||
this.githubOwner = process.env.GITHUB_REPOSITORY_OWNER || 'n8n-io';
|
||||
this.dockerUsername = process.env.DOCKER_USERNAME || 'n8nio';
|
||||
this.githubOutput = process.env.GITHUB_OUTPUT || null;
|
||||
}
|
||||
|
||||
generate({ image, version, platform, includeDockerHub = false }) {
|
||||
let imageName = image;
|
||||
let versionSuffix = '';
|
||||
|
||||
if (image === 'runners-distroless') {
|
||||
imageName = 'runners';
|
||||
versionSuffix = '-distroless';
|
||||
}
|
||||
|
||||
const platformSuffix = platform ? `-${platform.split('/').pop()}` : '';
|
||||
const fullVersion = `${version}${versionSuffix}${platformSuffix}`;
|
||||
|
||||
const tags = {
|
||||
ghcr: [`ghcr.io/${this.githubOwner}/${imageName}:${fullVersion}`],
|
||||
docker: includeDockerHub ? [`${this.dockerUsername}/${imageName}:${fullVersion}`] : [],
|
||||
};
|
||||
|
||||
tags.all = [...tags.ghcr, ...tags.docker];
|
||||
return tags;
|
||||
}
|
||||
|
||||
output(tags, prefix = '') {
|
||||
if (this.githubOutput) {
|
||||
const prefixStr = prefix ? `${prefix}_` : '';
|
||||
const primaryTag = tags.ghcr[0] ? tags.ghcr[0].replace(/-amd64$|-arm64$/, '') : '';
|
||||
const outputs = [
|
||||
`${prefixStr}tags=${tags.all.join(',')}`,
|
||||
`${prefixStr}ghcr_tag=${tags.ghcr[0] || ''}`,
|
||||
`${prefixStr}docker_tag=${tags.docker[0] || ''}`,
|
||||
`${prefixStr}primary_tag=${primaryTag}`,
|
||||
];
|
||||
appendFileSync(this.githubOutput, outputs.join('\n') + '\n');
|
||||
} else {
|
||||
console.log(JSON.stringify(tags, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
generateAll({ version, platform, includeDockerHub = false }) {
|
||||
const images = ['n8n', 'runners', 'runners-distroless'];
|
||||
const results = {};
|
||||
|
||||
for (const image of images) {
|
||||
const tags = this.generate({ image, version, platform, includeDockerHub });
|
||||
const prefix = image.replace('-distroless', '_distroless');
|
||||
results[prefix] = tags;
|
||||
|
||||
if (this.githubOutput) {
|
||||
this.output(tags, prefix);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
const args = process.argv.slice(2);
|
||||
const getArg = (name) => {
|
||||
const index = args.indexOf(`--${name}`);
|
||||
return index !== -1 && args[index + 1] ? args[index + 1] : undefined;
|
||||
};
|
||||
const hasFlag = (name) => args.includes(`--${name}`);
|
||||
|
||||
try {
|
||||
const generator = new TagGenerator();
|
||||
const version = getArg('version');
|
||||
|
||||
if (!version) {
|
||||
console.error('Error: --version is required');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (hasFlag('all')) {
|
||||
const results = generator.generateAll({
|
||||
version,
|
||||
platform: getArg('platform'),
|
||||
includeDockerHub: hasFlag('include-docker'),
|
||||
});
|
||||
if (!generator.githubOutput) {
|
||||
console.log(JSON.stringify(results, null, 2));
|
||||
}
|
||||
} else {
|
||||
const image = getArg('image');
|
||||
if (!image) {
|
||||
console.error('Error: Either --image or --all is required');
|
||||
process.exit(1);
|
||||
}
|
||||
const tags = generator.generate({
|
||||
image,
|
||||
version,
|
||||
platform: getArg('platform'),
|
||||
includeDockerHub: hasFlag('include-docker'),
|
||||
});
|
||||
generator.output(tags);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
export default TagGenerator;
|
||||
63
.github/scripts/docker/get-manifest-digests.mjs
vendored
Normal file
63
.github/scripts/docker/get-manifest-digests.mjs
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Extracts manifest digests and image names for SLSA provenance and VEX attestation.
|
||||
*
|
||||
* Usage:
|
||||
* N8N_TAG=ghcr.io/n8n-io/n8n:1.0.0 node get-manifest-digests.mjs
|
||||
*
|
||||
* Environment variables:
|
||||
* N8N_TAG - Full image reference for n8n image
|
||||
* RUNNERS_TAG - Full image reference for runners image
|
||||
* DISTROLESS_TAG - Full image reference for runners-distroless image
|
||||
* GITHUB_OUTPUT - Path to GitHub Actions output file (optional)
|
||||
*/
|
||||
|
||||
import { execSync } from 'node:child_process';
|
||||
import { appendFileSync } from 'node:fs';
|
||||
|
||||
const githubOutput = process.env.GITHUB_OUTPUT || null;
|
||||
|
||||
function getDigest(imageRef) {
|
||||
if (!imageRef) return '';
|
||||
const raw = execSync(`docker buildx imagetools inspect "${imageRef}" --raw`, {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
const hash = execSync('sha256sum', { input: raw, encoding: 'utf8' }).split(' ')[0].trim();
|
||||
return `sha256:${hash}`;
|
||||
}
|
||||
|
||||
function getImageName(imageRef) {
|
||||
if (!imageRef) return '';
|
||||
return imageRef.replace(/:([^:]+)$/, '');
|
||||
}
|
||||
|
||||
function setOutput(name, value) {
|
||||
if (githubOutput && value) appendFileSync(githubOutput, `${name}=${value}\n`);
|
||||
}
|
||||
|
||||
const n8nTag = process.env.N8N_TAG || '';
|
||||
const runnersTag = process.env.RUNNERS_TAG || '';
|
||||
const distrolessTag = process.env.DISTROLESS_TAG || '';
|
||||
|
||||
const results = {
|
||||
n8n: { digest: getDigest(n8nTag), image: getImageName(n8nTag) },
|
||||
runners: { digest: getDigest(runnersTag), image: getImageName(runnersTag) },
|
||||
runners_distroless: { digest: getDigest(distrolessTag), image: getImageName(distrolessTag) },
|
||||
};
|
||||
|
||||
setOutput('n8n_digest', results.n8n.digest);
|
||||
setOutput('n8n_image', results.n8n.image);
|
||||
setOutput('runners_digest', results.runners.digest);
|
||||
setOutput('runners_image', results.runners.image);
|
||||
setOutput('runners_distroless_digest', results.runners_distroless.digest);
|
||||
setOutput('runners_distroless_image', results.runners_distroless.image);
|
||||
|
||||
console.log('=== Manifest Digests ===');
|
||||
console.log(`n8n: ${results.n8n.digest || 'N/A'}`);
|
||||
console.log(`runners: ${results.runners.digest || 'N/A'}`);
|
||||
console.log(`runners-distroless: ${results.runners_distroless.digest || 'N/A'}`);
|
||||
console.log('');
|
||||
console.log('=== Image Names ===');
|
||||
console.log(`n8n: ${results.n8n.image || 'N/A'}`);
|
||||
console.log(`runners: ${results.runners.image || 'N/A'}`);
|
||||
console.log(`runners-distroless: ${results.runners_distroless.image || 'N/A'}`);
|
||||
Reference in New Issue
Block a user