Chapter 20Lesson 05280–390 min

Checkpoint Lab — REST API, Repository Provisioning, Security Automation, Pagination, and Infrastructure-as-Code Patterns

Integrate Chapter 20 into a production-style checkpoint: declare a complete disposable repository topology and scoped service identity, provision it from code, prove the second run is a no-op, inject one controlled manual drift, reconcile it safely, and destroy only resources explicitly marked as Chapter 20 lab state.

CheckpointTopologyIdempotenceDrift reconciliationTeardown

Learning objectives

  • Declare and provision a disposable hosted/proxy/group topology through documented REST APIs.
  • Create a least-privilege service identity from code without committing its secret.
  • Run reconciliation twice and prove the second run produces no configuration mutation.
  • Detect one manual drift, review the plan, and reconcile the owned field safely.
  • Produce an evidence packet and tear down only named/marked disposable resources.

Version baseline (26 August 2026). These lessons use Sonatype Nexus Repository 3.95.2-01 as the dated self-hosted reference point, verified from Sonatype's public release repository, with Java 21 as the current runtime requirement. The mandatory lab assumes a small, disposable, loopback-only self-hosted Community Edition installation using H2 and a file blob store. The local instance's own /service/rest/swagger.json is authoritative for the exact endpoint schema you are about to call; API fields, repository recipes, security endpoints, and edition entitlements can change between releases.

Automation safety boundary. Do not put real passwords, user-token passcodes, Authorization headers, production base URLs, or employer namespaces into source files or lesson evidence. The examples use environment variables, synthetic names, loopback URLs, and disposable resources. User Tokens are a Pro capability in current self-hosted Nexus Repository; Community labs therefore use a disposable local account/password only for bootstrap and immediately reduce privileges for the service identity.

Pagination documentation note. Current Sonatype pages agree on the continuationToken contract but currently disagree on the documented default item count for components/assets. The robust automation rule is independent of page size: process every returned items array, pass the returned token unchanged, and stop only when continuationToken is null.

1. Checkpoint scenario and architecture

Provision a repeatable Raw artifact topology for synthetic build outputs:

  • learner-ch20-hosted — authoritative internal publications;
  • learner-ch20-proxy — proxy to a harmless loopback fixture server, not a public registry;
  • learner-ch20-group — hosted then proxy in deliberate order;
  • learner-ch20-publisher role and svc-learner-ch20 local user — upload/read only to hosted;
  • all resource names prefixed learner-ch20- for teardown ownership.
Desired state reconciles the control plane; scoped identity verifies the data plane
flowchart TD
Y[desired-state.yaml] --> R[Chapter 20 reconciler]
R -->|Admin REST, bootstrap credential| N[Nexus API]
N --> H[learner-ch20-hosted]
N --> P[learner-ch20-proxy]
N --> G[learner-ch20-group]
N --> S[role + local service user]
CI[Local CI-like publisher] -->|scoped credential| H
C[Consumer] --> G
P --> F[Loopback fixture upstream]
R --> E[Plan + evidence]
H --> DB[(database)]
H --> B[(file blob store)]

2. Preflight and exact assumptions

Assumption Requirement
Nexus Disposable self-hosted Community 3.95.2-01 reference; verify local release and Swagger
Runtime Java 21 current requirement
Database/blob H2 + disposable file blob store for this learning lab only
Network Nexus and fixture on loopback/private addresses
Authentication Bootstrap admin secret + separate service secret injected at runtime
Paid features None required; User Tokens/Pro not mandatory
CI Local shell/Python publisher; hosted CI optional

Start a tiny local upstream fixture:

mkdir -p /tmp/ch20-upstream/third-party
printf 'fixture upstream artifact\n' > /tmp/ch20-upstream/third-party/upstream-1.0.txt
python -m http.server 8099 --bind 127.0.0.1 --directory /tmp/ch20-upstream

3. Required predictions before execution

  1. A: repository creation changes configuration/database state but stores no artifact bytes until upload/cache.
  2. B: the first proxy request should create proxy asset metadata and cached blob bytes; before it, only configuration exists.
  3. C: the scoped publisher can upload/read hosted content but cannot administer repositories or roles.
  4. D: second reconciliation against unchanged desired state produces zero POST/PUT/DELETE mutations.

4. Desired-state document

managed_prefix: learner-ch20-
repositories:
  hosted:
    name: learner-ch20-hosted
    online: true
    blobStoreName: default
    strictContentTypeValidation: true
    writePolicy: ALLOW_ONCE
  proxy:
    name: learner-ch20-proxy
    online: true
    blobStoreName: default
    strictContentTypeValidation: true
    remoteUrl: http://127.0.0.1:8099/
  group:
    name: learner-ch20-group
    online: true
    blobStoreName: default
    memberNames: [learner-ch20-hosted, learner-ch20-proxy]
security:
  role: learner-ch20-publisher
  user: svc-learner-ch20
  password_from_env: NEXUS_CH20_SERVICE_PASSWORD

Hosted-first order is intentional for the owned synthetic namespace. In real designs revisit namespace shadowing/dependency-confusion from Chapters 4 and 13.

5. Reconciler behavior contract

  1. Validate base URL host is the disposable target.
  2. Fetch/checksum Swagger and assert required Raw/security paths.
  3. GET each deterministic repository name.
  4. Create missing resources only with current schema-approved payloads.
  5. Normalize/diff owned fields; PUT only changed resources.
  6. After hosted creation, query exact generated browse/read/add privilege IDs.
  7. Create/update role to the intended privilege set.
  8. Create local service user only if absent; never print password.
  9. Emit create, update, or no-op per resource.
  10. Never delete during normal reconciliation; teardown is separate.

