# Omega Cloud -- CI/Automation Integration Guide (TEMPLATE)

TEMPLATE NOTE (removed before delivery): fields written as {{THIS}} are filled
in by us per client; fields marked "you fill" come from the credential files
delivered to your custodians and are entered by you, on your side, into your
CI system's secret store.

How to connect a CI system (Jenkins, GitLab CI, GitHub Actions, or plain
scripts) to your Omega Cloud environment. Read the Handover Pack first --
sections 4 (credential handling) and 6 (boundary items) are assumed here.

## 1. The one rule: pipelines use the application credential

All automation authenticates as the `{{TENANT_SHORT_NAME}}-svc` account's
**application credential** (`{{TENANT_SHORT_NAME}}-svc-cred`). Never a
password. Why this pattern:

- **Scoped**: it acts only inside your project, with exactly the rights of
  the `-svc` account (`member` + `load-balancer_member`). It cannot touch
  identity (users, roles), quotas, or anything outside your domain -- so a
  leaked pipeline credential is contained by design.
- **Revocable without collateral**: delete the application credential and
  every pipeline using it stops -- but the `-svc` account, its password, and
  your other credentials are untouched. Rotations never require us.
- **No interactive dependency**: it does not expire with a password change.

The corollary boundaries (platform-enforced, not policy suggestions):

- The application credential **cannot create or delete Kubernetes clusters**
  (section 5 below).
- It cannot create users, grant roles, or change quotas. Pipelines that try
  will get a permission error -- that error is correct behavior.

## 2. Storing the credential in your CI system

You received the credential's `id` and `secret` in the delivered file
`{{TENANT_SHORT_NAME}}-svc-appcred.txt`. Put both into your CI system's
native secret store (in Jenkins: Manage Jenkins > Credentials, as
"secret text" entries or a username/password pair), then inject them into
jobs as environment variables. Never:

- commit them to a repository (including a private one),
- bake them into a container or VM image,
- echo them in build output (mask them in your CI configuration).

## 3. clouds.yaml for pipelines

The OpenStack CLI and SDKs read `clouds.yaml`. A working template -- also
delivered ready to copy as `scripts/clouds.yaml.template` in your starter
kit -- values in angle brackets are yours to fill from the delivered
credential file, and the secret should be injected at job runtime, not
written to disk in a repository:

    clouds:
      {{TENANT_SHORT_NAME}}:
        auth_type: v3applicationcredential
        auth:
          auth_url: {{AUTH_URL}}
          application_credential_id: <id from the delivered credential file>
          application_credential_secret: <injected from your CI secret store>
        region_name: {{REGION}}
        identity_api_version: 3
        cacert: <path to the delivered CA bundle {{CA_BUNDLE_FILE}}>

This block is delivered ready-to-copy as `scripts/clouds.yaml.template` in the
starter kit; edit that file rather than retyping the stanza.

Then in a pipeline step:

    export OS_CLOUD={{TENANT_SHORT_NAME}}
    openstack token issue

A token comes back scoped to your project; that is your smoke test. The
starter-kit script `scripts/smoke-test.sh` wraps this (token, catalog,
quota) as one pipeline-ready step, and `scripts/Jenkinsfile.example` shows
a full Jenkins pipeline built this way.
Equivalent environment-variable form (no clouds.yaml file needed):

    export OS_AUTH_TYPE=v3applicationcredential
    export OS_AUTH_URL={{AUTH_URL}}
    export OS_IDENTITY_API_VERSION=3
    export OS_CACERT=<path to the delivered CA bundle>
    export OS_APPLICATION_CREDENTIAL_ID=<id>
    export OS_APPLICATION_CREDENTIAL_SECRET=<from your secret store>

The same values work for Terraform/OpenTofu's OpenStack provider, Ansible's
openstack.cloud collection, and the OpenStack SDK -- they all understand
`clouds.yaml` and the `OS_*` variables. Do not hardcode service endpoint
URLs anywhere: authenticate against `{{AUTH_URL}}` and let the client
library discover the rest from the service catalog (`openstack catalog
list` shows what it sees).

## 4. Least privilege: mint narrower credentials per pipeline

Your handover credential is intentionally capable (it can do everything the
`-svc` account can). For production-grade hygiene, use it only as the
"root" automation credential and mint narrower ones per pipeline: sign in
as `{{TENANT_SHORT_NAME}}-svc` (password, interactively -- not from CI) and
create additional application credentials with an expiry date and, where
useful, a reduced role list:

    openstack application credential create pipeline-nightly \
      --role member --expiration 2027-01-01T00:00:00

Give each pipeline its own credential so you can revoke one without
stopping the others, and so your audit trail says which pipeline did what.
Deleting a credential is immediate and self-service:

    openstack application credential delete pipeline-nightly

