Conan, Ansible Galaxy, Terraform, Swift, and Emerging Repository Formats: Guided Hands-On Workflow and Core Operations
Verify the current format matrix, run a disposable native Ansible Galaxy workflow, inspect Terraform registry paths and authentication safely, and use faithful fixtures for Conan and Swift where local tooling or entitlement is uncertain.
Learning objectives
- Verify current recipe/API support for all four named formats on the running Nexus instance.
- Create a disposable Ansible hosted/proxy/group topology and build a harmless collection locally.
- Publish and consume the Ansible collection using isolated client configuration and protected temporary credentials.
- Create a Terraform hosted repository, upload a synthetic module through the documented registry path, and inspect generated state.
- Use precise fixtures for Conan and Swift without pretending uninstalled clients or uncertain entitlements were exercised live.
Lab contract. Mandatory live work uses Nexus Repository Community Edition 3.95.2 on loopback/private networking. Ansible Galaxy 3.93+ and Terraform 3.89+ hosted behavior are current CE-compatible paths. Use only synthetic namespaces. Commands are POSIX/Bash unless labeled. If Ansible/Terraform tooling is absent, complete the repository/API evidence path and use the provided client fixtures; do not modify the workstation globally merely to satisfy the lesson.
1. Create a disposable workspace and credential boundary
export NX_URL="http://127.0.0.1:8081"
export LAB="${TMPDIR:-/tmp}/nexus-ch12"
rm -rf "$LAB"
mkdir -p "$LAB"/{evidence,ansible-src,ansible-home,terraform-src,terraform-home,fixtures}
chmod 700 "$LAB"
curl -fsS "$NX_URL/service/rest/v1/status" | tee "$LAB/evidence/status.txt"
curl -fsS "$NX_URL/service/rest/v1/repositories" | tee "$LAB/evidence/repositories-before.json"
ansible-galaxy --version 2>&1 | tee "$LAB/evidence/ansible-version.txt" || true
terraform version 2>&1 | tee "$LAB/evidence/terraform-version.txt" || true
swift --version 2>&1 | tee "$LAB/evidence/swift-version.txt" || true
conan --version 2>&1 | tee "$LAB/evidence/conan-version.txt" || true
read -r -p 'Disposable Nexus username: ' NX_USER
read -r -s -p 'Disposable Nexus password: ' NX_PASS; echo
export NX_AUTH_FILE="$LAB/nexus.netrc"
umask 077
printf 'machine 127.0.0.1 login %s password %s\n' "$NX_USER" "$NX_PASS" > "$NX_AUTH_FILE"
unset NX_PASS
The netrc file keeps the password out of curl command arguments. It is still a secret-bearing file, so the lab restricts permissions and deletes it during cleanup.
2. Verify support from the running instance before creating recipes
curl -fsS "$NX_URL/service/rest/swagger.json" > "$LAB/evidence/swagger.json"
python - <<'PYI'
from pathlib import Path
import json, os, re
p=Path(os.environ['LAB'],'evidence','swagger.json')
text=p.read_text(errors='replace')
for fmt in ('ansible','terraform','swift','conan'):
paths=sorted(set(re.findall(r'/v1/repositories/'+fmt+r'[^"\s<]*', text, re.I)))
print(f'[{fmt}]')
print('\n'.join(paths) if paths else 'no matching repository API path found')
PYI
This does not prove every client operation, but it prevents a common failure: following instructions for a recipe absent from the actual server.
3. Create the Ansible topology
In Settings → Repository → Repositories, create three disposable repositories:
| Name | Recipe | Purpose |
|---|---|---|
academy-ch12-ansible-hosted |
ansiblegalaxy (hosted) | Authoritative internal collection publication target. |
academy-ch12-ansible-proxy |
ansiblegalaxy (proxy) |
Remote https://galaxy.ansible.com; controlled
public cache.
|
academy-ch12-ansible-group |
ansiblegalaxy (group) | Hosted first, proxy second; one consumer read URL. |
Create a disposable user that can browse/read the group and
add/browse/read the hosted repository. If authenticated
ansible-galaxy reads are required, enable the Ansible
Galaxy Bearer Token Realm for the lab and record that security
change. Do not use an administrator identity as the package
publisher.
4. Build a synthetic Ansible collection
cd "$LAB/ansible-src"
if command -v ansible-galaxy >/dev/null 2>&1; then
ansible-galaxy collection init learner_example.runtime_probe
cd learner_example/runtime_probe
python - <<'PYI'
from pathlib import Path
p=Path('galaxy.yml')
s=p.read_text()
s=s.replace('version: 1.0.0', 'version: 1.0.0')
p.write_text(s)
Path('README.md').write_text('# Disposable Nexus Ansible collection\n')
PYI
ansible-galaxy collection build --output-path "$LAB/ansible-src/dist" | tee "$LAB/evidence/ansible-build.txt"
else
mkdir -p "$LAB/ansible-src/dist"
printf 'fixture: ansible-galaxy not installed; use UI upload walkthrough only\n' > "$LAB/evidence/ansible-build-fixture.txt"
fi
The archive filename identifies namespace, collection, and version. It is still not “just a tarball”: the Galaxy client expects API metadata and collection semantics around it.
5. Publish without putting credentials in process arguments
If the collection archive exists, use the Nexus UI upload for the
academy-ch12-ansible-hosted repository. This avoids
copying Sonatype documentation examples that place basic credentials
directly in curl arguments. Then capture the resulting
component/asset state:
curl -fsS --netrc-file "$NX_AUTH_FILE" "$NX_URL/service/rest/v1/components?repository=academy-ch12-ansible-hosted" | tee "$LAB/evidence/ansible-components.json"
curl -fsS --netrc-file "$NX_AUTH_FILE" "$NX_URL/service/rest/v1/assets?repository=academy-ch12-ansible-hosted" | tee "$LAB/evidence/ansible-assets.json"
If UI upload is unavailable on a future release, stop and consult that release's current Ansible upload documentation instead of guessing a raw HTTP path.
6. Isolate Ansible client state
For anonymous read, an isolated requirements file can point directly
at the group. If authentication is required, construct the Base64
credential only inside a temporary ansible.cfg and
never print it:
export ANSIBLE_CONFIG="$LAB/ansible-home/ansible.cfg"
export ANSIBLE_COLLECTIONS_PATH="$LAB/ansible-home/collections"
mkdir -p "$ANSIBLE_COLLECTIONS_PATH"
chmod 700 "$LAB/ansible-home"
read -r -p 'Disposable Nexus username again: ' A_USER
read -r -s -p 'Disposable Nexus password again: ' A_PASS; echo
A_TOKEN="$(printf '%s:%s' "$A_USER" "$A_PASS" | base64 | tr -d '\n')"
umask 077
cat > "$ANSIBLE_CONFIG" <<EOF
[defaults]
collections_path = $ANSIBLE_COLLECTIONS_PATH
[galaxy]
server_list = nexus_group
[galaxy_server.nexus_group]
url = $NX_URL/repository/academy-ch12-ansible-group/
token = $A_TOKEN
EOF
cat > "$LAB/ansible-home/requirements.yml" <<EOF
---
collections:
- name: learner_example.runtime_probe
version: "1.0.0"
source: http://127.0.0.1:8081/repository/academy-ch12-ansible-group/
token: "$A_TOKEN"
EOF
chmod 600 "$ANSIBLE_CONFIG" "$LAB/ansible-home/requirements.yml"
unset A_PASS A_TOKEN A_USER
if command -v ansible-galaxy >/dev/null 2>&1; then
ansible-galaxy collection install -r "$LAB/ansible-home/requirements.yml" | tee "$LAB/evidence/ansible-install.txt"
fi
Base64 is only transport representation. The temporary config remains secret-bearing and must be removed at cleanup.
7. Create and populate a Terraform hosted repository
Create academy-ch12-terraform-hosted using
terraform (hosted). Then build a tiny module archive
containing real .tf content and upload it through the
documented registry path.
mkdir -p "$LAB/terraform-src/module"
cat > "$LAB/terraform-src/module/main.tf" <<'EOF'
variable "message" { type = string; default = "nexus-ch12" }
output "message" { value = var.message }
EOF
cat > "$LAB/terraform-src/module/versions.tf" <<'EOF'
terraform { required_version = ">= 1.0" }
EOF
(cd "$LAB/terraform-src/module" && zip -q -r "$LAB/terraform-src/learner-module-1.0.0.zip" .)
sha256sum "$LAB/terraform-src/learner-module-1.0.0.zip" | tee "$LAB/evidence/terraform-module-sha256.txt"
curl --fail --silent --show-error --netrc-file "$NX_AUTH_FILE" -X PUT "$NX_URL/repository/academy-ch12-terraform-hosted/v1/modules/learner-example/runtime-probe/local/1.0.0/learner-module-1.0.0.zip" -H 'Content-Type: application/zip' --data-binary "@$LAB/terraform-src/learner-module-1.0.0.zip" -o /dev/null -w 'HTTP %{http_code}\n' | tee "$LAB/evidence/terraform-upload.txt"
curl -fsS --netrc-file "$NX_AUTH_FILE" "$NX_URL/service/rest/v1/assets?repository=academy-ch12-terraform-hosted" | tee "$LAB/evidence/terraform-assets.json"
Nexus validates/classifies Terraform content according to registry paths and archive rules. A generic Raw upload would not create the same registry contract.
8. Build a non-URL authentication fixture for 3.95+
Enable the Terraform Token Realm only if you intend to exercise client authentication. Retrieve the bearer token with the same protected netrc boundary, write it into a disposable CLI config, and never echo it:
export TF_CLI_CONFIG_FILE="$LAB/terraform-home/terraform.rc"
TOKEN_JSON="$LAB/terraform-home/token.json"
curl -fsS --netrc-file "$NX_AUTH_FILE" "$NX_URL/repository/academy-ch12-terraform-hosted/v1/api/token" > "$TOKEN_JSON"
chmod 600 "$TOKEN_JSON"
python - <<'PYI'
from pathlib import Path
import json, os
lab=Path(os.environ['LAB'])
token=json.loads((lab/'terraform-home/token.json').read_text())['token']
config="""credentials "127.0.0.1:8081" {
token = "__TOKEN__"
}
host "academy-ch12-terraform.local" {
services = {
modules.v1 = "http://127.0.0.1:8081/repository/academy-ch12-terraform-hosted/v1/modules/"
}
}
"""
config=config.replace("__TOKEN__", token)
p=lab/'terraform-home/terraform.rc'
p.write_text(config)
p.chmod(0o600)
PYI
rm -f "$TOKEN_JSON"
This is a configuration artifact, not a reason to invent DNS. A full Terraform client resolution exercise requires a registry hostname/source address that matches the configured host and is best performed in the dedicated Terraform course. Here we prove Nexus's token and service-endpoint boundary without leaking credentials.
9. Conan and Swift: use truthful compatibility fixtures
Conan decision record
- Nexus: 3.95.2 Community Edition
- Client major: record installed Conan major version
- Conan 1.x: proxy + hosted; no group
- Conan 2.x: current docs describe proxy + hosted + group
- Historical entitlement note: 3.76 release notes called native Conan 2 Pro-only
- Action: verify recipe/license visibility before live rollout
- Never mix Conan 1 and Conan 2 content in one assumed protocol lane
{
"registries": {
"[default]": {
"url": "https://repo.example.invalid/repository/swift-group/"
}
},
"version": 1
}
The Swift fixture models the registry endpoint but deliberately uses
.invalid because this lesson is not a TLS/DNS
deployment lab. Record that proxy arrived in 3.89, hosted in 3.90,
group in 3.91, SPM 5.7+ is required, and Windows publishing is
currently unsupported.
10. Challenge: choose the correct control
A team asks for one “emerging-all” group URL containing Ansible, Terraform, Swift, and Conan. Reject the design. Nexus groups aggregate repositories of a compatible format/protocol; they are not a cross-format multiplexing endpoint. Produce four client-specific endpoint decisions, and for Conan include the protocol-generation check before deciding whether a group is valid.
11. Knowledge check
Why does the lab upload Terraform through
/v1/modules/... instead of Raw?
Because the Terraform registry path carries module identity/classification semantics and allows Nexus to provide registry metadata. Raw would only store path-addressed bytes.
Why is the Ansible token file still sensitive even though it contains Base64?
Base64 is reversible. The token represents credentials and must be protected and deleted.
What does the Swagger inspection prove?
It proves which repository-management API paths the running Nexus build exposes. It does not by itself prove every client operation or entitlement.
Why are Conan and Swift fixtures acceptable here?
The lesson must not fake live client compatibility when tooling, platform, or entitlement is uncertain. A precise fixture plus version/feature record is safer than an unverified command sequence.
Which repository receives writes in the Ansible topology?
The hosted repository. The group is the consumer aggregation endpoint; the proxy caches upstream content.
12. Summary and next step
The workflow demonstrated the operator pattern: verify recipes, isolate credentials and client state, publish only synthetic content, inspect database/blob-visible repository state through supported APIs, and record what was simulated rather than claiming it was executed.
Lesson 3 turns those mechanics into architecture decisions: when standardization on Nexus helps and when maturity, migration, or protocol gaps justify a narrower rollout.
Official references and version notes
- Sonatype: Download and 3.95.0–3.95.2 release notes.
- Sonatype: Self-Hosted feature matrix.
- Sonatype: Conan Repositories.
- Sonatype: Ansible Repositories, repository creation, and client configuration.
- Sonatype: Terraform Repositories, repository creation, client configuration, and CLI usage.
- Sonatype: Swift Repositories, repository creation, and SPM configuration.
- Sonatype: Nexus Repository API Reference.
- Sonatype: Repository Export and Repository Import — verify current Pro entitlement and format coverage before adoption.
Version-sensitive statements were rechecked on 2026-08-26. The mandatory lab pins Nexus Repository 3.95.2, which Sonatype's current download page lists as the latest downloadable self-hosted release and whose release notes date it to 2026-08-21. Java 21 remains the current Nexus runtime requirement. The mandatory path uses Community-compatible Ansible Galaxy and Terraform behavior and avoids depending on Pro-only export/import, user-token, staging, HA, or content-replication features.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.