The "Nexus3 as Code" project provides assets to manage Sonatype Nexus 3 as code using Terraform.
This code is based on the Nexus Provider: https://registry.terraform.io/providers/datadrivers/nexus/latest/docs
- Nexus3 as Code
- Getting Started
- Quick Make Guide
- Adding a New Project
- Configuring Maven Repositories
- Setting up Maven Staging Repositories
- Configuring Other Repository Types
- Configuring Proxies
- Advanced Configuration
- Terraform Best Practices
- Bot Management
- Troubleshooting
Getting Started
Prerequisites
- Terraform: Install from HashiCorp's website
- kubectl: For Kubernetes backend state management
- jsonnet (optional): For using template-based configurations
- Access credentials: Nexus admin credentials and secretsmanager token
Initial Setup
-
Set your environment variables in
.env.sh:export NEXUS_ENV="prod" # or "dev", "staging"
-
Source the environment:
-
Initialize Terraform:
-
Select or create workspace:
-
Plan and apply:
Quick Make Guide
This project uses a Makefile to simplify Terraform operations. Always use make commands instead of running terraform directly to ensure proper variable file handling, validation, and compilation.
Available Make Commands
| Command | Description | What it does |
|---|---|---|
make help |
Show all available commands | Lists all make targets with descriptions |
make init |
Initialize Terraform | Runs terraform init with correct backend config |
make select |
Select/create workspace | Selects workspace or creates if doesn't exist |
make validate |
Validate configuration | Checks Terraform syntax and configuration |
make fmt |
Format Terraform files | Auto-formats all .tf files |
make compile-jsonnet |
Compile Jsonnet to JSON | Converts .jsonnet template to .tfvars.json |
make plan |
Plan changes | Shows what will be changed (dry-run) |
make apply |
Apply changes | Applies the configuration to Nexus |
make destroy |
Destroy all resources | DANGEROUS: Destroys all managed resources |
make refresh |
Refresh state | Updates state from actual infrastructure |
make outputs |
Show outputs | Displays Terraform outputs |
make outputs-json |
Show outputs as JSON | Displays outputs in JSON format |
make status |
Show current state | Displays current Terraform state |
make clean |
Clean local files | Removes .terraform/ and lock files |
Advanced Make Usage
Using Jsonnet Templates
# Edit Jsonnet template vim env/terraform.prod.tfvars.jsonnet # Compile and plan (compile happens automatically) make plan # Or compile manually make compile-jsonnet
Environment Variables
The Makefile uses these environment variables:
| Variable | Description | Example |
|---|---|---|
NEXUS_ENV |
Target environment | prod, staging, dev |
TF_PARALLELISM |
Terraform parallelism level | 30 (default) |
JSONNET_FILE |
Source Jsonnet file | Auto-detected from $NEXUS_ENV |
TF_VAR_FILE |
Target tfvars JSON file | Auto-detected from $NEXUS_ENV |
Important: Always set NEXUS_ENV before running any make commands!
Adding a New Project
There are two approaches to adding a project: using templates (recommended) or custom configuration.
Prerequisites: Helm Configuration
IMPORTANT: Before creating a project in Terraform, you must first configure the Helm deployment to create the necessary Persistent Volume Claim (PVC) for the project's blob store.
-
Add the project to the Nexus Helm chart repository:
- Repository: https://github.com/eclipse-cbi/sonatype-nexus
-
Update the
values.yamlfile:- File: https://github.com/eclipse-cbi/sonatype-nexus/blob/master/charts/nexus3/values.yaml
- Add your project's blob store configuration and quotas if needed to create the PVC
-
Apply the Helm changes:
# Update the Nexus deployment with the new PVC configuration ./helm-deploy-nexus3.sh <env>
-
Verify PVC creation:
# Check that the PVC was created successfully kubectl get pvc -n repo3-eclipse-org | grep <your-project>
Once the PVC is created on the cluster, you can proceed with the Terraform configuration below.
Method 1: Using Templates (Recommended)
Templates provide pre-configured repository sets for common use cases. Available templates:
maven2Standard: Creates releases + snapshots repositories with auto-groupmaven2StandardWithStaging: Adds staging repository to maven2Standardmaven2StagingOnly: Only staging repositorymaven2StandardNoStrictValidation: Maven2 with permissive layout policyhelmStandard: Helm releases + stagingaptStandard: APT stable + unstable distributions
Example: Add a Project Using Templates
Edit env/<env>.json and add to the projects array:
{
"id": "technology.myproject",
"template": "maven2StandardWithStaging"
}This will automatically create:
myproject-maven2-releasesrepositorymyproject-maven2-snapshotsrepositorymyproject-maven2-stagingrepositorymyproject-maven2group (containing all three)- Bot user:
eclipse-myproject-bot - Roles and permissions for the project
With Optional Parameters
{
"id": "technology.myproject",
"template": "maven2Standard",
"archived": true, // archived do not create bot
"shortNameOverride": "custom-name", // Override default short name
"blobstore_soft_quota_limit": 95 // specific soft quota when set above default size 50G
}Template Examples
Example 1: maven2Standard Template
{
"id": "technology.simple",
"template": "maven2Standard"
}Created Resources:
Repositories:
simple-maven2-releases- URL:
https://repo.eclipse.org/repository/simple-maven2-releases/ - Type: Maven2 (RELEASE)
- URL:
simple-maven2-snapshots- URL:
https://repo.eclipse.org/repository/simple-maven2-snapshots/ - Type: Maven2 (SNAPSHOT)
- URL:
Groups:
simple-maven2- URL:
https://repo.eclipse.org/repository/simple-maven2/ - Members: simple-maven2-releases, simple-maven2-snapshots
- URL:
Users:
eclipse-simple-bot(with credentials in the secretsmanager)
Roles & Permissions:
simple-repository-admin- View and admin permissions for all repositories
Example 2: maven2StandardWithStaging Template
{
"id": "technology.complex",
"template": "maven2StandardWithStaging",
"blobstore_soft_quota_limit": 100
}Created Resources:
Repositories:
complex-maven2-releases- URL:
https://repo.eclipse.org/repository/complex-maven2-releases/ - Type: Maven2 (RELEASE)
- URL:
complex-maven2-snapshots- URL:
https://repo.eclipse.org/repository/complex-maven2-snapshots/ - Type: Maven2 (SNAPSHOT)
- URL:
complex-maven2-staging- URL:
https://repo.eclipse.org/repository/complex-maven2-staging/ - Type: Maven2 (MIXED)
- URL:
Groups:
complex-maven2- URL:
https://repo.eclipse.org/repository/complex-maven2/ - Members: complex-maven2-releases, complex-maven2-snapshots, complex-maven2-staging
- URL:
Blob Store:
technology-complex(with 100GB soft quota limit)
Users:
eclipse-complex-bot(with credentials in the secretsmanager)
Roles & Permissions:
complex-repository-admin- View and admin permissions for all repositories
Example 3: helmStandard Template
{
"id": "automotive.tractusx",
"template": "helmStandard"
}Created Resources:
Repositories:
tractusx-helm-releases- URL:
https://repo.eclipse.org/repository/tractusx-helm-releases/ - Type: Helm
- URL:
tractusx-helm-staging- URL:
https://repo.eclipse.org/repository/tractusx-helm-staging/ - Type: Helm
- URL:
Groups:
tractusx-helm- URL:
https://repo.eclipse.org/repository/tractusx-helm/ - Members: tractusx-helm-releases, tractusx-helm-staging
- URL:
Users:
eclipse-tractusx-bot(with credentials in secretsmanager)
Roles & Permissions:
tractusx-repository-admin- View and admin permissions for all repositories
Method 2: Custom Configuration
For projects with specific requirements, use the custom template:
{
"id": "technology.myproject",
"template": "custom",
"config": {
"project_id": "technology.myproject",
"repositories": [
{
"type": "maven2",
"env": "releases",
"maven": {
"version_policy": "RELEASE",
"layout_policy": "STRICT"
}
},
{
"type": "docker",
"env": "releases",
"docker": {
"http_port": 8082,
"https_port": 8443,
"force_basic_auth": true
}
}
],
"create_group_auto": true
}
}Custom Configuration Examples
Example 1: Multi-Repository Project
{
"id": "technology.multi",
"template": "custom",
"config": {
"project_id": "technology.multi",
"repositories": [
{ "type": "maven2", "env": "releases" },
{ "type": "maven2", "env": "snapshots" },
{ "type": "docker", "env": "releases" },
{ "type": "npm", "env": "releases" }
],
"create_group_auto": true
}
}Created Resources:
Repositories:
multi-maven2-releases- URL:
https://repo.eclipse.org/repository/multi-maven2-releases/ - Type: Maven2 (RELEASE)
- URL:
multi-maven2-snapshots- URL:
https://repo.eclipse.org/repository/multi-maven2-snapshots/ - Type: Maven2 (SNAPSHOT)
- URL:
multi-docker-releases- URL:
https://repo.eclipse.org/repository/multi-docker-releases/ - Type: Docker (requires docker login)
- URL:
multi-npm-releases- URL:
https://repo.eclipse.org/repository/multi-npm-releases/ - Type: NPM
- URL:
Groups (auto-created per type):
multi-maven2- URL:
https://repo.eclipse.org/repository/multi-maven2/ - Members: multi-maven2-releases, multi-maven2-snapshots
- URL:
multi-docker- URL:
https://repo.eclipse.org/repository/multi-docker/ - Members: multi-docker-releases
- URL:
multi-npm- URL:
https://repo.eclipse.org/repository/multi-npm/ - Members: multi-npm-releases
- URL:
Users:
eclipse-multi-bot(with credentials in secretsmanager)
Roles & Permissions:
multi-repository-admin- View and admin permissions for all repositories and types
Example 2: Project with Custom Proxy
{
"id": "technology.custom",
"template": "custom",
"config": {
"project_id": "technology.custom",
"repositories": [
{ "type": "maven2", "env": "releases" },
{ "type": "maven2", "env": "snapshots" }
],
"proxies": [
{
"type": "maven2",
"custom_name": "custom-proxy",
"remote_url": "https://custom.maven.repo/maven2/",
"proxy": {
"metadata_max_age": 60
},
"negative_cache": {
"enabled": true,
"ttl": 10
}
}
],
"create_group_auto": true
}
}Created Resources:
Repositories:
custom-maven2-releases- URL:
https://repo.eclipse.org/repository/custom-maven2-releases/ - Type: Maven2 (RELEASE)
- URL:
custom-maven2-snapshots- URL:
https://repo.eclipse.org/repository/custom-maven2-snapshots/ - Type: Maven2 (SNAPSHOT)
- URL:
Proxies:
custom-proxy-maven2-proxy- URL:
https://repo.eclipse.org/repository/custom-proxy-maven2-proxy/ - Remote:
https://custom.maven.repo/maven2/ - Cache: Metadata TTL 60min, Negative cache 10min
- URL:
Groups:
custom-maven2- URL:
https://repo.eclipse.org/repository/custom-maven2/ - Members: custom-maven2-releases, custom-maven2-snapshots
- URL:
Users:
eclipse-custom-bot(with credentials in the secretsmanager)
Roles & Permissions:
custom-repository-admin(for repositories)custom-proxy-admin(for proxies)- View and admin permissions for all repositories and proxies
Usage Example:
<!-- pom.xml --> <repositories> <repository> <id>custom-maven2</id> <url>https://repo.eclipse.org/repository/custom-maven2/</url> </repository> </repositories>
Naming Conventions
- Default repository name:
<short-name>-<type>-<env>- Example:
myproject-maven2-releases
- Example:
- Short name extraction: Last segment of project ID
technology.nebula.nattable→nattable
- Custom names: Use
custom_nameattributes to override
Configuring Maven Repositories
Terraform Documentation: Maven Hosted | Maven Proxy | Maven Group
Basic Maven Repository Configuration
Standard Repositories (Releases + Snapshots)
{
"project_id": "technology.myproject",
"repositories": [
{
"type": "maven2",
"env": "releases"
},
{
"type": "maven2",
"env": "snapshots"
}
],
"create_group_auto": true
}This creates:
myproject-maven2-releasesmyproject-maven2-snapshotsmyproject-maven2(group containing both)
Advanced Maven Configuration
Custom Maven Settings
{
"type": "maven2",
"env": "releases",
"maven": {
"version_policy": "RELEASE", // RELEASE, SNAPSHOT, MIXED
"layout_policy": "STRICT", // STRICT, PERMISSIVE
"content_disposition": "INLINE" // INLINE, ATTACHMENT
},
"storage": {
"blob_store_name": "custom-blob",
"strict_content_type_validation": true,
"write_policy": "ALLOW" // ALLOW, ALLOW_ONCE, DENY
}
}Custom Repository Name
{
"type": "maven2",
"env": "releases",
"name": "custom-repo-name" // Overrides default naming
}Maven Groups
Groups aggregate multiple repositories for simplified access.
Automatic Group Creation
Set create_group_auto: true to automatically create a group containing all project repositories:
{
"project_id": "technology.myproject",
"repositories": [
{ "type": "maven2", "env": "releases" },
{ "type": "maven2", "env": "snapshots" }
],
"create_group_auto": true
}Creates: myproject-maven2 group with both repositories.
Manual Group Configuration
{
"project_id": "technology.myproject",
"repositories": [
{ "type": "maven2", "env": "releases" },
{ "type": "maven2", "env": "snapshots" }
],
"groups": [
{
"type": "maven2",
"custom_name": "myproject-public",
"include_type_in_name": false,
"members": [
"myproject-maven2-releases",
"myproject-maven2-snapshots",
"maven-central-releases-proxy"
]
}
]
}Setting up Maven Staging Repositories
Staging repositories are used for release candidate validation before promoting to production.
Method 1: Using Templates
Use the maven2StandardWithStaging template:
{
"id": "technology.myproject",
"template": "maven2StandardWithStaging"
}Creates:
myproject-maven2-releasesmyproject-maven2-snapshotsmyproject-maven2-stagingmyproject-maven2group (containing all three)
Method 2: Staging Only
For projects that only need staging (common for Jakarta EE projects):
{
"id": "ee4j.myproject",
"template": "maven2StagingOnly"
}Creates only:
myproject-maven2-staging
Method 3: Manual Configuration
{
"project_id": "technology.myproject",
"repositories": [
{ "type": "maven2", "env": "releases" },
{ "type": "maven2", "env": "snapshots" },
{
"type": "maven2",
"env": "staging",
"maven": {
"version_policy": "MIXED", // Allows both releases and snapshots
"layout_policy": "STRICT"
}
}
],
"create_group_auto": true
}Staging Group Example
For aggregating multiple staging repositories:
{
"project_id": "ee4j",
"groups": [
{
"type": "maven2",
"custom_name": "ee4j-staging",
"include_type_in_name": false,
"members": [
"angus-maven2-staging",
"batch-maven2-staging",
"cdi-maven2-staging",
"faces-maven2-staging"
]
}
]
}Configuring Other Repository Types
In addition to Maven repositories, Nexus supports multiple repository formats for different package ecosystems.
NPM Repositories
Terraform Documentation: NPM Hosted | NPM Proxy | NPM Group
NPM repositories store Node.js packages and dependencies.
Basic NPM Configuration
{
"project_id": "technology.myproject",
"repositories": [
{
"type": "npm",
"env": "releases"
},
{
"type": "npm",
"env": "snapshots"
}
]
}Creates:
myproject-npm-releasesmyproject-npm-snapshots
Advanced NPM Configuration
{
"type": "npm",
"env": "releases",
"storage": {
"blob_store_name": "npm-blob",
"strict_content_type_validation": true,
"write_policy": "ALLOW_ONCE"
},
"component": {
"proprietary_components": true
}
}NPM-specific options:
component.proprietary_components: Mark packages as proprietary (default: false)
PyPI Repositories
Terraform Documentation: PyPI Hosted | PyPI Proxy | PyPI Group
PyPI repositories store Python packages.
Basic PyPI Configuration
{
"project_id": "technology.myproject",
"repositories": [
{
"type": "pypi",
"env": "releases"
}
]
}Creates:
myproject-pypi-releases
Advanced PyPI Configuration
{
"type": "pypi",
"env": "releases",
"storage": {
"blob_store_name": "pypi-blob",
"strict_content_type_validation": true,
"write_policy": "ALLOW"
},
"component": {
"proprietary_components": false
}
}Docker Repositories
Terraform Documentation: Docker Hosted | Docker Proxy | Docker Group
Docker repositories store container images.
IMPORTANT: we won't support Docker repositories for projects, alternative options: dockerhub, GitLab container registry, ghcr.io, quay.io and later harbor.
Basic Docker Configuration
{
"project_id": "technology.myproject",
"repositories": [
{
"type": "docker",
"env": "releases"
}
]
}Creates: myproject-docker-releases
Advanced Docker Configuration
{
"type": "docker",
"env": "releases",
"docker": {
"http_port": 8082,
"https_port": 8443,
"force_basic_auth": true,
"v1_enabled": false
},
"storage": {
"blob_store_name": "docker-blob",
"write_policy": "ALLOW"
}
}Helm Repositories
Terraform Documentation: Helm Hosted | Helm Proxy
Helm repositories store Kubernetes Helm charts.
Basic Helm Configuration
{
"project_id": "technology.myproject",
"repositories": [
{
"type": "helm",
"env": "releases"
},
{
"type": "helm",
"env": "staging"
}
]
}Creates:
myproject-helm-releasesmyproject-helm-staging
Advanced Helm Configuration
{
"type": "helm",
"env": "releases",
"storage": {
"blob_store_name": "helm-blob",
"strict_content_type_validation": true,
"write_policy": "ALLOW_ONCE"
}
}Helm Usage Example
# Add repository helm repo add myproject https://repo.eclipse.org/repository/myproject-helm-releases/ # Update repos helm repo update # Install chart helm install my-release myproject/my-chart
APT Repositories
Terraform Documentation: APT Hosted | APT Proxy
APT repositories store Debian/Ubuntu packages.
Basic APT Configuration
{
"project_id": "technology.myproject",
"repositories": [
{
"type": "apt",
"distribution": "stable"
},
{
"type": "apt",
"env": "dev",
"distribution": "unstable"
}
]
}Creates:
myproject-apt(for stable)myproject-apt-dev(for unstable)
Advanced APT Configuration
{
"type": "apt",
"distribution": "stable",
"storage": {
"blob_store_name": "apt-blob",
"strict_content_type_validation": true,
"write_policy": "ALLOW"
},
"apt_signing": {
"keypair": "-----BEGIN PGP PRIVATE KEY BLOCK-----...",
"passphrase": "secret-passphrase"
},
"component": {
"proprietary_components": false
}
}APT-specific options:
| Option | Description | Required |
|---|---|---|
distribution |
Debian distribution name (e.g., stable, testing, unstable) | Yes |
apt_signing.keypair |
GPG private key for signing packages | Yes* |
apt_signing.passphrase |
Passphrase for GPG key | Yes* |
*Signing credentials are typically stored in the secretsmanager and auto-retrieved.
Important Notes:
- APT repositories require GPG signing for package integrity
- The system automatically retrieves GPG keys from the secretsmanager at path:
cbi/<project_id>/gpg
RAW Repositories
Terraform Documentation: RAW Hosted | RAW Proxy | RAW Group
RAW repositories store arbitrary files (binaries, archives, etc.).
Basic RAW Configuration
{
"project_id": "technology.myproject",
"repositories": [
{
"type": "raw",
"env": "releases"
}
]
}Creates: myproject-raw-releases
Advanced RAW Configuration
{
"type": "raw",
"env": "releases",
"storage": {
"blob_store_name": "raw-blob",
"strict_content_type_validation": false,
"write_policy": "ALLOW"
}
}Multi-Format Project Example
A complete project with multiple repository types:
{
"id": "technology.multiformat",
"template": "custom",
"config": {
"project_id": "technology.multiformat",
"repositories": [
{
"type": "maven2",
"env": "releases",
"maven": {
"version_policy": "RELEASE",
"layout_policy": "STRICT"
}
},
{
"type": "docker",
"env": "releases",
"docker": {
"http_port": 8090,
"force_basic_auth": true
}
},
{
"type": "npm",
"env": "releases"
},
{
"type": "pypi",
"env": "releases"
},
{
"type": "helm",
"env": "releases"
},
{
"type": "raw",
"env": "releases"
}
],
"create_group_auto": true
}
}This creates:
multiformat-maven2-releasesmultiformat-docker-releasesmultiformat-npm-releasesmultiformat-pypi-releasesmultiformat-helm-releasesmultiformat-raw-releases- Auto-generated groups per type
Configuring Proxies
Terraform Documentation: Proxy resources are type-specific - see Maven Proxy, Docker Proxy, NPM Proxy, etc.
Proxies cache artifacts from external repositories.
Global Proxies
Global proxies are shared across all projects. Configure in the global_proxies section:
{
"config": {
"global_proxies": [
{
"type": "maven2",
"custom_name": "maven-central-releases",
"include_type_in_name": false,
"remote_url": "https://repo1.maven.org/maven2/",
"maven": {
"version_policy": "RELEASE",
"layout_policy": "STRICT"
},
"storage": {
"blob_store_name": "blobs-proxy-maven2"
},
"negative_cache": {
"enabled": true,
"ttl": 10
},
"proxy": {
"content_max_age": 1440,
"metadata_max_age": 60
}
}
]
}
}Project-Specific Proxies
Add proxies to individual projects:
{
"project_id": "technology.myproject",
"proxies": [
{
"type": "maven2",
"remote_url": "https://custom-maven-repo.com/maven2/",
"proxy": {
"content_max_age": 1440,
"metadata_max_age": 1440
},
"storage": {
"blob_store_name": "custom-blob"
}
}
]
}Proxy Configuration Options
Cache Settings
{
"negative_cache": {
"enabled": true,
"ttl": 10 // Minutes to cache 404 responses
},
"proxy": {
"content_max_age": 1440, // Minutes to cache content
"metadata_max_age": 60 // Minutes to cache metadata
}
}Docker Proxy Example
{
"type": "docker",
"custom_name": "docker-hub",
"include_type_in_name": false,
"remote_url": "https://registry-1.docker.io",
"docker": {
"force_basic_auth": true,
"v1_enabled": false
},
"docker_proxy": {
"index_type": "HUB" // HUB, REGISTRY, CUSTOM
},
"storage": {
"blob_store_name": "blobs-proxy-docker"
}
}Advanced Configuration
Using Jsonnet Templates
For complex, repetitive configurations, use Jsonnet templates.
- Edit the Jsonnet file:
env/terraform.<env>.tfvars.jsonnet - Compile to JSON:
Or manually:
jsonnet env/terraform.prod.tfvars.jsonnet > terraform.prod.tfvars.json
Default Values
Set defaults for all repositories/proxies of a specific type:
{
"config": {
"defaults": {
"repositories": {
"maven2": {
"storage": {
"blob_store_name": "default-maven-blob"
},
"maven": {
"layout_policy": "STRICT"
}
}
},
"proxies": {
"docker": {
"docker": {
"force_basic_auth": true
},
"storage": {
"blob_store_name": "default-docker-blob"
}
}
}
}
}
}Global Groups with Auto-Collection
Automatically collect repositories across projects:
{
"global_groups": [
{
"type": "maven2",
"custom_name": "maven2-releases",
"include_type_in_name": false,
"auto_collect": {
"env": "releases",
"type": "maven2"
},
"additional_members": ["maven-central-releases-proxy"]
}
]
}This automatically adds all maven2 repositories with env: "releases" to the group.
Multiple Repositories of Different Types
{
"project_id": "technology.myproject",
"repositories": [
{ "type": "maven2", "env": "releases" },
{ "type": "docker", "env": "releases" },
{ "type": "npm", "env": "releases" },
{ "type": "pypi", "env": "releases" }
]
}Terraform Best Practices
1. Project Renaming and Terraform State
When renaming a project, Terraform will destroy and recreate all resources unless you manage the state.
Option A: State Move (Recommended)
If changing the project ID from old.project to new.project:
# List current state make status | grep old-project # Or directly with terraform terraform state list | grep old-project # Move each resource (requires direct terraform command) terraform state mv \ 'nexus_repository_maven_hosted.maven-repositories["old-project-maven2-releases"]' \ 'nexus_repository_maven_hosted.maven-repositories["new-project-maven2-releases"]' # Verify changes make plan
Option B: Import Existing Resources
If resources already exist in Nexus:
# Import repository (requires direct terraform command) terraform import \ 'nexus_repository_maven_hosted.maven-repositories["project-maven2-releases"]' \ project-maven2-releases # Verify changes make plan
2. Handling Large Changes
For projects with many resources:
# Increase parallelism (default: 30)
TF_PARALLELISM=50 make apply3. Targeting Specific Resources
To apply changes to specific resources only:
# Target specific resources (requires direct terraform command) # Note: Make doesn't support -target flag, use terraform directly terraform apply -var-file=terraform.${NEXUS_ENV}.tfvars.json \ -target=module.blobstores # Or target specific resources terraform apply -var-file=terraform.${NEXUS_ENV}.tfvars.json \ -target='nexus_repository_maven_hosted.maven-repositories["myproject-maven2-releases"]'
4. Workspace Management
Always work in the correct workspace:
# List workspaces (requires direct terraform command) terraform workspace list # Select workspace (use make instead) make select # Uses $NEXUS_ENV variable # Or select manually terraform workspace select prod # Verify current workspace terraform workspace show
5. Handling Archived Projects
To mark a project as archived without deleting:
{
"id": "old.project",
"template": "maven2Standard",
"archived": true
}The archived flag is metadata only and doesn't affect Terraform resources.
6. Blob Store Management
When changing blob stores, data must be manually migrated:
- Create new blob store (https://github.com/eclipse-cbi/sonatype-nexus)
- Update repository configuration
- Apply changes (creates new repo or updates existing)
- Manually migrate data in Nexus UI
- Delete old blob store when empty
7. Handling Terraform Lock Files
When encountering lock issues:
# Force unlock (use with caution!) - requires direct terraform command terraform force-unlock <lock-id> # Verify workspace is clean make status # Or list resources terraform state list
Bot Management
Sharing Permissions Between Projects (shared_perms_from)
When you have multiple related projects that need access to the same repositories but require separate bot accounts, use the shared_perms_from feature. This allows projects to share repository resources and permissions without duplicating Nexus resources.
Use Case
Projects eclipse.platform and eclipse.platform.releng both need access to the same Maven repositories, but each requires its own bot account with similar permissions.
Configuration
In the environment configuration file (e.g., env/<env>.json):
{
"projects": [
{
"id": "eclipse.platform.releng",
"template": "maven2StandardWithStaging",
"config": {
"project_id": "eclipse.platform.releng"
}
},
{
"id": "eclipse.platform",
"template": "custom",
"config": {
"project_id": "eclipse.platform",
"shared_perms_from": "eclipse.platform.releng"
}
}
]
}What This Creates
For eclipse.platform.releng (main project):
- Blobstore:
eclipse-platform-releng - Repositories:
eclipse-releng-maven2-releaseseclipse-releng-maven2-snapshotseclipse-releng-maven2-staging
- Repository group:
eclipse-releng-maven2 - Bot:
eclipse-releng-bot - Role:
releng-repository-bot-role(with permissions to all repositories)
For eclipse.platform (shared permissions project):
- Bot:
eclipse-platform-bot - Role:
platform-repository-bot-role(with same permissions as releng role) - No duplicate resources: Uses existing repositories from
eclipse.platform.releng
NOTE:
- The referenced project (specified in
shared_perms_from) must be defined before the sharing project in the configuration - Bot credentials are stored separately in Vault:
repo.eclipse.org/bot/<bot-userid> - Both bots can publish to the same repositories simultaneously
Renewing Bot Secrets (force_token_update)
When you need to regenerate bot credentials (e.g., for security rotation or when credentials are compromised), use the force_token_update flag.
Use Case
The bot password for eclipse.platform.releng needs to be regenerated and updated in Vault.
Procedure
- Add force_token_update flag to your project configuration:
{
"id": "eclipse.platform.releng",
"template": "maven2StandardWithStaging",
"config": {
"project_id": "eclipse.platform.releng",
"force_token_update": true
}
}- Apply the configuration:
This will:
- Generate a new random password for the bot
- Update the bot's password in Nexus
- Store the new credentials in Vault at:
cbi/eclipse.platform.releng/repo.eclipse.org
- Remove the flag after successful update:
{
"id": "eclipse.platform.releng",
"template": "maven2StandardWithStaging",
"config": {
"project_id": "eclipse.platform.releng"
}
}- Apply again to persist the clean configuration:
Important Notes
- Temporary Flag: The
force_token_updateflag should only be present during the credential renewal operation - Automatic Cleanup: Always remove the flag after credentials are successfully updated
- Vault Integration: New credentials are automatically stored in Vault
- Secret Protection: The
ignore_changeslifecycle rule prevents accidental credential updates - Required for Username Changes: Also use this flag when bot usernames change (e.g., after refactoring project structure)
Common Scenarios
Scenario 1: Renew Bot Password
// Step 1: Add flag { "id": "myproject", "template": "maven2Standard", "force_token_update": true } // Step 2: Apply → password regenerated // Step 3: Remove flag { "id": "myproject", "template": "maven2Standard" } // Step 4: Apply → configuration cleaned
Scenario 2: Update Bot Username After Refactoring
If bot userid changes (e.g., from eclipse-platform-bot to eclipse-releng-bot), the Vault secret must be updated:
// Before: eclipse.platform with bot eclipse-platform-bot // After: Renamed to eclipse.platform.releng, bot should be eclipse-releng-bot // Step 1: Add force_token_update to trigger Vault secret update { "id": "eclipse.platform.releng", "template": "maven2StandardWithStaging", "force_token_update": true } // Step 2: Apply → Vault secret updated with new username // Step 3: Remove flag and apply again
The force_token_update flag bypasses Terraform's ignore_changes protection on the Vault secret, allowing the username and other fields to be updated.
Accessing Updated Credentials
After renewal, retrieve credentials from Vault:
# Using Vault CLI vault kv get -mount="cbi" "<project_id>/repo.eclipse.org"
Troubleshooting
Common Issues
1. "Error: workspace doesn't exist"
Cause: Terraform workspace not initialized.
Solution:
# Set environment and use make export NEXUS_ENV="prod" make select # Or manually with terraform terraform workspace new prod # or terraform workspace select prod
2. Permission Denied
Cause: Invalid Nexus or credentials.
Solution:
# Check environment variables echo $VAULT_TOKEN echo $NEXUS_URL vaultctl status # Re-source environment . ./.env.sh
3. State Lock Errors
Cause: Previous operation didn't complete properly.
Solution:
# Force unlock (last resort) terraform force-unlock <lock-id>
4. Jsonnet Compilation Errors
Cause: Syntax errors in .jsonnet file.
Solution:
# Test jsonnet compilation jsonnet env/terraform.prod.tfvars.jsonnet # Check for syntax errors jsonnetfmt --test env/terraform.prod.tfvars.jsonnet
Debugging Tips
Enable Verbose Logging
# Enable debug logging export TF_LOG=DEBUG make apply
Verify Resource Configuration
# Show specific resource (requires direct terraform command) terraform state show 'nexus_repository_maven_hosted.maven-repositories["myproject-maven2-releases"]' # List all resources terraform state list # Get outputs make outputs # Or in JSON format make outputs-json