Chapter 12Lesson 02205–270 min

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.

Ansible native labTerraform registryClient isolationProtocol evidenceCE-compatible

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?

Why is the Ansible token file still sensitive even though it contains Base64?

What does the Swagger inspection prove?

Why are Conan and Swift fixtures acceptable here?

Which repository receives writes in the Ansible topology?

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

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.