Skip to content
Docs BETA

Provisioner API reference

For platform teams. This page lists what a provisioner must implement: the calls, the fields, the limits and the answers. The contract is version 0.2.0 of the Joomiz provisioner API. It uses OpenAPI 3.1.

What a provisioner is explains the idea. Build a provisioner with the starter shows how to write one.

Rules for the whole API

Calls

Call Required Request Answers
POST /plan Yes Change set 200 outcome, or 202 operation
POST /apply Yes Change set 200 outcome, or 202 operation
POST /inspect Yes Inspect request 200 inspect outcome
GET /operations/{operationId} Only for a provisioner that answers 202 None 200 operation outcome, or 404

The paths are relative to the address that you register.

plan

POST /plan shows what apply would do. It changes nothing in the system that the provisioner manages. This is a request for one database. Save it as plan-request.json:

{
  "applicationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "kind": "managed-resource",
  "resourceType": "postgres",
  "changes": [
    {
      "id": "shop-db",
      "environment": "test",
      "action": "created",
      "description": "Create a PostgreSQL database named shop-db.",
      "spec": {
        "flavor": "cf-plan-small",
        "version": "16",
        "storage-gb": 20,
        "multi-az": false
      },
      "unresolvedFields": []
    }
  ]
}
curl -s -w '\nHTTP %{http_code}\n' -X POST "$PROVISIONER_URL/plan" -H 'Content-Type: application/json' -d @plan-request.json
{
  "outcome": "succeeded",
  "description": "Handled the database shop-db.",
  "results": [
    {
      "id": "shop-db",
      "environment": "test",
      "outcome": "succeeded",
      "description": "Handled the database shop-db.",
      "outputs": {
        "password": "https://vault.example.com/secrets/shop-db-password"
      }
    }
  ]
}
HTTP 200

apply

POST /apply makes the changes. It must give the same end state when it runs again with the same change set. A repeat for a resource that is already correct is a success. The request is the same as for plan, and unresolvedFields is always empty.

{
  "outcome": "succeeded",
  "description": "Handled the database shop-db.",
  "results": [
    {
      "id": "shop-db",
      "environment": "test",
      "outcome": "succeeded",
      "description": "Handled the database shop-db.",
      "outputs": {
        "password": "https://vault.example.com/secrets/shop-db-password"
      }
    }
  ]
}
HTTP 200

inspect

POST /inspect reports what the listed resources look like now. It changes nothing and is safe to repeat. The control plane calls it before it computes a plan, to find drift. It has no 202 answer.

{
  "applicationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "environment": "test",
  "resources": [
    {
      "kind": "managed-resource",
      "resourceType": "postgres",
      "id": "shop-db",
      "spec": {
        "flavor": "cf-plan-small",
        "version": "16",
        "storage-gb": 20,
        "multi-az": false
      }
    }
  ]
}
{
  "results": [
    {
      "id": "shop-db",
      "state": "present",
      "spec": {
        "flavor": "cf-plan-small",
        "version": "16",
        "storage-gb": 20,
        "multi-az": false
      },
      "reason": null
    }
  ]
}
HTTP 200

Say only what you know:

The control plane does not send a setting whose recorded value is a ${{ ... }} reference. Do not report such a setting back.

Any status other than 200 is a failed inspection. For 404, 405 and 501, the plan shows that the provisioner does not implement inspection, and the plan continues from the record.

operations

GET /operations/{operationId} answers for an operation that you accepted with 202. The answer is always 200, and the outcome is in the body. Support slow work in a provisioner shows the flow.

{
  "outcome": "in_progress",
  "description": "Pipeline run 4821 is at the deploy stage.",
  "results": []
}
HTTP 200

An unknown ID answers 404 with no body. The control plane fails the work at once.

Fields of the requests

Change set

The body of plan and apply.

Field Type Rule
applicationId string Required. A UUID of exactly 36 characters.
kind string Required. One of target, tool, deployment, managed-resource and consumed-resource.
resourceType string Required. At most 255 characters.
changes array Required. From 1 to 1000 changes.

A provisioner that receives a pair it does not handle answers 200 with the outcome failed.

Change

Field Type Rule
id string Required. At most 255 characters. It is unique for one environment.
environment string or null Required. At most 255 characters. It is null for a tooling change.
action string Required. One of created, updated and removed.
description string Required. At most 4096 characters. Text for a person. Do not parse it.
spec object or null Required. The settings of the resource, as the catalog entry of the type allows. It is null for removed.
unresolvedFields array of strings Required. Up to 512 entries of up to 1024 characters. Names the settings that hold a placeholder because their value is not known yet.

Treat id, environment and resourceType as names, and check them before you use them to name or find anything. The values of spec come from a user. Check every value against what your system accepts.

Inspect request

