diff --git a/src/job.ts b/src/job.ts index 899bc707..03acd11e 100644 --- a/src/job.ts +++ b/src/job.ts @@ -690,6 +690,17 @@ export class Job { this.refreshLongRunningSilentTimeout(writeStreams); if (imageName && !this._containerId) { + const CI_DEPENDENCY_PROXY_SERVER = this._variables["CI_DEPENDENCY_PROXY_SERVER"]; + if (imageName.startsWith(CI_DEPENDENCY_PROXY_SERVER)) { + try { + await Utils.spawn([this.argv.containerExecutable, "login", CI_DEPENDENCY_PROXY_SERVER], cwd); + } catch (e: any) { + assert(!e.stderr.includes("Cannot perform an interactive login"), + `Please authenticate to the Dependency Proxy (${CI_DEPENDENCY_PROXY_SERVER}) https://docs.gitlab.com/ee/user/packages/dependency_proxy/#authenticate-with-the-dependency-proxy` + ); + throw e; + } + } await this.pullImage(writeStreams, imageName); let dockerCmd = `${this.argv.containerExecutable} create --interactive ${this.generateInjectSSHAgentOptions()} `; diff --git a/src/predefined-variables.ts b/src/predefined-variables.ts index 02ade0bf..b5200736 100644 --- a/src/predefined-variables.ts +++ b/src/predefined-variables.ts @@ -1,5 +1,6 @@ import camelCase from "camelcase"; import {GitData} from "./git-data"; +import {Utils} from "./utils"; type PredefinedVariablesOpts = { gitData: GitData; @@ -7,6 +8,10 @@ type PredefinedVariablesOpts = { }; export function init ({gitData, argv}: PredefinedVariablesOpts): {[name: string]: string} { + const CI_SERVER_URL = `https://${gitData.remote.host}:443`; + const CI_PROJECT_ROOT_NAMESPACE = gitData.remote.group.split("/")[0]; + const CI_PROJECT_NAMESPACE = gitData.remote.group; + const CI_DEPENDENCY_PROXY_SERVER = Utils.removePrefix(CI_SERVER_URL, "https://"); const predefinedVariables: {[key: string]: string} = { CI: "true", GITLAB_USER_LOGIN: gitData.user["GITLAB_USER_LOGIN"], @@ -16,10 +21,11 @@ export function init ({gitData, argv}: PredefinedVariablesOpts): {[name: string] CI_COMMIT_SHORT_SHA: gitData.commit.SHA.slice(0, 8), // Changes CI_COMMIT_SHA: gitData.commit.SHA, CI_PROJECT_NAME: gitData.remote.project, + CI_PROJECT_ROOT_NAMESPACE: CI_PROJECT_ROOT_NAMESPACE, CI_PROJECT_TITLE: `${camelCase(gitData.remote.project)}`, CI_PROJECT_PATH: `${gitData.remote.group}/${gitData.remote.project}`, CI_PROJECT_PATH_SLUG: `${gitData.remote.group.replace(/\//g, "-")}-${gitData.remote.project}`.toLowerCase(), - CI_PROJECT_NAMESPACE: `${gitData.remote.group}`, + CI_PROJECT_NAMESPACE: CI_PROJECT_NAMESPACE, CI_PROJECT_VISIBILITY: "internal", CI_PROJECT_ID: "1217", CI_COMMIT_REF_PROTECTED: "false", @@ -37,12 +43,16 @@ export function init ({gitData, argv}: PredefinedVariablesOpts): {[name: string] CI_SERVER_FQDN: `${gitData.remote.host}`, CI_SERVER_HOST: `${gitData.remote.host}`, CI_SERVER_PORT: `${gitData.remote.port}`, - CI_SERVER_URL: `https://${gitData.remote.host}:443`, + CI_SERVER_URL: CI_SERVER_URL, CI_SERVER_PROTOCOL: "https", CI_API_V4_URL: `https://${gitData.remote.host}/api/v4`, CI_PROJECT_URL: `https://${gitData.remote.host}/${gitData.remote.group}/${gitData.remote.project}`, CI_TEMPLATE_REGISTRY_HOST: "registry.gitlab.com", GITLAB_CI: "false", + + CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX: `${CI_DEPENDENCY_PROXY_SERVER}/${CI_PROJECT_ROOT_NAMESPACE}/dependency_proxy/containers`, + CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX: `${CI_DEPENDENCY_PROXY_SERVER}/${CI_PROJECT_NAMESPACE}/dependency_proxy/containers`, + CI_DEPENDENCY_PROXY_SERVER: CI_DEPENDENCY_PROXY_SERVER, }; // Delete variables the user intentionally wants unset diff --git a/src/utils.ts b/src/utils.ts index 19b6ad5f..cd1b7ea1 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -24,6 +24,9 @@ type ExpandWith = { }; export class Utils { + static removePrefix = (value: string, prefix: string) => + value.startsWith(prefix) ? value.slice(prefix.length) : value; + static bash (shellScript: string, cwd = process.cwd()): Promise<{stdout: string; stderr: string; exitCode: number}> { return execa(shellScript, {shell: "bash", cwd}); } diff --git a/tests/test-cases/dependency-proxy/.gitlab-ci.yml b/tests/test-cases/dependency-proxy/.gitlab-ci.yml new file mode 100644 index 00000000..430c541f --- /dev/null +++ b/tests/test-cases/dependency-proxy/.gitlab-ci.yml @@ -0,0 +1,4 @@ +job: + image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/busybox:latest + script: + - echo 1 diff --git a/tests/test-cases/dependency-proxy/integration.dependency-proxy.test.ts b/tests/test-cases/dependency-proxy/integration.dependency-proxy.test.ts new file mode 100644 index 00000000..2de45a66 --- /dev/null +++ b/tests/test-cases/dependency-proxy/integration.dependency-proxy.test.ts @@ -0,0 +1,66 @@ +import {WriteStreamsMock} from "../../../src/write-streams"; +import {handler} from "../../../src/handler"; +// import chalk from "chalk"; +import {initSpawnSpy, initSpawnSpyReject} from "../../mocks/utils.mock"; +import {WhenStatics} from "../../mocks/when-statics"; +import {cleanupJobResources, Job} from "../../../src/job"; +import {Utils} from "../../../src/utils"; +import {type} from "os"; + +let jobs: Job[] = []; + +beforeEach(() => { + jobs = []; + initSpawnSpy(WhenStatics.all); +}); + +afterEach(async () => { + await cleanupJobResources(jobs); +}); + +describe("dependency-proxy", () => { + test("dependency proxy server not authenticated", async () => { + try { + const writeStreams = new WriteStreamsMock(); + // const ciDependencyProxyServerNotAuthenticated = { + // cmdArgs: "docker login gitlab.com:443".split(" "), + // rejection: { + // stderr: "Error: Cannot perform an interactive login from a non TTY device", + // }, + // }; + // initSpawnSpyReject([ciDependencyProxyServerNotAuthenticated]); + + await handler({ + cwd: "tests/test-cases/dependency-proxy", + }, writeStreams, jobs); + + } catch (e: any) { + expect(e.message).toEqual("Please authenticate to the Dependency Proxy (gitlab.com:443) https://docs.gitlab.com/ee/user/packages/dependency_proxy/#authenticate-with-the-dependency-proxy"); + return; + } + throw new Error("Error is expected but not thrown/caught"); + }); + + test("should attempt to pull the correct image", async () => { + const writeStreams = new WriteStreamsMock(); + + const ciDependencyProxyServerAuthenticated = { + cmdArgs: "docker login gitlab.com:443".split(" "), + returnValue: "Login Succeeded", + }; + initSpawnSpy([ciDependencyProxyServerAuthenticated]); + + try { + await handler({ + cwd: "tests/test-cases/dependency-proxy", + }, writeStreams, jobs); + } catch (e: any) { + // In ci environment, gitlab.com:443 is not authenticated, but at least this shows that we're pulling from the correct path + expect(e.shortMessage).toEqual("Command failed with exit code 1: docker pull gitlab.com:443/gcl/dependency_proxy/containers/busybox:latest"); + return; + } + throw new Error("Error is expected but not thrown/caught"); + + }); + +});