## 5. Kubernetes clusters and CI

The platform's cluster machinery requires a password login (`-cluster`
account); an application credential cannot create or delete clusters. This
shapes the recommended CI pattern:

- **Recommended**: create long-lived clusters manually (or on a controlled
  schedule) as `{{TENANT_SHORT_NAME}}-cluster`, outside CI. Pipelines then
  deploy INTO the cluster using its kubeconfig, stored in your CI secret
  store like any other credential.
- **Not recommended**: putting the `-cluster` password into CI so pipelines
  can create clusters. If your workflow genuinely needs cluster-per-run,
  raise it with your account contact first -- there are capacity and quota
  implications too.

Everything else in this guide (networks, VMs, volumes, load balancers,
secrets, floating IPs) is fully pipeline-drivable with the application
credential.

## 6. Quota and pacing expectations

- **Your quota is the hard envelope.** Every create call is checked against
  it server-side. Pipelines must treat quota-exceeded errors as expected,
  actionable failures (clean up leaked resources, or request a raise via an
  authorized requester) -- not as retryable platform faults. `openstack
  quota show` and `openstack limits show --absolute` report your envelope
  and current consumption; a pre-flight check in long pipelines is cheap
  insurance.
- **Pace your polling.** Waiting on a server to go ACTIVE or a load
  balancer to go ONLINE should poll at 10-second intervals or slower (or
  use the CLI `--wait` flags, which do this for you). Tight polling loops
  add load and gain nothing.
- **Serialize heavyweight creates.** Load balancers and Kubernetes clusters
  take minutes to build; launching many concurrently mostly makes them
  queue. Prefer sequential creation with waits.
- **Clean up what you create.** Tag or name every pipeline-created resource
  with the job/build id (for example `ci-{{TENANT_SHORT_NAME}}-build123-web`)
  and make teardown a step that runs on failure too. A periodic sweep job
  that deletes resources older than your longest pipeline is the cheapest
  way to keep quota headroom. Floating IPs count against quota even when
  detached -- release them. The starter-kit script
  `scripts/ci-cleanup-sweep.sh` implements exactly this sweep (dry-run by
  default; it deletes nothing without `--apply`).

## 7. The worked sequence your pipelines should exercise

This is the canonical order of operations for an infrastructure pipeline on
this platform -- the same order the Acceptance Checklist tests. Each step
lists the surface it proves. Names below use a `ci-` prefix; substitute
your build id convention.

1. **Authenticate + discover** -- `openstack token issue`, then
   `openstack catalog list`. Proves credential and endpoint discovery.
2. **Network build-out** -- create network, subnet (any private range that
   does not collide with `{{TENANT_SHORT_NAME}}-subnet` or your other
   networks), router with gateway to `provider-ext`, attach subnet. Or reuse the
   handover network for simple runs. Proves networking self-service.
3. **Security group** -- create a group and rules for exactly the ports the
   deployment needs. Proves firewall self-service.
4. **Compute** -- boot a server from a shared base image on your network
   with your keypair; wait for ACTIVE. Proves compute + image access.
5. **Block storage** -- create a volume, attach it to the server, verify it
   is in-use, detach. Proves the storage path end to end.
6. **Public access** -- allocate a floating IP from `provider-ext`, attach
   it, verify reachability (for example SSH or your app's health port).
   Proves the north-south path.
7. **Load balancer** -- create a load balancer on your subnet with a
   listener, pool, and the server as a member; wait for ACTIVE/ONLINE.
   Proves the load-balancing service (your accounts already hold the
   required role).
8. **Secrets** -- store, retrieve, and delete a test secret (this is also
   where TLS certificates for load balancer listeners live). Proves the
   secrets service.
9. **Teardown in reverse order** -- member/pool/listener/load balancer,
   floating IP (release it), server, volume, security group, router
   interface/router/subnet/network. Verify with the corresponding `list`
   commands that nothing is left. Proves your cleanup discipline and
   returns your quota.

Run the full sequence once at onboarding (that is the Acceptance Checklist;
the starter-kit script `scripts/acceptance-run.sh` automates its
self-service rows) and keep a trimmed version as your pipeline's recurring
smoke test.

## 8. When a pipeline call fails

1. Re-run `openstack token issue` -- authentication failures explain most
   sudden breakage (revoked/expired credential, missing CA bundle in the
   job environment).
2. Check quota (`openstack limits show --absolute`) -- the second most
   common cause.
3. A permission error on identity, quota, or cluster operations is the
   platform working as designed (sections 1 and 5), not an outage.
4. Anything else that looks like a platform fault: capture the exact
   command and the exact error text and send them to {{ACCOUNT_CONTACT}}.
   Verbatim errors get fast answers; paraphrased ones do not.
