Skip to content
This repository was archived by the owner on May 15, 2025. It is now read-only.

Commit 73ef0dc

Browse files
authored
Add JFrog (OAuth) integration module (#97)
1 parent 4e7f1e0 commit 73ef0dc

File tree

9 files changed

+286
-31
lines changed

9 files changed

+286
-31
lines changed

.images/jfrog-oauth.png

73.8 KB
Loading

jfrog-oauth/README.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
---
2+
display_name: JFrog (OAuth)
3+
description: Install the JF CLI and authenticate with Artifactory using OAuth.
4+
icon: ../.icons/jfrog.svg
5+
maintainer_github: coder
6+
partner_github: jfrog
7+
verified: true
8+
tags: [integration, jfrog]
9+
---
10+
11+
# JFrog
12+
13+
Install the JF CLI and authenticate package managers with Artifactory using OAuth configured via the Coder `external-auth` feature.
14+
15+
![JFrog OAuth](../.images/jfrog-oauth.png)
16+
17+
```hcl
18+
module "jfrog" {
19+
source = "https://registry.coder.com/modules/jfrog-oauth"
20+
agent_id = coder_agent.example.id
21+
jfrog_url = "https://jfrog.example.com"
22+
auth_method = "oauth"
23+
username_field = "username" # If you are using GitHub to login to both Coder and Artifactory, use username_field = "username"
24+
package_managers = {
25+
"npm": "npm",
26+
"go": "go",
27+
"pypi": "pypi"
28+
}
29+
}
30+
```
31+
32+
## Prerequisites
33+
34+
- Coder [`external-auth`](https://docs.coder.com/docs/admin/external-auth/) configured with Artifactory. This requires a [custom integration](https://jfrog.com/help/r/jfrog-installation-setup-documentation/enable-new-integrations) in Artifactory with **Callback URL** set to `https://<your-coder-url>/external-auth/jfrog/callback`.
35+
36+
## Examples
37+
38+
Configure the Python pip package manager to fetch packages from Artifactory while mapping the Coder email to the Artifactory username.
39+
40+
```hcl
41+
module "jfrog" {
42+
source = "https://registry.coder.com/modules/jfrog-oauth"
43+
agent_id = coder_agent.example.id
44+
jfrog_url = "https://jfrog.example.com"
45+
auth_method = "oauth"
46+
username_field = "email"
47+
package_managers = {
48+
"pypi": "pypi"
49+
}
50+
}
51+
```
52+
53+
You should now be able to install packages from Artifactory using both the `jf pip` and `pip` command.
54+
55+
```shell
56+
jf pip install requests
57+
```
58+
59+
```shell
60+
pip install requests
61+
```

jfrog-oauth/main.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { serve } from "bun";
2+
import { describe } from "bun:test";
3+
import {
4+
createJSONResponse,
5+
runTerraformInit,
6+
testRequiredVariables,
7+
} from "../test";
8+
9+
describe("jfrog-oauth", async () => {
10+
await runTerraformInit(import.meta.dir);
11+
12+
testRequiredVariables(import.meta.dir, {
13+
agent_id: "some-agent-id",
14+
jfrog_url: "http://localhost:8081",
15+
package_managers: "{}",
16+
});
17+
});

jfrog-oauth/main.tf

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
terraform {
2+
required_version = ">= 1.0"
3+
4+
required_providers {
5+
coder = {
6+
source = "coder/coder"
7+
version = ">= 0.12"
8+
}
9+
}
10+
}
11+
12+
variable "jfrog_url" {
13+
type = string
14+
description = "JFrog instance URL. e.g. https://jfrog.example.com"
15+
}
16+
17+
variable "username_field" {
18+
type = string
19+
description = "The field to use for the artifactory username. i.e. Coder username or email."
20+
default = "username"
21+
validation {
22+
condition = can(regex("^(email|username)$", var.username_field))
23+
error_message = "username_field must be either 'email' or 'username'"
24+
}
25+
}
26+
27+
variable "external_auth_id" {
28+
type = string
29+
description = "JFrog external auth ID. Default: 'jfrog'"
30+
default = "jfrog"
31+
}
32+
33+
variable "agent_id" {
34+
type = string
35+
description = "The ID of a Coder agent."
36+
}
37+
38+
variable "package_managers" {
39+
type = map(string)
40+
description = <<EOF
41+
A map of package manager names to their respective artifactory repositories.
42+
For example:
43+
{
44+
"npm": "npm-local",
45+
"go": "go-local",
46+
"pypi": "pypi-local"
47+
}
48+
EOF
49+
}
50+
51+
locals {
52+
# The username field to use for artifactory
53+
username = var.username_field == "email" ? data.coder_workspace.me.owner_email : data.coder_workspace.me.owner
54+
}
55+
56+
data "coder_workspace" "me" {}
57+
58+
data "coder_external_auth" "jfrog" {
59+
id = var.external_auth_id
60+
}
61+
62+
resource "coder_script" "jfrog" {
63+
agent_id = var.agent_id
64+
display_name = "jfrog"
65+
icon = "/icon/jfrog.svg"
66+
script = templatefile("${path.module}/run.sh", {
67+
JFROG_URL : var.jfrog_url,
68+
JFROG_HOST : replace(var.jfrog_url, "https://", ""),
69+
ARTIFACTORY_USERNAME : local.username,
70+
ARTIFACTORY_EMAIL : data.coder_workspace.me.owner_email,
71+
ARTIFACTORY_ACCESS_TOKEN : data.coder_external_auth.jfrog.access_token,
72+
REPOSITORY_NPM : lookup(var.package_managers, "npm", ""),
73+
REPOSITORY_GO : lookup(var.package_managers, "go", ""),
74+
REPOSITORY_PYPI : lookup(var.package_managers, "pypi", ""),
75+
})
76+
run_on_start = true
77+
}
Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1-
#!/usr/bin/env sh
1+
#!/usr/bin/env bash
22

33
BOLD='\033[0;1m'
4-
printf "$${BOLD}Installing JFrog CLI..."
54

6-
# Install the JFrog CLI.
7-
curl -fL https://install-cli.jfrog.io | sudo sh
8-
sudo chmod 755 /usr/local/bin/jf
5+
# check if JFrog CLI is already installed
6+
if command -v jf >/dev/null 2>&1; then
7+
echo "✅ JFrog CLI is already installed, skipping installation."
8+
else
9+
echo "📦 Installing JFrog CLI..."
10+
# Install the JFrog CLI.
11+
curl -fL https://install-cli.jfrog.io | sudo sh
12+
sudo chmod 755 /usr/local/bin/jf
13+
fi
914

1015
# The jf CLI checks $CI when determining whether to use interactive
1116
# flows.
@@ -14,14 +19,16 @@ export CI=true
1419
jf c rm 0 || true
1520
echo "${ARTIFACTORY_ACCESS_TOKEN}" | jf c add --access-token-stdin --url "${JFROG_URL}" 0
1621

17-
# Configure the `npm` CLI to use the Artifactory "npm" repository.
1822
if [ -z "${REPOSITORY_NPM}" ]; then
1923
echo "🤔 REPOSITORY_NPM is not set, skipping npm configuration."
2024
else
21-
echo "📦 Configuring npm..."
22-
jf npmc --global --repo-resolve "${JFROG_URL}/artifactory/api/npm/${REPOSITORY_NPM}"
25+
# check if npm is installed and configure it to use the Artifactory "npm" repository.
26+
if command -v npm >/dev/null 2>&1; then
27+
echo "📦 Configuring npm..."
28+
jf npmc --global --repo-resolve "${REPOSITORY_NPM}"
29+
fi
2330
cat <<EOF >~/.npmrc
24-
email = ${ARTIFACTORY_USERNAME}
31+
email = ${ARTIFACTORY_EMAIL}
2532
registry = ${JFROG_URL}/artifactory/api/npm/${REPOSITORY_NPM}
2633
EOF
2734
jf rt curl /api/npm/auth >>~/.npmrc
@@ -32,6 +39,7 @@ if [ -z "${REPOSITORY_PYPI}" ]; then
3239
echo "🤔 REPOSITORY_PYPI is not set, skipping pip configuration."
3340
else
3441
echo "🐍 Configuring pip..."
42+
jf pipc --global --repo-resolve "${REPOSITORY_PYPI}"
3543
mkdir -p ~/.pip
3644
cat <<EOF >~/.pip/pip.conf
3745
[global]
@@ -44,6 +52,7 @@ if [ -z "${REPOSITORY_GO}" ]; then
4452
echo "🤔 REPOSITORY_GO is not set, skipping go configuration."
4553
else
4654
echo "🐹 Configuring go..."
55+
jf go-config --global --repo-resolve "${REPOSITORY_GO}"
4756
export GOPROXY="https://${ARTIFACTORY_USERNAME}:${ARTIFACTORY_ACCESS_TOKEN}@${JFROG_HOST}/artifactory/api/go/${REPOSITORY_GO}"
4857
fi
4958
echo "🥳 Configuration complete!"

jfrog/README.md renamed to jfrog-token/README.md

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
---
2-
display_name: JFrog
3-
description: Install the JF CLI and authenticate with Artifactory
2+
display_name: JFrog (Token)
3+
description: Install the JF CLI and authenticate with Artifactory using Artifactory terraform provider.
44
icon: ../.icons/jfrog.svg
55
maintainer_github: coder
66
partner_github: jfrog
77
verified: true
8-
tags: [integration]
8+
tags: [integration, jfrog]
99
---
1010

1111
# JFrog
1212

13-
Install the JF CLI and authenticate package managers with Artifactory.
13+
Install the JF CLI and authenticate package managers with Artifactory using Artifactory terraform provider.
1414

1515
```hcl
1616
module "jfrog" {
17-
source = "https://registry.coder.com/modules/jfrog"
17+
source = "https://registry.coder.com/modules/jfrog-token"
1818
agent_id = coder_agent.example.id
1919
jfrog_url = "https://YYYY.jfrog.io"
2020
artifactory_access_token = var.artifactory_access_token # An admin access token
2121
package_managers = {
22-
"npm": "npm-remote",
23-
"go": "go-remote",
24-
"pypi": "pypi-remote"
22+
"npm": "npm",
23+
"go": "go",
24+
"pypi": "pypi"
2525
}
2626
}
2727
```
@@ -43,7 +43,7 @@ variable "artifactory_access_token" {
4343

4444
```hcl
4545
module "jfrog" {
46-
source = "https://registry.coder.com/modules/jfrog"
46+
source = "https://registry.coder.com/modules/jfrog-token"
4747
agent_id = coder_agent.example.id
4848
jfrog_url = "https://YYYY.jfrog.io"
4949
artifactory_access_token = var.artifactory_access_token # An admin access token
@@ -54,3 +54,17 @@ module "jfrog" {
5454
}
5555
}
5656
```
57+
58+
You should now be able to install packages from Artifactory using both the `jf npm`, `jf go`, `jf pip` and `npm`, `go`, `pip` commands.
59+
60+
```shell
61+
jf npm install prettier
62+
jf go get github.com/golang/example/hello
63+
jf pip install requests
64+
```
65+
66+
```shell
67+
npm install prettier
68+
go get github.com/golang/example/hello
69+
pip install requests
70+
```

jfrog/main.test.ts renamed to jfrog-token/main.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
testRequiredVariables,
77
} from "../test";
88

9-
describe("jfrog", async () => {
9+
describe("jfrog-token", async () => {
1010
await runTerraformInit(import.meta.dir);
1111

1212
// Run a fake JFrog server so the provider can initialize
@@ -25,7 +25,7 @@ describe("jfrog", async () => {
2525
return createJSONResponse({
2626
token_id: "xxx",
2727
access_token: "xxx",
28-
scope: "any",
28+
scopes: "any",
2929
});
3030
return createJSONResponse({});
3131
},

jfrog/main.tf renamed to jfrog-token/main.tf

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ terraform {
88
}
99
artifactory = {
1010
source = "registry.terraform.io/jfrog/artifactory"
11-
version = "~> 8.4.0"
11+
version = "~> 9.8.0"
1212
}
1313
}
1414
}
@@ -23,15 +23,14 @@ variable "artifactory_access_token" {
2323
description = "The admin-level access token to use for JFrog."
2424
}
2525

26-
# Configure the Artifactory provider
27-
provider "artifactory" {
28-
url = join("/", [var.jfrog_url, "artifactory"])
29-
access_token = var.artifactory_access_token
30-
}
31-
resource "artifactory_scoped_token" "me" {
32-
# This is hacky, but on terraform plan the data source gives empty strings,
33-
# which fails validation.
34-
username = length(data.coder_workspace.me.owner_email) > 0 ? data.coder_workspace.me.owner_email : "plan"
26+
variable "username_field" {
27+
type = string
28+
description = "The field to use for the artifactory username. i.e. Coder username or email."
29+
default = "email"
30+
validation {
31+
condition = can(regex("^(email|username)$", var.username_field))
32+
error_message = "username_field must be either 'email' or 'username'"
33+
}
3534
}
3635

3736
variable "agent_id" {
@@ -52,6 +51,25 @@ For example:
5251
EOF
5352
}
5453

54+
locals {
55+
# The username field to use for artifactory
56+
username = var.username_field == "email" ? data.coder_workspace.me.owner_email : data.coder_workspace.me.owner
57+
}
58+
59+
# Configure the Artifactory provider
60+
provider "artifactory" {
61+
url = join("/", [var.jfrog_url, "artifactory"])
62+
access_token = var.artifactory_access_token
63+
}
64+
65+
resource "artifactory_scoped_token" "me" {
66+
# This is hacky, but on terraform plan the data source gives empty strings,
67+
# which fails validation.
68+
username = length(local.username) > 0 ? local.username : "dummy"
69+
scopes = ["applied-permissions/user"]
70+
refreshable = true
71+
}
72+
5573
data "coder_workspace" "me" {}
5674

5775
resource "coder_script" "jfrog" {
@@ -61,7 +79,8 @@ resource "coder_script" "jfrog" {
6179
script = templatefile("${path.module}/run.sh", {
6280
JFROG_URL : var.jfrog_url,
6381
JFROG_HOST : replace(var.jfrog_url, "https://", ""),
64-
ARTIFACTORY_USERNAME : data.coder_workspace.me.owner_email,
82+
ARTIFACTORY_USERNAME : local.username,
83+
ARTIFACTORY_EMAIL : data.coder_workspace.me.owner_email,
6584
ARTIFACTORY_ACCESS_TOKEN : artifactory_scoped_token.me.access_token,
6685
REPOSITORY_NPM : lookup(var.package_managers, "npm", ""),
6786
REPOSITORY_GO : lookup(var.package_managers, "go", ""),

0 commit comments

Comments
 (0)