The provisioners for Azure DevOps and Cloud Foundry
Goal
For platform teams. In this task you learn what the two provisioners for real infrastructure do, you decide which one you need, and you configure and register it. Both run as separate services that you deploy.
Before you start
This is not available yet. Joomiz has no download for these two provisioners. Ask the team that gives you the Joomiz platform for the images and for the exact names of the settings in your version.
- You read What a provisioner is.
- You have an Azure DevOps organization and a project.
- You have the role
operatorto register a provisioner. - For the Cloud Foundry app provisioner, you also have a Cloud Foundry foundation and a shared pipeline template. Your platform team keeps the template.
Steps
Choose the provisioner
| Azure DevOps pipeline provisioner | Cloud Foundry app provisioner | |
|---|---|---|
| What it does | Starts a pipeline that already exists, and waits for the pipeline to report the result. | Creates or updates a pipeline definition for one application. It never starts a run. |
| How the calls work | Slow work. It answers 202, and the pipeline calls it back. | Direct. It answers each call at once. |
| Types that it handles | Any pairs that you map to pipelines in its settings. | Only deployment and cloud-foundry-app. |
| Outputs | The pipeline reports them in the callback. | The ID and the address of the pipeline definition. |
The two provisioners do different jobs. The Cloud Foundry app provisioner does not create a Cloud Foundry organization or space. A deployment of the type cloud-foundry-app names a target of the type cloud-foundry-space, and that target needs a provisioner too. You can serve it with a pipeline that you write, behind the Azure DevOps pipeline provisioner.
Set up the Azure DevOps pipeline provisioner
This provisioner never creates infrastructure itself. A pipeline that you write does the real work, for example a terraform apply. The provisioner starts the right pipeline run with the right parameters and reports what the pipeline says.
- Say which organization and project to use. Run one deployment of the provisioner for each organization and project.
azure:
devops:
organization: my-org
project: my-project
-
Give it an identity for Azure DevOps. It signs in as a Microsoft Entra ID application. Set
azure.devops.credential.tenant-idandazure.devops.credential.client-id. Then set exactly one ofclient-secretorcertificate. The provisioner does not start with both or with neither. Read the secret or the certificate password from your secret store. -
Say how to accept the callback of a pipeline. Set
azure.devops.callback.jwks-urito the signing keys of your tenant. Setazure.devops.callback.required-claimsto the exact values of the claimsissandaudof the token that your pipeline sends. Addsubto accept only the service connection of your pipeline. A callback is accepted only when its token matches every claim that you set, and names an operation that is still waiting. -
Map each pair to a pipeline. Use the pipeline catalog:
azure:
devops:
pipelines:
- kind: managed-resource
type: postgres
pipeline-id: 42
- kind: target
type: kubernetes-namespace
plan-pipeline-id: 10
apply-pipeline-id: 11
An entry has either pipeline-id, or both plan-pipeline-id and apply-pipeline-id. With one pipeline, the parameter mode tells the pipeline if it runs plan or apply. With two pipelines, no mode is sent. Two entries for the same pair stop the start.
- Write the pipeline. Each run receives these parameters:
| Parameter | Value |
|---|---|
resourceId | The id of the resource in the change. |
environment | The environment of the change. |
action | created, updated or removed. |
operationId | The ID that the callback must use in its address. |
mode | plan or apply. It is sent only with one pipeline for both. |
Each field of the spec of the resource also becomes a parameter. The name changes from kebab-case to camelCase, so storage-gb becomes storageGb. A field that has the name of a reserved parameter, or that gives the same name as another field, fails the change.
- At the end of the run, make the pipeline send a
POSTto/callbacks/{operationId}of the provisioner. The body is JSON:
{
"outcome": "succeeded",
"description": "Provisioned the postgres-01 database.",
"outputs": {
"password": "https://vault.example.com/secrets/postgres-01-password"
}
}
The fields outcome and description are required. The provisioner treats every value of outcome other than the exact text succeeded as a failure. The field outputs is optional. The control plane checks the outputs against the catalog entry of the type. A password or a key is never an output value. Report the https:// address of the vault where the pipeline stored it.
If a callback never arrives, the control plane stops waiting when the longest wait that you registered has passed. It then fails the work. The longest wait is at most 30 minutes. Support slow work in a provisioner explains it.
The provisioner keeps the state of its operations in memory. A restart loses them, and the control plane then fails the work that was waiting.
The setting azure.devops.operations.max-tracked limits the operations that the provisioner holds. The default is 1024. When the limit is reached, a new call fails at once with a clear reason. Do not restart the provisioner while work is waiting.
Set up the Cloud Foundry app provisioner
For each deployment of the type cloud-foundry-app, this provisioner creates or updates one Azure DevOps pipeline definition. The definition points at the shared pipeline template of your platform team. The pipeline runs when someone pushes to the default branch of the application. That run builds the application and pushes it to Cloud Foundry.
When a deployment is removed, the provisioner deletes the pipeline definition. A definition that is already gone is a success.
Before it changes a definition, the provisioner checks that the organization and the space exist in the foundation. It only reads. If either does not exist, the change fails and nothing is created.
- Say which Azure DevOps organization and project hold the definitions:
cloud-foundry:
pipeline:
organization: my-org
project: my-project
- Say where the shared template is. Set
repository-idwith the ID that Azure DevOps gives to the place where the template lives. The ID stays the same when someone renames that place:
cloud-foundry:
pipeline:
template:
repository-id: <the ID of the template>
repository-name: pipeline-templates
branch: refs/heads/main
yaml-path: cloud-foundry-app.yml
- Say how to name and file the definitions. The
agent-pool-queue-idis required. The folder and the prefix have defaults:
cloud-foundry:
pipeline:
definition:
folder: \Joomiz
agent-pool-queue-id: 12
name-prefix: joomiz
- Give it one identity for each system. Use two separate credentials:
| Settings | System | What the credential may do |
|---|---|---|
cloud-foundry.pipeline.credential.* | Azure DevOps | Create and edit pipeline definitions. It is a Microsoft Entra ID application. Set tenant-id, client-id and one of client-secret or certificate. |
cloud-foundry.foundation.credential.* | Cloud Foundry | List organizations and spaces. It is a client of the Cloud Foundry UAA (User Account and Authentication service). Set token-url, client-id and client-secret. |
- Say which foundation it serves, with
cloud-foundry.foundation.api-endpoint. One deployment serves one foundation. Run one deployment for each foundation. A deployment that targets another foundation is refused.
If the UAA client cannot see an organization, the provisioner reports it as missing, and a valid deployment fails. Give the client a read-only role that sees the organizations that your deployments use.
Register the provisioner
Register each pair that the provisioner handles, as Register a provisioner shows.
- For the Cloud Foundry app provisioner, register
deploymentandcloud-foundry-app. - For the Azure DevOps pipeline provisioner, register every pair that you mapped to a pipeline, and set the longest wait. Self-registration is off in the shipped settings, because a provisioner that handles several pairs registers them by hand.
- Allow the network of the provisioner. Allow the address of your provisioner explains it.
Check
- Start the provisioner. A start with a missing or contradictory setting fails, and the log names the setting.
- Register it. The registration answers
201. - Run
joomiz planfor a file that uses one of its types. The plan shows the answer of the provisioner. - For the Azure DevOps pipeline provisioner, run
joomiz apply, and follow the run withjoomiz runs watch. The run is waiting for provisioner until the callback arrives. - For the Cloud Foundry app provisioner, look in Azure DevOps. The definition is in the folder that you set, and its name starts with the prefix.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| The provisioner does not start | A required setting is missing, or two settings contradict each other. | Read the first error in the log. It names the setting. |
| A call fails with a list of the pairs that the provisioner serves | The pair is not in the pipeline catalog. | Add the pair to azure.devops.pipelines, or check the spelling. |
| A pipeline finished, but the run stays waiting for provisioner | The callback did not arrive, or the provisioner refused it. | Check the callback step of the pipeline, and the claims in required-claims. |
A change fails because of a spec field | The field has the name of a reserved parameter. | Rename the field in the spec. |
| The Cloud Foundry app provisioner says that an organization or a space is missing | The name is wrong, or the UAA client cannot see it. | Check the names. Give the client a read-only role that sees them. |
| The change is refused because of the foundation | The target points at another foundation. | Use a target of the foundation that this deployment serves. |
Next
Learn which types exist, and how a new type arrives, in Own the catalog of types.