From f3e5bacb1ed2379bb351027b00174ba0908d1e18 Mon Sep 17 00:00:00 2001 From: Yao Ding Date: Fri, 11 Jul 2025 19:48:53 -0400 Subject: [PATCH] Add conda package manager support to JFrog modules --- registry/coder/modules/jfrog-oauth/README.md | 33 ++++++++++++++-- .../coder/modules/jfrog-oauth/condarc.tftpl | 5 +++ .../coder/modules/jfrog-oauth/main.test.ts | 21 ++++++++++ registry/coder/modules/jfrog-oauth/main.tf | 10 ++++- registry/coder/modules/jfrog-oauth/run.sh | 17 +++++++++ registry/coder/modules/jfrog-token/README.md | 38 ++++++++++++++++--- .../coder/modules/jfrog-token/condarc.tftpl | 5 +++ .../coder/modules/jfrog-token/main.test.ts | 22 +++++++++++ registry/coder/modules/jfrog-token/main.tf | 10 ++++- registry/coder/modules/jfrog-token/run.sh | 17 +++++++++ 10 files changed, 167 insertions(+), 11 deletions(-) create mode 100644 registry/coder/modules/jfrog-oauth/condarc.tftpl create mode 100644 registry/coder/modules/jfrog-token/condarc.tftpl diff --git a/registry/coder/modules/jfrog-oauth/README.md b/registry/coder/modules/jfrog-oauth/README.md index a1b9d295..f9f7bb79 100644 --- a/registry/coder/modules/jfrog-oauth/README.md +++ b/registry/coder/modules/jfrog-oauth/README.md @@ -28,6 +28,7 @@ module "jfrog" { go = ["go", "another-go-repo"] pypi = ["pypi", "extra-index-pypi"] docker = ["example-docker-staging.jfrog.io", "example-docker-production.jfrog.io"] + conda = ["conda", "another-conda-repo"] } } ``` @@ -68,6 +69,31 @@ jf pip install requests pip install requests ``` +### Configure conda package manager + +Configure the conda package manager to fetch packages from Artifactory. + +```tf +module "jfrog" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/jfrog-oauth/coder" + version = "1.0.19" + agent_id = coder_agent.example.id + jfrog_url = "https://example.jfrog.io" + username_field = "email" + + package_managers = { + conda = ["conda"] + } +} +``` + +You should now be able to install packages from Artifactory using conda. + +```shell +conda install numpy +``` + ### Configure code-server with JFrog extension The [JFrog extension](https://open-vsx.org/extension/JFrog/jfrog-vscode-extension) for VS Code allows you to interact with Artifactory from within the IDE. @@ -82,9 +108,10 @@ module "jfrog" { username_field = "username" # If you are using GitHub to login to both Coder and Artifactory, use username_field = "username" configure_code_server = true # Add JFrog extension configuration for code-server package_managers = { - npm = ["npm"] - go = ["go"] - pypi = ["pypi"] + npm = ["npm"] + go = ["go"] + pypi = ["pypi"] + conda = ["conda"] } } ``` diff --git a/registry/coder/modules/jfrog-oauth/condarc.tftpl b/registry/coder/modules/jfrog-oauth/condarc.tftpl new file mode 100644 index 00000000..0cb73b14 --- /dev/null +++ b/registry/coder/modules/jfrog-oauth/condarc.tftpl @@ -0,0 +1,5 @@ +channels: +%{ for REPO in REPOS ~} + - https://${ARTIFACTORY_USERNAME}:${ARTIFACTORY_ACCESS_TOKEN}@${JFROG_HOST}/artifactory/api/conda/${REPO} +%{ endfor ~} +ssl_verify: true \ No newline at end of file diff --git a/registry/coder/modules/jfrog-oauth/main.test.ts b/registry/coder/modules/jfrog-oauth/main.test.ts index 20ace697..7eb87867 100644 --- a/registry/coder/modules/jfrog-oauth/main.test.ts +++ b/registry/coder/modules/jfrog-oauth/main.test.ts @@ -126,4 +126,25 @@ EOF`; 'if [ -z "YES" ]; then\n not_configured go', ); }); + + it("generates a conda config with multiple repos", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "some-agent-id", + jfrog_url: fakeFrogUrl, + package_managers: JSON.stringify({ + conda: ["conda-main", "conda-forge"], + }), + }); + const coderScript = findResourceInstance(state, "coder_script"); + const condaStanza = `cat << EOF > ~/.condarc +channels: + - https://${user}:@${fakeFrogApi}/conda/conda-main + - https://${user}:@${fakeFrogApi}/conda/conda-forge +ssl_verify: true +EOF`; + expect(coderScript.script).toContain(condaStanza); + expect(coderScript.script).toContain( + 'if [ -z "YES" ]; then\n not_configured conda', + ); + }); }); diff --git a/registry/coder/modules/jfrog-oauth/main.tf b/registry/coder/modules/jfrog-oauth/main.tf index 0bc22568..1eff5975 100644 --- a/registry/coder/modules/jfrog-oauth/main.tf +++ b/registry/coder/modules/jfrog-oauth/main.tf @@ -58,6 +58,7 @@ variable "package_managers" { go = optional(list(string), []) pypi = optional(list(string), []) docker = optional(list(string), []) + conda = optional(list(string), []) }) description = <<-EOF A map of package manager names to their respective artifactory repositories. Unused package managers can be omitted. @@ -67,6 +68,7 @@ variable "package_managers" { 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"] + conda = ["YOUR_CONDA_REPO_KEY", "ANOTHER_CONDA_REPO_KEY"] } EOF } @@ -90,7 +92,7 @@ locals { { REPOS = [ for r in var.package_managers.npm : - strcontains(r, ":") ? zipmap(["SCOPE", "NAME"], ["${split(":", r)[0]}:", split(":", r)[1]]) : { SCOPE = "", NAME = r } + length(split(":", r)) > 1 ? zipmap(["SCOPE", "NAME"], ["${split(":", r)[0]}:", split(":", r)[1]]) : { SCOPE = "", NAME = r } ] } ) @@ -98,6 +100,9 @@ locals { pip_conf = templatefile( "${path.module}/pip.conf.tftpl", merge(local.common_values, { REPOS = var.package_managers.pypi }) ) + condarc = templatefile( + "${path.module}/condarc.tftpl", merge(local.common_values, { REPOS = var.package_managers.conda }) + ) } data "coder_workspace" "me" {} @@ -125,6 +130,9 @@ resource "coder_script" "jfrog" { REPOSITORY_PYPI = try(element(var.package_managers.pypi, 0), "") HAS_DOCKER = length(var.package_managers.docker) == 0 ? "" : "YES" REGISTER_DOCKER = join("\n", formatlist("register_docker \"%s\"", var.package_managers.docker)) + HAS_CONDA = length(var.package_managers.conda) == 0 ? "" : "YES" + CONDARC = local.condarc + REPOSITORY_CONDA = try(element(var.package_managers.conda, 0), "") } )) run_on_start = true diff --git a/registry/coder/modules/jfrog-oauth/run.sh b/registry/coder/modules/jfrog-oauth/run.sh index 7d36e47c..e82e9237 100644 --- a/registry/coder/modules/jfrog-oauth/run.sh +++ b/registry/coder/modules/jfrog-oauth/run.sh @@ -81,6 +81,23 @@ else fi fi +# Configure conda to use the Artifactory "conda" repository. +if [ -z "${HAS_CONDA}" ]; then + not_configured conda +else + echo "🐍 Configuring conda..." + if command -v conda > /dev/null 2>&1; then + cat << EOF > ~/.condarc +${CONDARC} +EOF + # Clear conda package cache to ensure fresh resolution + conda clean -a -y + else + echo "🤔 no conda is installed, skipping conda configuration." + fi + config_complete +fi + # Install the JFrog vscode extension for code-server. if [ "${CONFIGURE_CODE_SERVER}" == "true" ]; then while ! [ -x /tmp/code-server/bin/code-server ]; do diff --git a/registry/coder/modules/jfrog-token/README.md b/registry/coder/modules/jfrog-token/README.md index 26d98e61..1f6ed8f0 100644 --- a/registry/coder/modules/jfrog-token/README.md +++ b/registry/coder/modules/jfrog-token/README.md @@ -24,6 +24,7 @@ module "jfrog" { go = ["go", "another-go-repo"] pypi = ["pypi", "extra-index-pypi"] docker = ["example-docker-staging.jfrog.io", "example-docker-production.jfrog.io"] + conda = ["conda", "another-conda-repo"] } } ``` @@ -47,9 +48,10 @@ module "jfrog" { jfrog_url = "https://YYYY.jfrog.io" artifactory_access_token = var.artifactory_access_token # An admin access token package_managers = { - npm = ["npm-local"] - go = ["go-local"] - pypi = ["pypi-local"] + npm = ["npm-local"] + go = ["go-local"] + pypi = ["pypi-local"] + conda = ["conda-local"] } } ``` @@ -68,6 +70,29 @@ go get github.com/golang/example/hello pip install requests ``` +### Configure conda package manager + +Configure the conda package manager to fetch packages from Artifactory. + +```tf +module "jfrog" { + source = "registry.coder.com/coder/jfrog-token/coder" + version = "1.0.30" + agent_id = coder_agent.example.id + jfrog_url = "https://YYYY.jfrog.io" + artifactory_access_token = var.artifactory_access_token + package_managers = { + conda = ["conda-local"] + } +} +``` + +You should now be able to install packages from Artifactory using conda. + +```shell +conda install numpy +``` + ### Configure code-server with JFrog extension The [JFrog extension](https://open-vsx.org/extension/JFrog/jfrog-vscode-extension) for VS Code allows you to interact with Artifactory from within the IDE. @@ -81,9 +106,10 @@ module "jfrog" { artifactory_access_token = var.artifactory_access_token configure_code_server = true # Add JFrog extension configuration for code-server package_managers = { - npm = ["npm"] - go = ["go"] - pypi = ["pypi"] + npm = ["npm"] + go = ["go"] + pypi = ["pypi"] + conda = ["conda"] } } ``` diff --git a/registry/coder/modules/jfrog-token/condarc.tftpl b/registry/coder/modules/jfrog-token/condarc.tftpl new file mode 100644 index 00000000..0cb73b14 --- /dev/null +++ b/registry/coder/modules/jfrog-token/condarc.tftpl @@ -0,0 +1,5 @@ +channels: +%{ for REPO in REPOS ~} + - https://${ARTIFACTORY_USERNAME}:${ARTIFACTORY_ACCESS_TOKEN}@${JFROG_HOST}/artifactory/api/conda/${REPO} +%{ endfor ~} +ssl_verify: true \ No newline at end of file diff --git a/registry/coder/modules/jfrog-token/main.test.ts b/registry/coder/modules/jfrog-token/main.test.ts index 4aeaba35..1133c5ef 100644 --- a/registry/coder/modules/jfrog-token/main.test.ts +++ b/registry/coder/modules/jfrog-token/main.test.ts @@ -162,4 +162,26 @@ EOF`; 'if [ -z "YES" ]; then\n not_configured go', ); }); + + it("generates a conda config with multiple repos", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "some-agent-id", + jfrog_url: fakeFrogUrl, + artifactory_access_token: "XXXX", + package_managers: JSON.stringify({ + conda: ["conda-main", "conda-forge"], + }), + }); + const coderScript = findResourceInstance(state, "coder_script"); + const condaStanza = `cat << EOF > ~/.condarc +channels: + - https://${user}:${token}@${fakeFrogApi}/conda/conda-main + - https://${user}:${token}@${fakeFrogApi}/conda/conda-forge +ssl_verify: true +EOF`; + expect(coderScript.script).toContain(condaStanza); + expect(coderScript.script).toContain( + 'if [ -z "YES" ]; then\n not_configured conda', + ); + }); }); diff --git a/registry/coder/modules/jfrog-token/main.tf b/registry/coder/modules/jfrog-token/main.tf index 720e2d8c..e59c64c1 100644 --- a/registry/coder/modules/jfrog-token/main.tf +++ b/registry/coder/modules/jfrog-token/main.tf @@ -91,6 +91,7 @@ variable "package_managers" { go = optional(list(string), []) pypi = optional(list(string), []) docker = optional(list(string), []) + conda = optional(list(string), []) }) description = <<-EOF A map of package manager names to their respective artifactory repositories. Unused package managers can be omitted. @@ -100,6 +101,7 @@ variable "package_managers" { 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"] + conda = ["YOUR_CONDA_REPO_KEY", "ANOTHER_CONDA_REPO_KEY"] } EOF } @@ -123,7 +125,7 @@ locals { { REPOS = [ for r in var.package_managers.npm : - strcontains(r, ":") ? zipmap(["SCOPE", "NAME"], ["${split(":", r)[0]}:", split(":", r)[1]]) : { SCOPE = "", NAME = r } + length(split(":", r)) > 1 ? zipmap(["SCOPE", "NAME"], ["${split(":", r)[0]}:", split(":", r)[1]]) : { SCOPE = "", NAME = r } ] } ) @@ -131,6 +133,9 @@ locals { pip_conf = templatefile( "${path.module}/pip.conf.tftpl", merge(local.common_values, { REPOS = var.package_managers.pypi }) ) + condarc = templatefile( + "${path.module}/condarc.tftpl", merge(local.common_values, { REPOS = var.package_managers.conda }) + ) } # Configure the Artifactory provider @@ -171,6 +176,9 @@ resource "coder_script" "jfrog" { REPOSITORY_PYPI = try(element(var.package_managers.pypi, 0), "") HAS_DOCKER = length(var.package_managers.docker) == 0 ? "" : "YES" REGISTER_DOCKER = join("\n", formatlist("register_docker \"%s\"", var.package_managers.docker)) + HAS_CONDA = length(var.package_managers.conda) == 0 ? "" : "YES" + CONDARC = local.condarc + REPOSITORY_CONDA = try(element(var.package_managers.conda, 0), "") } )) run_on_start = true diff --git a/registry/coder/modules/jfrog-token/run.sh b/registry/coder/modules/jfrog-token/run.sh index d3a1a74c..bbc2a0d9 100644 --- a/registry/coder/modules/jfrog-token/run.sh +++ b/registry/coder/modules/jfrog-token/run.sh @@ -80,6 +80,23 @@ else fi fi +# Configure conda to use the Artifactory "conda" repository. +if [ -z "${HAS_CONDA}" ]; then + not_configured conda +else + echo "🐍 Configuring conda..." + if command -v conda > /dev/null 2>&1; then + cat << EOF > ~/.condarc +${CONDARC} +EOF + # Clear conda package cache to ensure fresh resolution + conda clean -a -y + else + echo "🤔 no conda is installed, skipping conda configuration." + fi + config_complete +fi + # Install the JFrog vscode extension for code-server. if [ "${CONFIGURE_CODE_SERVER}" == "true" ]; then while ! [ -x /tmp/code-server/bin/code-server ]; do