Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/.test-bake.yml
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,10 @@ jobs:
context: test
output: image
push: false
registry-identities: |
- type: dockerhub
username: dockereng
connection_id: ${{ vars.MISSING_DOCKERHUB_OIDC_CONNECTIONID }}
set: |
*.args.VERSION={{meta.version}}
target: hello-cross
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/.test-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,10 @@ jobs:
output: image
platforms: linux/amd64,linux/arm64
push: false
registry-identities: |
- type: dockerhub
username: dockereng
connection_id: ${{ vars.MISSING_DOCKERHUB_OIDC_CONNECTIONID }}
meta-images: ghcr.io/docker/github-builder-test
meta-tags: |
type=raw,value=build-${{ github.run_id }}
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/bake.yml
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ jobs:
registry-identities:
uses: ./.github/workflows/setup-registry-identities.yml
with:
registry-login: ${{ inputs.push && inputs.output == 'image' }}
registry-identities: ${{ inputs.registry-identities }}

prepare:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ jobs:
registry-identities:
uses: ./.github/workflows/setup-registry-identities.yml
with:
registry-login: ${{ inputs.push && inputs.output == 'image' }}
registry-identities: ${{ inputs.registry-identities }}

prepare:
Expand Down
203 changes: 119 additions & 84 deletions .github/workflows/setup-registry-identities.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ on:
type: string
description: "Keyless registry identity configuration as YAML objects"
required: false
registry-login:
type: boolean
description: "Whether registry login is enabled"
required: false
default: true
outputs:
aws-ecr-enabled:
description: "Whether an AWS ECR registry identity was configured"
Expand Down Expand Up @@ -86,14 +91,22 @@ jobs:
INPUT_RUNTIME-MODULE: ${{ env.RUNTIME_MODULE }}
INPUT_RUNTIME-INSTALL-ARGS: ${{ env.RUNTIME_INSTALL_ARGS }}
INPUT_REGISTRY-IDENTITIES: ${{ inputs.registry-identities }}
INPUT_REGISTRY-LOGIN: ${{ inputs.registry-login }}
with:
script: |
const registryLogin = core.getBooleanInput('registry-login');
if (!registryLogin) {
core.info('Registry authentication not required; skipping npm install');
return;
}

const registryIdentities = core.getInput('registry-identities', {trimWhitespace: false});
if (!registryIdentities.trim()) {
core.info('No registry identities configured; skipping npm install');
return;
}

core.info('Registry identities configured; installing validation dependencies');
const npmArgs = ['install', ...core.getMultilineInput('runtime-install-args'), core.getInput('runtime-module')];
const maxAttempts = 3;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
Expand All @@ -115,6 +128,7 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
INPUT_REGISTRY-IDENTITIES: ${{ inputs.registry-identities }}
INPUT_REGISTRY-LOGIN: ${{ inputs.registry-login }}
with:
script: |
const setEmptyOutputs = () => {
Expand All @@ -133,6 +147,13 @@ jobs:
core.setOutput('dockerhub-oidc-connection-id', '');
};

const registryLogin = core.getBooleanInput('registry-login');
if (!registryLogin) {
core.info('Registry authentication not required; skipping validation');
setEmptyOutputs();
return;
}

const registryIdentities = core.getInput('registry-identities', {trimWhitespace: false});
if (!registryIdentities.trim()) {
core.info('No registry identities configured; skipping validation');
Expand All @@ -150,116 +171,130 @@ jobs:
const fail = message => {
throw new Error(`Invalid registry-identities input: ${message}`);
};

const ensureObject = (value, path) => {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
fail(`${path} must be an object`);
}
};

const requireString = (entry, key, path) => {
const value = entry[key];
if (typeof value !== 'string' || !value.trim()) {
fail(`${path}.${key} must be a non-empty string`);
}
return value.trim();
};

