Skip to content

User-facing Endpoints

This section documents the user-facing endpoints.

Those user-facing endpoints are used by tools and clients that interact with your orchestrator, such as opentf-ctl.

It can be used as a reference if you want to configure a reverse proxy in front of your orchestrator.

Info

Most services also expose an inbox endpoint, so that they can receive the publications they have subscribed to, but those inboxes are not user-facing endpoints and are hence not listed here (you can find them in each service configuration documentation). You do not have to include those in a reverse proxy configuration.

Services and plugins exposing user-facing endpoints

Note

All xxx_id below are UUIDs. They have the following form:

123e4567-e89b-12d3-a456-426655440000

Agent Channel plugin

This plugin exposes the following endpoints:

Event Bus service

This service exposes three endpoints:

  • /publications (POST)
  • /subscriptions (GET, POST)
  • /subscriptions/{subscription_id} (DELETE)

Killswitch service

This service exposes one endpoint:

  • /workflows/{workflow_id} (DELETE)

Localstore service

This service exposes one endpoint:

  • /workflows/{workflow_id}/files/{attachment_id} (GET, HEAD)

Observer service

This service exposes the following endpoints:

  • /channelhandlers (GET)
  • /channels (GET)
  • /namespaces (GET)
  • /version (GET)
  • /workflows (GET)
  • /workflows/status (GET)
  • /workflows/{workflow_id}/status (GET)
  • /workflows/{workflow_id}/workers (GET)

Quality Gate service

This plugin exposes two endpoints:

  • /workflows/{workflow_id}/qualitygate (GET, POST)

Receptionist service

This service exposes one endpoint:

Authentication

Whenever calling an endpoint, a signed token must be specified via the Authorization header.

This header will be of the form:

Authorization: Bearer xxxxxxxx

It must be signed by one of the trusted authorities known to the orchestrator.

If the token is invalid or missing, the endpoint will respond with an Unauthorized (401) error code.

If the token does not grant access to the required resource, the endpoint will respond with a Forbidden (403) error code.

Responses

Except when noted otherwise, the user-facing endpoints return status manifests.

Status manifests are JSON documents with the following format:

{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "message": "...",
  "status": "Success", // if code is 2xx, else "Failure"
  "reason": "...",
  "code": 200
  // ...
}

Where code is a standard HTTP return code, reason an element of the following table which results from the return code, message a descriptive text, and details an optional part whose nature depends on the exchange.

Reason Code Description
OK 200 The request was successfully handled
Created 201 The request was successfully handled, the corresponding resource was created
NoContent 204 The request was successfully handled, no response was required
BadRequest 400 The request was malformed
Unauthorized 401 The token is missing, invalid, or not recognized
Forbidden 403 The token is not allowed to perform the required action
NotFound 404 The requested object does not exist
AlreadyExists 409
Conflict 409
Invalid 422 The request was invalid
Internalerror 500 An internal error occurred

If an error is returned (a code starting with 4 or 5), message describes the problem and there may be an error entry in the details object, providing further explanations.

Selectors

Several endpoints allow for optional field and label selectors in request:

Those selectors may be used to restrict the endpoint response to the items of interest.

To filter the response on fields, use the fieldSelector parameter. To filter the response on labels, use the labelSelector parameter. For example, this request will return only idle channels:

GET /channels?fieldSelector=status.phase=IDLE

Currently, label selectors may be used to filter /workflows/{workflow_id}/status endpoint request results. The following request will return only checkout category events of a workflow:

GET /workflows/3e1b7929-8a44-4196-a9b4-6e0b65d35a68/status?labelSelector=opentestfactory.org/category=checkout

Logical operators supported in selector conditions are =, !=, in and notin. Multiple conditions may be provided, separated by commas (,). The conditions are linked by a logical AND operator. For instance, this request will return only the channels that are not idle in the ns1 or ns2 namespace:

GET /channels?fieldSelector=status.phase!=IDLE,metadata.namespace in (ns1, ns2)

You can use fieldSelector and labelSelector together. The conditions will be linked by a logical AND operator.

Here are the possible selector formats:

key                                # the key or label exists
!key                               # the key or label does not exist
key==value                         # the key exists and has the specified value
key!=value                         # the key exists but has a different value
                                   # or the key does not exist
key in (value1, value2, ...)       # the key exists and its value is in the list
key notin (value1, value2, ...)    # the key exists and has a value not in the
                                   # list or the key does not exist
(value1, value2) in key            # the key contains all values (and possibly
                                   # others)
(value1, value2) notin key         # the key does not contains all the values
                                   # (but it may contain some) or the key does
                                   # not exist

For label selectors, the key is the label’s name. It may contain dots. For field selectors, the key is a series of field names separated by dots. The last field name may be surrounded by [ and ] to allow for dots in the field name.

Here are examples of fieldSelector keys:

apiVersion
metadata.name
spec.selector.matchLabels[example.org/label]

[apiVersion]                                    # Invalid
spec[selector][matchLabels][example.org/label]  # Invalid

Warning

You cannot use the [] notation for labelSelector.

Endpoints

Important

What is accessible and returned can be constrained by what the specified token is allowed to access. For example, if the specified token only has access to a specific namespace, resources not accessible from this namespace will not be shown.

/agents (GET)

This endpoint is exposed by the Agent Channel plugin. It lists the currently registered agents.

It returns an AgentRegistrationList manifest (a JSON document), not a Status manifest.

This document has an apiVersion entry (v1), a kind entry (AgentRegistrationList), and an items entry which is a list of AgentRegistration objects.

Endpoint allows for field selectors to filter returned agent manifests.

Example: querying the currently registered agents
GET /agents
{
  "apiVersion": "v1",
  "kind": "AgentRegistrationList",
  "items": [
    {
      "apiVersion": "opentestfactory.org/v1alpha1",
      "kind": "AgentRegistration",
      "metadata": {
        "agent_id": "7311820e-4497-4668-a007-e834dd5ee3b8",
        "creationTimestamp": "2023-03-10T07:58:25.860576",
        "name": "windows test agent",
        "namespaces": "default"
      },
      "spec": {
        "encoding": "utf-8",
        "script_path": "C:\\Users\\nobody\\work",
        "tags": ["windows", "robotframework"]
      },
      "status": {
        "communicationCount": 0,
        "communicationStatusSummary": {},
        "lastCommunicationTimestamp": "2023-03-10T08:55:09.547106"
      }
    }
  ]
}

/agents (POST)

This endpoint is exposed by the Agent Channel plugin. It allows agents to register to the agent channel plugin.

It returns a status manifest (a JSON document) with the following entries:

  • message: a summary of the agent registration including the agent’s name, ID, and tags (a string)
  • details: an object with two elements: uuid, the agent ID (a string), and version, the orchestrator version (also a string)

The agent ID can be used to deregister the agent.

