diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 695df12..44965f1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,6 +20,7 @@ jobs: uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} + - run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin - run: npm ci - run: npm run lint - run: npm test diff --git a/README.md b/README.md index 0fc69ba..b890554 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,20 @@ _Optional_ Use `additional-docker-arguments` if you need to provide additional a additional-docker-arguments: --build-arg env=staging ``` +### `external-registry-url` + +_Optional_ Push the image to an external container registry. This registry does not need to be [registered with Humanitec](https://docs.humanitec.com/guides/connect-ci-setup/container-registries) and authentication needs to be done before calling this action (e.g. using workload identity). + +```yaml + uses: humanitec/build-push-to-humanitec@v1 + with: + humanitec-token: ${{ secrets.HUMANITEC_TOKEN }} + organization: awesome-company + external-registry-url: europe-west3-docker.pkg.dev/gcp-project/repository +``` + +Will push the resulting image to `europe-west3-docker.pkg.dev/gcp-project/repository/{image-name}`. + ## Outputs _None._ diff --git a/action.test.ts b/action.test.ts index 32dc950..b98e37b 100644 --- a/action.test.ts +++ b/action.test.ts @@ -132,4 +132,30 @@ describe('action', () => { ), ); }); + + test('supports an external registry', async () => { + repo = 'test-image'; + process.env['GITHUB_REPOSITORY'] = repo; + + setInput('external-registry-url', 'ghcr.io/humanitec/build-push-to-humanitec'); + + await runAction(); + expect(process.exitCode).toBeFalsy; + + const res = await humanitecReq(`orgs/${orgId}/artefact-versions`, {method: 'GET'}); + expect(res.status).toBe(200); + + const body = await res.json(); + + expect(body).toEqual( + expect.arrayContaining( + [ + expect.objectContaining({ + commit: commit, + name: `ghcr.io/humanitec/build-push-to-humanitec/${repo}`, + }), + ], + ), + ); + }); }); diff --git a/action.ts b/action.ts index 49a1f4e..8f72235 100644 --- a/action.ts +++ b/action.ts @@ -14,12 +14,13 @@ export async function runAction() { const imageName = core.getInput('image-name') || (process.env.GITHUB_REPOSITORY || '').replace(/.*\//, ''); const context = core.getInput('context') || core.getInput('dockerfile') || '.'; const file = core.getInput('file') || ''; - const registryHost = core.getInput('humanitec-registry') || 'registry.humanitec.io'; + let registryHost = core.getInput('humanitec-registry') || 'registry.humanitec.io'; const apiHost = core.getInput('humanitec-api') || 'api.humanitec.io'; const tag = core.getInput('tag') || ''; const commit = process.env.GITHUB_SHA || ''; const autoTag = /^\s*(true|1)\s*$/i.test(core.getInput('auto-tag')); const additionalDockerArguments = core.getInput('additional-docker-arguments') || ''; + const externalRegistryUrl = core.getInput('external-registry-url') || ''; const ref = process.env.GITHUB_REF || ''; if (!existsSync(`${process.env.GITHUB_WORKSPACE}/.git`)) { @@ -44,20 +45,25 @@ export async function runAction() { const humanitec = humanitecFactory(token, orgId, apiHost); - let registryCreds; - try { - registryCreds = await humanitec.getRegistryCredentials(); - } catch (error) { - core.error('Unable to fetch repository credentials.'); - core.error('Did you add the token to your Github Secrets? ' + + if (externalRegistryUrl == '') { + let registryCreds; + try { + registryCreds = await humanitec.getRegistryCredentials(); + } catch (error) { + core.error('Unable to fetch repository credentials.'); + core.error('Did you add the token to your Github Secrets? ' + 'http:/docs.humanitec.com/connecting-your-ci#github-actions'); - core.setFailed('Unable to access Humanitec.'); - return; - } + core.setFailed('Unable to access Humanitec.'); + return; + } - if (!docker.login(registryCreds.username, registryCreds.password, registryHost)) { - core.setFailed('Unable to connect to the humanitec registry.'); - return; + if (!docker.login(registryCreds.username, registryCreds.password, registryHost)) { + core.setFailed('Unable to connect to the humanitec registry.'); + return; + } + registryHost = `${registryHost}/${orgId}`; + } else { + registryHost = externalRegistryUrl; } process.chdir((process.env.GITHUB_WORKSPACE || '')); @@ -70,21 +76,22 @@ export async function runAction() { } else { version = commit; } - const localTag = `${orgId}/${imageName}:${version}`; + const imageWithVersion = `${imageName}:${version}`; + const localTag = `${orgId}/${imageWithVersion}`; const imageId = await docker.build(localTag, file, additionalDockerArguments, context); if (!imageId) { core.setFailed('Unable build image from Dockerfile.'); return; } - const remoteTag = `${registryHost}/${localTag}`; + const remoteTag = `${registryHost}/${imageWithVersion}`; if (!docker.push(imageId, remoteTag)) { core.setFailed('Unable to push image to registry'); return; } const payload = { - name: `${registryHost}/${orgId}/${imageName}`, + name: `${registryHost}/${imageName}`, type: 'container', version, ref, diff --git a/action.yaml b/action.yaml index 8e2e62e..ced87c5 100644 --- a/action.yaml +++ b/action.yaml @@ -38,6 +38,10 @@ inputs: description: 'Allows the default humanitec api to be overidden for testing.' required: false default: 'api.humanitec.io' + external-registry-url: + description: 'Push the image to an external container registry. This registry does not need to be [registered with Humanitec](https://docs.humanitec.com/guides/connect-ci-setup/container-registries) and authentication needs to be done by the explicitly (e.g. using workload identity).' + required: false + default: '' runs: using: 'node16' main: 'dist/index.js' diff --git a/chunk.ts b/chunk.ts deleted file mode 100644 index e310f48..0000000 --- a/chunk.ts +++ /dev/null @@ -1,68 +0,0 @@ -export const args = (str: string): string[] => { - const args = []; - str = str.trim(); - let currentArg = ''; - for (let i = 0; i < str.length; i++) { - switch (str[i]) { - case '\'': - const endQuoteIndex = str.indexOf('\'', i+1); - if (endQuoteIndex < 0) { - throw 'single quote not closed'; - } - currentArg = currentArg + str.substring(i+1, endQuoteIndex); - i = endQuoteIndex; - break; - case '"': - // Double quotes can contain escaped characters - for (i++; i < str.length && str[i] !== '"'; i++) { - if (str[i] === '\\' && (i+1) < str.length) { - i++; - switch (str[i]) { - case 'n': - currentArg += '\n'; - break; - case 'r': - currentArg += '\r'; - break; - case 't': - currentArg += '\t'; - break; - default: - currentArg += str[i]; - } - } else { - currentArg += str[i]; - } - } - if (i >= str.length) { - throw 'double quote not closed'; - } - break; - case ' ': - case '\t': - args.push(currentArg); - currentArg = ''; - while (i < str.length && (str[i] === ' ' || str[i] === '\t')) { - i++; - } - // We will have advanced to the next non-whitespace - i--; - break; - case '\\': - i++; - if (i < str.length) { - currentArg = currentArg + str[i]; - } else { - throw 'uncompleted escape character'; - } - break; - default: - currentArg = currentArg + str[i]; - break; - } - } - if (currentArg != '') { - args.push(currentArg); - } - return args; -} diff --git a/dist/index.js b/dist/index.js index 89a4edd..15adac7 100644 --- a/dist/index.js +++ b/dist/index.js @@ -7773,12 +7773,13 @@ async function runAction() { const imageName = core.getInput('image-name') || (process.env.GITHUB_REPOSITORY || '').replace(/.*\//, ''); const context = core.getInput('context') || core.getInput('dockerfile') || '.'; const file = core.getInput('file') || ''; - const registryHost = core.getInput('humanitec-registry') || 'registry.humanitec.io'; + let registryHost = core.getInput('humanitec-registry') || 'registry.humanitec.io'; const apiHost = core.getInput('humanitec-api') || 'api.humanitec.io'; const tag = core.getInput('tag') || ''; const commit = process.env.GITHUB_SHA || ''; const autoTag = /^\s*(true|1)\s*$/i.test(core.getInput('auto-tag')); const additionalDockerArguments = core.getInput('additional-docker-arguments') || ''; + const externalRegistryUrl = core.getInput('external-registry-url') || ''; const ref = process.env.GITHUB_REF || ''; if (!(0, node_fs_1.existsSync)(`${process.env.GITHUB_WORKSPACE}/.git`)) { core.error('It does not look like anything was checked out.'); @@ -7798,20 +7799,26 @@ async function runAction() { return; } const humanitec = (0, humanitec_1.humanitecFactory)(token, orgId, apiHost); - let registryCreds; - try { - registryCreds = await humanitec.getRegistryCredentials(); - } - catch (error) { - core.error('Unable to fetch repository credentials.'); - core.error('Did you add the token to your Github Secrets? ' + - 'http:/docs.humanitec.com/connecting-your-ci#github-actions'); - core.setFailed('Unable to access Humanitec.'); - return; + if (externalRegistryUrl == '') { + let registryCreds; + try { + registryCreds = await humanitec.getRegistryCredentials(); + } + catch (error) { + core.error('Unable to fetch repository credentials.'); + core.error('Did you add the token to your Github Secrets? ' + + 'http:/docs.humanitec.com/connecting-your-ci#github-actions'); + core.setFailed('Unable to access Humanitec.'); + return; + } + if (!docker.login(registryCreds.username, registryCreds.password, registryHost)) { + core.setFailed('Unable to connect to the humanitec registry.'); + return; + } + registryHost = `${registryHost}/${orgId}`; } - if (!docker.login(registryCreds.username, registryCreds.password, registryHost)) { - core.setFailed('Unable to connect to the humanitec registry.'); - return; + else { + registryHost = externalRegistryUrl; } process.chdir((process.env.GITHUB_WORKSPACE || '')); let version = ''; @@ -7824,19 +7831,20 @@ async function runAction() { else { version = commit; } - const localTag = `${orgId}/${imageName}:${version}`; + const imageWithVersion = `${imageName}:${version}`; + const localTag = `${orgId}/${imageWithVersion}`; const imageId = await docker.build(localTag, file, additionalDockerArguments, context); if (!imageId) { core.setFailed('Unable build image from Dockerfile.'); return; } - const remoteTag = `${registryHost}/${localTag}`; + const remoteTag = `${registryHost}/${imageWithVersion}`; if (!docker.push(imageId, remoteTag)) { core.setFailed('Unable to push image to registry'); return; } const payload = { - name: `${registryHost}/${orgId}/${imageName}`, + name: `${registryHost}/${imageName}`, type: 'container', version, ref,