Skip to content

Commit 950f1dd

Browse files
committed
add registry identity parsing workflow
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
1 parent 2ed0b42 commit 950f1dd

1 file changed

Lines changed: 191 additions & 0 deletions

File tree

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
name: registry-identities
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
registry-identities:
7+
type: string
8+
description: "Keyless registry identity configuration as YAML objects"
9+
required: false
10+
outputs:
11+
aws-ecr-enabled:
12+
description: "Whether an AWS ECR registry identity was configured"
13+
value: ${{ jobs.validate.outputs.aws-ecr-enabled }}
14+
aws-ecr-role-to-assume:
15+
description: "AWS IAM role ARN to assume for ECR authentication"
16+
value: ${{ jobs.validate.outputs.aws-ecr-role-to-assume }}
17+
aws-ecr-region:
18+
description: "AWS region to use for ECR authentication"
19+
value: ${{ jobs.validate.outputs.aws-ecr-region }}
20+
aws-ecr-registry-auth:
21+
description: "Registry auth YAML for Docker login"
22+
value: ${{ jobs.validate.outputs.aws-ecr-registry-auth }}
23+
24+
env:
25+
RUNTIME_MODULE: "@docker/github-builder-runtime@0.91.0"
26+
RUNTIME_INSTALL_ARGS: |
27+
--loglevel=error
28+
--no-save
29+
--package-lock=false
30+
--ignore-scripts
31+
--omit=dev
32+
--prefer-offline
33+
--fund=false
34+
--audit=false
35+
NPM_CONFIG_FETCH_RETRIES: "5"
36+
37+
jobs:
38+
setup-registry-identities:
39+
runs-on: ubuntu-24.04
40+
outputs:
41+
aws-ecr-enabled: ${{ steps.validate.outputs.aws-ecr-enabled }}
42+
aws-ecr-role-to-assume: ${{ steps.validate.outputs.aws-ecr-role-to-assume }}
43+
aws-ecr-region: ${{ steps.validate.outputs.aws-ecr-region }}
44+
aws-ecr-registry-auth: ${{ steps.validate.outputs.aws-ecr-registry-auth }}
45+
steps:
46+
-
47+
name: Install dependencies
48+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
49+
env:
50+
INPUT_RUNTIME-MODULE: ${{ env.RUNTIME_MODULE }}
51+
INPUT_RUNTIME-INSTALL-ARGS: ${{ env.RUNTIME_INSTALL_ARGS }}
52+
with:
53+
script: |
54+
const npmArgs = ['install', ...core.getMultilineInput('runtime-install-args'), core.getInput('runtime-module')];
55+
const maxAttempts = 3;
56+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
57+
const exitCode = await exec.exec('npm', npmArgs, {ignoreReturnCode: true});
58+
if (exitCode === 0) {
59+
return;
60+
}
61+
if (attempt === maxAttempts) {
62+
core.setFailed(`npm install failed after ${maxAttempts} attempts`);
63+
return;
64+
}
65+
const retryDelayMs = attempt * 50;
66+
core.info(`npm install failed with exit code ${exitCode}; retrying in ${retryDelayMs}ms`);
67+
await new Promise(resolve => setTimeout(resolve, retryDelayMs));
68+
}
69+
-
70+
name: Validate registry identities
71+
id: validate
72+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
73+
env:
74+
INPUT_REGISTRY-IDENTITIES: ${{ inputs.registry-identities }}
75+
with:
76+
script: |
77+
let yaml;
78+
try {
79+
yaml = require('js-yaml');
80+
} catch {
81+
yaml = require('@docker/github-builder-runtime/node_modules/js-yaml');
82+
}
83+
84+
const setEmptyOutputs = () => {
85+
core.setOutput('aws-ecr-enabled', 'false');
86+
core.setOutput('aws-ecr-role-to-assume', '');
87+
core.setOutput('aws-ecr-region', '');
88+
core.setOutput('aws-ecr-registry-auth', '');
89+
};
90+
91+
const input = core.getInput('registry-identities', {trimWhitespace: false});
92+
if (!input.trim()) {
93+
setEmptyOutputs();
94+
return;
95+
}
96+
97+
const fail = message => {
98+
throw new Error(`Invalid registry-identities input: ${message}`);
99+
};
100+
const ensureObject = (value, path) => {
101+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
102+
fail(`${path} must be an object`);
103+
}
104+
};
105+
const requireString = (entry, key, path) => {
106+
const value = entry[key];
107+
if (typeof value !== 'string' || !value.trim()) {
108+
fail(`${path}.${key} must be a non-empty string`);
109+
}
110+
return value.trim();
111+
};
112+
const requireStringAny = (entry, keys, path) => {
113+
for (const key of keys) {
114+
if (entry[key] !== undefined) {
115+
return requireString(entry, key, path);
116+
}
117+
}
118+
fail(`${path}.${keys[0]} must be a non-empty string`);
119+
};
120+
const normalizeRegistries = (value, path) => {
121+
if (value === undefined || value === null || value === '') {
122+
fail(`${path}.registries must be a non-empty string or list`);
123+
}
124+
const values = Array.isArray(value) ? value : String(value).split(/[,\r\n]+/);
125+
const registries = values.map(item => String(item).trim()).filter(Boolean);
126+
if (registries.length === 0) {
127+
fail(`${path}.registries must contain at least one registry`);
128+
}
129+
return [...new Set(registries)];
130+
};
131+
const toAwsEcrLoginRegistry = (registry, region) => {
132+
if (registry.includes('.')) {
133+
return registry.split('/')[0];
134+
}
135+
return `${registry}.dkr.ecr.${region}.amazonaws.com`;
136+
};
137+
138+
let parsed;
139+
try {
140+
parsed = yaml.load(input);
141+
} catch (error) {
142+
fail(error.message);
143+
}
144+
if (parsed === null || parsed === undefined) {
145+
setEmptyOutputs();
146+
return;
147+
}
148+
149+
const entries = Array.isArray(parsed) ? parsed : [parsed];
150+
if (entries.length === 0) {
151+
setEmptyOutputs();
152+
return;
153+
}
154+
155+
let awsEcr;
156+
entries.forEach((entry, index) => {
157+
const path = `registry-identities[${index}]`;
158+
ensureObject(entry, path);
159+
const type = requireString(entry, 'type', path);
160+
switch (type) {
161+
case 'aws-ecr': {
162+
const allowedKeys = new Set(['type', 'aws-region', 'aws_region', 'role-to-assume', 'role_to_assume', 'registries']);
163+
for (const key of Object.keys(entry)) {
164+
if (!allowedKeys.has(key)) {
165+
fail(`${path}.${key} is not supported for aws-ecr`);
166+
}
167+
}
168+
if (awsEcr) {
169+
fail('only one aws-ecr registry identity is supported');
170+
}
171+
const awsRegion = requireStringAny(entry, ['aws-region', 'aws_region'], path);
172+
const roleToAssume = requireStringAny(entry, ['role-to-assume', 'role_to_assume'], path);
173+
const registries = normalizeRegistries(entry.registries, path);
174+
awsEcr = {
175+
awsRegion,
176+
roleToAssume,
177+
registryAuth: yaml.dump(registries.map(registry => ({
178+
registry: toAwsEcrLoginRegistry(registry, awsRegion)
179+
})), {lineWidth: -1})
180+
};
181+
break;
182+
}
183+
default:
184+
fail(`${path}.type has unsupported provider ${type}`);
185+
}
186+
});
187+
188+
core.setOutput('aws-ecr-enabled', awsEcr ? 'true' : 'false');
189+
core.setOutput('aws-ecr-role-to-assume', awsEcr?.roleToAssume || '');
190+
core.setOutput('aws-ecr-region', awsEcr?.awsRegion || '');
191+
core.setOutput('aws-ecr-registry-auth', awsEcr?.registryAuth || '');

0 commit comments

Comments
 (0)