Example: registering an agent
POST /agents
{
  "apiVersion": "opentestfactory.org/v1alpha1",
  "kind": "AgentRegistration",
  "metadata": {
    "name": "test agent",
    "namespaces": "default",
    "version": "1.8.0"
  },
  "spec": {
    "tags": ["windows", "robotframework"],
    "encoding": "utf-8",
    "script_path": "C:\\Users\\nobody\\work"
  }
}
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "message": "Agent 'test agent' successfully registered (id=a9504b60-eb72-4f95-87b5-d146feeeb784, tags=windows).",
  "details": {
    "uuid": "a9504b60-eb72-4f95-87b5-d146feeeb784",
    "version": "0.48.0"
  },
  "status": "Success",
  "reason": "Created",
  "code": 201
}
Example: registering an agent without providing tags
POST /agents
{
  "apiVersion": "opentestfactory.org/v1alpha1",
  "kind": "AgentRegistration",
  "metadata": {
    "name": "test agent",
    "namespaces": "default",
    "version": "1.8.0"
  },
  "spec": {
    "tags": [],
    "encoding": "utf-8",
    "script_path": "C:\\Users\\nobody\\work"
  }
}
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "message": "Not a valid AgentRegistration manifest.",
  "details": {
    "error": "[] is too short\n\nFailed validating 'minItems' in schema['properties']['spec']['properties']['tags']:\n    {'items': {'pattern': '^[a-zA-Z][a-zA-Z0-9-]*$', 'type': 'string'},\n     'minItems': 1,\n     'type': 'array'}\n\nOn instance['spec']['tags']:\n    []"
  },
  "status": "Failure",
  "reason": "Invalid",
  "code": 422
}

/agents/{agent_id} (DELETE)

This endpoint is exposed by the Agent Channel plugin. It deregisters the specified agent.

It returns a status manifest (a JSON document) with the following entry:

  • message: the Agent {agent_id} de-registered. string

If the agent is not known, a Failure status is returned instead, with a 404 code.

Example: deregistering an agent
DELETE /agents/a9504b60-eb72-4f95-87b5-d146feeeb784
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "message": "Agent a9504b60-eb72-4f95-87b5-d146feeeb784 de-registered.",
  "details": null,
  "status": "Success",
  "reason": "OK",
  "code": 200
}
Example: attempting to deregister an unknown agent
DELETE /agents/a9504b60-eb72-4f95-87b5-d146feeeb784
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "message": "Agent a9504b60-eb72-4f95-87b5-d146feeeb784 not known.",
  "details": null,
  "status": "Failure",
  "reason": "NotFound",
  "code": 404
}

/agents/{agent_id} (GET, POST)

This endpoint is exposed by the Agent Channel plugin. It allows agents to interact with the agent channel plugin.

The exchange format depends on agent/agent channel plugin versions and is out of this document’s scope.

/agents/{agent_id}/files/{file_id} (GET, POST, PUT)

This endpoint is exposed by the Agent Channel plugin. It allows agents to interact with the agent channel plugin.

The exchange format depends on agent/agent channel plugin versions and is out of this document’s scope.

/channelhandlers (GET)

This endpoint is exposed by the Observer service. It lists the known channel handlers.

It returns a status manifest (a JSON document) with the following entries:

  • message: the Known channel handlers string
  • details: an object with an items element which is a possibly empty list of strings, the known channel handlers IDs.
Example: querying the currently known channel handlers
GET /channelhandlers
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "message": "Known channel handlers",
  "details": {
    "items": [
      "50c7e5d1-7bdc-46f0-9422-3cc6660d00c0",
      "dd43b0bb-b359-4e9d-9459-c42ee7dc16d9"
    ],
  },
  "status": "Success",
  "reason": "OK",
  "code": 200
}

/channels (GET)

This endpoint is exposed by the Observer service. It lists the known channels.

It returns a status manifest (a JSON document) with the following entries:

  • message: the Known channels string
  • details: an object with an items element which is a possibly empty list of objects, the known channel manifests.

Each channel manifest has the following structure:

{
  "apiVersion": "opentestfactory.org/v1alpha1",
  "kind": "Channel",
  "metadata": {
    "name": string,
    "namespaces": string,
    "channelhandler_id": uuid
  },
  "spec": {
    "tags": [string, ...]
  },
  "status": {
    "lastCommunicationTimestamp": timestamp,
    "phase": "IDLE" or "BUSY" or "PENDING",
    "currentJobID": uuid or null
  }
}

namespaces is either *, a name (foo), or a comma-separated list of names (foo,bar,baz).

If namespaces is * the channel is accessible from all namespaces. Otherwise, it is accessible from the listed namespace(s).

Endpoint allows for field selectors to filter returned channel manifests.

Example: querying the currently known channels
GET /channels
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "message": "Known channels",
  "details": {
    "items": [
      {
        "apiVersion": "opentestfactory.org/v1alpha1",
        "kind": "Channel",
        "metadata": {
          "name": "robotframework.agents",
          "namespaces": "default",
          "channelhandler_id": "2af7585e-73dc-4f63-855e-4072fc9aa635"
        },
        "spec": {
          "tags": ["ssh", "linux", "robotframework"]
        },
        "status": {
          "currentJobID": null,
          "lastCommunicationTimestamp": "2022-05-02T10:00:49.933028",
          "phase": "IDLE"
        }
      },
      {
        "apiVersion": "opentestfactory.org/v1alpha1",
        "kind": "Channel",
        "metadata": {
          "name": "cypress-agent.agents",
          "namespaces": "*",
          "channelhandler_id": "2af7585e-73dc-4f63-855e-4072fc9aa635"
        },
        "spec": {
          "tags": ["ssh", "linux", "cypress"]
        },
        "status": {
          "currentJobID": "2af7585e-73dc-4f63-855e-4072fc9aa635",
          "lastCommunicationTimestamp": "2022-05-02T10:00:49.933028",
          "phase": "PENDING"
        }
      }
    ]
  },
  "status": "Success",
  "reason": "OK",
  "code": 200
}
Example: querying active channels in namespace default
GET /channels?fieldSelector=metadata.namespaces=default,status.phase=BUSY
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "message": "Known channels",
  "details": {
    "items": [
      {
        "apiVersion": "opentestfactory.org/v1alpha1",
        "kind": "Channel",
        "metadata": {
          "name": "robotframework.agents",
          "namespaces": "default",
          "channelhandler_id": "2af7585e-73dc-4f63-855e-4072fc9aa635"
        },
        "spec": {
          "tags": ["ssh", "linux", "robotframework"]
        },
        "status": {
          "currentJobID": "2cf7575e-73dc-4f63-855e-4072fc9aa635",
          "lastCommunicationTimestamp": "2022-05-02T10:00:49.933028",
          "phase": "BUSY"
        }
      },
    ]
  },
  "status": "Success",
  "reason": "OK",
  "code": 200
}

/namespaces (GET)

This endpoint is exposed by the Observer service. It lists namespaces in which operations are allowed.

It accepts two optional query parameters, resource and verb, which must be either both defined or undefined. If they are defined, the returned namespaces are those in which the requested operation is allowed. If they are not defined, the returned namespaces are those in which at least some operation is allowed.

The possible values for resources are agents, channelhandlers, channels, qualitygates, status, subscriptions, and workflows, and the possible values for verb are list, get, create, and delete.

The endpoint returns a status manifest (a JSON document) with the following entries:

  • message: the Accessible namespaces string
  • details: an object with an items element which is a possibly empty list of strings.

The items element may be ["*"], in which case all namespaces allow for the requested operation (or for some operations if no resource/verb query parameters were specified).