const optionalString = (entry, key, path, defaultValue) => {
if (!Object.prototype.hasOwnProperty.call(entry, key)) {
return defaultValue;
}
return requireString(entry, key, path);
};
let parsed;

try {
parsed = yaml.load(registryIdentities);
} catch (error) {
fail(error.message);
}
if (parsed === null || parsed === undefined) {
setEmptyOutputs();
return;
}
let parsed;
try {
parsed = yaml.load(registryIdentities);
} catch (error) {
fail(error.message);
}
if (parsed === null || parsed === undefined) {
core.info('Registry identities input is empty after parsing; disabling registry identity outputs');
setEmptyOutputs();
return;
}

const entries = Array.isArray(parsed) ? parsed : [parsed];
if (entries.length === 0) {
setEmptyOutputs();
return;
}
const entries = Array.isArray(parsed) ? parsed : [parsed];
if (entries.length === 0) {
core.info('No registry identity entries parsed; disabling registry identity outputs');
setEmptyOutputs();
return;
}
core.info(`Validating ${entries.length} registry identity ${entries.length === 1 ? 'entry' : 'entries'}`);

let awsEcr;
let gcpWif;
let dockerhubOidc;
entries.forEach((entry, index) => {
const path = `registry-identities[${index}]`;
ensureObject(entry, path);
const type = requireString(entry, 'type', path);
switch (type) {
case 'aws-ecr': {
const allowedKeys = new Set(['type', 'registry', 'role-to-assume', 'region']);
for (const key of Object.keys(entry)) {
if (!allowedKeys.has(key)) {
fail(`${path}.${key} is not supported for aws-ecr`);
let awsEcr;
let gcpWif;
let dockerhubOidc;
entries.forEach((entry, index) => {
const path = `registry-identities[${index}]`;
ensureObject(entry, path);
const type = requireString(entry, 'type', path);
switch (type) {
case 'aws-ecr': {
const allowedKeys = new Set(['type', 'registry', 'role-to-assume', 'region']);
for (const key of Object.keys(entry)) {
if (!allowedKeys.has(key)) {
fail(`${path}.${key} is not supported for aws-ecr`);
}
}
}
if (awsEcr) {
fail('only one aws-ecr registry identity is supported');
}
awsEcr = {
registry: requireString(entry, 'registry', path),
roleToAssume: requireString(entry, 'role-to-assume', path),
region: requireString(entry, 'region', path)
};
break;
}
case 'gcp-wif': {
const allowedKeys = new Set(['type', 'registry', 'workload_identity_provider', 'service_account', 'project_id']);
for (const key of Object.keys(entry)) {
if (!allowedKeys.has(key)) {
fail(`${path}.${key} is not supported for gcp-wif`);
if (awsEcr) {
fail('only one aws-ecr registry identity is supported');
}
awsEcr = {
registry: requireString(entry, 'registry', path),
roleToAssume: requireString(entry, 'role-to-assume', path),
region: requireString(entry, 'region', path)
};
core.info(`Configured aws-ecr registry identity for ${awsEcr.registry}`);
break;
}
if (gcpWif) {
fail('only one gcp-wif registry identity is supported');
}
gcpWif = {
registry: requireString(entry, 'registry', path),
workloadIdentityProvider: requireString(entry, 'workload_identity_provider', path),
serviceAccount: requireString(entry, 'service_account', path),
projectId: requireString(entry, 'project_id', path)
};
break;
}
case 'dockerhub': {
const allowedKeys = new Set(['type', 'registry', 'username', 'connection_id']);
for (const key of Object.keys(entry)) {
if (!allowedKeys.has(key)) {
fail(`${path}.${key} is not supported for dockerhub`);
case 'gcp-wif': {
const allowedKeys = new Set(['type', 'registry', 'workload_identity_provider', 'service_account', 'project_id']);
for (const key of Object.keys(entry)) {
if (!allowedKeys.has(key)) {
fail(`${path}.${key} is not supported for gcp-wif`);
}
}
if (gcpWif) {
fail('only one gcp-wif registry identity is supported');
}
gcpWif = {
registry: requireString(entry, 'registry', path),
workloadIdentityProvider: requireString(entry, 'workload_identity_provider', path),
serviceAccount: requireString(entry, 'service_account', path),
projectId: optionalString(entry, 'project_id', path, '')
};
core.info(`Configured gcp-wif registry identity for ${gcpWif.registry}`);
break;
}
if (dockerhubOidc) {
fail('only one dockerhub registry identity is supported');
case 'dockerhub': {
const allowedKeys = new Set(['type', 'registry', 'username', 'connection_id']);
for (const key of Object.keys(entry)) {
if (!allowedKeys.has(key)) {
fail(`${path}.${key} is not supported for dockerhub`);
}
}
if (dockerhubOidc) {
fail('only one dockerhub registry identity is supported');
}
dockerhubOidc = {
registry: optionalString(entry, 'registry', path, 'docker.io'),
username: requireString(entry, 'username', path),
connectionID: requireString(entry, 'connection_id', path)
};
core.info(`Configured dockerhub registry identity for ${dockerhubOidc.registry}`);
break;
}
dockerhubOidc = {
registry: optionalString(entry, 'registry', path, 'docker.io'),
username: requireString(entry, 'username', path),
connectionID: requireString(entry, 'connection_id', path)
};
break;
default:
fail(`${path}.type has unsupported provider ${type}`);
}
default:
fail(`${path}.type has unsupported provider ${type}`);
}
});
});

core.setOutput('aws-ecr-enabled', awsEcr ? 'true' : 'false');
core.setOutput('aws-ecr-registry', awsEcr?.registry || '');
core.setOutput('aws-ecr-role-to-assume', awsEcr?.roleToAssume || '');
core.setOutput('aws-ecr-region', awsEcr?.region || '');
core.setOutput('gcp-wif-enabled', gcpWif ? 'true' : 'false');
core.setOutput('gcp-wif-registry', gcpWif?.registry || '');
core.setOutput('gcp-wif-workload-identity-provider', gcpWif?.workloadIdentityProvider || '');
core.setOutput('gcp-wif-service-account', gcpWif?.serviceAccount || '');
core.setOutput('gcp-wif-project-id', gcpWif?.projectId || '');
core.setOutput('dockerhub-oidc-enabled', dockerhubOidc ? 'true' : 'false');
core.setOutput('dockerhub-oidc-registry', dockerhubOidc?.registry || '');
core.setOutput('dockerhub-oidc-username', dockerhubOidc?.username || '');
core.setOutput('dockerhub-oidc-connection-id', dockerhubOidc?.connectionID || '');
core.setOutput('aws-ecr-enabled', awsEcr ? 'true' : 'false');
core.setOutput('aws-ecr-registry', awsEcr?.registry || '');
core.setOutput('aws-ecr-role-to-assume', awsEcr?.roleToAssume || '');
core.setOutput('aws-ecr-region', awsEcr?.region || '');
core.setOutput('gcp-wif-enabled', gcpWif ? 'true' : 'false');
core.setOutput('gcp-wif-registry', gcpWif?.registry || '');
core.setOutput('gcp-wif-workload-identity-provider', gcpWif?.workloadIdentityProvider || '');
core.setOutput('gcp-wif-service-account', gcpWif?.serviceAccount || '');
core.setOutput('gcp-wif-project-id', gcpWif?.projectId || '');
core.setOutput('dockerhub-oidc-enabled', dockerhubOidc ? 'true' : 'false');
core.setOutput('dockerhub-oidc-registry', dockerhubOidc?.registry || '');
core.setOutput('dockerhub-oidc-username', dockerhubOidc?.username || '');
core.setOutput('dockerhub-oidc-connection-id', dockerhubOidc?.connectionID || '');
} catch (error) {
core.setFailed(error.message || error);
}
Loading
Loading