Add maven package manager support to JFrog modules (#414)

Closes #33 
/claim #33 
## Description
Jfrog Modules doesn't support conda package manager, This PR adds
support of that



## Type of Change

- [ ] New module
- [ ] Bug fix
- [x] Feature/enhancement
- [ ] Documentation
- [ ] Other

## Testing & Validation

- [x] Tests pass (`bun test`)
- [ ] Code formatted (`bun run fmt`)
- [ ] Changes tested locally

## Related Issues

#33 
## video


https://github.com/user-attachments/assets/61c33963-e1a7-43e2-b1cc-fdb747405cf5
This commit is contained in:
हिमांशु 2025-09-09 11:06:31 +05:30 committed by GitHub
parent 17734c073a
commit f1010ee7a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 236 additions and 8 deletions

View File

@ -16,7 +16,7 @@ Install the JF CLI and authenticate package managers with Artifactory using OAut
module "jfrog" { module "jfrog" {
count = data.coder_workspace.me.start_count count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/jfrog-oauth/coder" source = "registry.coder.com/coder/jfrog-oauth/coder"
version = "1.1.0" version = "1.2.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
jfrog_url = "https://example.jfrog.io" 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" username_field = "username" # If you are using GitHub to login to both Coder and Artifactory, use username_field = "username"
@ -27,6 +27,7 @@ module "jfrog" {
pypi = ["pypi", "extra-index-pypi"] pypi = ["pypi", "extra-index-pypi"]
docker = ["example-docker-staging.jfrog.io", "example-docker-production.jfrog.io"] docker = ["example-docker-staging.jfrog.io", "example-docker-production.jfrog.io"]
conda = ["conda", "conda-local"] conda = ["conda", "conda-local"]
maven = ["maven", "maven-local"]
} }
} }
``` ```
@ -46,7 +47,7 @@ Configure the Python pip package manager to fetch packages from Artifactory whil
module "jfrog" { module "jfrog" {
count = data.coder_workspace.me.start_count count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/jfrog-oauth/coder" source = "registry.coder.com/coder/jfrog-oauth/coder"
version = "1.1.0" version = "1.2.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
jfrog_url = "https://example.jfrog.io" jfrog_url = "https://example.jfrog.io"
username_field = "email" username_field = "email"
@ -75,7 +76,7 @@ The [JFrog extension](https://open-vsx.org/extension/JFrog/jfrog-vscode-extensio
module "jfrog" { module "jfrog" {
count = data.coder_workspace.me.start_count count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/jfrog-oauth/coder" source = "registry.coder.com/coder/jfrog-oauth/coder"
version = "1.1.0" version = "1.2.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
jfrog_url = "https://example.jfrog.io" 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" username_field = "username" # If you are using GitHub to login to both Coder and Artifactory, use username_field = "username"

View File

@ -150,4 +150,38 @@ EOF`;
'if [ -z "YES" ]; then\n not_configured conda', 'if [ -z "YES" ]; then\n not_configured conda',
); );
}); });
it("generates a maven settings.xml with multiple repos", async () => {
const state = await runTerraformApply<TestVariables>(import.meta.dir, {
agent_id: "some-agent-id",
jfrog_url: fakeFrogUrl,
package_managers: JSON.stringify({
maven: ["central", "snapshots", "local"],
}),
});
const coderScript = findResourceInstance(state, "coder_script");
expect(coderScript.script).toContain(
'jf mvnc --global --repo-resolve "central"',
);
expect(coderScript.script).toContain("<servers>");
expect(coderScript.script).toContain("<id>central</id>");
expect(coderScript.script).toContain("<id>snapshots</id>");
expect(coderScript.script).toContain("<id>local</id>");
expect(coderScript.script).toContain(
"<url>http://localhost:8081/artifactory/central</url>",
);
expect(coderScript.script).toContain(
"<url>http://localhost:8081/artifactory/snapshots</url>",
);
expect(coderScript.script).toContain(
"<url>http://localhost:8081/artifactory/local</url>",
);
expect(coderScript.script).toContain(
'if [ -z "YES" ]; then\n not_configured maven',
);
});
}); });

View File

@ -59,6 +59,7 @@ variable "package_managers" {
pypi = optional(list(string), []) pypi = optional(list(string), [])
docker = optional(list(string), []) docker = optional(list(string), [])
conda = optional(list(string), []) conda = optional(list(string), [])
maven = optional(list(string), [])
}) })
description = <<-EOF description = <<-EOF
A map of package manager names to their respective artifactory repositories. Unused package managers can be omitted. A map of package manager names to their respective artifactory repositories. Unused package managers can be omitted.
@ -69,6 +70,7 @@ variable "package_managers" {
pypi = ["YOUR_PYPI_REPO_KEY", "ANOTHER_PYPI_REPO_KEY"] pypi = ["YOUR_PYPI_REPO_KEY", "ANOTHER_PYPI_REPO_KEY"]
docker = ["YOUR_DOCKER_REPO_KEY", "ANOTHER_DOCKER_REPO_KEY"] docker = ["YOUR_DOCKER_REPO_KEY", "ANOTHER_DOCKER_REPO_KEY"]
conda = ["YOUR_CONDA_REPO_KEY", "ANOTHER_CONDA_REPO_KEY"] conda = ["YOUR_CONDA_REPO_KEY", "ANOTHER_CONDA_REPO_KEY"]
maven = ["YOUR_MAVEN_REPO_KEY", "ANOTHER_MAVEN_REPO_KEY"]
} }
EOF EOF
} }
@ -103,6 +105,9 @@ locals {
conda_conf = templatefile( conda_conf = templatefile(
"${path.module}/conda.conf.tftpl", merge(local.common_values, { REPOS = var.package_managers.conda }) "${path.module}/conda.conf.tftpl", merge(local.common_values, { REPOS = var.package_managers.conda })
) )
maven_settings = templatefile(
"${path.module}/settings.xml.tftpl", merge(local.common_values, { REPOS = var.package_managers.maven })
)
} }
data "coder_workspace" "me" {} data "coder_workspace" "me" {}
@ -133,6 +138,9 @@ resource "coder_script" "jfrog" {
HAS_CONDA = length(var.package_managers.conda) == 0 ? "" : "YES" HAS_CONDA = length(var.package_managers.conda) == 0 ? "" : "YES"
CONDA_CONF = local.conda_conf CONDA_CONF = local.conda_conf
REPOSITORY_CONDA = try(element(var.package_managers.conda, 0), "") REPOSITORY_CONDA = try(element(var.package_managers.conda, 0), "")
HAS_MAVEN = length(var.package_managers.maven) == 0 ? "" : "YES"
MAVEN_SETTINGS = local.maven_settings
REPOSITORY_MAVEN = try(element(var.package_managers.maven, 0), "")
} }
)) ))
run_on_start = true run_on_start = true

View File

@ -94,6 +94,20 @@ EOF
config_complete config_complete
fi fi
# Configure Maven to use the Artifactory "maven" repository.
if [ -z "${HAS_MAVEN}" ]; then
not_configured maven
else
echo "☕ Configuring maven..."
jf mvnc --global --repo-resolve "${REPOSITORY_MAVEN}"
# Create Maven config directory if it doesn't exist
mkdir -p ~/.m2
cat << EOF > ~/.m2/settings.xml
${MAVEN_SETTINGS}
EOF
config_complete
fi
# Install the JFrog vscode extension for code-server. # Install the JFrog vscode extension for code-server.
if [ "${CONFIGURE_CODE_SERVER}" == "true" ]; then if [ "${CONFIGURE_CODE_SERVER}" == "true" ]; then
while ! [ -x /tmp/code-server/bin/code-server ]; do while ! [ -x /tmp/code-server/bin/code-server ]; do

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
http://maven.apache.org/xsd/settings-1.0.0.xsd">
<servers>
%{ for REPO in REPOS ~}
<server>
<id>${REPO}</id>
<username>${ARTIFACTORY_USERNAME}</username>
<password>${ARTIFACTORY_ACCESS_TOKEN}</password>
</server>
%{ endfor ~}
</servers>
<profiles>
<profile>
<id>artifactory</id>
<repositories>
%{ for REPO in REPOS ~}
<repository>
<id>${REPO}</id>
<url>${JFROG_URL}/artifactory/${REPO}</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
%{ endfor ~}
</repositories>
<pluginRepositories>
%{ for REPO in REPOS ~}
<pluginRepository>
<id>${REPO}</id>
<url>${JFROG_URL}/artifactory/${REPO}</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
%{ endfor ~}
</pluginRepositories>
</profile>
</profiles>
<activeProfiles>
<activeProfile>artifactory</activeProfile>
</activeProfiles>
</settings>

View File

@ -13,7 +13,7 @@ Install the JF CLI and authenticate package managers with Artifactory using Arti
```tf ```tf
module "jfrog" { module "jfrog" {
source = "registry.coder.com/coder/jfrog-token/coder" source = "registry.coder.com/coder/jfrog-token/coder"
version = "1.1.0" version = "1.2.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
jfrog_url = "https://XXXX.jfrog.io" jfrog_url = "https://XXXX.jfrog.io"
artifactory_access_token = var.artifactory_access_token artifactory_access_token = var.artifactory_access_token
@ -23,6 +23,7 @@ module "jfrog" {
pypi = ["pypi", "extra-index-pypi"] pypi = ["pypi", "extra-index-pypi"]
docker = ["example-docker-staging.jfrog.io", "example-docker-production.jfrog.io"] docker = ["example-docker-staging.jfrog.io", "example-docker-production.jfrog.io"]
conda = ["conda", "conda-local"] conda = ["conda", "conda-local"]
maven = ["maven", "maven-local"]
} }
} }
``` ```
@ -41,7 +42,7 @@ For detailed instructions, please see this [guide](https://coder.com/docs/v2/lat
```tf ```tf
module "jfrog" { module "jfrog" {
source = "registry.coder.com/coder/jfrog-token/coder" source = "registry.coder.com/coder/jfrog-token/coder"
version = "1.1.0" version = "1.2.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
jfrog_url = "https://YYYY.jfrog.io" jfrog_url = "https://YYYY.jfrog.io"
artifactory_access_token = var.artifactory_access_token # An admin access token artifactory_access_token = var.artifactory_access_token # An admin access token
@ -50,17 +51,19 @@ module "jfrog" {
go = ["go-local"] go = ["go-local"]
pypi = ["pypi-local"] pypi = ["pypi-local"]
conda = ["conda-local"] conda = ["conda-local"]
maven = ["maven-local"]
} }
} }
``` ```
You should now be able to install packages from Artifactory using both the `jf npm`, `jf go`, `jf pip` and `npm`, `go`, `pip`, `conda` commands. 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.
```shell ```shell
jf npm install prettier jf npm install prettier
jf go get github.com/golang/example/hello jf go get github.com/golang/example/hello
jf pip install requests jf pip install requests
conda install numpy conda install numpy
mvn clean install
``` ```
```shell ```shell
@ -68,6 +71,7 @@ npm install prettier
go get github.com/golang/example/hello go get github.com/golang/example/hello
pip install requests pip install requests
conda install numpy conda install numpy
mvn clean install
``` ```
### Configure code-server with JFrog extension ### Configure code-server with JFrog extension
@ -77,7 +81,7 @@ The [JFrog extension](https://open-vsx.org/extension/JFrog/jfrog-vscode-extensio
```tf ```tf
module "jfrog" { module "jfrog" {
source = "registry.coder.com/coder/jfrog-token/coder" source = "registry.coder.com/coder/jfrog-token/coder"
version = "1.1.0" version = "1.2.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
jfrog_url = "https://XXXX.jfrog.io" jfrog_url = "https://XXXX.jfrog.io"
artifactory_access_token = var.artifactory_access_token artifactory_access_token = var.artifactory_access_token
@ -97,7 +101,7 @@ data "coder_workspace" "me" {}
module "jfrog" { module "jfrog" {
source = "registry.coder.com/coder/jfrog-token/coder" source = "registry.coder.com/coder/jfrog-token/coder"
version = "1.1.0" version = "1.2.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
jfrog_url = "https://XXXX.jfrog.io" jfrog_url = "https://XXXX.jfrog.io"
artifactory_access_token = var.artifactory_access_token artifactory_access_token = var.artifactory_access_token

View File

@ -187,4 +187,39 @@ EOF`;
'if [ -z "YES" ]; then\n not_configured conda', 'if [ -z "YES" ]; then\n not_configured conda',
); );
}); });
it("generates a maven settings.xml with multiple repos", async () => {
const state = await runTerraformApply<TestVariables>(import.meta.dir, {
agent_id: "some-agent-id",
jfrog_url: fakeFrogUrl,
artifactory_access_token: "XXXX",
package_managers: JSON.stringify({
maven: ["central", "snapshots", "local"],
}),
});
const coderScript = findResourceInstance(state, "coder_script");
expect(coderScript.script).toContain(
'jf mvnc --global --repo-resolve "central"',
);
expect(coderScript.script).toContain("<servers>");
expect(coderScript.script).toContain("<id>central</id>");
expect(coderScript.script).toContain("<id>snapshots</id>");
expect(coderScript.script).toContain("<id>local</id>");
expect(coderScript.script).toContain(
`<url>${fakeFrogUrl}/artifactory/central</url>`,
);
expect(coderScript.script).toContain(
`<url>${fakeFrogUrl}/artifactory/snapshots</url>`,
);
expect(coderScript.script).toContain(
`<url>${fakeFrogUrl}/artifactory/local</url>`,
);
expect(coderScript.script).toContain(
'if [ -z "YES" ]; then\n not_configured maven',
);
});
}); });

