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.
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-publisherrole andsvc-learner-ch20local user — upload/read only to hosted; -
all resource names prefixed
learner-ch20-for teardown ownership.
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
- A: repository creation changes configuration/database state but stores no artifact bytes until upload/cache.
- B: the first proxy request should create proxy asset metadata and cached blob bytes; before it, only configuration exists.
- C: the scoped publisher can upload/read hosted content but cannot administer repositories or roles.
- 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
- Validate base URL host is the disposable target.
- Fetch/checksum Swagger and assert required Raw/security paths.
- GET each deterministic repository name.
- Create missing resources only with current schema-approved payloads.
- Normalize/diff owned fields; PUT only changed resources.
- After hosted creation, query exact generated browse/read/add privilege IDs.
- Create/update role to the intended privilege set.
- Create local service user only if absent; never print password.
-
Emit
create,update, orno-opper resource. - 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?
It makes caching/failure behavior reproducible and keeps routing experiments isolated.
Second run issues PUT for every repository although values are unchanged. Is that idempotence?
No. This checkpoint requires state comparison and no-op when state already matches.
Why is reversing group order meaningful drift?
Group order can change which member supplies matching content, creating real routing/shadowing implications.
Why delete the group before member repositories in teardown?
It removes the aggregate dependency first and makes the dependency graph explicit.
What does Chapter 21 add?
CI publication, webhooks/events, build identity, and promotion of the exact already-built artifact.
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
- Sonatype: Automation / REST API — OpenAPI/Swagger location, API integration, and Jetty 12 URL-encoding requirements.
- Sonatype: Nexus Repository API Reference — current endpoint catalog and request/response models.
- Sonatype: Repositories API — generic and format/type-specific repository configuration endpoints.
- Sonatype: Components API — component list/get/upload/delete behavior and continuation tokens.
- Sonatype: Pagination — continuation-token iteration contract.
- Sonatype: Security Management API — users, roles, privileges, content selectors, LDAP/SAML/OIDC boundaries.
- Sonatype: Authentication — local/external authentication and automation identity boundaries.
- Sonatype: User Tokens — current self-hosted Pro entitlement and token behavior.
- Sonatype: System Requirements — Java/database/storage baseline.
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.