Example: querying available namespaces on an non-RBAC-enabled orchestrator
GET /namespaces
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "message": "Accessible namespaces",
  "details": {
    "items": [
      "*"
    ]
  },
  "status": "Success",
  "reason": "OK",
  "code": 200
}
Example: filtering with a ‘verb’ but with no ‘resource’
GET /namespaces?verb=create
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "details": null,
  "message": "resource and verb must be both provided or not provided at all.",
  "status": "Failure",
  "reason": "Invalid",
  "code": 422
}

/publications (POST)

This endpoint is exposed by the Event Bus service. It allows services and plugins to publish messages.

The published content, the body of the POST request, is a JSON object. It does not have to have any specific entries. It can be an empty.

The endpoint returns a status manifest (a JSON document).

On successful publication, the publication is dispatched to all corresponding subscriptions, asynchronously. If there is no corresponding subscription, the response code will be 200, but its message part will be Publication received, but no matching subscription.

If there are corresponding subscriptions, the publication will be posted to their inbox endpoints. There will be a X-Subscription-ID header containing the subscription ID and a X-Publication-ID header containing the publication ID, to help disambiguate duplicates:

X-Subscription-ID: uuid
X-Publication-ID: uuid
Example: publishing a plain JSON document
POST /publications
{
}
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "message": "Publication received, but no matching subscription.",
  "details": null,
  "status": "Success",
  "reason": "OK",
  "code": 200
}
Example: publishing a consumed event
POST /publications
{
  "apiVersion": "opentestfactory.org/v1beta1",
  "kind": "Workflow"
}
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "message": "Publication received.",
  "details": null,
  "status": "Success",
  "reason": "OK",
  "code": 200
}

Note

Please note that this published event is not a valid workflow, and hence will be rejected by its consumers.

/subscriptions (GET)

This endpoint is exposed by the Event Bus service. It lists the current subscriptions.

It returns a SubscriptionList manifest (a JSON document), not a Status manifest.

This document has an apiVersion entry (v1), a kind entry (SubscriptionList), and an items entry which is an object with one entry per subscription. The entry key is the subscription ID, and the entry value is a Subscription manifest.

Each subscription manifest has the following structure:

{
  "apiVersion": "opentestfactory.org/v1alpha1",
  "kind": "Subscription",
  "metadata": {
    "name": string,
    "annotations": {
      "opentestfactory.org/fieldselector": string,
      "opentestfactory.org/labelselector": string
    },
    "creationTimestamp": timestamp,
    "subscription_id": uuid
  },
  "spec": {
    "selector": object,
    "subscriber": {
      "endpoint": string
    }
  },
  "status": {
    "lastPublicationTimestamp": timestamp or null,
    "publicationCount": integer,
    "publicationStatusSummary": object
  }
}

Endpoint allows for field selectors to filter returned subscription manifests.

Example: querying the currently active subscriptions
GET /subscriptions
{
  "apiVersion": "v1",
  "kind": "SubscriptionList",
  "items": {
    "007677e7-0705-4dbb-809b-8260fc02bad1": {
      "apiVersion": "opentestfactory.org/v1alpha1",
      "kind": "Subscription",
      "metadata": {
        "annotations": {
          "opentestfactory.org/fieldselector": "kind==ExecutionCommand,apiVersion==opentestfactory.org/v1beta1",
          "opentestfactory.org/labelselector": ""
        },
        "creationTimestamp": "2023-03-14T15:05:04.490989",
        "name": "agentchannel",
        "subscription_id": "007677e7-0705-4dbb-809b-8260fc02bad1"
      },
      "spec": {
        "selector": {
          "matchFields": {
            "apiVersion": "opentestfactory.org/v1beta1"
          },
          "matchKind": "ExecutionCommand"
        },
        "subscriber": {
          "endpoint": "http://127.0.0.1:24368/inbox"
        }
      },
      "status": {
        "lastPublicationTimestamp": null,
        "publicationCount": 0,
        "publicationStatusSummary": {}
      }
    },
    "01c32d94-6b6a-435f-bd7e-89d3aa6a38a3": {
      "apiVersion": "opentestfactory.org/v1alpha1",
      "kind": "Subscription",
      "metadata": {
        "annotations": {
          "opentestfactory.org/fieldselector": "kind==ProviderCommand,apiVersion==opentestfactory.org/v1beta1",
          "opentestfactory.org/labelselector": "opentestfactory.org/category==get-file,opentestfactory.org/categoryPrefix==actions"
        },
        "creationTimestamp": "2023-03-14T15:05:05.359665",
        "name": "actionprovider",
        "subscription_id": "01c32d94-6b6a-435f-bd7e-89d3aa6a38a3"
      },
      "spec": {
        "selector": {
          "matchFields": {
            "apiVersion": "opentestfactory.org/v1beta1"
          },
          "matchKind": "ProviderCommand",
          "matchLabels": {
            "opentestfactory.org/category": "get-file",
            "opentestfactory.org/categoryPrefix": "actions"
          }
        },
        "subscriber": {
          "endpoint": "http://127.0.0.1:7780/inbox"
        }
      },
      "status": {
        "lastPublicationTimestamp": null,
        "publicationCount": 0,
        "publicationStatusSummary": {}
      }
    }
  }
}
Example: querying the currently active subscriptions for WorkflowResult events
GET /subscriptions?fieldSelector=spec.selector.matchKind=WorkflowResult
{
  "apiVersion": "v1",
  "kind": "SubscriptionsList",
  "items": {
    "4bd41d4f-94dc-4499-ae98-83088bc0a8d3": {
      "apiVersion": "opentestfactory.org/v1alpha1",
      "kind": "Subscription",
      "metadata": {
        "annotations": {
          "opentestfactory.org/fieldselector": "kind==WorkflowResult,apiVersion==opentestfactory.org/v1alpha1",
          "opentestfactory.org/labelselector": ""
        },
        "creationTimestamp": "2023-08-11T16:08:13.876609",
        "name": "localcleaner",
        "subscription_id": "4bd41d4f-94dc-4499-ae98-83088bc0a8d3"
      },
      "spec": {
        "selector": {
          "matchFields": {
            "apiVersion": "opentestfactory.org/v1alpha1"
          },
          "matchKind": "WorkflowResult"
        },
        "subscriber": {
          "endpoint": "http://127.0.0.1:7777/inbox"
        }
      },
      "status": {
        "lastPublicationTimestamp": null,
        "publicationCount": 0,
        "publicationStatusSummary": {},
        "quarantine":0
      }
    },
    "b1364c01-320d-48aa-8d39-1a38b25f14f4": {
      "apiVersion": "opentestfactory.org/v1alpha1",
      "kind": "Subscription",
      "metadata": {
        "annotations": {
          "opentestfactory.org/fieldselector": "kind==WorkflowResult,apiVersion==opentestfactory.org/v1alpha1",
          "opentestfactory.org/labelselector": ""
        },
        "creationTimestamp": "2023-08-11T16:08:17.941666",
        "name": "s3publisher",
        "subscription_id": "b1364c01-320d-48aa-8d39-1a38b25f14f4"
      },
      "spec": {
        "selector": {
          "matchFields": {
            "apiVersion": "opentestfactory.org/v1alpha1"
          },
          "matchKind": "WorkflowResult"
        },
        "subscriber": {
          "endpoint":"http://127.0.0.1:7787//inbox"
        }
      },
      "status": {
        "lastPublicationTimestamp": null,
        "publicationCount": 0,
        "publicationStatusSummary": {},
        "quarantine":0
      }
    },
    "ee3d8943-9876-4a22-aa27-da3549cbb5fb": {
      "apiVersion": "opentestfactory.org/v1alpha1",
      "kind": "Subscription",
      "metadata": {
        "annotations": {
          "opentestfactory.org/fieldselector": "kind==WorkflowResult,apiVersion==opentestfactory.org/v1alpha1",
          "opentestfactory.org/labelselector": ""
        },
        "creationTimestamp": "2023-08-11T16:08:13.901678",
        "name": "observer",
        "subscription_id": "ee3d8943-9876-4a22-aa27-da3549cbb5fb"
      },
      "spec": {
        "selector": {
          "matchFields": {
            "apiVersion": "opentestfactory.org/v1alpha1"
          },
          "matchKind": "WorkflowResult"
        },
        "subscriber": {
          "endpoint":"http://127.0.0.1:7775/inbox"
        }
      },
      "status": {
        "lastPublicationTimestamp": null,
        "publicationCount": 0,
        "publicationStatusSummary": {},
        "quarantine": 0
      }
    }
  }
}