6. First run: provision and verify

learner-ch20-hosted     create
learner-ch20-proxy      create
learner-ch20-group      create
learner-ch20-publisher  create
svc-learner-ch20        create

GET each repository from its format/type endpoint and GET role/user. Save redacted responses under evidence/run1/. Confirm group member order exactly.

7. Verify data-plane behavior and artifact identity

export NEXUS_SERVICE_USER='svc-learner-ch20'
read -r -s -p 'Disposable service password: ' NEXUS_CH20_SERVICE_PASSWORD
export NEXUS_CH20_SERVICE_PASSWORD
printf '\n'
printf 'build-id=ch20-001\n' > /tmp/ch20-build.txt
sha256sum /tmp/ch20-build.txt > /tmp/ch20-build.sha256
curl --fail --silent --show-error -u "$NEXUS_SERVICE_USER:$NEXUS_CH20_SERVICE_PASSWORD" \
  --upload-file /tmp/ch20-build.txt \
  "$NEXUS_URL/repository/learner-ch20-hosted/learner-example/ch20/build-1.0.0.txt"
curl --fail --silent --show-error -u "$NEXUS_SERVICE_USER:$NEXUS_CH20_SERVICE_PASSWORD" \
  "$NEXUS_URL/repository/learner-ch20-group/learner-example/ch20/build-1.0.0.txt" \
  --output /tmp/ch20-build-via-group.txt
sha256sum /tmp/ch20-build-via-group.txt

Then request third-party/upstream-1.0.txt through the group, verify it resolves via proxy, and record assets before/after to prove cache state changed.

8. Prove least privilege with a negative test

status=$(curl --silent --show-error -u "$NEXUS_SERVICE_USER:$NEXUS_CH20_SERVICE_PASSWORD" \
  -o /tmp/ch20-admin-denied.json -w '%{http_code}' "$NEXUS_URL/service/rest/v1/security/roles")
printf 'security roles status=%s\n' "$status"
# Expected: 403 for the scoped publisher.

If it succeeds, the role is too broad.

9. Second run: prove idempotence

learner-ch20-hosted     no-op
learner-ch20-proxy      no-op
learner-ch20-group      no-op
learner-ch20-publisher  no-op
svc-learner-ch20        no-op

Capture evidence proving no POST/PUT/DELETE mutation occurred. “Already exists” after repeated conflicts is not idempotence.

10. Inject one controlled manual drift

Change only the disposable group member order to ["learner-ch20-proxy", "learner-ch20-hosted"]. Rerun in plan/detect-only mode. Expected output identifies only member-order drift. Explain that member order can alter which matching content is returned.

11. Review and reconcile drift safely

Approve the plan and PUT the desired group configuration. Verify member order, hosted artifact SHA-256 through group, proxy fixture availability, unchanged service-user authorization, and that no repository was deleted/recreated.

12. Complete inventory evidence

Run continuation-token iteration for assets in all three lab repositories. Preserve page count, item count, final continuationToken=null, paths, repository names, checksums where available, and credential-free download URLs.

13. Guarded teardown: destroy only what the lab owns

Destructive step. Teardown is permitted only on the disposable Chapter 20 instance and only for exact Chapter 20 resources.

Before each DELETE, GET the object, print non-secret normalized identity, validate base URL and prefix/allowlist, and require explicit --destroy. Delete dependency-aware: service user → role → group → proxy → hosted after final checksum/evidence. Verify absence afterward. Blob-space reclamation remains a separate supported maintenance step; never delete blob/database files manually.

14. Required evidence packet

Evidence What it proves
Version/edition/runtime + Swagger checksum Compatibility baseline
Desired state without secrets Reviewable intent
Run 1 and Run 2 plans Create then idempotent no-op
Drift plan + reconciliation Controlled ownership/correction
Repository/security GETs Actual control-plane state
Positive upload/read + negative 403 Least privilege
SHA-256 before/after group path Artifact identity
Complete paginated inventory No first-page truncation
Guarded teardown result Only managed resources removed

15. Verification checklist

  • □ Local Swagger read before mutation.
  • □ No credential in desired state/source/evidence.
  • □ First run created exactly managed objects.
  • □ Control plane and data plane verified separately.
  • □ Service identity succeeds only on intended repository operations and receives 403 on admin API.
  • □ Every pagination loop ended with null token.
  • □ Second run performed no mutation.
  • □ Manual group-order drift detected before correction.
  • □ Drift reconciled in place, not by delete/recreate.
  • □ Final artifact checksum matched.
  • □ Teardown guard verified target and names.
  • □ No blob/database files edited manually.

16. Knowledge check

Why use a local upstream fixture instead of a public registry?

Second run issues PUT for every repository although values are unchanged. Is that idempotence?

Why is reversing group order meaningful drift?

Why delete the group before member repositories in teardown?

What does Chapter 21 add?

17. Chapter checkpoint summary

You can now automate Nexus administration as controlled reconciliation: derive behavior from the instance API contract, preserve secret hygiene, separate bootstrap from scoped service identities, paginate completely, compare desired and actual state, detect drift, update in place, verify data-plane effects, and destroy only explicitly managed disposable resources. Chapter 21 connects this control plane to CI publication, webhooks, build identity, and promotion without rebuilding artifacts.

Official references and version notes

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.