View File

@ -92,6 +92,7 @@ variable "package_managers" {
pypi = optional(list(string), []) pypi = optional(list(string), [])
docker = optional(list(string), []) docker = optional(list(string), [])
conda = optional(list(string), []) conda = optional(list(string), [])
maven = optional(list(string), [])
}) })
description = <<-EOF description = <<-EOF
A map of package manager names to their respective artifactory repositories. Unused package managers can be omitted. A map of package manager names to their respective artifactory repositories. Unused package managers can be omitted.
@ -102,6 +103,7 @@ variable "package_managers" {
pypi = ["YOUR_PYPI_REPO_KEY", "ANOTHER_PYPI_REPO_KEY"] pypi = ["YOUR_PYPI_REPO_KEY", "ANOTHER_PYPI_REPO_KEY"]
docker = ["YOUR_DOCKER_REPO_KEY", "ANOTHER_DOCKER_REPO_KEY"] docker = ["YOUR_DOCKER_REPO_KEY", "ANOTHER_DOCKER_REPO_KEY"]
conda = ["YOUR_CONDA_REPO_KEY", "ANOTHER_CONDA_REPO_KEY"] conda = ["YOUR_CONDA_REPO_KEY", "ANOTHER_CONDA_REPO_KEY"]
maven = ["YOUR_MAVEN_REPO_KEY", "ANOTHER_MAVEN_REPO_KEY"]
} }
EOF EOF
} }
@ -136,6 +138,9 @@ locals {
conda_conf = templatefile( conda_conf = templatefile(
"${path.module}/conda.conf.tftpl", merge(local.common_values, { REPOS = var.package_managers.conda }) "${path.module}/conda.conf.tftpl", merge(local.common_values, { REPOS = var.package_managers.conda })
) )
maven_settings = templatefile(
"${path.module}/settings.xml.tftpl", merge(local.common_values, { REPOS = var.package_managers.maven })
)
} }
# Configure the Artifactory provider # Configure the Artifactory provider
@ -179,6 +184,9 @@ resource "coder_script" "jfrog" {
HAS_CONDA = length(var.package_managers.conda) == 0 ? "" : "YES" HAS_CONDA = length(var.package_managers.conda) == 0 ? "" : "YES"
CONDA_CONF = local.conda_conf CONDA_CONF = local.conda_conf
REPOSITORY_CONDA = try(element(var.package_managers.conda, 0), "") REPOSITORY_CONDA = try(element(var.package_managers.conda, 0), "")
HAS_MAVEN = length(var.package_managers.maven) == 0 ? "" : "YES"
MAVEN_SETTINGS = local.maven_settings
REPOSITORY_MAVEN = try(element(var.package_managers.maven, 0), "")
} }
)) ))
run_on_start = true run_on_start = true