/subscriptions (POST)

This endpoint is exposed by the Event Bus service. It allows services and plugins to subscribe to publications.

It returns a status manifest (a JSON document) with the following entries:

  • message: the Subscription 'name' successfully registred (id=subscription_id).
  • details: an object with an uuid entry, the subscription ID

The subscription ID can be used to cancel the subscription.

If the subscription request is invalid, message will summarize the reason and there will be an error entry (a string) in the details object.

Example: subscribing to opentestfactory.org/v1beta1/ExecutionCommand events
POST /subscriptions
{
  "apiVersion": "opentestfactory.org/v1alpha1",
  "kind": "Subscription",
  "metadata": {
    "name": "example",
  },
  "spec": {
    "selector": {
      "matchFields": {
        "apiVersion": "opentestfactory.org/v1beta1"
      },
      "matchKind": "ExecutionCommand"
    },
    "subscriber": {
      "endpoint": "http://127.0.0.1:12345/inbox"
    }
  }
}
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "message": "Subscription 'example' successfully registered (id=683f6807-c41a-4eda-966e-7b664b65c56a).",
  "details": {
    "uuid": "683f6807-c41a-4eda-966e-7b664b65c56a"
  },
  "status": "Success",
  "reason": "Created",
  "code": 201
}
Example: attempting to subscribe, failing to provide a subscriber endpoint
POST /subscriptions
{
  "apiVersion": "opentestfactory.org/v1alpha1",
  "kind": "Subscription",
  "metadata": {
    "name": "example",
  },
  "spec": {
    "selector": {
      "matchFields": {
        "apiVersion": "opentestfactory.org/v1beta1"
      },
      "matchKind": "ExecutionCommand"
    },
    "sub scribe r": {
      "endpoint": "http://127.0.0.1:12345/inbox"
    }
  }
}
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "message": "Not a valid Subscription manifest.",
  "details": {
    "error": "'subscriber' is a required property\n\nFailed validating 'required' in schema['properties']['spec']:\n    {'additionalProperties': False,\n     'properties': {'selector': {'additionalProperties': False,\n                                 'minProperties': 1,\n                                 'properties': {'matchFields': {'additionalProperties': False,\n                                                                'minProperties': 1,\n
                                                 'patternProperties': {'^[a-z]([a-zA-Z0-9.-]*[a-zA-Z0-9])?$': {'type': 'string'}},\n
                                        'type': 'object'},\n                                                'matchKind': {'type': 'string'},\n
                                  'matchLabels': {'additionalProperties': False,\n                                                                'minProperties': 1,\n                                                                'patternProperties': {'^([a-zA-Z0-9-.]+/)?[a-zA-Z0-9]([a-zA-Z0-9._-]*[a-zA-Z0-9])?$': {'type': 'string'}},\n                                                                'type': 'object'}},\n                                 'type': 'object'},\n                    'subscriber': {'additionalProperties': False,\n                                   'properties': {'endpoint': {'type': 'string'},\n                                                  'insecure-skip-tls-verify': {'type': 'boolean'}},\n                                   'required': ['endpoint'],\n                                   'type': 'object'}},\n     'required': ['subscriber'],\n     'type': 'object'}\n\nOn instance['spec']:\n    {'selector': {'matchFields': {'apiVersion': 'opentestfactory.org/v1beta1'},\n                  'matchKind': 'ExecutionCommand'},\n     'sub scribe r': {'endpoint': 'http://127.0.0.1:12345/inbox'}}"
  },
  "status": "Failure",
  "reason": "Invalid",
  "code": 422
}

/subscriptions/{subscription_id} (DELETE)

This endpoint is exposed by the Event Bus service. It cancels the specified subscription.

It returns a status manifest (a JSON document) with the following entry:

  • message: the Subscription subscription_id canceled. string

If the subscription is not known, a Failure status is returned instead, with a 404 code.

Example: deregistering an existing subscription
DELETE /subscriptions/683f6807-c41a-4eda-966e-7b664b65c56a
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "message": "Subscription 683f6807-c41a-4eda-966e-7b664b65c56a canceled.",
  "details": null,
  "status": "Success",
  "reason": "OK",
  "code": 200
}
Example: attempting to deregister an unknown subscription
DELETE /subscriptions/683f6807-c41a-4eda-966e-7b664b65c56a
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "message": "Subscription 683f6807-c41a-4eda-966e-7b664b65c56a not known.",
  "details": null,
  "status": "Failure",
  "reason": "NotFound",
  "code": 404
}

/version (GET)

This endpoint is exposed by the Observer service. It returns the build version information.

It returns a status manifest (a JSON document) with the following entries:

  • message: the BOM string
  • details: an object with an items element which is a possibly empty BOM, i.e. a JSON file that is used by OpenTestFactory’s build infrastructure to generate Docker images.
Example: querying build information on a nightly release
GET /version
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "message": "BOM",
  "details": {
    "items": {
      "name": "otf-images",
      "internal-components": [
        {
          "env_version_key": "ORCHESTRATOR_VERSION",
          "name": "opentf-orchestrator",
          "type": "python",
          "version": "0.53.0.dev1533+main.1708123a"
        },
        {
          "destination": "",
          "env_version_key": "JAVA_PLUGINS_VERSION",
          "name": "org.opentestfactory:java-plugins",
          "type": "java",
          "version": "1.6.0-SNAPSHOT"
        }
      ]
    }
  },
  "status": "Success",
  "reason": "OK",
  "code": 200
}
Example: querying build information on a third party nightly release
GET /version
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "message": "BOM",
  "details": {
    "items": {
      "name": "squash-orchestrator",
      "base-images": [
        {
          "env_version_key": "OTF_ALLINONE_VERSION",
          "name": "allinone",
          "type": "docker",
          "version": "nightly"
        }
      ],
      "internal-components": [
        {
          "env_version_key": "TM_SQUASHFREEMIUM_VERSION",
          "name": "squash-freemium",
          "type": "python",
          "version": "0.7.0.dev76+main.6a970632"
        },
        {
          "env_version_key": "TM_CONNECTOR_VERSION",
          "name": "org.squashtest.tf2:squash-tf-2-squash-tm-plugins",
          "type": "java",
          "version": "3.11.0-SNAPSHOT"
        }
      ]
    }
  },
  "status": "Success",
  "reason": "OK",
  "code": 200
}

