Atif Ali d21db0d220
fix(jfrog-oauth): fail when access_token is empty (#574)
## Summary

Fixes #72 - The `jfrog-oauth` module now fails with a clear error
message when the JFrog access token is empty, instead of silently
creating configurations with empty tokens.

## Changes

### 1. Added Precondition Validation (`main.tf`)

```hcl
lifecycle {
  precondition {
    condition     = data.coder_external_auth.jfrog.access_token != ""
    error_message = "JFrog access token is empty. Please authenticate with JFrog using external auth."
  }
}
```

This ensures the module fails at **plan time** with a clear error when
users haven't authenticated via external auth.

### 2. Replaced `main.test.ts` with `jfrog-oauth.tftest.hcl`

**Why we removed the TypeScript tests:**

The TypeScript tests used `runTerraformApply()` which runs `terraform
apply` directly. This approach **cannot mock data sources** like
`coder_external_auth`. The Coder provider returns empty strings for
tokens by default when running outside a real Coder workspace.

With our new precondition, the TypeScript tests would always fail
because:
1. `terraform apply` runs → empty `access_token` from mock provider
2. Precondition check fails → "JFrog access token is empty"
3. Test fails before any assertions run

**The solution:** Terraform's native `.tftest.hcl` format supports
`override_data` blocks that can properly mock data sources:

```hcl
override_data {
  target = data.coder_external_auth.jfrog
  values = {
    access_token = "valid-token-value"  # or "" to test failure
  }
}
```

### 3. Comprehensive Test Coverage

The new `jfrog-oauth.tftest.hcl` includes **12 tests** (up from 7):

| Test | What it validates |
|------|------------------|
| `test_required_vars` | Basic module works with required variables |
| `test_empty_access_token_fails` | **NEW:** Precondition rejects empty
tokens |
| `test_valid_access_token_succeeds` | Module works with valid token |
| `test_jfrog_url_validation` | **NEW:** URL must start with http(s)://
|
| `test_username_field_validation` | **NEW:** Must be "email" or
"username" |
| `test_with_npm_package_manager` | NPM config with scoped repos (script
content) |
| `test_configure_code_server` | **NEW:** IDE env vars created when
enabled |
| `test_go_proxy_env` | GOPROXY env value with multiple repos |
| `test_pypi_package_manager` | pip.conf with extra-index-url |
| `test_docker_package_manager` | register_docker commands for all repos
|
| `test_conda_package_manager` | .condarc channels configuration |
| `test_maven_package_manager` | settings.xml with servers and repos |

All package manager tests use `strcontains()` to verify the actual
script content matches expected configuration formats.

## Test Limitations (Acknowledged)

The tests verify **template rendering** but not **runtime execution**:

|  What we test |  What we don't test |
|----------------|----------------------|
| Configuration file formats | Script syntax errors at runtime |
| Variable interpolation | JFrog CLI compatibility |
| Precondition validation | Actual JFrog authentication |
| Script contains expected content | Commands execute successfully |

**Rationale:** The original TypeScript tests also only checked script
content (`toContain()`), not execution. Full execution testing would
require a mock JFrog server, which adds significant complexity for
limited benefit. The script is straightforward bash that configures
files and runs CLI commands.

## Testing

```bash
cd registry/coder/modules/jfrog-oauth
terraform test
# Success! 12 passed, 0 failed.
```

_Generated with [mux](https://github.com/coder/mux)_
2025-12-02 13:17:39 -06:00
..

display_name description icon verified tags
JFrog (OAuth) Install the JF CLI and authenticate with Artifactory using OAuth. ../../../../.icons/jfrog.svg true
integration
jfrog
helper

JFrog

Install the JF CLI and authenticate package managers with Artifactory using OAuth configured via the Coder external-auth feature.

JFrog OAuth

module "jfrog" {
  count          = data.coder_workspace.me.start_count
  source         = "registry.coder.com/coder/jfrog-oauth/coder"
  version        = "1.2.4"
  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"]
    go     = ["go", "another-go-repo"]
    pypi   = ["pypi", "extra-index-pypi"]
    docker = ["example-docker-staging.jfrog.io", "example-docker-production.jfrog.io"]
    conda  = ["conda", "conda-local"]
    maven  = ["maven", "maven-local"]
  }

}

Note This module does not install npm, go, pip, etc but only configure them. You need to handle the installation of these tools yourself.

Prerequisites

This module is usable by JFrog self-hosted (on-premises) Artifactory as it requires configuring a custom integration. This integration benefits from Coder's external-auth feature and allows each user to authenticate with Artifactory using an OAuth flow and issues user-scoped tokens to each user. For configuration instructions, see this guide on the Coder documentation.

Username Handling

The module automatically extracts your JFrog username directly from the OAuth token's JWT payload. This preserves special characters like dots (.), hyphens (-), and accented characters that Coder normalizes in usernames.

Priority order:

  1. JWT extraction (default) - Extracts username from OAuth token, preserving special characters
  2. Fallback to username_field - If JWT extraction fails, uses Coder username or email

Examples

Configure the Python pip package manager to fetch packages from Artifactory while mapping the Coder email to the Artifactory username.

module "jfrog" {
  count          = data.coder_workspace.me.start_count
  source         = "registry.coder.com/coder/jfrog-oauth/coder"
  version        = "1.2.4"
  agent_id       = coder_agent.main.id
  jfrog_url      = "https://example.jfrog.io"
  username_field = "email"

  package_managers = {
    pypi = ["pypi"]
  }

}

You should now be able to install packages from Artifactory using both the jf pip and pip command.

jf pip install requests
pip install requests

Configure code-server with JFrog extension

The JFrog extension for VS Code allows you to interact with Artifactory from within the IDE.

module "jfrog" {
  count                 = data.coder_workspace.me.start_count
  source                = "registry.coder.com/coder/jfrog-oauth/coder"
  version               = "1.2.3"
  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"
  configure_code_server = true       # Add JFrog extension configuration for code-server
  package_managers = {
    npm  = ["npm"]
    go   = ["go"]
    pypi = ["pypi"]
  }

}

Using the access token in other terraform resources

JFrog Access token is also available as a terraform output. You can use it in other terraform resources. For example, you can use it to configure an Artifactory docker registry with the docker terraform provider.

provider "docker" {
  # ...
  registry_auth {
    address  = "https://example.jfrog.io/artifactory/api/docker/REPO-KEY"
    username = try(module.jfrog[0].username, "")
    password = try(module.jfrog[0].access_token, "")
  }
}

Here REPO_KEY is the name of docker repository in Artifactory.