diff --git a/jfrog-oauth/README.md b/jfrog-oauth/README.md index 8aad1e7..1cead1c 100644 --- a/jfrog-oauth/README.md +++ b/jfrog-oauth/README.md @@ -5,20 +5,20 @@ icon: ../.icons/jfrog.svg maintainer_github: coder partner_github: jfrog verified: true -tags: [integration] +tags: [integration, jfrog] --- # JFrog -Install the JF CLI and authenticate package managers with Artifactory using OAuth configured via Coder [`external-auth`](https://docs.coder.com/docs/admin/external-auth/) feature. +Install the JF CLI and authenticate package managers with Artifactory using OAuth configured via the Coder `external-auth` feature. ![JFrog OAuth](../.images/jfrog-oauth.png) ```hcl module "jfrog" { - source = "https://registry.coder.com/modules/jfrog" + source = "https://registry.coder.com/modules/jfrog-oauth" agent_id = coder_agent.example.id - jfrog_url = "https://YYYY.jfrog.io" + jfrog_url = "https://jfrog.example.com" auth_method = "oauth" username_field = "username" # If you are using GitHub to login to both Coder and Artifactory, use username_field = "username" package_managers = { diff --git a/jfrog-oauth/main.test.ts b/jfrog-oauth/main.test.ts index 82ad38b..a3931cc 100644 --- a/jfrog-oauth/main.test.ts +++ b/jfrog-oauth/main.test.ts @@ -6,7 +6,7 @@ import { testRequiredVariables, } from "../test"; -describe("jfrog", async () => { +describe("jfrog-oauth", async () => { await runTerraformInit(import.meta.dir); // Run a fake JFrog server so the provider can initialize @@ -35,7 +35,6 @@ describe("jfrog", async () => { testRequiredVariables(import.meta.dir, { agent_id: "some-agent-id", jfrog_url: "http://" + fakeFrogHost.hostname + ":" + fakeFrogHost.port, - artifactory_access_token: "XXXX", package_managers: "{}", }); }); diff --git a/jfrog-oauth/main.tf b/jfrog-oauth/main.tf index c74cd9f..0df4ae5 100644 --- a/jfrog-oauth/main.tf +++ b/jfrog-oauth/main.tf @@ -11,7 +11,7 @@ terraform { variable "jfrog_url" { type = string - description = "JFrog instance URL. e.g. https://YYY.jfrog.io" + description = "JFrog instance URL. e.g. https://jfrog.example.com" } variable "username_field" { diff --git a/jfrog-token/README.md b/jfrog-token/README.md new file mode 100644 index 0000000..ce1f7b7 --- /dev/null +++ b/jfrog-token/README.md @@ -0,0 +1,56 @@ +--- +display_name: JFrog (Token) +description: Install the JF CLI and authenticate with Artifactory using Artifactory terraform provider. +icon: ../.icons/jfrog.svg +maintainer_github: coder +partner_github: jfrog +verified: true +tags: [integration, jfrog] +--- + +# JFrog + +Install the JF CLI and authenticate package managers with Artifactory. + +```hcl +module "jfrog" { + source = "https://registry.coder.com/modules/jfrog-token" + agent_id = coder_agent.example.id + jfrog_url = "https://YYYY.jfrog.io" + artifactory_access_token = var.artifactory_access_token # An admin access token + package_managers = { + "npm": "npm-remote", + "go": "go-remote", + "pypi": "pypi-remote" + } +} +``` + +Get a JFrog access token from your Artifactory instance. The token must have admin permissions. It is recommended to store the token in a secret terraform variable. + +```hcl +variable "artifactory_access_token" { + type = string + sensitive = true +} +``` + +![JFrog](../.images/jfrog.png) + +## Examples + +### Configure npm, go, and pypi to use Artifactory local repositories + +```hcl +module "jfrog" { + source = "https://registry.coder.com/modules/jfrog-token" + agent_id = coder_agent.example.id + 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" + } +} +``` diff --git a/jfrog-token/main.test.ts b/jfrog-token/main.test.ts new file mode 100644 index 0000000..dc5f9bb --- /dev/null +++ b/jfrog-token/main.test.ts @@ -0,0 +1,41 @@ +import { serve } from "bun"; +import { describe } from "bun:test"; +import { + createJSONResponse, + runTerraformInit, + testRequiredVariables, +} from "../test"; + +describe("jfrog-token", async () => { + await runTerraformInit(import.meta.dir); + + // Run a fake JFrog server so the provider can initialize + // correctly. This saves us from having to make remote requests! + const fakeFrogHost = serve({ + fetch: (req) => { + const url = new URL(req.url); + // See https://jfrog.com/help/r/jfrog-rest-apis/license-information + if (url.pathname === "/artifactory/api/system/license") + return createJSONResponse({ + type: "Commercial", + licensedTo: "JFrog inc.", + validThrough: "May 15, 2036", + }); + if (url.pathname === "/access/api/v1/tokens") + return createJSONResponse({ + token_id: "xxx", + access_token: "xxx", + scope: "any", + }); + return createJSONResponse({}); + }, + port: 0, + }); + + testRequiredVariables(import.meta.dir, { + agent_id: "some-agent-id", + jfrog_url: "http://" + fakeFrogHost.hostname + ":" + fakeFrogHost.port, + artifactory_access_token: "XXXX", + package_managers: "{}", + }); +}); diff --git a/jfrog-token/main.tf b/jfrog-token/main.tf new file mode 100644 index 0000000..3a69512 --- /dev/null +++ b/jfrog-token/main.tf @@ -0,0 +1,89 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + coder = { + source = "coder/coder" + version = ">= 0.12" + } + artifactory = { + source = "registry.terraform.io/jfrog/artifactory" + version = "~> 9.8.0" + } + } +} + +variable "jfrog_url" { + type = string + description = "JFrog instance URL. e.g. https://YYY.jfrog.io" +} + +variable "artifactory_access_token" { + type = string + description = "The admin-level access token to use for JFrog." +} + +variable "username_field" { + type = string + description = "The field to use for the artifactory username. i.e. Coder username or email." + default = "email" + validation { + condition = can(regex("^(email|username)$", var.username_field)) + error_message = "username_field must be either 'email' or 'username'" + } +} + +variable "agent_id" { + type = string + description = "The ID of a Coder agent." +} + +variable "package_managers" { + type = map(string) + description = < 0 ? local.username : "dummy" + scopes = ["applied-permissions/user"] + refreshable = true +} + +data "coder_workspace" "me" {} + +resource "coder_script" "jfrog" { + agent_id = var.agent_id + display_name = "jfrog" + icon = "/icon/jfrog.svg" + script = templatefile("${path.module}/run.sh", { + JFROG_URL : var.jfrog_url, + JFROG_HOST : replace(var.jfrog_url, "https://", ""), + ARTIFACTORY_USERNAME : data.coder_workspace.me.owner_email, + ARTIFACTORY_ACCESS_TOKEN : artifactory_scoped_token.me.access_token, + REPOSITORY_NPM : lookup(var.package_managers, "npm", ""), + REPOSITORY_GO : lookup(var.package_managers, "go", ""), + REPOSITORY_PYPI : lookup(var.package_managers, "pypi", ""), + }) + run_on_start = true +} diff --git a/jfrog-token/run.sh b/jfrog-token/run.sh new file mode 100644 index 0000000..6787449 --- /dev/null +++ b/jfrog-token/run.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +BOLD='\033[0;1m' + +# check if JFrog CLI is already installed +if command -v jf >/dev/null 2>&1; then + echo "✅ JFrog CLI is already installed, skipping installation." +else + echo "📦 Installing JFrog CLI..." + # Install the JFrog CLI. + curl -fL https://install-cli.jfrog.io | sudo sh + sudo chmod 755 /usr/local/bin/jf +fi + +# The jf CLI checks $CI when determining whether to use interactive +# flows. +export CI=true +# Authenticate with the JFrog CLI. +jf c rm 0 || true +echo "${ARTIFACTORY_ACCESS_TOKEN}" | jf c add --access-token-stdin --url "${JFROG_URL}" 0 + +# Configure the `npm` CLI to use the Artifactory "npm" repository. +if [ -z "${REPOSITORY_NPM}" ]; then + echo "🤔 REPOSITORY_NPM is not set, skipping npm configuration." +else + echo "📦 Configuring npm..." + jf npmc --global --repo-resolve "${JFROG_URL}/artifactory/api/npm/${REPOSITORY_NPM}" + cat <~/.npmrc +email = ${ARTIFACTORY_USERNAME} +registry = ${JFROG_URL}/artifactory/api/npm/${REPOSITORY_NPM} +EOF + jf rt curl /api/npm/auth >>~/.npmrc +fi + +# Configure the `pip` to use the Artifactory "python" repository. +if [ -z "${REPOSITORY_PYPI}" ]; then + echo "🤔 REPOSITORY_PYPI is not set, skipping pip configuration." +else + echo "🐍 Configuring pip..." + jf pipc --global --repo-resolve ${JFROG_URL}/artifactory/api/pypi/${REPOSITORY_PYPI} + mkdir -p ~/.pip + cat <~/.pip/pip.conf +[global] +index-url = https://${ARTIFACTORY_USERNAME}:${ARTIFACTORY_ACCESS_TOKEN}@${JFROG_HOST}/artifactory/api/pypi/${REPOSITORY_PYPI}/simple +EOF +fi + +# Set GOPROXY to use the Artifactory "go" repository. +if [ -z "${REPOSITORY_GO}" ]; then + echo "🤔 REPOSITORY_GO is not set, skipping go configuration." +else + echo "🐹 Configuring go..." + jf go-config --global --repo-resolve ${JFROG_URL}/artifactory/api/go/${REPOSITORY_GO} + export GOPROXY="https://${ARTIFACTORY_USERNAME}:${ARTIFACTORY_ACCESS_TOKEN}@${JFROG_HOST}/artifactory/api/go/${REPOSITORY_GO}" +fi +echo "🥳 Configuration complete!"