/workflows (GET)

This endpoint is exposed by the Observer service. It lists the currently running and recently completed workflows.

It returns a status manifest (a JSON document) with the following entries:

  • message: the Running and recent workflows string
  • details: an object with an items element which is a possibly empty list of strings, the running and recent workflows IDs

The workflow IDs can be used to get detailed information on the corresponding workflow.

Endpoint allows for field selectors to filter returned workflow manifests.

Example: querying the currently known workflows
GET /workflows
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "message": "Running and recent workflows",
  "details": {
    "items": [
      "50c7e5d1-7bdc-46f0-9422-3cc6660d00c0",
      "dd43b0bb-b359-4e9d-9459-c42ee7dc16d9"
    ],
  },
  "status": "Success",
  "reason": "OK",
  "code": 200
}

/workflows (POST)

This endpoint is exposed by the Receptionist service. It starts a workflow execution.

It accepts one optional query parameter, namespace. If it is defined, the namespace the workflow will run on will be the one specified by the query parameter. If it is not defined, the namespace the workflow will run on will be the one specified in its metadata.namespace section, if any, or default.

It returns a status manifest (a JSON document). On successful invocation, it has the following entries:

  • message: the Workflow name accepted (workflow_id=uuid). string
  • details: an object with a workflow_id entry, a string, the started workflow ID
  • status: the Success string
  • reason: the Created string
  • code: 201

If the provided workflow manifest is incorrect, an Invalid reason is returned (code 422), and the message entry describes the cause of the rejection.

Formatting the request

There are two ways a workflow can be provided.

The first way is by using a JSON or YAML request body containing the workflow definition.

The second way is by using a multipart/form-data request body. In this case, you must provide a workflow field or file. You may also provide a variables field or file as well as resource files.

Tip

The examples below use the curl command. If you intend to start a lot of workflows from the command line, it is recommended to use a tool such as opentf-ctl. Please refer to “Tools - Run workflows” for more information.

Variables

variables, if specified, are merged in the workflow variables. Variables already defined in the workflow are overwritten by the ones provided by a field or file.

You must use a multipart/form-data request if you want to add environment- specific variables to your workflow.

If provided by a field, variables must be separated by line feed. If provided by a file, there must be one variable per line.

Warning

If you are on Windows and cannot use PowerShell, use a file, as described below. It is not practical to pass line feeds using CMD.

By field:

curl ... -F variables=$'FOO=abc def\nBAR=123' ...
curl.exe ... -F variables="FOO=abc def`nBAR=123" ...

By file:

curl ... -F variables=@MyVariables ...

The MyVariables file being for example:

FOO=abc def
BAR=123

If a variable is defined more than once, its last definition is used.

Resource files

If your workflow contains a resources.files part, you must also provide one file per defined entry in resources.files. You cannot use the field format for files.

You must use a multipart/form-data request if your workflow contains a resources.files part.

Examples

Example: starting a simple YAML workflow

Assuming an existing workflow.yaml YAML workflow in the current directory, you can call the endpoint using a POST request as such:

curl -X POST \
     -H "Authorization: Bearer ${TOKEN}" \
     -H "Content-type: application/x-yaml" \
     --data-binary @workflow.yaml \
     http://orchestrator.example.com/workflows
curl -X POST ^
     -H "Authorization: Bearer %TOKEN%" ^
     -H "Content-type: application/x-yaml" ^
     --data-binary @workflow.yaml ^
     http://orchestrator.example.com/workflows
curl.exe -X POST `
     -H "Authorization: Bearer $Env:TOKEN" `
     -H "Content-type: application/x-yaml" `
     --data-binary '@workflow.yaml' `
     http://orchestrator.example.com/workflows
{
  "apiVersion":"v1",
  "kind":"Status",
  "metadata":{},
  "message":"Workflow YAML Example accepted (workflow_id=bd801408-442e-4a64-896a-e91a1551b38e).",
  "details": {
    "workflow_id":"bd801408-442e-4a64-896a-e91a1551b38e"
  },
  "status":"Success",
  "reason":"Created",
  "code":201
}
Example: starting a simple JSON workflow in a non-default namespace

If workflow.json is a JSON workflow, you can alternatively use the following command:

curl -X POST \
     -H "Authorization: Bearer ${TOKEN}" \
     -H "Content-Type: application/json" \
     --data-binary @workflow.json \
     http://orchestrator.example.com/workflows?namespace=mynamespace
curl -X POST ^
     -H "Authorization: Bearer %TOKEN%" ^
     -H "Content-Type: application/json" ^
     --data-binary @workflow.json ^
     http://orchestrator.example.com/workflows?namespace=mynamespace
curl.exe -X POST `
     -H "Authorization: Bearer $Env:TOKEN" `
     -H "Content-Type: application/json" `
     --data-binary '@workflow.json' `
     http://orchestrator.example.com/workflows?namespace=mynamespace

(Strictly speaking, as YAML is a superset of JSON, using a -H "Content-type: x-yaml" header would work just as well.)

{
  "apiVersion":"v1",
  "kind":"Status",
  "metadata":{},
  "message":"Workflow JSON Example accepted (workflow_id=aeefb009-f793-41c0-8cf3-48a05ce9d2c5).",
  "details": {
    "workflow_id":"aeefb009-f793-41c0-8cf3-48a05ce9d2c5"
  },
  "status":"Success",
  "reason":"Created",
  "code":201
}
Example: starting a workflow using a multipart/form-data request

You can call the endpoint using a multipart/form-data request too. In this case, you do not specify the Content-Type header:

curl -X POST \
     -H "Authorization: Bearer ${TOKEN}" \
     -F workflow=@workflow.yaml \
     https://orchestrator.example.com/workflows
curl -X POST ^
     -H "Authorization: Bearer %TOKEN%" ^
     -F workflow=@workflow.yaml ^
     https://orchestrator.example.com/workflows