Field Type Rule
applicationId string Required. A UUID of exactly 36 characters.
environment string Required. At most 255 characters.
resources array Required. From 1 to 1000 resources.

Each resource has the required fields kind, resourceType (at most 255 characters), id (at most 255 characters) and spec. The spec is the configuration that Joomiz applied last.

Fields of the answers

Outcome

The answer of plan and apply with status 200.

Field Type Rule
outcome string Required. succeeded or failed. There is no partial outcome.
description string Required. At most 4096 characters. Put the reason here when the outcome is failed.
results array Required. From 0 to 1000 results. Return one for each change.

The control plane matches a result to a change by id. A change with no result counts as failed. Either level can fail the call.

Result

Field Type Rule
id string Required. The id of the change, unchanged. At most 255 characters.
environment string or null Required. The environment of the change, unchanged.
outcome string Required. succeeded or failed.
description string Required. At most 4096 characters.
outputs object or null Required. What the change produced, or null. Always null for failed.

The control plane checks outputs against the outputs entry of the type in the catalog. An output that does not fit fails that result. Report a sensitive output as an https:// address, with no user name and no password, that names where the value is stored.

Accepted operation

The answer 202 of plan and apply.

Field Type Rule
operationId string Required. A UUID of exactly 36 characters, chosen by the provisioner.

The body has no other field.

Operation outcome

The answer 200 of GET /operations/{operationId}.

Field Type Rule
outcome string Required. in_progress, succeeded or failed.
description string Required. At most 4096 characters. A progress note, and the reason at the end.
results array Required. From 0 to 1000 results. Empty while in_progress.

A final answer is final. The control plane never asks again about that ID.

Inspect outcome

The answer 200 of POST /inspect.

Field Type Rule
results array Required. From 0 to 1000 inspect results. Return one for each resource.

Each inspect result has four required fields:

Field Type Rule
id string The id of the resource, unchanged. At most 255 characters.
state string present, absent or unknown.
spec object or null The real settings for present. null otherwise.
reason string or null A short reason for unknown. At most 512 characters. null otherwise.

A resource with no result, or with more than one, counts as unknown. A result that breaks these rules counts as unknown.

Answers for a bad request

A provisioner that is written with the starter refuses a request that is not valid with 400 and a problem detail. The body lists each problem under violations. This is the answer for a request with an extra field resourceTyp:

{"detail":"the request body is not a valid ResourceChangeSet: resourceTyp is not a recognised field","instance":"/plan","status":400,"title":"Malformed change set","type":"https://joomiz.example.invalid/problems/provisioner/malformed-change-set","violations":["resourceTyp is not a recognised field"]}
HTTP 400

This is the answer for an empty list changes:

{"detail":"the request body is not a valid ResourceChangeSet: changes must hold at least 1 entry","instance":"/plan","status":400,"title":"Malformed change set","type":"https://joomiz.example.invalid/problems/provisioner/malformed-change-set","violations":["changes must hold at least 1 entry"]}
HTTP 400

This is the answer for a body that ends in the middle of the JSON:

{"detail":"the request body is not a valid ResourceChangeSet: body is not readable JSON: Unexpected end-of-input: expected close marker for Object (start marker at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); byte offset: #UNKNOWN])","instance":"/plan","status":400,"title":"Malformed change set","type":"https://joomiz.example.invalid/problems/provisioner/malformed-change-set","violations":["body is not readable JSON: Unexpected end-of-input: expected close marker for Object (start marker at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); byte offset: #UNKNOWN])"]}
HTTP 400

The control plane treats every status other than 200 and 202 as a failed call. A pair that the provisioner does not handle is not a bad request. The provisioner answers 200 with the outcome failed:

{
  "outcome": "failed",
  "description": "This provisioner owns managed-resource/postgres; the change set names managed-resource/mysql.",
  "results": [
    {
      "id": "shop-db",
      "environment": "test",
      "outcome": "failed",
      "description": "This provisioner owns managed-resource/postgres; the change set names managed-resource/mysql.",
      "outputs": null
    }
  ]
}
HTTP 200

Limits

What Limit
changes and resources in a request 1 to 1000 entries
results in an answer 0 to 1000 entries
id, environment and resourceType 255 characters
description 4096 characters
unresolvedFields 512 entries of 1024 characters
reason of an inspect result 512 characters
applicationId and operationId exactly 36 characters
Longest wait of an operation 30 minutes (PT30M)
Time between two polls of an operation 10 seconds by default. The control plane setting joomiz.jobs.poll-interval changes it

Version

This page describes version 0.2.0. The call POST /inspect is required in this version. A provisioner written for version 0.1.0 does not serve it, so it is not a version 0.2.0 provisioner. The control plane still plans with such a provisioner. It reports that drift detection failed for the type, and it plans from its own record.

The paths have no version prefix.

See also