View File

@ -93,6 +93,20 @@ EOF
config_complete config_complete
fi fi
# Configure Maven to use the Artifactory "maven" repository.
if [ -z "${HAS_MAVEN}" ]; then
not_configured maven
else
echo "☕ Configuring maven..."
jf mvnc --global --repo-resolve "${REPOSITORY_MAVEN}"
# Create Maven config directory if it doesn't exist
mkdir -p ~/.m2
cat << EOF > ~/.m2/settings.xml
${MAVEN_SETTINGS}
EOF
config_complete
fi
# Install the JFrog vscode extension for code-server. # Install the JFrog vscode extension for code-server.
if [ "${CONFIGURE_CODE_SERVER}" == "true" ]; then if [ "${CONFIGURE_CODE_SERVER}" == "true" ]; then
while ! [ -x /tmp/code-server/bin/code-server ]; do while ! [ -x /tmp/code-server/bin/code-server ]; do

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
http://maven.apache.org/xsd/settings-1.0.0.xsd">
<servers>
%{ for REPO in REPOS ~}
<server>
<id>${REPO}</id>
<username>${ARTIFACTORY_USERNAME}</username>
<password>${ARTIFACTORY_ACCESS_TOKEN}</password>
</server>
%{ endfor ~}
</servers>
<profiles>
<profile>
<id>artifactory</id>
<repositories>
%{ for REPO in REPOS ~}
<repository>
<id>${REPO}</id>
<url>${JFROG_URL}/artifactory/${REPO}</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
%{ endfor ~}
</repositories>
<pluginRepositories>
%{ for REPO in REPOS ~}
<pluginRepository>
<id>${REPO}</id>
<url>${JFROG_URL}/artifactory/${REPO}</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
%{ endfor ~}
</pluginRepositories>
</profile>
</profiles>
<activeProfiles>
<activeProfile>artifactory</activeProfile>
</activeProfiles>
</settings>