curl.exe -X POST `
     -H "Authorization: Bearer $Env:TOKEN" `
     -F workflow='@workflow.yaml' `
     https://orchestrator.example.com/workflows
{
  "apiVersion":"v1",
  "kind":"Status",
  "metadata":{},
  "message":"Workflow YAML Example accepted (workflow_id=132a859f-b35b-4cf1-8987-3cae5a7fd91d).",
  "details": {
    "workflow_id":"132a859f-b35b-4cf1-8987-3cae5a7fd91d"
  },
  "status":"Success",
  "reason":"Created",
  "code":201
}
Example: starting a workflow with attached resource files

Assuming your workflow declares two resources.files entries, as such:

workflow.yaml
metadata:
  name: Plenty
resources:
  files:
  - report1
  - report2
jobs:
  a-job:
    runs-on: [linux]
    steps:
    - run: echo "hello"

You must call the endpoint using the following multipart/form-data request:

curl -X POST \
     -H "Authorization: Bearer ${TOKEN}" \
     -F workflow=@workflow.yaml \
     -F report1=@report1.html \
     -F report2=@report2.xml \
     https://orchestrator.example.com/workflows
curl -X POST ^
     -H "Authorization: Bearer %TOKEN%" ^
     -F workflow=@workflow.yaml ^
     -F report1=@report1.html ^
     -F report2=@report2.xml ^
     https://orchestrator.example.com/workflows
curl.exe -X POST \
     -H "Authorization: Bearer $Env:TOKEN" `
     -F workflow='@workflow.yaml' `
     -F report1='@report1.html' `
     -F report2='@report2.xml' `
     https://orchestrator.example.com/workflows
{
  "apiVersion":"v1",
  "kind":"Status",
  "metadata":{},
  "message":"Workflow Plenty accepted (workflow_id=d8eab90b-39ad-4f40-a0e7-b40cc09c20f8).",
  "details": {
    "workflow_id":"d8eab90b-39ad-4f40-a0e7-b40cc09c20f8"
  },
  "status":"Success",
  "reason":"Created",
  "code":201
}
Example: starting a workflow with attached resource files and variables

Assuming your workflow declares two resources.files entries, as such:

workflow.yaml
metadata:
  name: Plenty with variables
variables:
  SERVER: foo
  SQUASH_USER: foobar
resources:
  files:
  - report1
  - report2
jobs:
  a-job:
    runs-on: [linux]
    steps:
    - run: echo "$SQUASH_USER"

You must call the endpoint using the following multipart/form-data request:

curl -X POST \
     -H "Authorization: Bearer ${TOKEN}" \
     -F workflow=@workflow.yaml \
     -F report1=@report1.html \
     -F report2=@report2.xml \
     -F variables=SQUASH_USER=${USER}$'\n'SQUASH_PASSWORD=${PASSWD} \
     https://orchestrator.example.com/workflows
curl.exe -X POST \
     -H "Authorization: Bearer $Env:TOKEN" `
     -F workflow='@workflow.yaml' `
     -F report1='@report1.html' `
     -F report2='@report2.xml' `
     -F variables="SQUASH_USER=$Env:USER`nSQUASH_PASSWORD=$Env:PASSWD" `
     https://orchestrator.example.com/workflows

Here, we also provide two variables, SQUASH_USER and SQUASH_PASSWORD.

The SQUASH_USER variable is specified in the workflow, but its value will be overwritten with the value set in the curl call.

The SQUASH_PASSWORD variable will be available for use in the workflow, with the value set in the curl call.

The SERVER variable specified in the workflow remains available and unchanged.

Example: failing to provide a resource file

Assuming your workflow declares two resources.files entries, as such:

workflow.yaml
metadata:
  name: Two files
resources:
  files:
  - report1
  - report2
jobs:
  a-job:
    runs-on: [linux]
    steps:
    - run: echo "hello"

If you call the endpoint using the following multipart/form-data request:

curl -X POST \
     -H "Authorization: Bearer ${TOKEN}" \
     -F workflow=@workflow.yaml \
     -F report1=@report1.html \
     https://orchestrator.example.com/workflows
curl -X POST ^
     -H "Authorization: Bearer %TOKEN%" ^
     -F workflow=@workflow.yaml ^
     -F report1=@report1.html ^
     https://orchestrator.example.com/workflows
