From 073b6c28c32db4f9bb2e4a5f9d49fd3474b53f21 Mon Sep 17 00:00:00 2001 From: eddymarc <132223353+Edd88-pixel@users.noreply.github.com> Date: Tue, 25 Aug 2026 14:35:32 +0100 Subject: [PATCH 1/2] feat(jfrog): add pnpm package manager support --- registry/coder/modules/jfrog-oauth/README.md | 36 +++++++++++-- .../jfrog-oauth/jfrog-oauth.tftest.hcl | 53 +++++++++++++++++++ registry/coder/modules/jfrog-oauth/main.tf | 16 +++++- registry/coder/modules/jfrog-oauth/run.sh | 12 +++++ registry/coder/modules/jfrog-token/README.md | 20 ++++--- .../coder/modules/jfrog-token/main.test.ts | 21 ++++++++ registry/coder/modules/jfrog-token/main.tf | 16 +++++- registry/coder/modules/jfrog-token/run.sh | 12 +++++ 8 files changed, 172 insertions(+), 14 deletions(-) diff --git a/registry/coder/modules/jfrog-oauth/README.md b/registry/coder/modules/jfrog-oauth/README.md index 49668545e..f05a80235 100644 --- a/registry/coder/modules/jfrog-oauth/README.md +++ b/registry/coder/modules/jfrog-oauth/README.md @@ -8,7 +8,7 @@ tags: [integration, jfrog, helper] # JFrog -Install the JFrog CLI (`jf`) and authenticate package managers (npm, Go, pip, Docker, Conda, and Maven) with Artifactory using OAuth, configured via the Coder [`external-auth`](https://coder.com/docs/admin/external-auth) feature. Each user authenticates through an OAuth flow and receives a user-scoped access token, so no API keys or passwords are stored in the template or the workspace. +Install the JFrog CLI (`jf`) and authenticate package managers (npm, pnpm, Go, pip, Docker, Conda, and Maven) with Artifactory using OAuth, configured via the Coder [`external-auth`](https://coder.com/docs/admin/external-auth) feature. Each user authenticates through an OAuth flow and receives a user-scoped access token, so no API keys or passwords are stored in the template or the workspace. ![JFrog OAuth](../../.images/jfrog-oauth.png) @@ -16,13 +16,14 @@ Install the JFrog CLI (`jf`) and authenticate package managers (npm, Go, pip, Do module "jfrog" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/jfrog-oauth/coder" - version = "1.2.5" + version = "1.3.0" agent_id = coder_agent.main.id jfrog_url = "https://example.jfrog.io" username_field = "username" # If you are using GitHub to login to both Coder and Artifactory, use username_field = "username" package_managers = { npm = ["npm", "@scoped:npm-scoped"] + pnpm = ["npm", "@scoped:npm-scoped"] go = ["go", "another-go-repo"] pypi = ["pypi", "extra-index-pypi"] docker = ["example-docker-staging.jfrog.io", "example-docker-production.jfrog.io"] @@ -34,7 +35,9 @@ module "jfrog" { ``` > Note -> This module does not install `npm`, `go`, `pip`, etc but only configure them. You need to handle the installation of these tools yourself. +> This module does not install `npm`, `pnpm`, `go`, `pip`, etc but only configures them. You need to handle the installation of these tools yourself. +> +> `jf pnpm` requires Node.js 20 or newer and pnpm 10.x or 11.x. Use JFrog CLI 2.98.0 or newer for pnpm 10.x and 2.116.0 or newer for pnpm 11.x. ## Prerequisites @@ -90,13 +93,36 @@ The module automatically extracts your JFrog username directly from the OAuth to ## Examples +### Configure pnpm to use an Artifactory npm repository + +```tf +module "jfrog" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/jfrog-oauth/coder" + version = "1.3.0" + agent_id = coder_agent.main.id + jfrog_url = "https://example.jfrog.io" + username_field = "username" + + package_managers = { + pnpm = ["npm-local"] + } +} +``` + +pnpm and npm share `~/.npmrc`, so their repository lists must match when both are configured. After pnpm is installed in the workspace, run: + +```shell +jf pnpm install +``` + Configure the Python pip package manager to fetch packages from Artifactory while mapping the Coder email to the Artifactory username. ```tf module "jfrog" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/jfrog-oauth/coder" - version = "1.2.5" + version = "1.3.0" agent_id = coder_agent.main.id jfrog_url = "https://example.jfrog.io" username_field = "email" @@ -126,7 +152,7 @@ The [JFrog extension](https://open-vsx.org/extension/JFrog/jfrog-vscode-extensio module "jfrog" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/jfrog-oauth/coder" - version = "1.2.5" + version = "1.3.0" agent_id = coder_agent.main.id jfrog_url = "https://example.jfrog.io" username_field = "username" # If you are using GitHub to login to both Coder and Artifactory, use username_field = "username" diff --git a/registry/coder/modules/jfrog-oauth/jfrog-oauth.tftest.hcl b/registry/coder/modules/jfrog-oauth/jfrog-oauth.tftest.hcl index e89f78d97..e7659e0a1 100644 --- a/registry/coder/modules/jfrog-oauth/jfrog-oauth.tftest.hcl +++ b/registry/coder/modules/jfrog-oauth/jfrog-oauth.tftest.hcl @@ -152,6 +152,59 @@ run "test_with_npm_package_manager" { } } +run "test_with_pnpm_package_manager" { + command = plan + + variables { + agent_id = "test-agent-id" + jfrog_url = "https://example.jfrog.io" + package_managers = { + pnpm = ["global", "@foo:foo"] + } + } + + override_data { + target = data.coder_external_auth.jfrog + values = { + access_token = "valid-token-value" + } + } + + assert { + condition = strcontains(resource.coder_script.jfrog.script, "jf pnpmc --global --repo-resolve \"global\"") + error_message = "script should contain jf pnpmc command for pnpm" + } + + assert { + condition = strcontains(resource.coder_script.jfrog.script, "@foo:registry=https://example.jfrog.io/artifactory/api/npm/foo") + error_message = "script should contain scoped pnpm registry for @foo" + } +} + +run "test_mismatched_npm_pnpm_repositories" { + command = plan + + variables { + agent_id = "test-agent-id" + jfrog_url = "https://example.jfrog.io" + package_managers = { + npm = ["npm-virtual"] + pnpm = ["pnpm-virtual"] + } + } + + override_data { + target = data.coder_external_auth.jfrog + values = { + access_token = "valid-token-value" + } + } + + expect_failures = [ + var.package_managers, + ] +} + run "test_configure_code_server" { command = plan diff --git a/registry/coder/modules/jfrog-oauth/main.tf b/registry/coder/modules/jfrog-oauth/main.tf index 0bfb02bea..e70f2f21a 100644 --- a/registry/coder/modules/jfrog-oauth/main.tf +++ b/registry/coder/modules/jfrog-oauth/main.tf @@ -55,6 +55,7 @@ variable "configure_code_server" { variable "package_managers" { type = object({ npm = optional(list(string), []) + pnpm = optional(list(string), []) go = optional(list(string), []) pypi = optional(list(string), []) docker = optional(list(string), []) @@ -66,6 +67,7 @@ variable "package_managers" { For example: { npm = ["GLOBAL_NPM_REPO_KEY", "@SCOPED:NPM_REPO_KEY"] + pnpm = ["GLOBAL_NPM_REPO_KEY", "@SCOPED:NPM_REPO_KEY"] go = ["YOUR_GO_REPO_KEY", "ANOTHER_GO_REPO_KEY"] pypi = ["YOUR_PYPI_REPO_KEY", "ANOTHER_PYPI_REPO_KEY"] docker = ["YOUR_DOCKER_REPO_KEY", "ANOTHER_DOCKER_REPO_KEY"] @@ -73,6 +75,15 @@ variable "package_managers" { maven = ["YOUR_MAVEN_REPO_KEY", "ANOTHER_MAVEN_REPO_KEY"] } EOF + + validation { + condition = ( + length(var.package_managers.npm) == 0 || + length(var.package_managers.pnpm) == 0 || + var.package_managers.npm == var.package_managers.pnpm + ) + error_message = "package_managers.npm and package_managers.pnpm must use the same repository list when both are configured because npm and pnpm share ~/.npmrc." + } } locals { @@ -106,13 +117,14 @@ locals { ARTIFACTORY_EMAIL = data.coder_workspace_owner.me.email ARTIFACTORY_ACCESS_TOKEN = data.coder_external_auth.jfrog.access_token } + npm_repositories = length(var.package_managers.npm) > 0 ? var.package_managers.npm : var.package_managers.pnpm npmrc = templatefile( "${path.module}/.npmrc.tftpl", merge( local.common_values, { REPOS = [ - for r in var.package_managers.npm : + for r in local.npm_repositories : strcontains(r, ":") ? zipmap(["SCOPE", "NAME"], ["${split(":", r)[0]}:", split(":", r)[1]]) : { SCOPE = "", NAME = r } ] } @@ -145,8 +157,10 @@ resource "coder_script" "jfrog" { { CONFIGURE_CODE_SERVER = var.configure_code_server HAS_NPM = length(var.package_managers.npm) == 0 ? "" : "YES" + HAS_PNPM = length(var.package_managers.pnpm) == 0 ? "" : "YES" NPMRC = local.npmrc REPOSITORY_NPM = try(element(var.package_managers.npm, 0), "") + REPOSITORY_PNPM = try(element(var.package_managers.pnpm, 0), "") HAS_GO = length(var.package_managers.go) == 0 ? "" : "YES" REPOSITORY_GO = try(element(var.package_managers.go, 0), "") HAS_PYPI = length(var.package_managers.pypi) == 0 ? "" : "YES" diff --git a/registry/coder/modules/jfrog-oauth/run.sh b/registry/coder/modules/jfrog-oauth/run.sh index 52f1e851e..97c2f6af9 100644 --- a/registry/coder/modules/jfrog-oauth/run.sh +++ b/registry/coder/modules/jfrog-oauth/run.sh @@ -46,6 +46,18 @@ EOF config_complete fi +# Configure pnpm to use the Artifactory npm repository. +if [ -z "${HAS_PNPM}" ]; then + not_configured pnpm +else + echo "📦 Configuring pnpm..." + jf pnpmc --global --repo-resolve "${REPOSITORY_PNPM}" + cat << EOF > ~/.npmrc +${NPMRC} +EOF + config_complete +fi + # Configure the `pip` to use the Artifactory "python" repository. if [ -z "${HAS_PYPI}" ]; then not_configured pypi diff --git a/registry/coder/modules/jfrog-token/README.md b/registry/coder/modules/jfrog-token/README.md index 5252a8642..9cba50859 100644 --- a/registry/coder/modules/jfrog-token/README.md +++ b/registry/coder/modules/jfrog-token/README.md @@ -13,12 +13,13 @@ Install the JF CLI and authenticate package managers with Artifactory using Arti ```tf module "jfrog" { source = "registry.coder.com/coder/jfrog-token/coder" - version = "1.2.2" + version = "1.3.0" agent_id = coder_agent.main.id jfrog_url = "https://XXXX.jfrog.io" artifactory_access_token = var.artifactory_access_token package_managers = { npm = ["npm", "@scoped:npm-scoped"] + pnpm = ["npm", "@scoped:npm-scoped"] go = ["go", "another-go-repo"] pypi = ["pypi", "extra-index-pypi"] docker = ["example-docker-staging.jfrog.io", "example-docker-production.jfrog.io"] @@ -32,23 +33,26 @@ module "jfrog" { For detailed instructions, please see this [guide](https://coder.com/docs/v2/latest/guides/artifactory-integration#jfrog-token) on the Coder documentation. > Note -> This module does not install `npm`, `go`, `pip`, etc but only configure them. You need to handle the installation of these tools yourself. +> This module does not install `npm`, `pnpm`, `go`, `pip`, etc but only configures them. You need to handle the installation of these tools yourself. +> +> `jf pnpm` requires Node.js 20 or newer and pnpm 10.x or 11.x. Use JFrog CLI 2.98.0 or newer for pnpm 10.x and 2.116.0 or newer for pnpm 11.x. ![JFrog](../../.images/jfrog.png) ## Examples -### Configure npm, go, and pypi to use Artifactory local repositories +### Configure npm, pnpm, go, and pypi to use Artifactory local repositories ```tf module "jfrog" { source = "registry.coder.com/coder/jfrog-token/coder" - version = "1.2.2" + version = "1.3.0" agent_id = coder_agent.main.id jfrog_url = "https://YYYY.jfrog.io" artifactory_access_token = var.artifactory_access_token # An admin access token package_managers = { npm = ["npm-local"] + pnpm = ["npm-local"] go = ["go-local"] pypi = ["pypi-local"] conda = ["conda-local"] @@ -58,10 +62,11 @@ module "jfrog" { } ``` -You should now be able to install packages from Artifactory using both the `jf npm`, `jf go`, `jf pip` and `npm`, `go`, `pip`, `conda`, `maven` commands. +Because npm and pnpm share `~/.npmrc`, their repository lists must match when both are configured. You should now be able to install packages from Artifactory using `jf npm`, `jf pnpm`, `jf go`, `jf pip`, and the native package-manager commands. ```shell jf npm install prettier +jf pnpm install jf go get github.com/golang/example/hello jf pip install requests conda install numpy @@ -70,6 +75,7 @@ mvn clean install ```shell npm install prettier +pnpm install go get github.com/golang/example/hello pip install requests conda install numpy @@ -83,7 +89,7 @@ The [JFrog extension](https://open-vsx.org/extension/JFrog/jfrog-vscode-extensio ```tf module "jfrog" { source = "registry.coder.com/coder/jfrog-token/coder" - version = "1.2.2" + version = "1.3.0" agent_id = coder_agent.main.id jfrog_url = "https://XXXX.jfrog.io" artifactory_access_token = var.artifactory_access_token @@ -104,7 +110,7 @@ data "coder_workspace" "me" {} module "jfrog" { source = "registry.coder.com/coder/jfrog-token/coder" - version = "1.2.2" + version = "1.3.0" agent_id = coder_agent.main.id jfrog_url = "https://XXXX.jfrog.io" artifactory_access_token = var.artifactory_access_token diff --git a/registry/coder/modules/jfrog-token/main.test.ts b/registry/coder/modules/jfrog-token/main.test.ts index cabd7e80f..3a8e18114 100644 --- a/registry/coder/modules/jfrog-token/main.test.ts +++ b/registry/coder/modules/jfrog-token/main.test.ts @@ -91,6 +91,27 @@ EOF`; ); }); + it("configures pnpm with npm-compatible repositories", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "some-agent-id", + jfrog_url: fakeFrogUrl, + artifactory_access_token: "XXXX", + package_managers: JSON.stringify({ + pnpm: ["global", "@foo:foo"], + }), + }); + const coderScript = findResourceInstance(state, "coder_script"); + expect(coderScript.script).toContain( + 'jf pnpmc --global --repo-resolve "global"', + ); + expect(coderScript.script).toContain( + `@foo:registry=http://${fakeFrogApi}/npm/foo`, + ); + expect(coderScript.script).toContain( + 'if [ -z "YES" ]; then\n not_configured pnpm', + ); + }); + it("generates a pip config with extra-indexes", async () => { const state = await runTerraformApply(import.meta.dir, { agent_id: "some-agent-id", diff --git a/registry/coder/modules/jfrog-token/main.tf b/registry/coder/modules/jfrog-token/main.tf index 4de91da5a..6ca2b3e81 100644 --- a/registry/coder/modules/jfrog-token/main.tf +++ b/registry/coder/modules/jfrog-token/main.tf @@ -88,6 +88,7 @@ variable "configure_code_server" { variable "package_managers" { type = object({ npm = optional(list(string), []) + pnpm = optional(list(string), []) go = optional(list(string), []) pypi = optional(list(string), []) docker = optional(list(string), []) @@ -99,6 +100,7 @@ variable "package_managers" { For example: { npm = ["GLOBAL_NPM_REPO_KEY", "@SCOPED:NPM_REPO_KEY"] + pnpm = ["GLOBAL_NPM_REPO_KEY", "@SCOPED:NPM_REPO_KEY"] go = ["YOUR_GO_REPO_KEY", "ANOTHER_GO_REPO_KEY"] pypi = ["YOUR_PYPI_REPO_KEY", "ANOTHER_PYPI_REPO_KEY"] docker = ["YOUR_DOCKER_REPO_KEY", "ANOTHER_DOCKER_REPO_KEY"] @@ -106,6 +108,15 @@ variable "package_managers" { maven = ["YOUR_MAVEN_REPO_KEY", "ANOTHER_MAVEN_REPO_KEY"] } EOF + + validation { + condition = ( + length(var.package_managers.npm) == 0 || + length(var.package_managers.pnpm) == 0 || + var.package_managers.npm == var.package_managers.pnpm + ) + error_message = "package_managers.npm and package_managers.pnpm must use the same repository list when both are configured because npm and pnpm share ~/.npmrc." + } } locals { @@ -120,13 +131,14 @@ locals { ARTIFACTORY_EMAIL = data.coder_workspace_owner.me.email ARTIFACTORY_ACCESS_TOKEN = artifactory_scoped_token.me.access_token } + npm_repositories = length(var.package_managers.npm) > 0 ? var.package_managers.npm : var.package_managers.pnpm npmrc = templatefile( "${path.module}/.npmrc.tftpl", merge( local.common_values, { REPOS = [ - for r in var.package_managers.npm : + for r in local.npm_repositories : strcontains(r, ":") ? zipmap(["SCOPE", "NAME"], ["${split(":", r)[0]}:", split(":", r)[1]]) : { SCOPE = "", NAME = r } ] } @@ -172,8 +184,10 @@ resource "coder_script" "jfrog" { { CONFIGURE_CODE_SERVER = var.configure_code_server HAS_NPM = length(var.package_managers.npm) == 0 ? "" : "YES" + HAS_PNPM = length(var.package_managers.pnpm) == 0 ? "" : "YES" NPMRC = local.npmrc REPOSITORY_NPM = try(element(var.package_managers.npm, 0), "") + REPOSITORY_PNPM = try(element(var.package_managers.pnpm, 0), "") HAS_GO = length(var.package_managers.go) == 0 ? "" : "YES" REPOSITORY_GO = try(element(var.package_managers.go, 0), "") HAS_PYPI = length(var.package_managers.pypi) == 0 ? "" : "YES" diff --git a/registry/coder/modules/jfrog-token/run.sh b/registry/coder/modules/jfrog-token/run.sh index 182b37a2b..a0b2a4e19 100644 --- a/registry/coder/modules/jfrog-token/run.sh +++ b/registry/coder/modules/jfrog-token/run.sh @@ -45,6 +45,18 @@ EOF config_complete fi +# Configure pnpm to use the Artifactory npm repository. +if [ -z "${HAS_PNPM}" ]; then + not_configured pnpm +else + echo "📦 Configuring pnpm..." + jf pnpmc --global --repo-resolve "${REPOSITORY_PNPM}" + cat << EOF > ~/.npmrc +${NPMRC} +EOF + config_complete +fi + # Configure the `pip` to use the Artifactory "python" repository. if [ -z "${HAS_PYPI}" ]; then not_configured pypi From 6f829963019359c91e0c497874a3e1137fca1adf Mon Sep 17 00:00:00 2001 From: eddymarc <132223353+Edd88-pixel@users.noreply.github.com> Date: Thu, 27 Aug 2026 13:44:21 +0100 Subject: [PATCH 2/2] fix(jfrog): avoid duplicate npmrc writes --- .../jfrog-oauth/jfrog-oauth.tftest.hcl | 35 +++++++++++++++++++ registry/coder/modules/jfrog-oauth/run.sh | 8 ++--- .../coder/modules/jfrog-token/main.test.ts | 22 ++++++++++++ registry/coder/modules/jfrog-token/run.sh | 8 ++--- 4 files changed, 65 insertions(+), 8 deletions(-) diff --git a/registry/coder/modules/jfrog-oauth/jfrog-oauth.tftest.hcl b/registry/coder/modules/jfrog-oauth/jfrog-oauth.tftest.hcl index e7659e0a1..ec6f3049d 100644 --- a/registry/coder/modules/jfrog-oauth/jfrog-oauth.tftest.hcl +++ b/registry/coder/modules/jfrog-oauth/jfrog-oauth.tftest.hcl @@ -181,6 +181,41 @@ run "test_with_pnpm_package_manager" { } } +run "test_with_npm_and_pnpm_package_managers" { + command = plan + + variables { + agent_id = "test-agent-id" + jfrog_url = "https://example.jfrog.io" + package_managers = { + npm = ["global", "@foo:foo"] + pnpm = ["global", "@foo:foo"] + } + } + + override_data { + target = data.coder_external_auth.jfrog + values = { + access_token = "valid-token-value" + } + } + + assert { + condition = strcontains(resource.coder_script.jfrog.script, "jf npmc --global --repo-resolve \"global\"") + error_message = "script should contain jf npmc command for npm" + } + + assert { + condition = strcontains(resource.coder_script.jfrog.script, "jf pnpmc --global --repo-resolve \"global\"") + error_message = "script should contain jf pnpmc command for pnpm" + } + + assert { + condition = length(regexall("cat << EOF > ~/.npmrc", resource.coder_script.jfrog.script)) == 1 + error_message = "script should write the shared npmrc exactly once" + } +} + run "test_mismatched_npm_pnpm_repositories" { command = plan diff --git a/registry/coder/modules/jfrog-oauth/run.sh b/registry/coder/modules/jfrog-oauth/run.sh index 97c2f6af9..5ff0531d3 100644 --- a/registry/coder/modules/jfrog-oauth/run.sh +++ b/registry/coder/modules/jfrog-oauth/run.sh @@ -40,9 +40,6 @@ if [ -z "${HAS_NPM}" ]; then else echo "📦 Configuring npm..." jf npmc --global --repo-resolve "${REPOSITORY_NPM}" - cat << EOF > ~/.npmrc -${NPMRC} -EOF config_complete fi @@ -52,10 +49,13 @@ if [ -z "${HAS_PNPM}" ]; then else echo "📦 Configuring pnpm..." jf pnpmc --global --repo-resolve "${REPOSITORY_PNPM}" + config_complete +fi + +if [ -n "${HAS_NPM}" ] || [ -n "${HAS_PNPM}" ]; then cat << EOF > ~/.npmrc ${NPMRC} EOF - config_complete fi # Configure the `pip` to use the Artifactory "python" repository. diff --git a/registry/coder/modules/jfrog-token/main.test.ts b/registry/coder/modules/jfrog-token/main.test.ts index 3a8e18114..a286ffdd4 100644 --- a/registry/coder/modules/jfrog-token/main.test.ts +++ b/registry/coder/modules/jfrog-token/main.test.ts @@ -112,6 +112,28 @@ EOF`; ); }); + it("writes one npmrc when npm and pnpm are configured", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "some-agent-id", + jfrog_url: fakeFrogUrl, + artifactory_access_token: "XXXX", + package_managers: JSON.stringify({ + npm: ["global", "@foo:foo"], + pnpm: ["global", "@foo:foo"], + }), + }); + const coderScript = findResourceInstance(state, "coder_script"); + expect(coderScript.script).toContain( + 'jf npmc --global --repo-resolve "global"', + ); + expect(coderScript.script).toContain( + 'jf pnpmc --global --repo-resolve "global"', + ); + expect( + coderScript.script.match(/cat << EOF > ~\/\.npmrc/g) ?? [], + ).toHaveLength(1); + }); + it("generates a pip config with extra-indexes", async () => { const state = await runTerraformApply(import.meta.dir, { agent_id: "some-agent-id", diff --git a/registry/coder/modules/jfrog-token/run.sh b/registry/coder/modules/jfrog-token/run.sh index a0b2a4e19..519e01399 100644 --- a/registry/coder/modules/jfrog-token/run.sh +++ b/registry/coder/modules/jfrog-token/run.sh @@ -39,9 +39,6 @@ if [ -z "${HAS_NPM}" ]; then else echo "📦 Configuring npm..." jf npmc --global --repo-resolve "${REPOSITORY_NPM}" - cat << EOF > ~/.npmrc -${NPMRC} -EOF config_complete fi @@ -51,10 +48,13 @@ if [ -z "${HAS_PNPM}" ]; then else echo "📦 Configuring pnpm..." jf pnpmc --global --repo-resolve "${REPOSITORY_PNPM}" + config_complete +fi + +if [ -n "${HAS_NPM}" ] || [ -n "${HAS_PNPM}" ]; then cat << EOF > ~/.npmrc ${NPMRC} EOF - config_complete fi # Configure the `pip` to use the Artifactory "python" repository.