curl.exe -X POST \
     -H "Authorization: Bearer $Env:TOKEN" `
     -F workflow='@workflow.yaml' `
     -F report1='@report1.html' `
     https://orchestrator.example.com/workflows

(Note that report2 is not provided.)

{
  "apiVersion":"v1",
  "kind":"Status",
  "metadata":{},
  "message":"Not all expected files were attached: {'report2'}.",
  "details":null,
  "status":"Failure",
  "reason":"Invalid",
  "code":422
}
Example: attempting to start a workflow with attached resource files via a Content-Type: application/x-yaml request

Assuming your workflow declares two resources.files entries, as such:

workflow.yaml
metadata:
  name: Plenty
resources:
  files:
  - report1
  - report2
jobs:
  a-job:
    runs-on: [linux]
    steps:
    - run: echo "hello"

Attempting a Content-type: application/x-yaml request to start this workflow will result in an error:

curl -X POST \
     -H "Authorization: Bearer ${TOKEN}" \
     -H "Content-Type: application/x-yaml" \
     --data-binary @workflow.yaml \
     http://orchestrator.example.com/workflows?namespace=mynamespace
curl -X POST ^
     -H "Authorization: Bearer %TOKEN%" ^
     -H "Content-Type: application/x-yaml" ^
     --data-binary @workflow.yaml ^
     http://orchestrator.example.com/workflows
curl.exe -X POST `
     -H "Authorization: Bearer $Env:TOKEN" `
     -H "Content-Type: application/x-yaml" `
     --data-binary '@workflow.yaml' `
     http://orchestrator.example.com/workflows
{
  "apiVersion":"v1",
  "kind":"Status",
  "metadata":{},
  "message":"Expecting files, must use multipart/form-data.",
  "details":null,
  "status":"Failure",
  "reason":"Invalid",
  "code":422
}

/workflows/{workflow_id} (DELETE)

This endpoint is exposed by the Killswitch service. It stops a running workflow.

It returns a status manifest (a JSON document) with the following entry:

  • message: the Workflow {workflow_id} canceled. string

Please note that the workflow may not stop immediately. Running steps and cleaning tasks may require some time to complete.

Stopping a completed workflow is allowed and does not produce an error.

Example: stopping a running workflow
DELETE /workflows/c75c7853-2cfa-42ab-80c4-f84966a8c016
{
  "apiVersion":"v1",
  "kind":"Status",
  "metadata":{},
  "message":"Workflow c75c7853-2cfa-42ab-80c4-f84966a8c016 canceled.",
  "details":null,
  "status":"Success",
  "reason":"OK",
  "code":200
}
Example: stopping a completed or canceled workflow
DELETE /workflows/c75c7853-2cfa-42ab-80c4-f84966a8c016
{
  "apiVersion":"v1",
  "kind":"Status",
  "metadata":{},
  "message":"Workflow c75c7853-2cfa-42ab-80c4-f84966a8c016 canceled.",
  "details":null,
  "status":"Success",
  "reason":"OK",
  "code":200
}
Example: attempting to stop an unknown workflow
DELETE /workflows/00000000-0000-0000-0000-000000000000
{
  "apiVersion":"v1",
  "kind":"Status",
  "metadata":{},
  "message":"Workflow 00000000-0000-0000-0000-000000000000 not found.",
  "details":null,
  "status":"Failure",
  "reason":"NotFound",
  "code":404
}

/workflows/{workflow_id}/files/{attachment_id} (GET, HEAD)

This endpoint is exposed by the Localstore service. It returns a requested attachment content or its headers only.

If the workflow or the attachment is not known, a Failure status is returned instead, with a 404 code.

/workflows/{workflow_id}/status (GET)

This endpoint is exposed by the Observer service. It lists the workflow events and status.

It returns a status manifest (a JSON document) with the following entries:

  • message: a summary of the workflow status (a string)
  • details: an object with two elements: items, a list of all events about the workflow, and status, one of the following strings: RUNNING, DONE, or FAILED.

If the workflow is not known, a Failure status is returned instead, with a 404 code.

Pagination

The list of events, items, may be paginated. The pagination, if used, is as per RFC8288 and relies on the Link header attribute:

import requests

base_url = '...'
response = requests.get(f'{base_url}/workflows/4420a8ad-1880-45dd-af07-9162583efcff/status')
events = response.json()['details']['items']
while 'next' in response.links:
    response = requests.get(response.links['next']['url'])
    events += response.json()['details']['items']

# All events have been collected

By default, the page size is determined by the observer service, but this can be changed by using the per_page parameter. There may be a maximum value allowed for per_page. (In the current implementation the default value is 100 and the maximum value is 1000.)

If a specific per_page value is set and assuming it is within the allowed limits, the links will use this value.

A specific page can be specified using the page parameter. page starts at 1, as per RFC8288.

Even if items is paginated, the status is for the whole workflow.

Endpoint allows for field and label selectors to filter returned workflow events. Selectors can be used with page and per_page parameters.

Examples

Example: querying the status of a completed workflow
GET /workflows/4420a8ad-1880-45dd-af07-9162583efcff/status
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "status": "Success",
  "message": "Workflow completed",
  "details": {
    "items": [...],
    "status": "DONE"
  },
  "code": 200,
  "reason": "OK"
}
Example: querying the status of a failed workflow
GET /workflows/4420a8ad-1880-45dd-af07-9162583efcff/status
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "status": "Success",
  "message": "Workflow failed",
  "details": {
    "items": [...],
    "status": "FAILED"
  },
  "code": 200,
  "reason": "OK"
}
Example: querying only notification events of a workflow
GET /workflows/7e9fd692-d8d5-4928-8329-230cc43c6b9f/status?fieldSelector=kind=Notification
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "status": "Success",
  "message": "Workflow failed",
  "details": {
    "items": [
      {
        [...]
      }
      {
        "apiVersion": "v1",
        "kind": "Notification",
        "metadata": {
          "job_id": "7e9fd692-d8d5-4928-8329-230cc43c6b9f"
          [...]
        }
        "spec": {
          "logs": [
            "[DEBUG] Received execution result d3e6e34a-77ed-4872-a846-516d40d60e44 for job 7e9fd692-d8d5-4928-8329-230cc43c6b9f"
          ]
        }
      },
      {
        "apiVersion": "v1",
        "kind": "Notification",
        "metadata": {
          "job_id": "7e9fd692-d8d5-4928-8329-230cc43c6b9f"
          [...]
        }
        "spec": {
          "logs": [
            "[DEBUG] Received execution result aa7d872f-d110-43ac-aec6-abdc0597db5a for job 7e9fd692-d8d5-4928-8329-230cc43c6b9f"
          ]
        }
      }
      {
        [...]
      }
    ]
    "status": "DONE"
  },
  "code": 200,
  "reason": "OK"
}
Example: querying ExecutionResult events of a workflow with pagination
GET /workflows/4420a8ad-1880-45dd-af07-9162583efcff/status?fieldSelector=kind=ExecutionResult&per_page=2&page=5
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "status": "Success",
  "message": "Workflow completed",
  "details": {
    "items": [
      {
        "apiVersion": "v1",
        "kind": "ExecutionResult",
        "metadata": {
          [...]
        }
        "status": 0
      },
      {
        "apiVersion": "v1",
        "kind": "ExecutionResult",
        "metadata": {
          [...]
        }
        "status": 1
      }
    ]
    "status": "DONE"
  },
  "code": 200,
  "reason": "OK"
}
Example: attempting to query the status of an unknown workflow
GET /workflows/00000000-0000-0000-0000-000000000000/status
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "message": "Workflow 00000000-0000-0000-0000-000000000000 not found.",
  "details": null,
  "status": "Failure",
  "reason": "NotFound",
  "code": 404
}

/workflows/{workflow_id}/workers (GET)

This endpoint is exposed by the Observer service. It lists the workers currently working on the workflow.

It returns a status manifest (a JSON document) with the following entries:

  • message: a summary of the workflow status (a string)
  • details: an object with two elements: items, a list of active workers on the workflow, and status, one of the following strings: BUSY or IDLE.

If the workflow is not known, a Failure status is returned instead, with a 404 code.

Example: checking if some workers are processing the specified workflow (2 still busy)
GET /workflows/4420a8ad-1880-45dd-af07-9162583efcff/workers
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "message": "2 active workers on workflow",
  "details": {
    "items": [
      "3137d9e2-0873-4cba-b5d3-f36dff8d9b3f",
      "e276b403-6d27-4d63-b3f1-1415c258736c"
    ],
    "status": "BUSY"
  },
  "status": "Success",
  "reason": "OK",
  "code": 200
}
Example: checking if some workers are processing the specified workflow (not anymore)
GET /workflows/4420a8ad-1880-45dd-af07-9162583efcff/workers
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "message": "0 active workers on workflow",
  "details": {
    "items": [
    ],
    "status": "IDLE"
  },
  "status": "Success",
  "reason": "OK",
  "code": 200
}
Example: attempting to check whether some workers are processing an unknown workflow
GET /workflows/00000000-0000-0000-0000-000000000000/workers
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "message": "Workflow 00000000-0000-0000-0000-000000000000 not found.",
  "details": null,
  "status": "Failure",
  "reason": "NotFound",
  "code": 404
}

/workflows/{workflow_id}/qualitygate (GET)

This endpoint is exposed by the Quality Gate plugin. It checks whether a workflow satisfies a quality gate defined by the mode parameter.

It returns a status manifest (a JSON document) with one of the following standard HTTP response codes: OK, Invalid, Unauthorized, or NotFound.

OK means the service knows the specified workflow. The details.status contains the quality gate status for the workflow. It can be one of the following:

  • SUCCESS: the workflow has succeeded and passed the quality gate.
  • NOTEST: the workflow has succeeded, but it produced no test result, or it contains no test results matching the quality gate rules scope.
  • FAILURE: the workflow has failed, or failed the quality gate.
  • RUNNING: the workflow execution is still ongoing.

Invalid means the specified mode is not found in the definition file or is not one of the expected values (strict or passing).

NotFound means the workflow is not known. It may be because the workflow is not yet known to the quality gate service, or that the workflow is no longer known to the quality gate service. Quality gate results for a given workflow are kept for a limited period after workflow completion (typically 60 minutes, but this can be configured).

Unauthorized means the provided token is invalid or not recognized by the quality gate service.

Modes

This endpoint allows to use a mode (i.e. a quality gate) defined in the definition file loaded at the quality gate service launch. Two default modes are always available: strict and passing. If no mode is specified, strict is used.

  • strict means a workflow will pass the quality gate if and only if it has been completed successfully and all test cases were OK.

  • passing means a workflow will pass the quality gate if it has been completed successfully, even if some test cases were not OK.

Examples

Example: checking the quality gate status of a workflow with several tests

This is a status manifest that contains test evaluation results and the general workflow execution status (in details.status). The JUnit tests execution result is evaluated as SUCCESS even if some tests have failed, but the general execution status is FAILURE, because Cypress tests did not pass the threshold.

GET /workflows/5dcc66dd-5d0d-4dcf-9b18-f39eafc0f279/qualitygate?mode=junit.cypress.quality.gate
{
  "apiVersion": "v1",
  "code": 200,
  "details": {
      "rules": {
          "Cypress tests": {
              "result": "FAILURE",
              "scope": "test.technology == 'cypress'",
              "success_ratio": "50.0%",
              "tests_failed": 10,
              "tests_in_scope": 20,
              "tests_passed": 10
          },
          "JUnit tests": {
              "result": "SUCCESS",
              "scope": "test.technology == 'junit'",
              "success_ratio": "80.0%",
              "tests_failed": 10,
              "tests_in_scope": 50,
              "tests_passed": 40
          },
      },
      "status": "FAILURE"
  },
  "kind": "Status",
  "message": "",
  "metadata": {},
  "reason": "OK",
  "status": "Success"
}
Example: checking the quality gate status on passing mode of a workflow with a failed test

This is a status manifest for a workflow with at least one failed test, using the passing mode. As long as the workflow has been completed successfully, it will pass the quality gate:

GET /workflows/5dcc66dd-5d0d-4dcf-9b18-f39eafc0f279/qualitygate?mode=passing
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "message": "",
  "details": {
    "status": "SUCCESS"
  },
  "status": "Success",
  "reason": "OK"
  "code": 200,
}
Example: checking the quality gate status of a workflow with one failed test on strict mode

This is a status manifest for the same workflow, using the default (strict) mode. As it has at least one failed test, it fails that quality gate:

GET /workflows/5dcc66dd-5d0d-4dcf-9b18-f39eafc0f279/qualitygate
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "message": "",
  "details": {
    "status": "FAILURE"
  },
  "status": "Success",
  "reason": "OK",
  "code": 200
}
Example: checking the quality gate status of a workflow where no test matches the quality gate rules

This is a status manifest for a workflow that contains no tests matching user-defined my.quality.gate quality gate rules scope:

GET /workflows/5dcc44dd-5d0d-4dcf-9b18-f39eafc0f279/qualitygate?mode=my.quality.gate
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "message": "",
  "details": {
    "status": "NOTEST"
  },
  "status": "Success",
  "reason": "OK",
  "code": 200
}
Example: attempting to check the quality gate status of an unknown workflow
GET /workflows/00000000-0000-0000-0000-000000000000/qualitygate
{
  "apiVersion": "v1",
  "kind": "Status",
  "message": "Workflow 00000000-0000-0000-0000-000000000000 not found.",
  "metadata": {},
  "status": "Failure",
  "reason": "NotFound",
  "code": 404
}

/workflows/{workflow_id}/qualitygate (POST)

This endpoint is the second endpoint exposed by the Quality Gate plugin. It allows to check whether a workflow satisfies a quality gate from the quality gate definition joined to the request. The quality gate is defined by the mode parameter.

The following quality gate definition formats are accepted: JSON or YAML.

It returns a status manifest (a JSON document). The response codes and the details.status are the same as for the GET endpoint.

This endpoint does not have any default quality gate. If no mode is specified or mode is not found in the quality gate definition, an error is returned.

Examples

Example: checking that a workflow passes the quality gate from the quality gate definition joined to request

Assuming an existing my_qualitygates.yaml quality gate definition with the junit quality gate, the endpoint can be called using the following POST request. The status manifest indicates that all the tests in a workflow satisfying to the quality gate junit rules scope are in success:

curl -X POST \
     -H "Authorization: Bearer ${TOKEN}" \
     -H "Content-type: application/x-yaml" \
     --data-binary @my_qualitygates.yaml \
     http://orchestrator.example.com/workflows/5dcc44dd-5d0d-4dcf-9b18-f39eafc0f279/qualitygate?mode=junit
curl -X POST ^
     -H "Authorization: Bearer %TOKEN%" ^
     -H "Content-type: application/x-yaml" ^
     --data-binary @my_qualitygates.yaml ^
     http://orchestrator.example.com/workflows/5dcc44dd-5d0d-4dcf-9b18-f39eafc0f279/qualitygate?mode=junit
curl.exe -X POST `
     -H "Authorization: Bearer $Env:TOKEN" `
     -H "Content-type: application/x-yaml" `
     --data-binary '@my_qualitygates.yaml' `
     http://orchestrator.example.com/workflows/5dcc44dd-5d0d-4dcf-9b18-f39eafc0f279/qualitygate?mode=junit
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "message": "",
  "details": {
    "status": "SUCCESS"
  },
  "status": "Success",
  "reason": "OK",
  "code": 200
}
Example: checking that a workflow passes the quality gate from the definition file joined to request

You may also join a quality gate definition to the request as a file. In this case, use qualitygates as a parameter name.

curl -X POST \
     -H "Authorization: Bearer ${TOKEN}" \
     -F qualitygates=@my_qualitygates.yaml \
     http://orchestrator.example.com/workflows/5dcc44dd-5d0d-4dcf-9b18-f39eafc0f279/qualitygate?mode=junit
curl -X POST ^
     -H "Authorization: Bearer %TOKEN%" ^
     -F qualitygates=@my_qualitygates.yaml ^
     http://orchestrator.example.com/workflows/5dcc44dd-5d0d-4dcf-9b18-f39eafc0f279/qualitygate?mode=junit
curl.exe -X POST `
     -H "Authorization: Bearer $Env:TOKEN" `
     -F qualitygates='@my_qualitygates.yaml' `
     http://orchestrator.example.com/workflows/5dcc44dd-5d0d-4dcf-9b18-f39eafc0f279/qualitygate?mode=junit
Example: checking that a workflow passes the quality gate with mode not in quality gate definition

Assuming that my_qualitygates.yaml does not contain a quality gate named cypress, calling the endpoint with the following request will return a failure:

curl -X POST \
     -H "Authorization: Bearer ${TOKEN}" \
     -H "Content-type: application/x-yaml" \
     --data-binary @my_qualitygates.yaml \
     http://orchestrator.example.com/workflows/5dcc44dd-5d0d-4dcf-9b18-f39eafc0f279/qualitygate?mode=cypress
curl -X POST ^
     -H "Authorization: Bearer %TOKEN%" ^
     -H "Content-type: application/x-yaml" ^
     --data-binary @my_qualitygates.yaml ^
     http://orchestrator.example.com/workflows/5dcc44dd-5d0d-4dcf-9b18-f39eafc0f279/qualitygate?mode=cypress
curl.exe -X POST `
     -H "Authorization: Bearer $Env:TOKEN" `
     -H "Content-type: application/x-yaml" `
     --data-binary '@my_qualitygates.yaml' `
     http://orchestrator.example.com/workflows/5dcc44dd-5d0d-4dcf-9b18-f39eafc0f279/qualitygate?mode=cypress
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "message": "Quality gate cypress not found in definition file.",
  "status": "Failure",
  "reason": "Invalid",
  "code": 422
}

/workflows/status (GET)

This endpoint is exposed by the Observer service. It queries the status of the orchestrator.

It returns a status manifest (a JSON document) with the following entries:

  • message: a summary of the orchestrator status (a string)
  • details: an object with two elements: items, a list of all still running workflows, and status, one of the following strings: IDLE or BUSY.

If there are no running workflows, and if there are no active workers on any workflow, details.status is IDLE. It is BUSY otherwise.

Example: querying the status of a busy orchestrator

In the following example, a workflow is still running (or has at least one active worker):

GET /workflows/status
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "message": "1 workflows in progress",
  "details": {
    "items": ["50c7e5d1-7bdc-46f0-9422-3cc6660d00c0"],
    "status": "BUSY"
  },
  "status": "Success",
  "reason": "OK",
  "code": 200
}
Example: querying the status of an idle orchestrator

In this example, all workflows have been completed, and no worker is still active. It is safe to stop the orchestrator service.

GET /workflows/status
{
  "apiVersion": "v1",
  "kind": "Status",
  "metadata": {},
  "message": "No workflow in progress",
  "details": {
    "items": [],
    "status": "IDLE"
  },
  "status": "Success",
  "reason": "OK",
  "code": 200
}