diff --git a/openapi.json b/openapi.json index 4759fbf3..2dcadb9d 100644 --- a/openapi.json +++ b/openapi.json @@ -1 +1 @@ -{"openapi": "3.1.0", "info": {"title": "exodus-gw", "description": "The exodus-gw service provides APIs for uploading and publishing content on the exodus CDN.\n\n\n\nAvailable APIs are grouped into the following categories:\n\n- [service](#tag/service): inspect the state of the exodus-gw service.\n- [upload](#tag/upload): upload blobs to the CDN without exposing them to end-users.\n S3 compatible.\n- [publish](#tag/publish): atomically publish a set of blobs on the CDN under specified paths,\n making them accessible to end-users.\n- [deploy](#tag/deploy): deploy configuration influencing the behavior of the CDN.\n- [cdn](#tag/cdn): utilities for accessing the CDN.\n\n## Overview of API usage\n\nA typical content publishing workflow using exodus-gw will consist of:\n\n- Use the upload APIs to ensure desired blobs are uploaded.\n - As this API is partially S3-compatible, this can typically be done using\n an existing S3 client library.\n- Use the publish API to create a publish object and create a (URI => blob)\n mapping for the blobs you want to publish.\n- When you are ready to expose content to end-users, commit the publish object.\n This will atomically unveil new content at all of the requested URIs.\n\n\n## Authentication\n\n\nThe exodus-gw API does not include any direct support for authentication and is\ninstead expected to be deployed behind a reverse-proxy implementing any desired\nauthentication mechanism.\n\nIf you are deploying an instance of exodus-gw, see\n[the deployment guide](https://release-engineering.github.io/exodus-gw/deployment.html)\nfor information on how to integrate an authentication mechanism.\n\nIf you are a client looking to make use of exodus-gw, consult your organization's\ninternal documentation for advice on how to authenticate with exodus-gw.\n\n\n## Environments\n\nMany APIs in exodus-gw use the concept of an \"environment\" to control the target system\nof an operation.\n\n\nThe set of environments is configured when exodus-gw is deployed.\nA typical scenario is to deploy a \"pre\" environment for pre-release content and a\n\"live\" environment for live content.\n\nDifferent environments will also require the user to hold different roles. For example,\na client might be permitted only to write to one of the configured environments, or all\nof them, depending on the configuration of the server.\n\nIf you are deploying an instance of exodus-gw, see\n[the deployment guide](https://release-engineering.github.io/exodus-gw/deployment.html)\nfor information on how to configure environments.\n\nIf you are a client looking to make use of exodus-gw, consult your organization's\ninternal documentation for advice on which environment(s) you should be using.\n\n", "version": ""}, "paths": {"/healthcheck": {"get": {"tags": ["service"], "summary": "Healthcheck", "description": "Returns a successful response if the service is running.", "operationId": "healthcheck_healthcheck_get", "responses": {"200": {"description": "Service is up", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageResponse"}}}}}}}, "/healthcheck-worker": {"get": {"tags": ["service"], "summary": "Healthcheck Worker", "description": "Returns a successful response if background workers are running.", "operationId": "healthcheck_worker_healthcheck_worker_get", "responses": {"200": {"description": "Worker(s) are responding", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageResponse"}}}}}}}, "/whoami": {"get": {"tags": ["service"], "summary": "Whoami", "description": "Return basic information on the caller's authentication & authorization context.\n\nThis endpoint may be used to determine whether the caller is authenticated to\nthe exodus-gw service, and if so, which set of role(s) are held by the caller.\n\nIt is a read-only endpoint intended for diagnosing authentication issues.", "operationId": "whoami_whoami_get", "responses": {"200": {"description": "Caller's auth context retrieved", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CallContext"}, "examples": [{"value": {"client": {"roles": ["someRole", "anotherRole"], "authenticated": true, "serviceAccountId": "clientappname"}, "user": {"roles": ["viewer"], "authenticated": true, "internalUsername": "someuser"}}}]}}}}}}, "/task/{task_id}": {"get": {"tags": ["service"], "summary": "Get Task", "description": "Return existing task object from database using given task ID.", "operationId": "get_task_task__task_id__get", "parameters": [{"name": "task_id", "in": "path", "required": true, "schema": {"type": "string", "title": "task ID", "description": "UUID of an existing task object."}, "description": "UUID of an existing task object."}], "responses": {"200": {"description": "Sucessfully retrieved task", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Task"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/upload/{env}/{key}": {"post": {"tags": ["upload"], "summary": "Create/complete multipart upload", "description": "Create or complete a multi-part upload.\n\n**Required roles**: `{env}-blob-uploader`\n\nTo create a multi-part upload:\n- include ``uploads`` in query string, with no value (e.g. ``POST /upload/{env}/{key}?uploads``)\n- see also: https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateMultipartUpload.html\n\nTo complete a multi-part upload:\n- include ``uploadId`` in query string\n- include parts with ETags in request body\n- see also: https://docs.aws.amazon.com/AmazonS3/latest/API/API_CompleteMultipartUpload.html", "operationId": "multipart_upload_upload__env___key__post", "parameters": [{"name": "key", "in": "path", "required": true, "schema": {"type": "string", "description": "S3 object key", "title": "Key"}, "description": "S3 object key"}, {"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}, {"name": "uploadId", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "\nID of an existing multi-part upload.\n\nIf this argument is provided, it must be the ID of a multi-part upload\ncreated previously. The upload will be validated and completed.\n\nMust not be passed together with ``uploads``.", "title": "Uploadid"}, "description": "\nID of an existing multi-part upload.\n\nIf this argument is provided, it must be the ID of a multi-part upload\ncreated previously. The upload will be validated and completed.\n\nMust not be passed together with ``uploads``."}, {"name": "uploads", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "\nIf this argument is provided, a new multi-part upload will be created\nand its ID returned. The provided value should be an empty string.\n\nMust not be passed together with ``uploadId``.", "title": "Uploads"}, "description": "\nIf this argument is provided, a new multi-part upload will be created\nand its ID returned. The provided value should be an empty string.\n\nMust not be passed together with ``uploadId``."}], "responses": {"200": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "put": {"tags": ["upload"], "summary": "Upload bytes", "description": "Write to an object, either as a standalone operation or within a multi-part upload.\n\n**Required roles**: `{env}-blob-uploader`\n\nTo upload an entire object:\n- include all object bytes in request body\n- see also: https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html\n\nTo upload part of an object:\n- provide multipart upload ID in ``uploadId``\n- provide part number from 1 to 10,000 in ``partNumber``\n- include part of an object in request body (must be at least 5MB in size, except last part)\n- retain the `ETag` from the response, as it will be required to complete the upload\n- see also: https://docs.aws.amazon.com/AmazonS3/latest/API/API_UploadPart.html", "operationId": "upload_upload__env___key__put", "parameters": [{"name": "key", "in": "path", "required": true, "schema": {"type": "string", "description": "S3 object key", "title": "Key"}, "description": "S3 object key"}, {"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}, {"name": "uploadId", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "ID of an existing multi-part upload.", "title": "Uploadid"}, "description": "ID of an existing multi-part upload."}, {"name": "partNumber", "in": "query", "required": false, "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "description": "Part number, where multi-part upload is used.", "title": "Partnumber"}, "description": "Part number, where multi-part upload is used."}], "responses": {"200": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["upload"], "summary": "Abort multipart upload", "description": "Abort a multipart upload.\n\n**Required roles**: `{env}-blob-uploader`\n\nIf an upload cannot be completed, explicitly aborting it is recommended in order\nto free up resources as early as possible, although this is not mandatory.\n\nSee also: https://docs.aws.amazon.com/AmazonS3/latest/API/API_AbortMultipartUpload.html", "operationId": "abort_multipart_upload_upload__env___key__delete", "parameters": [{"name": "key", "in": "path", "required": true, "schema": {"type": "string", "description": "S3 object key", "title": "Key"}, "description": "S3 object key"}, {"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}, {"name": "uploadId", "in": "query", "required": true, "schema": {"type": "string", "description": "ID of a multipart upload", "title": "Uploadid"}, "description": "ID of a multipart upload"}], "responses": {"200": {"description": "Empty response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "head": {"tags": ["upload"], "summary": "Get object metadata", "description": "Retrieve metadata from an S3 object.\n\nNote that, as explained in [the upload API overview](#tag/upload), AWS-specific\nheaders such as `x-amz-*` are not included in the response.\nThe main purpose of this API is to determine whether or not an object\nidentified by checksum exists on the CDN.\n\n**Required roles**: `{env}-blob-uploader`", "operationId": "head_upload__env___key__head", "parameters": [{"name": "key", "in": "path", "required": true, "schema": {"type": "string", "description": "S3 object key", "title": "Key"}, "description": "S3 object key"}, {"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}], "responses": {"200": {"description": "Object exists"}, "404": {"description": "Object or environment does not exist"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/{env}/publish": {"post": {"tags": ["publish"], "summary": "Create new publish", "description": "Creates and returns a new publish object.\n\n**Required roles**: `{env}-publisher`", "operationId": "publish__env__publish_post", "parameters": [{"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}], "responses": {"200": {"description": "Publish created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Publish"}, "examples": [{"value": {"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "env": "live", "links": {"self": "/live/publish/497f6eca-6276-4993-bfeb-53cbbbba6f08", "commit": "/live/publish/497f6eca-6276-4993-bfeb-53cbbbba6f08/commit"}, "items": []}}]}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/{env}/publish/{publish_id}": {"put": {"tags": ["publish"], "summary": "Update Publish Items", "description": "Add publish items to an existing publish object.\n\n**Required roles**: `{env}-publisher`\n\nPublish items primarily are a mapping between a URI relative to the root of the CDN,\nand the key of a binary object which should be exposed from that URI.\n\nAdding items to a publish does not immediately make them available from the CDN;\nthe publish object must first be committed.\n\nItems cannot be added to a publish once it has been committed.", "operationId": "update_publish_items__env__publish__publish_id__put", "parameters": [{"name": "publish_id", "in": "path", "required": true, "schema": {"type": "string", "title": "publish ID", "description": "UUID of an existing publish object."}, "description": "UUID of an existing publish object."}, {"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "array", "items": {"$ref": "#/components/schemas/ItemBase"}, "examples": [[{"web_uri": "/my/awesome/file.iso", "object_key": "aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f", "content_type": "application/octet-stream"}, {"web_uri": "/my/slightly-less-awesome/other-file.iso", "object_key": "c06545d4e1a1c8e221d47e7d568c035fb32c6b6124881fd0bc17983bd9088ae0", "content_type": "application/octet-stream"}, {"web_uri": "/another/route/to/my/awesome/file.iso", "link_to": "/my/awesome/file.iso"}, {"web_uri": "/my/awesome/deletion.iso", "object_key": "absent"}]], "title": "Items"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/EmptyResponse"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "get": {"tags": ["publish"], "summary": "Get Publish", "description": "Return an existing publish object from database using the given publish ID.\n\nFor performance reasons, the returned item list is always empty.", "operationId": "get_publish__env__publish__publish_id__get", "parameters": [{"name": "publish_id", "in": "path", "required": true, "schema": {"type": "string", "title": "publish ID", "description": "UUID of an existing publish object."}, "description": "UUID of an existing publish object."}, {"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}], "responses": {"200": {"description": "Publish found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Publish"}, "examples": [{"value": {"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "env": "live", "links": {"self": "/live/publish/497f6eca-6276-4993-bfeb-53cbbbba6f08", "commit": "/live/publish/497f6eca-6276-4993-bfeb-53cbbbba6f08/commit"}, "items": []}}]}}}, "404": {"description": "Publish not found", "content": "No publish found for ID 497f6eca-6276-4993-bfeb-53cbbbba6f08"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/{env}/publish/{publish_id}/commit": {"post": {"tags": ["publish"], "summary": "Commit Publish", "description": "Commit an existing publish object.\n\n**Required roles**: `{env}-publisher`\n\nCommitting a publish is required in order to expose published content from the CDN.\n\nThere are two available commit modes, \"phase1\" and \"phase2\" (default).\n\n### Phase 1\n\nA phase 1 commit:\n\n- is optional.\n- can be performed more than once.\n- does not prevent further modifications to the publish.\n- will commit all phase 1 content (e.g. packages in yum repos), but not phase 2\n content (e.g. repodata in yum repos); see\n [Two-phase commit](#section/Two-phase-commit).\n- is not rolled back if a later phase 2 commit fails (or never occurs).\n\n### Phase 2\n\nA phase 2 commit:\n\n- is the default when no commit mode is specified.\n- can (and should) be performed exactly once.\n- freezes the associated publish object - no further items can be added.\n- will commit all content with near-atomic behavior; see\n [Atomicity](#section/Atomicity).\n\n### Notes\n\nCommit occurs asynchronously. This API returns a Task object which may be used\nto monitor the progress of the commit.\n\nNote that exodus-gw does not require that any given path is only modified by\na single publish. If multiple publish objects updating the same path are being\ncommitted at the same time, after both commits succeed, the path will point\nat whichever item was updated most recently.", "operationId": "commit_publish__env__publish__publish_id__commit_post", "parameters": [{"name": "publish_id", "in": "path", "required": true, "schema": {"type": "string", "title": "publish ID", "description": "UUID of an existing publish object."}, "description": "UUID of an existing publish object."}, {"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}, {"name": "commit_mode", "in": "query", "required": false, "schema": {"anyOf": [{"$ref": "#/components/schemas/CommitModes"}, {"type": "null"}], "title": "commit mode", "description": "See: [Two-phase commit](#section/Two-phase-commit)", "examples": ["phase1", "phase2"]}, "description": "See: [Two-phase commit](#section/Two-phase-commit)"}, {"name": "deadline", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "A timestamp by which this task may be abandoned if not completed.\n\nWhen omitted, a server default will apply.", "examples": ["2022-07-25T15:47:47Z"], "title": "Deadline"}, "description": "A timestamp by which this task may be abandoned if not completed.\n\nWhen omitted, a server default will apply."}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Task"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/{env}/deploy-config": {"post": {"tags": ["deploy"], "summary": "Deploy Config", "description": "Deploys CDN configuration data for use by Exodus components.\n\n**Required roles**: `{env}-config-deployer`\n\nDeployment occurs asynchronously. This API returns a Task object\nwhich may be used to monitor the progress of the deployment.\n\nThis endpoint is deprecated, use /{env}/config instead.", "operationId": "deploy_config__env__deploy_config_post", "deprecated": true, "parameters": [{"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "examples": [{"listing": {"/content/dist/rhel8": {"var": "releasever", "values": ["8", "8.0", "8.1", "8.2", "8.3", "8.4", "8.5"]}}, "origin_alias": [{"src": "/content/origin", "dest": "/origin", "exclude_paths": []}, {"src": "/origin/rpm", "dest": "/origin/rpms", "exclude_paths": ["/iso/"]}], "releasever_alias": [{"dest": "/content/dist/rhel8/8.5", "src": "/content/dist/rhel8/8", "exclude_paths": ["/files/", "/images/", "/iso/"]}], "rhui_alias": [{"dest": "/content/dist/rhel8", "src": "/content/dist/rhel8/rhui", "exclude_paths": ["/files/", "/images/", "/iso/"]}]}], "title": "Config", "properties": {"listing": {"type": "object", "description": "A mapping from paths to a yum variable name & list of values, used in generating 'listing' responses.", "patternProperties": {"^((?!/\\.+/)(/[\\w\\$\\.\\-]+))*$": {"type": "object", "properties": {"var": {"type": "string", "enum": ["releasever", "basearch"]}, "values": {"type": "array", "items": {"type": "string", "minLength": 1}, "uniqueItems": true}}}}, "additionalProperties": false}, "origin_alias": {"type": "array", "items": {"type": "object", "properties": {"src": {"type": "string", "pattern": "^((?!/\\.+/)(/[\\w\\$\\.\\-]+))*$", "description": "Path being aliased from, relative to CDN root."}, "dest": {"type": "string", "pattern": "^((?!/\\.+/)(/[\\w\\$\\.\\-]+))*$", "description": "Target of the alias, relative to CDN root."}, "exclude_paths": {"type": "array", "items": {"type": "string"}, "description": "Paths for which alias will not be resolved, treated as an unanchored regex."}}}, "uniqueItems": true, "description": "Aliases relating to /origin."}, "releasever_alias": {"type": "array", "items": {"type": "object", "properties": {"src": {"type": "string", "pattern": "^((?!/\\.+/)(/[\\w\\$\\.\\-]+))*$", "description": "Path being aliased from, relative to CDN root."}, "dest": {"type": "string", "pattern": "^((?!/\\.+/)(/[\\w\\$\\.\\-]+))*$", "description": "Target of the alias, relative to CDN root."}, "exclude_paths": {"type": "array", "items": {"type": "string"}, "description": "Paths for which alias will not be resolved, treated as an unanchored regex."}}}, "uniqueItems": true, "description": "Aliases relating to $releasever variables."}, "rhui_alias": {"type": "array", "items": {"type": "object", "properties": {"src": {"type": "string", "pattern": "^((?!/\\.+/)(/[\\w\\$\\.\\-]+))*$", "description": "Path being aliased from, relative to CDN root."}, "dest": {"type": "string", "pattern": "^((?!/\\.+/)(/[\\w\\$\\.\\-]+))*$", "description": "Target of the alias, relative to CDN root."}, "exclude_paths": {"type": "array", "items": {"type": "string"}, "description": "Paths for which alias will not be resolved, treated as an unanchored regex."}}}, "uniqueItems": true, "description": "Aliases relating to RHUI."}}, "required": ["listing", "origin_alias", "releasever_alias", "rhui_alias"], "additionalProperties": false}}}, "description": "Configuration data for the CDN. Will replace the previously deployed configuration."}, "responses": {"200": {"description": "Deployment enqueued", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Task"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/{env}/cdn/{url}": {"get": {"tags": ["cdn"], "summary": "Redirect (GET)", "description": "Redirects to a requested URL on the CDN.\n\nThe CDN requires a signature from an authorized signer in order to permit\nrequests. When using this endpoint, exodus-gw acts as an authorized signer\non the caller's behalf, thus allowing any exodus-gw client to access CDN\ncontent without holding the signing keys.\n\nThe URL used in the redirect will become invalid after a server-defined\ntimeout, typically less than one hour.", "operationId": "cdn_redirect__env__cdn__url__get", "parameters": [{"name": "url", "in": "path", "required": true, "schema": {"type": "string", "title": "URL", "description": "URL of a piece of content relative to CDN root", "examples": ["content/dist/rhel8/8/x86_64/baseos/os/repodata/repomd.xml"]}, "description": "URL of a piece of content relative to CDN root"}, {"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}], "responses": {"302": {"description": "Redirect", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/EmptyResponse"}}}, "headers": {"location": {"description": "An absolute, signed, temporary URL of CDN content"}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "head": {"tags": ["cdn"], "summary": "Redirect (HEAD)", "description": "Identical to GET redirect, but for HEAD method.", "operationId": "cdn_redirect__env__cdn__url__head", "parameters": [{"name": "url", "in": "path", "required": true, "schema": {"type": "string", "title": "URL", "description": "URL of a piece of content relative to CDN root", "examples": ["content/dist/rhel8/8/x86_64/baseos/os/repodata/repomd.xml"]}, "description": "URL of a piece of content relative to CDN root"}, {"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}], "responses": {"302": {"description": "Redirect", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/EmptyResponse"}}}, "headers": {"location": {"description": "An absolute, signed, temporary URL of CDN content"}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/{env}/cdn-access": {"get": {"tags": ["cdn"], "summary": "Access", "description": "Obtain signed cookies and other information needed for accessing\na specific CDN environment.\n\nThis endpoint may be used to look up the CDN origin server belonging\nto a particular environment and to obtain long-term signed cookies\nauthorizing requests to that environment. The cookies returned by\nthis endpoint should be treated as a secret.\n\n**Required roles**: `{env}-cdn-consumer`", "operationId": "cdn_access__env__cdn_access_get", "parameters": [{"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}, {"name": "expire_days", "in": "query", "required": false, "schema": {"type": "integer", "description": "Desired expiration time, in days, for generated signatures.\n\nIt is mandatory to provide a value.\n\nCannot exceed a maximum value configured by the server (typically 365 days).", "examples": [30], "default": -1, "title": "Expire Days"}, "description": "Desired expiration time, in days, for generated signatures.\n\nIt is mandatory to provide a value.\n\nCannot exceed a maximum value configured by the server (typically 365 days)."}, {"name": "resource", "in": "query", "required": false, "schema": {"type": "string", "description": "Desired resource to access. If included, must begin with '/'. Defaults to '/*', providing access to the entire CloudFront distribution.", "examples": ["/content/dist/rhel8/8.2/x86_64/baseos/iso/PULP_MANIFEST"], "default": "/*", "title": "Resource"}, "description": "Desired resource to access. If included, must begin with '/'. Defaults to '/*', providing access to the entire CloudFront distribution."}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AccessResponse"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/{env}/cdn-flush": {"post": {"tags": ["cdn"], "summary": "Flush cache", "description": "Flush given paths from CDN cache(s) corresponding to this environment.\n\nThis API may be used to request CDN edge servers downstream from exodus-gw\nand exodus-cdn to discard cached versions of content, ensuring that\nsubsequent requests will receive up-to-date content.\n\nThe API is provided for troubleshooting and for scenarios where it's\nknown that explicit cache flushes are needed. It's not necessary to use\nthis API during a typical upload and publish workflow.\n\nReturns a task. Successful completion of the task indicates that CDN\ncaches have been flushed.\n\n**Required roles**: `{env}-cdn-flusher`", "operationId": "flush_cdn_cache__env__cdn_flush_post", "parameters": [{"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}, {"name": "deadline", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "A timestamp by which this task may be abandoned if not completed.\n\nWhen omitted, a server default will apply.", "examples": ["2022-07-25T15:47:47Z"], "title": "Deadline"}, "description": "A timestamp by which this task may be abandoned if not completed.\n\nWhen omitted, a server default will apply."}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "array", "items": {"$ref": "#/components/schemas/FlushItem"}, "examples": [[{"web_uri": "/some/path/i/want/to/flush"}, {"web_uri": "/another/path/i/want/to/flush"}]], "title": "Items"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Task"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/{env}/config": {"post": {"tags": ["config"], "summary": "Config Post", "description": "Deploys CDN configuration data for use by Exodus components.\n\n**Required roles**: `{env}-config-deployer`\n\nDeployment occurs asynchronously. This API returns a Task object\nwhich may be used to monitor the progress of the deployment.", "operationId": "config_post__env__config_post", "parameters": [{"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "examples": [{"listing": {"/content/dist/rhel8": {"var": "releasever", "values": ["8", "8.0", "8.1", "8.2", "8.3", "8.4", "8.5"]}}, "origin_alias": [{"src": "/content/origin", "dest": "/origin", "exclude_paths": []}, {"src": "/origin/rpm", "dest": "/origin/rpms", "exclude_paths": ["/iso/"]}], "releasever_alias": [{"dest": "/content/dist/rhel8/8.5", "src": "/content/dist/rhel8/8", "exclude_paths": ["/files/", "/images/", "/iso/"]}], "rhui_alias": [{"dest": "/content/dist/rhel8", "src": "/content/dist/rhel8/rhui", "exclude_paths": ["/files/", "/images/", "/iso/"]}]}], "title": "Config", "properties": {"listing": {"type": "object", "description": "A mapping from paths to a yum variable name & list of values, used in generating 'listing' responses.", "patternProperties": {"^((?!/\\.+/)(/[\\w\\$\\.\\-]+))*$": {"type": "object", "properties": {"var": {"type": "string", "enum": ["releasever", "basearch"]}, "values": {"type": "array", "items": {"type": "string", "minLength": 1}, "uniqueItems": true}}}}, "additionalProperties": false}, "origin_alias": {"type": "array", "items": {"type": "object", "properties": {"src": {"type": "string", "pattern": "^((?!/\\.+/)(/[\\w\\$\\.\\-]+))*$", "description": "Path being aliased from, relative to CDN root."}, "dest": {"type": "string", "pattern": "^((?!/\\.+/)(/[\\w\\$\\.\\-]+))*$", "description": "Target of the alias, relative to CDN root."}, "exclude_paths": {"type": "array", "items": {"type": "string"}, "description": "Paths for which alias will not be resolved, treated as an unanchored regex."}}}, "uniqueItems": true, "description": "Aliases relating to /origin."}, "releasever_alias": {"type": "array", "items": {"type": "object", "properties": {"src": {"type": "string", "pattern": "^((?!/\\.+/)(/[\\w\\$\\.\\-]+))*$", "description": "Path being aliased from, relative to CDN root."}, "dest": {"type": "string", "pattern": "^((?!/\\.+/)(/[\\w\\$\\.\\-]+))*$", "description": "Target of the alias, relative to CDN root."}, "exclude_paths": {"type": "array", "items": {"type": "string"}, "description": "Paths for which alias will not be resolved, treated as an unanchored regex."}}}, "uniqueItems": true, "description": "Aliases relating to $releasever variables."}, "rhui_alias": {"type": "array", "items": {"type": "object", "properties": {"src": {"type": "string", "pattern": "^((?!/\\.+/)(/[\\w\\$\\.\\-]+))*$", "description": "Path being aliased from, relative to CDN root."}, "dest": {"type": "string", "pattern": "^((?!/\\.+/)(/[\\w\\$\\.\\-]+))*$", "description": "Target of the alias, relative to CDN root."}, "exclude_paths": {"type": "array", "items": {"type": "string"}, "description": "Paths for which alias will not be resolved, treated as an unanchored regex."}}}, "uniqueItems": true, "description": "Aliases relating to RHUI."}}, "required": ["listing", "origin_alias", "releasever_alias", "rhui_alias"], "additionalProperties": false}}}, "description": "Configuration data for the CDN. Will replace the previously deployed configuration."}, "responses": {"200": {"description": "Deployment enqueued", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Task"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "get": {"tags": ["config"], "summary": "Get CDN configuration", "description": "Retrieves current CDN configuration data for use by Exodus components.\n\n**Required roles**: `{env}-config-consumer`", "operationId": "config_get__env__config_get", "parameters": [{"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Config"}}}, "listing": {"/content/dist/rhel/server": {"values": ["7"], "var": "releasever"}}, "origin_alias": [{"src": "/content/origin", "dest": "/origin"}, {"src": "/origin/rpm", "dest": "/origin/rpms"}], "releasever_alias": [{"dest": "/content/dist/rhel-alt/server/7/7.9", "src": "/content/dist/rhel-alt/server/7/7Server"}], "rhui_alias": [{"dest": "/content/dist/rhel8", "src": "/content/dist/rhel8/rhui"}]}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}}, "components": {"schemas": {"AccessResponse": {"properties": {"url": {"type": "string", "title": "Url", "description": "Base URL of this CDN environment.", "examples": ["https://abc123.cloudfront.net"]}, "expires": {"type": "string", "title": "Expires", "description": "Expiration time of access information included in this response. ISO8601 UTC timestamp.", "examples": ["2024-04-18T05:30Z"]}, "cookie": {"type": "string", "title": "Cookie", "description": "A cookie granting access to this CDN environment.", "examples": ["CloudFront-Key-Pair-Id=K2266GIXCH; CloudFront-Policy=eyJTdGF0ZW1lbn...; CloudFront-Signature=kGkxpnrY9h..."]}}, "type": "object", "required": ["url", "expires", "cookie"], "title": "AccessResponse"}, "Alias": {"properties": {"src": {"type": "string", "title": "Src", "description": "Path being aliased from, relative to CDN root."}, "dest": {"type": "string", "title": "Dest", "description": "Target of the alias, relative to CDN root."}, "exclude_paths": {"anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "null"}], "title": "Exclude Paths", "description": "Paths for which alias will not be resolved, treated as an unanchored regex.", "default": []}}, "type": "object", "required": ["src", "dest"], "title": "Alias"}, "CallContext": {"properties": {"client": {"$ref": "#/components/schemas/ClientContext", "default": {"roles": [], "authenticated": false}}, "user": {"$ref": "#/components/schemas/UserContext", "default": {"roles": [], "authenticated": false}}}, "type": "object", "title": "CallContext", "description": "Represents an authenticated (or not) context for an incoming request.\n\nUse the fields on this model to decide whether the current request belongs\nto an authenticated user, and if so, to determine which role(s) are held\nby the user."}, "ClientContext": {"properties": {"roles": {"items": {"type": "string"}, "type": "array", "title": "Roles", "default": []}, "authenticated": {"type": "boolean", "title": "Authenticated", "default": false}, "serviceAccountId": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Serviceaccountid"}}, "type": "object", "title": "ClientContext", "description": "Call context data relating to service accounts / machine users."}, "CommitModes": {"type": "string", "enum": ["phase1", "phase2"], "title": "CommitModes"}, "Config": {"properties": {"listing": {"additionalProperties": {"$ref": "#/components/schemas/ListingItem"}, "type": "object", "title": "Listing", "description": "A mapping from paths to a yum variable name & list of values, used in generating 'listing' responses."}, "origin_alias": {"items": {"$ref": "#/components/schemas/Alias"}, "type": "array", "title": "Origin Alias", "description": "Aliases relating to /origin."}, "releasever_alias": {"items": {"$ref": "#/components/schemas/Alias"}, "type": "array", "title": "Releasever Alias", "description": "Aliases relating to $releasever variables."}, "rhui_alias": {"items": {"$ref": "#/components/schemas/Alias"}, "type": "array", "title": "Rhui Alias", "description": "Aliases relating to RHUI."}}, "type": "object", "required": ["listing", "origin_alias", "releasever_alias", "rhui_alias"], "title": "Config"}, "EmptyResponse": {"properties": {}, "type": "object", "title": "EmptyResponse", "description": "An empty object."}, "FlushItem": {"properties": {"web_uri": {"type": "string", "title": "Web Uri", "description": "URI, relative to CDN root, of which to flush cache"}}, "type": "object", "required": ["web_uri"], "title": "FlushItem"}, "HTTPValidationError": {"properties": {"detail": {"items": {"$ref": "#/components/schemas/ValidationError"}, "type": "array", "title": "Detail"}}, "type": "object", "title": "HTTPValidationError"}, "Item": {"properties": {"web_uri": {"type": "string", "title": "Web Uri", "description": "URI, relative to CDN root, which shall be used to expose this object."}, "object_key": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Object Key", "description": "Key of blob to be exposed; should be the SHA256 checksum of a previously uploaded piece of content, in lowercase hex-digest form. \n\nAlternatively, the string 'absent' to indicate that no content shall be exposed at the given URI. Publishing an item with key 'absent' can be used to effectively delete formerly published content from the point of view of a CDN consumer.", "default": ""}, "content_type": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Content Type", "description": "Content type of the content associated with this object.", "default": ""}, "link_to": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Link To", "description": "Path of file targeted by symlink.", "default": ""}, "publish_id": {"type": "string", "format": "uuid", "title": "Publish Id", "description": "Unique ID of publish object containing this item."}}, "type": "object", "required": ["web_uri", "publish_id"], "title": "Item"}, "ItemBase": {"properties": {"web_uri": {"type": "string", "title": "Web Uri", "description": "URI, relative to CDN root, which shall be used to expose this object."}, "object_key": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Object Key", "description": "Key of blob to be exposed; should be the SHA256 checksum of a previously uploaded piece of content, in lowercase hex-digest form. \n\nAlternatively, the string 'absent' to indicate that no content shall be exposed at the given URI. Publishing an item with key 'absent' can be used to effectively delete formerly published content from the point of view of a CDN consumer.", "default": ""}, "content_type": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Content Type", "description": "Content type of the content associated with this object.", "default": ""}, "link_to": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Link To", "description": "Path of file targeted by symlink.", "default": ""}}, "type": "object", "required": ["web_uri"], "title": "ItemBase"}, "ListingItem": {"properties": {"var": {"$ref": "#/components/schemas/YumVariable", "description": "YUM variable name."}, "values": {"items": {"type": "string"}, "type": "array", "title": "Values", "description": "Allowed values for YUM variable replacement."}}, "type": "object", "required": ["var", "values"], "title": "ListingItem"}, "MessageResponse": {"properties": {"detail": {"type": "string", "title": "Detail", "description": "A human-readable message with additional info."}}, "type": "object", "required": ["detail"], "title": "MessageResponse"}, "Publish": {"properties": {"id": {"type": "string", "title": "Id", "description": "Unique ID of publish object."}, "env": {"type": "string", "title": "Env", "description": "Environment to which this publish belongs."}, "state": {"$ref": "#/components/schemas/PublishStates", "description": "Current state of this publish."}, "updated": {"anyOf": [{"type": "string", "format": "date-time"}, {"type": "null"}], "title": "Updated", "description": "DateTime of last update to this publish. None if never updated."}, "links": {"additionalProperties": {"type": "string"}, "type": "object", "title": "Links", "description": "URL links related to this publish.", "default": {}}, "items": {"items": {"$ref": "#/components/schemas/Item"}, "type": "array", "title": "Items", "description": "All items (pieces of content) included in this publish.", "default": []}}, "type": "object", "required": ["id", "env", "state"], "title": "Publish"}, "PublishStates": {"type": "string", "enum": ["PENDING", "COMMITTING", "COMMITTED", "FAILED"], "title": "PublishStates"}, "Task": {"properties": {"id": {"type": "string", "format": "uuid", "title": "Id", "description": "Unique ID of task object."}, "publish_id": {"anyOf": [{"type": "string", "format": "uuid"}, {"type": "null"}], "title": "Publish Id", "description": "Unique ID of publish object related to this task, if any."}, "state": {"$ref": "#/components/schemas/TaskStates", "description": "Current state of this task."}, "updated": {"anyOf": [{"type": "string", "format": "date-time"}, {"type": "null"}], "title": "Updated", "description": "DateTime of last update to this task. None if never updated.", "examples": ["2019-08-24T14:15:22Z"]}, "deadline": {"anyOf": [{"type": "string", "format": "date-time"}, {"type": "null"}], "title": "Deadline", "description": "DateTime at which this task should be abandoned.", "examples": ["2019-08-24T18:15:22Z"]}, "links": {"additionalProperties": {"type": "string"}, "type": "object", "title": "Links", "description": "URL links related to this task.", "default": {}, "examples": [{"self": "/task/497f6eca-6276-4993-bfeb-53cbbbba6f08"}]}}, "type": "object", "required": ["id", "state"], "title": "Task"}, "TaskStates": {"type": "string", "enum": ["NOT_STARTED", "IN_PROGRESS", "COMPLETE", "FAILED"], "title": "TaskStates"}, "UserContext": {"properties": {"roles": {"items": {"type": "string"}, "type": "array", "title": "Roles", "default": []}, "authenticated": {"type": "boolean", "title": "Authenticated", "default": false}, "internalUsername": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Internalusername"}}, "type": "object", "title": "UserContext", "description": "Call context data relating to human users."}, "ValidationError": {"properties": {"loc": {"items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, "type": "array", "title": "Location"}, "msg": {"type": "string", "title": "Message"}, "type": {"type": "string", "title": "Error Type"}}, "type": "object", "required": ["loc", "msg", "type"], "title": "ValidationError"}, "YumVariable": {"type": "string", "enum": ["releasever", "basearch"], "title": "YumVariable"}}}, "tags": [{"name": "service", "description": "APIs for inspecting the state of the exodus-gw service."}, {"name": "upload", "description": "An API for uploading binary data.\n\nThis API provides endpoints for uploading files into the data store\nused by the Exodus CDN. Uploading files does not immediately expose\nthem to clients of the CDN, but is a prerequisite of publishing files,\nwhich is achieved via the [publish](#tag/publish) APIs.\n\nThe upload API is a partially compatible subset of the S3 API.\nIt supports at least enough functionality such that the AWS SDK may be\nused when uploading to exodus-gw.\n\nDifferences from the AWS S3 API include:\n\n- Most optional arguments are not supported.\n\n- All `x-amz-*` headers, other than `x-amz-meta-*`, are omitted from responses.\n\n- The usual AWS authentication mechanism is unused; request signatures are ignored.\n Authentication is expected to be performed by other means.\n\n- Object keys should always be SHA256 checksums of the objects being stored,\n in lowercase hex digest form. This allows the object store to be used\n as content-addressable storage.\n\n- When uploading content, the Content-MD5 and Content-Length headers are mandatory;\n chunked encoding is not supported.\n\n- The API may enforce stricter limits or policies on uploads than those imposed\n by the AWS API.\n\n## Using boto3 with the upload API\n\nAs the upload API is partially compatible with S3, it is possible to use\nexisting S3 clients such as the AWS SDK to perform uploads. This is the\nrecommended method of using the API. Here we show an example using boto, the\nAWS SDK for Python.\n\nUse `endpoint_url` when creating a boto resource or client to point at exodus-gw.\nRegion and credentials will be ignored.\n\nNote that, as the upload API provides only a subset of the S3 API, many boto methods\nwill not work. Uploading objects and querying the existence of an object are\nsupported.\n\n```python\nimport boto3\nfrom botocore.config import Config\n\n# Prepare S3 resource pointing at exodus-gw\ns3 = boto3.resource('s3',\n endpoint_url='https://exodus-gw.example.com/upload',\n # In a typical setup using PKI auth, these values are not used during\n # auth to exodus-gw, but boto client always insists on having *some* keys.\n # Dummy values can be provided to prevent boto looking for credentials\n # in config files.\n aws_access_key_id=\"dummy\",\n aws_secret_access_key=\"dummy\",\n # If SSL needs to be configured:\n verify='/path/to/bundle.pem',\n config=Config(client_cert=('client.crt', 'client.key')))\n\n# Bucket name must match one of the environment names\nbucket = s3.Bucket('live')\n\n# Basic APIs such as upload_file now work as usual\nbucket.upload_file('/tmp/hello.txt',\n 'aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f')\n```\n"}, {"name": "publish", "description": "APIs for publishing blobs.\n\nIn the context of exodus-gw, \"publishing\" a blob means exposing it\nvia one or more user-accessible paths on the CDN. These blobs should first\nbe uploaded using the [upload](#tag/upload) APIs.\n\n\n## Atomicity\n\nexodus-gw aims to enable atomic semantics for publishes; i.e., for a set\nof published content, committing the publish will make either *all* of it\navailable (if commit succeeds) or *none* of it available (if commit fails),\nwith no partial updates becoming visible from the point of view of a CDN\nclient.\n\nIn practice, limitations in the storage backend mean that true atomicity\nis not achieved for all but the smallest publishes, so it is more accurate\nto refer to the commit operation as \"near-atomic\".\n\nHere are a few of the strategies used by exodus-gw in order to achieve as\nclose as possible to atomic behavior:\n\n- exodus-gw performs writes to the underlying database (DynamoDB) in batches,\n which themselves are committed atomically.\n\n - (True atomicity could be achieved if the entire publish fits within\n a single batch, but as batches have a maximum size of 25 items, this\n is rarely the case.)\n\n- All operations are aggressively retried in case of error.\n\n- exodus-gw keeps track of what has been committed and is able to roll\n back a partially committed publish in case of unrecoverable errors.\n\n- During commit, the items to be committed are prioritized intelligently\n with knowledge of the types of content being published. Files which serve\n as an index or entry point to a set of content are committed last, to ensure\n minimal impact in the case that a commit is interrupted.\n See \"two-phase commit\" below for a more in-depth explanation of this.\n\n## Two-phase commit\n\nAll published content is categorized into two phases, phase 1 and phase 2,\nand committed in that order. exodus-gw performs this categorization internally\nand clients cannot influence this.\n\nSimple clients do not need to worry about this, but in more complicated scenarios\nthe client may wish to control the commit of each phase independently. In such\ncases it is important to understand how the two phases are intended to work.\n\nPhase 1 content:\n\n- includes the majority of content within a publish\n- should be immutable\n- is usually not discoverable by CDN users without consulting some form of index\n- examples: RPM files within a yum repo; any generic file\n\nPhase 2 content:\n\n- includes a small minority of content within a publish\n- is usually mutable, perhaps changing at every publish\n- contains indexes, repository entry points or other references pointing at\n phase 1 content (and thus must be committed last)\n- examples: `repodata/repomd.xml` within a yum repo; `PULP_MANIFEST` within a\n Pulp file repository\n\nAs an example of this phased approach, consider the publish of a yum repository.\nA client consuming packages from a yum repository discovers available packages\nvia a series of fetches involving multiple files which are published together,\ne.g.\n\n`repodata/repomd.xml` => `repodata/-primary.xml.gz`\n => `Packages/.rpm`\n\nIf no ordering were to be applied to the publish of these files it would be\npossible for `repomd.xml` to be published prior to `-primary.xml.gz`,\nor for `-primary.xml.gz` to be published prior to\n`Packages/.rpm`, either of which could cause a CDN consumer to\nattempt to fetch content which has not yet been published, resulting in 404\nerrors.\n\nThis problem is avoided by exodus-gw internally categorizing `repomd.xml` as\nphase 2 content and ensuring it is committed only after the rest of the files\nin the repo, which are categorized as phase 1 content.\n\n## Expiry of publish objects\n\nPublish objects should be treated as ephemeral; they are not persisted indefinitely.\n\n- All publish objects which have reached a terminal state (failed or committed) will be\n deleted after some server-defined timeout, defaulting to two weeks.\n- Publish objects which have been created but not committed within a server-defined timeout,\n typically one day, will be marked as failed.\n\n\n## Expiry of task objects\n\nLike publish objects, task objects created when publishes are committed are not persisted\nindefinitely.\n\n- All task objects which have reached a terminal state (failed or complete) will be\n deleted after some server-defined timeout, defaulting to two weeks.\n- Task objects not picked up by a worker within a server-defined time limit, defaulting\n to two hours, will be marked as failed along with the associated publish object. This\n prevents system overload in the event of a worker outage.\n"}, {"name": "deploy", "description": "APIs for adjusting configuration used by the CDN."}, {"name": "cdn", "description": "Utilities for accessing the Exodus CDN."}, {"name": "config", "description": "APIs for retrieving and deploying configuration used by the CDN."}]} \ No newline at end of file +{"openapi": "3.1.0", "info": {"title": "exodus-gw", "description": "The exodus-gw service provides APIs for uploading and publishing content on the exodus CDN.\n\n\n\nAvailable APIs are grouped into the following categories:\n\n- [service](#tag/service): inspect the state of the exodus-gw service.\n- [upload](#tag/upload): upload blobs to the CDN without exposing them to end-users.\n S3 compatible.\n- [publish](#tag/publish): atomically publish a set of blobs on the CDN under specified paths,\n making them accessible to end-users.\n- [deploy](#tag/deploy): deploy configuration influencing the behavior of the CDN.\n- [cdn](#tag/cdn): utilities for accessing the CDN.\n\n## Overview of API usage\n\nA typical content publishing workflow using exodus-gw will consist of:\n\n- Use the upload APIs to ensure desired blobs are uploaded.\n - As this API is partially S3-compatible, this can typically be done using\n an existing S3 client library.\n- Use the publish API to create a publish object and create a (URI => blob)\n mapping for the blobs you want to publish.\n- When you are ready to expose content to end-users, commit the publish object.\n This will atomically unveil new content at all of the requested URIs.\n\n\n## Authentication\n\n\nThe exodus-gw API does not include any direct support for authentication and is\ninstead expected to be deployed behind a reverse-proxy implementing any desired\nauthentication mechanism.\n\nIf you are deploying an instance of exodus-gw, see\n[the deployment guide](https://release-engineering.github.io/exodus-gw/deployment.html)\nfor information on how to integrate an authentication mechanism.\n\nIf you are a client looking to make use of exodus-gw, consult your organization's\ninternal documentation for advice on how to authenticate with exodus-gw.\n\n\n## Environments\n\nMany APIs in exodus-gw use the concept of an \"environment\" to control the target system\nof an operation.\n\n\nThe set of environments is configured when exodus-gw is deployed.\nA typical scenario is to deploy a \"pre\" environment for pre-release content and a\n\"live\" environment for live content.\n\nDifferent environments will also require the user to hold different roles. For example,\na client might be permitted only to write to one of the configured environments, or all\nof them, depending on the configuration of the server.\n\nIf you are deploying an instance of exodus-gw, see\n[the deployment guide](https://release-engineering.github.io/exodus-gw/deployment.html)\nfor information on how to configure environments.\n\nIf you are a client looking to make use of exodus-gw, consult your organization's\ninternal documentation for advice on which environment(s) you should be using.\n\n", "version": ""}, "paths": {"/healthcheck": {"get": {"tags": ["service"], "summary": "Healthcheck", "description": "Returns a successful response if the service is running.", "operationId": "healthcheck_healthcheck_get", "responses": {"200": {"description": "Service is up", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageResponse"}}}}}}}, "/healthcheck-worker": {"get": {"tags": ["service"], "summary": "Healthcheck Worker", "description": "Returns a successful response if background workers are running.", "operationId": "healthcheck_worker_healthcheck_worker_get", "responses": {"200": {"description": "Worker(s) are responding", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageResponse"}}}}}}}, "/whoami": {"get": {"tags": ["service"], "summary": "Whoami", "description": "Return basic information on the caller's authentication & authorization context.\n\nThis endpoint may be used to determine whether the caller is authenticated to\nthe exodus-gw service, and if so, which set of role(s) are held by the caller.\n\nIt is a read-only endpoint intended for diagnosing authentication issues.", "operationId": "whoami_whoami_get", "responses": {"200": {"description": "Caller's auth context retrieved", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CallContext"}, "examples": [{"value": {"client": {"roles": ["someRole", "anotherRole"], "authenticated": true, "serviceAccountId": "clientappname"}, "user": {"roles": ["viewer"], "authenticated": true, "internalUsername": "someuser"}}}]}}}}}}, "/task/{task_id}": {"get": {"tags": ["service"], "summary": "Get Task", "description": "Return existing task object from database using given task ID.", "operationId": "get_task_task__task_id__get", "parameters": [{"name": "task_id", "in": "path", "required": true, "schema": {"type": "string", "title": "task ID", "description": "UUID of an existing task object."}, "description": "UUID of an existing task object."}], "responses": {"200": {"description": "Sucessfully retrieved task", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Task"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/upload/{env}/{key}": {"post": {"tags": ["upload"], "summary": "Create/complete multipart upload", "description": "Create or complete a multi-part upload.\n\n**Required roles**: `{env}-blob-uploader`\n\nTo create a multi-part upload:\n- include ``uploads`` in query string, with no value (e.g. ``POST /upload/{env}/{key}?uploads``)\n- see also: https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateMultipartUpload.html\n\nTo complete a multi-part upload:\n- include ``uploadId`` in query string\n- include parts with ETags in request body\n- see also: https://docs.aws.amazon.com/AmazonS3/latest/API/API_CompleteMultipartUpload.html", "operationId": "multipart_upload_upload__env___key__post", "parameters": [{"name": "key", "in": "path", "required": true, "schema": {"type": "string", "description": "S3 object key", "title": "Key"}, "description": "S3 object key"}, {"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}, {"name": "uploadId", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "\nID of an existing multi-part upload.\n\nIf this argument is provided, it must be the ID of a multi-part upload\ncreated previously. The upload will be validated and completed.\n\nMust not be passed together with ``uploads``.", "title": "Uploadid"}, "description": "\nID of an existing multi-part upload.\n\nIf this argument is provided, it must be the ID of a multi-part upload\ncreated previously. The upload will be validated and completed.\n\nMust not be passed together with ``uploads``."}, {"name": "uploads", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "\nIf this argument is provided, a new multi-part upload will be created\nand its ID returned. The provided value should be an empty string.\n\nMust not be passed together with ``uploadId``.", "title": "Uploads"}, "description": "\nIf this argument is provided, a new multi-part upload will be created\nand its ID returned. The provided value should be an empty string.\n\nMust not be passed together with ``uploadId``."}], "responses": {"200": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "put": {"tags": ["upload"], "summary": "Upload bytes", "description": "Write to an object, either as a standalone operation or within a multi-part upload.\n\n**Required roles**: `{env}-blob-uploader`\n\nTo upload an entire object:\n- include all object bytes in request body\n- see also: https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html\n\nTo upload part of an object:\n- provide multipart upload ID in ``uploadId``\n- provide part number from 1 to 10,000 in ``partNumber``\n- include part of an object in request body (must be at least 5MB in size, except last part)\n- retain the `ETag` from the response, as it will be required to complete the upload\n- see also: https://docs.aws.amazon.com/AmazonS3/latest/API/API_UploadPart.html", "operationId": "upload_upload__env___key__put", "parameters": [{"name": "key", "in": "path", "required": true, "schema": {"type": "string", "description": "S3 object key", "title": "Key"}, "description": "S3 object key"}, {"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}, {"name": "uploadId", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "ID of an existing multi-part upload.", "title": "Uploadid"}, "description": "ID of an existing multi-part upload."}, {"name": "partNumber", "in": "query", "required": false, "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "description": "Part number, where multi-part upload is used.", "title": "Partnumber"}, "description": "Part number, where multi-part upload is used."}], "responses": {"200": {"description": "Successful Response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "delete": {"tags": ["upload"], "summary": "Abort multipart upload", "description": "Abort a multipart upload.\n\n**Required roles**: `{env}-blob-uploader`\n\nIf an upload cannot be completed, explicitly aborting it is recommended in order\nto free up resources as early as possible, although this is not mandatory.\n\nSee also: https://docs.aws.amazon.com/AmazonS3/latest/API/API_AbortMultipartUpload.html", "operationId": "abort_multipart_upload_upload__env___key__delete", "parameters": [{"name": "key", "in": "path", "required": true, "schema": {"type": "string", "description": "S3 object key", "title": "Key"}, "description": "S3 object key"}, {"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}, {"name": "uploadId", "in": "query", "required": true, "schema": {"type": "string", "description": "ID of a multipart upload", "title": "Uploadid"}, "description": "ID of a multipart upload"}], "responses": {"200": {"description": "Empty response"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "head": {"tags": ["upload"], "summary": "Get object metadata", "description": "Retrieve metadata from an S3 object.\n\nNote that, as explained in [the upload API overview](#tag/upload), AWS-specific\nheaders such as `x-amz-*` are not included in the response.\nThe main purpose of this API is to determine whether or not an object\nidentified by checksum exists on the CDN.\n\n**Required roles**: `{env}-blob-uploader`", "operationId": "head_upload__env___key__head", "parameters": [{"name": "key", "in": "path", "required": true, "schema": {"type": "string", "description": "S3 object key", "title": "Key"}, "description": "S3 object key"}, {"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}], "responses": {"200": {"description": "Object exists"}, "404": {"description": "Object or environment does not exist"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/{env}/publish": {"post": {"tags": ["publish"], "summary": "Create new publish", "description": "Creates and returns a new publish object.\n\n**Required roles**: `{env}-publisher`", "operationId": "publish__env__publish_post", "parameters": [{"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}], "responses": {"200": {"description": "Publish created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Publish"}, "examples": [{"value": {"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "env": "live", "links": {"self": "/live/publish/497f6eca-6276-4993-bfeb-53cbbbba6f08", "commit": "/live/publish/497f6eca-6276-4993-bfeb-53cbbbba6f08/commit"}, "items": []}}]}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/{env}/publish/{publish_id}": {"put": {"tags": ["publish"], "summary": "Update Publish Items", "description": "Add publish items to an existing publish object.\n\n**Required roles**: `{env}-publisher`\n\nPublish items primarily are a mapping between a URI relative to the root of the CDN,\nand the key of a binary object which should be exposed from that URI.\n\nAdding items to a publish does not immediately make them available from the CDN;\nthe publish object must first be committed.\n\nItems cannot be added to a publish once it has been committed.", "operationId": "update_publish_items__env__publish__publish_id__put", "parameters": [{"name": "publish_id", "in": "path", "required": true, "schema": {"type": "string", "title": "publish ID", "description": "UUID of an existing publish object."}, "description": "UUID of an existing publish object."}, {"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "array", "items": {"$ref": "#/components/schemas/ItemBase"}, "examples": [[{"web_uri": "/my/awesome/file.iso", "object_key": "aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f", "content_type": "application/octet-stream"}, {"web_uri": "/my/slightly-less-awesome/other-file.iso", "object_key": "c06545d4e1a1c8e221d47e7d568c035fb32c6b6124881fd0bc17983bd9088ae0", "content_type": "application/octet-stream"}, {"web_uri": "/another/route/to/my/awesome/file.iso", "link_to": "/my/awesome/file.iso"}, {"web_uri": "/my/awesome/deletion.iso", "object_key": "absent"}]], "title": "Items"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/EmptyResponse"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "get": {"tags": ["publish"], "summary": "Get Publish", "description": "Return an existing publish object from database using the given publish ID.\n\nFor performance reasons, the returned item list is always empty.", "operationId": "get_publish__env__publish__publish_id__get", "parameters": [{"name": "publish_id", "in": "path", "required": true, "schema": {"type": "string", "title": "publish ID", "description": "UUID of an existing publish object."}, "description": "UUID of an existing publish object."}, {"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}], "responses": {"200": {"description": "Publish found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Publish"}, "examples": [{"value": {"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "env": "live", "links": {"self": "/live/publish/497f6eca-6276-4993-bfeb-53cbbbba6f08", "commit": "/live/publish/497f6eca-6276-4993-bfeb-53cbbbba6f08/commit"}, "items": []}}]}}}, "404": {"description": "Publish not found", "content": "No publish found for ID 497f6eca-6276-4993-bfeb-53cbbbba6f08"}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/{env}/publish/{publish_id}/commit": {"post": {"tags": ["publish"], "summary": "Commit Publish", "description": "Commit an existing publish object.\n\n**Required roles**: `{env}-publisher`\n\nCommitting a publish is required in order to expose published content from the CDN.\n\nThere are two available commit modes, \"phase1\" and \"phase2\" (default).\n\n### Phase 1\n\nA phase 1 commit:\n\n- is optional.\n- can be performed more than once.\n- does not prevent further modifications to the publish.\n- will commit all phase 1 content (e.g. packages in yum repos), but not phase 2\n content (e.g. repodata in yum repos); see\n [Two-phase commit](#section/Two-phase-commit).\n- is not rolled back if a later phase 2 commit fails (or never occurs).\n\n### Phase 2\n\nA phase 2 commit:\n\n- is the default when no commit mode is specified.\n- can (and should) be performed exactly once.\n- freezes the associated publish object - no further items can be added.\n- will commit all content with near-atomic behavior; see\n [Atomicity](#section/Atomicity).\n\n### Notes\n\nCommit occurs asynchronously. This API returns a Task object which may be used\nto monitor the progress of the commit.\n\nNote that exodus-gw does not require that any given path is only modified by\na single publish. If multiple publish objects updating the same path are being\ncommitted at the same time, after both commits succeed, the path will point\nat whichever item was updated most recently.", "operationId": "commit_publish__env__publish__publish_id__commit_post", "parameters": [{"name": "publish_id", "in": "path", "required": true, "schema": {"type": "string", "title": "publish ID", "description": "UUID of an existing publish object."}, "description": "UUID of an existing publish object."}, {"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}, {"name": "commit_mode", "in": "query", "required": false, "schema": {"anyOf": [{"$ref": "#/components/schemas/CommitModes"}, {"type": "null"}], "title": "commit mode", "description": "See: [Two-phase commit](#section/Two-phase-commit)", "examples": ["phase1", "phase2"]}, "description": "See: [Two-phase commit](#section/Two-phase-commit)"}, {"name": "deadline", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "A timestamp by which this task may be abandoned if not completed.\n\nWhen omitted, a server default will apply.", "examples": ["2022-07-25T15:47:47Z"], "title": "Deadline"}, "description": "A timestamp by which this task may be abandoned if not completed.\n\nWhen omitted, a server default will apply."}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Task"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/{env}/deploy-config": {"post": {"tags": ["deploy"], "summary": "Deploy Config", "description": "Deploys CDN configuration data for use by Exodus components.\n\n**Required roles**: `{env}-config-deployer`\n\nDeployment occurs asynchronously. This API returns a Task object\nwhich may be used to monitor the progress of the deployment.\n\nThis endpoint is deprecated, use /{env}/config instead.", "operationId": "deploy_config__env__deploy_config_post", "deprecated": true, "parameters": [{"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "examples": [{"listing": {"/content/dist/rhel8": {"var": "releasever", "values": ["8", "8.0", "8.1", "8.2", "8.3", "8.4", "8.5"]}}, "origin_alias": [{"src": "/content/origin", "dest": "/origin", "exclude_paths": []}, {"src": "/origin/rpm", "dest": "/origin/rpms", "exclude_paths": ["/iso/"]}], "releasever_alias": [{"dest": "/content/dist/rhel8/8.5", "src": "/content/dist/rhel8/8", "exclude_paths": ["/files/", "/iso/"]}], "rhui_alias": [{"dest": "/content/dist/rhel8", "src": "/content/dist/rhel8/rhui", "exclude_paths": ["/files/", "/iso/"]}]}], "title": "Config", "properties": {"listing": {"type": "object", "description": "A mapping from paths to a yum variable name & list of values, used in generating 'listing' responses.", "patternProperties": {"^((?!/\\.+/)(/[\\w\\$\\.\\-]+))*$": {"type": "object", "properties": {"var": {"type": "string", "enum": ["releasever", "basearch"]}, "values": {"type": "array", "items": {"type": "string", "minLength": 1}, "uniqueItems": true}}}}, "additionalProperties": false}, "origin_alias": {"type": "array", "items": {"type": "object", "properties": {"src": {"type": "string", "pattern": "^((?!/\\.+/)(/[\\w\\$\\.\\-]+))*$", "description": "Path being aliased from, relative to CDN root."}, "dest": {"type": "string", "pattern": "^((?!/\\.+/)(/[\\w\\$\\.\\-]+))*$", "description": "Target of the alias, relative to CDN root."}, "exclude_paths": {"type": "array", "items": {"type": "string"}, "description": "Paths for which alias will not be resolved, treated as an unanchored regex."}}}, "uniqueItems": true, "description": "Aliases relating to /origin."}, "releasever_alias": {"type": "array", "items": {"type": "object", "properties": {"src": {"type": "string", "pattern": "^((?!/\\.+/)(/[\\w\\$\\.\\-]+))*$", "description": "Path being aliased from, relative to CDN root."}, "dest": {"type": "string", "pattern": "^((?!/\\.+/)(/[\\w\\$\\.\\-]+))*$", "description": "Target of the alias, relative to CDN root."}, "exclude_paths": {"type": "array", "items": {"type": "string"}, "description": "Paths for which alias will not be resolved, treated as an unanchored regex."}}}, "uniqueItems": true, "description": "Aliases relating to $releasever variables."}, "rhui_alias": {"type": "array", "items": {"type": "object", "properties": {"src": {"type": "string", "pattern": "^((?!/\\.+/)(/[\\w\\$\\.\\-]+))*$", "description": "Path being aliased from, relative to CDN root."}, "dest": {"type": "string", "pattern": "^((?!/\\.+/)(/[\\w\\$\\.\\-]+))*$", "description": "Target of the alias, relative to CDN root."}, "exclude_paths": {"type": "array", "items": {"type": "string"}, "description": "Paths for which alias will not be resolved, treated as an unanchored regex."}}}, "uniqueItems": true, "description": "Aliases relating to RHUI."}}, "required": ["listing", "origin_alias", "releasever_alias", "rhui_alias"], "additionalProperties": false}}}, "description": "Configuration data for the CDN. Will replace the previously deployed configuration."}, "responses": {"200": {"description": "Deployment enqueued", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Task"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/{env}/cdn/{url}": {"get": {"tags": ["cdn"], "summary": "Redirect (GET)", "description": "Redirects to a requested URL on the CDN.\n\nThe CDN requires a signature from an authorized signer in order to permit\nrequests. When using this endpoint, exodus-gw acts as an authorized signer\non the caller's behalf, thus allowing any exodus-gw client to access CDN\ncontent without holding the signing keys.\n\nThe URL used in the redirect will become invalid after a server-defined\ntimeout, typically less than one hour.", "operationId": "cdn_redirect__env__cdn__url__get", "parameters": [{"name": "url", "in": "path", "required": true, "schema": {"type": "string", "title": "URL", "description": "URL of a piece of content relative to CDN root", "examples": ["content/dist/rhel8/8/x86_64/baseos/os/repodata/repomd.xml"]}, "description": "URL of a piece of content relative to CDN root"}, {"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}], "responses": {"302": {"description": "Redirect", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/EmptyResponse"}}}, "headers": {"location": {"description": "An absolute, signed, temporary URL of CDN content"}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "head": {"tags": ["cdn"], "summary": "Redirect (HEAD)", "description": "Identical to GET redirect, but for HEAD method.", "operationId": "cdn_redirect__env__cdn__url__head", "parameters": [{"name": "url", "in": "path", "required": true, "schema": {"type": "string", "title": "URL", "description": "URL of a piece of content relative to CDN root", "examples": ["content/dist/rhel8/8/x86_64/baseos/os/repodata/repomd.xml"]}, "description": "URL of a piece of content relative to CDN root"}, {"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}], "responses": {"302": {"description": "Redirect", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/EmptyResponse"}}}, "headers": {"location": {"description": "An absolute, signed, temporary URL of CDN content"}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/{env}/cdn-access": {"get": {"tags": ["cdn"], "summary": "Access", "description": "Obtain signed cookies and other information needed for accessing\na specific CDN environment.\n\nThis endpoint may be used to look up the CDN origin server belonging\nto a particular environment and to obtain long-term signed cookies\nauthorizing requests to that environment. The cookies returned by\nthis endpoint should be treated as a secret.\n\n**Required roles**: `{env}-cdn-consumer`", "operationId": "cdn_access__env__cdn_access_get", "parameters": [{"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}, {"name": "expire_days", "in": "query", "required": false, "schema": {"type": "integer", "description": "Desired expiration time, in days, for generated signatures.\n\nIt is mandatory to provide a value.\n\nCannot exceed a maximum value configured by the server (typically 365 days).", "examples": [30], "default": -1, "title": "Expire Days"}, "description": "Desired expiration time, in days, for generated signatures.\n\nIt is mandatory to provide a value.\n\nCannot exceed a maximum value configured by the server (typically 365 days)."}, {"name": "resource", "in": "query", "required": false, "schema": {"type": "string", "description": "Desired resource to access. If included, must begin with '/'. Defaults to '/*', providing access to the entire CloudFront distribution.", "examples": ["/content/dist/rhel8/8.2/x86_64/baseos/iso/PULP_MANIFEST"], "default": "/*", "title": "Resource"}, "description": "Desired resource to access. If included, must begin with '/'. Defaults to '/*', providing access to the entire CloudFront distribution."}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AccessResponse"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/{env}/cdn-flush": {"post": {"tags": ["cdn"], "summary": "Flush cache", "description": "Flush given paths from CDN cache(s) corresponding to this environment.\n\nThis API may be used to request CDN edge servers downstream from exodus-gw\nand exodus-cdn to discard cached versions of content, ensuring that\nsubsequent requests will receive up-to-date content.\n\nThe API is provided for troubleshooting and for scenarios where it's\nknown that explicit cache flushes are needed. It's not necessary to use\nthis API during a typical upload and publish workflow.\n\nReturns a task. Successful completion of the task indicates that CDN\ncaches have been flushed.\n\n**Required roles**: `{env}-cdn-flusher`", "operationId": "flush_cdn_cache__env__cdn_flush_post", "parameters": [{"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}, {"name": "deadline", "in": "query", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "A timestamp by which this task may be abandoned if not completed.\n\nWhen omitted, a server default will apply.", "examples": ["2022-07-25T15:47:47Z"], "title": "Deadline"}, "description": "A timestamp by which this task may be abandoned if not completed.\n\nWhen omitted, a server default will apply."}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "array", "items": {"$ref": "#/components/schemas/FlushItem"}, "examples": [[{"web_uri": "/some/path/i/want/to/flush"}, {"web_uri": "/another/path/i/want/to/flush"}]], "title": "Items"}}}}, "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Task"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}, "/{env}/config": {"post": {"tags": ["config"], "summary": "Config Post", "description": "Deploys CDN configuration data for use by Exodus components.\n\n**Required roles**: `{env}-config-deployer`\n\nDeployment occurs asynchronously. This API returns a Task object\nwhich may be used to monitor the progress of the deployment.", "operationId": "config_post__env__config_post", "parameters": [{"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "examples": [{"listing": {"/content/dist/rhel8": {"var": "releasever", "values": ["8", "8.0", "8.1", "8.2", "8.3", "8.4", "8.5"]}}, "origin_alias": [{"src": "/content/origin", "dest": "/origin", "exclude_paths": []}, {"src": "/origin/rpm", "dest": "/origin/rpms", "exclude_paths": ["/iso/"]}], "releasever_alias": [{"dest": "/content/dist/rhel8/8.5", "src": "/content/dist/rhel8/8", "exclude_paths": ["/files/", "/iso/"]}], "rhui_alias": [{"dest": "/content/dist/rhel8", "src": "/content/dist/rhel8/rhui", "exclude_paths": ["/files/", "/iso/"]}]}], "title": "Config", "properties": {"listing": {"type": "object", "description": "A mapping from paths to a yum variable name & list of values, used in generating 'listing' responses.", "patternProperties": {"^((?!/\\.+/)(/[\\w\\$\\.\\-]+))*$": {"type": "object", "properties": {"var": {"type": "string", "enum": ["releasever", "basearch"]}, "values": {"type": "array", "items": {"type": "string", "minLength": 1}, "uniqueItems": true}}}}, "additionalProperties": false}, "origin_alias": {"type": "array", "items": {"type": "object", "properties": {"src": {"type": "string", "pattern": "^((?!/\\.+/)(/[\\w\\$\\.\\-]+))*$", "description": "Path being aliased from, relative to CDN root."}, "dest": {"type": "string", "pattern": "^((?!/\\.+/)(/[\\w\\$\\.\\-]+))*$", "description": "Target of the alias, relative to CDN root."}, "exclude_paths": {"type": "array", "items": {"type": "string"}, "description": "Paths for which alias will not be resolved, treated as an unanchored regex."}}}, "uniqueItems": true, "description": "Aliases relating to /origin."}, "releasever_alias": {"type": "array", "items": {"type": "object", "properties": {"src": {"type": "string", "pattern": "^((?!/\\.+/)(/[\\w\\$\\.\\-]+))*$", "description": "Path being aliased from, relative to CDN root."}, "dest": {"type": "string", "pattern": "^((?!/\\.+/)(/[\\w\\$\\.\\-]+))*$", "description": "Target of the alias, relative to CDN root."}, "exclude_paths": {"type": "array", "items": {"type": "string"}, "description": "Paths for which alias will not be resolved, treated as an unanchored regex."}}}, "uniqueItems": true, "description": "Aliases relating to $releasever variables."}, "rhui_alias": {"type": "array", "items": {"type": "object", "properties": {"src": {"type": "string", "pattern": "^((?!/\\.+/)(/[\\w\\$\\.\\-]+))*$", "description": "Path being aliased from, relative to CDN root."}, "dest": {"type": "string", "pattern": "^((?!/\\.+/)(/[\\w\\$\\.\\-]+))*$", "description": "Target of the alias, relative to CDN root."}, "exclude_paths": {"type": "array", "items": {"type": "string"}, "description": "Paths for which alias will not be resolved, treated as an unanchored regex."}}}, "uniqueItems": true, "description": "Aliases relating to RHUI."}}, "required": ["listing", "origin_alias", "releasever_alias", "rhui_alias"], "additionalProperties": false}}}, "description": "Configuration data for the CDN. Will replace the previously deployed configuration."}, "responses": {"200": {"description": "Deployment enqueued", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Task"}}}}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}, "get": {"tags": ["config"], "summary": "Get CDN configuration", "description": "Retrieves current CDN configuration data for use by Exodus components.\n\n**Required roles**: `{env}-config-consumer`", "operationId": "config_get__env__config_get", "parameters": [{"name": "env", "in": "path", "required": true, "schema": {"type": "string", "title": "environment", "description": "[Environment](#section/Environments) on which to operate."}, "description": "[Environment](#section/Environments) on which to operate."}], "responses": {"200": {"description": "Successful Response", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Config"}}}, "listing": {"/content/dist/rhel/server": {"values": ["7"], "var": "releasever"}}, "origin_alias": [{"src": "/content/origin", "dest": "/origin"}, {"src": "/origin/rpm", "dest": "/origin/rpms"}], "releasever_alias": [{"dest": "/content/dist/rhel-alt/server/7/7.9", "src": "/content/dist/rhel-alt/server/7/7Server"}], "rhui_alias": [{"dest": "/content/dist/rhel8", "src": "/content/dist/rhel8/rhui"}]}, "422": {"description": "Validation Error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPValidationError"}}}}}}}}, "components": {"schemas": {"AccessResponse": {"properties": {"url": {"type": "string", "title": "Url", "description": "Base URL of this CDN environment.", "examples": ["https://abc123.cloudfront.net"]}, "expires": {"type": "string", "title": "Expires", "description": "Expiration time of access information included in this response. ISO8601 UTC timestamp.", "examples": ["2024-04-18T05:30Z"]}, "cookie": {"type": "string", "title": "Cookie", "description": "A cookie granting access to this CDN environment.", "examples": ["CloudFront-Key-Pair-Id=K2266GIXCH; CloudFront-Policy=eyJTdGF0ZW1lbn...; CloudFront-Signature=kGkxpnrY9h..."]}}, "type": "object", "required": ["url", "expires", "cookie"], "title": "AccessResponse"}, "Alias": {"properties": {"src": {"type": "string", "title": "Src", "description": "Path being aliased from, relative to CDN root."}, "dest": {"type": "string", "title": "Dest", "description": "Target of the alias, relative to CDN root."}, "exclude_paths": {"anyOf": [{"items": {"type": "string"}, "type": "array"}, {"type": "null"}], "title": "Exclude Paths", "description": "Paths for which alias will not be resolved, treated as an unanchored regex.", "default": []}}, "type": "object", "required": ["src", "dest"], "title": "Alias"}, "CallContext": {"properties": {"client": {"$ref": "#/components/schemas/ClientContext", "default": {"roles": [], "authenticated": false}}, "user": {"$ref": "#/components/schemas/UserContext", "default": {"roles": [], "authenticated": false}}}, "type": "object", "title": "CallContext", "description": "Represents an authenticated (or not) context for an incoming request.\n\nUse the fields on this model to decide whether the current request belongs\nto an authenticated user, and if so, to determine which role(s) are held\nby the user."}, "ClientContext": {"properties": {"roles": {"items": {"type": "string"}, "type": "array", "title": "Roles", "default": []}, "authenticated": {"type": "boolean", "title": "Authenticated", "default": false}, "serviceAccountId": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Serviceaccountid"}}, "type": "object", "title": "ClientContext", "description": "Call context data relating to service accounts / machine users."}, "CommitModes": {"type": "string", "enum": ["phase1", "phase2"], "title": "CommitModes"}, "Config": {"properties": {"listing": {"additionalProperties": {"$ref": "#/components/schemas/ListingItem"}, "type": "object", "title": "Listing", "description": "A mapping from paths to a yum variable name & list of values, used in generating 'listing' responses."}, "origin_alias": {"items": {"$ref": "#/components/schemas/Alias"}, "type": "array", "title": "Origin Alias", "description": "Aliases relating to /origin."}, "releasever_alias": {"items": {"$ref": "#/components/schemas/Alias"}, "type": "array", "title": "Releasever Alias", "description": "Aliases relating to $releasever variables."}, "rhui_alias": {"items": {"$ref": "#/components/schemas/Alias"}, "type": "array", "title": "Rhui Alias", "description": "Aliases relating to RHUI."}}, "type": "object", "required": ["listing", "origin_alias", "releasever_alias", "rhui_alias"], "title": "Config"}, "EmptyResponse": {"properties": {}, "type": "object", "title": "EmptyResponse", "description": "An empty object."}, "FlushItem": {"properties": {"web_uri": {"type": "string", "title": "Web Uri", "description": "URI, relative to CDN root, of which to flush cache"}}, "type": "object", "required": ["web_uri"], "title": "FlushItem"}, "HTTPValidationError": {"properties": {"detail": {"items": {"$ref": "#/components/schemas/ValidationError"}, "type": "array", "title": "Detail"}}, "type": "object", "title": "HTTPValidationError"}, "Item": {"properties": {"web_uri": {"type": "string", "title": "Web Uri", "description": "URI, relative to CDN root, which shall be used to expose this object."}, "object_key": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Object Key", "description": "Key of blob to be exposed; should be the SHA256 checksum of a previously uploaded piece of content, in lowercase hex-digest form. \n\nAlternatively, the string 'absent' to indicate that no content shall be exposed at the given URI. Publishing an item with key 'absent' can be used to effectively delete formerly published content from the point of view of a CDN consumer.", "default": ""}, "content_type": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Content Type", "description": "Content type of the content associated with this object.", "default": ""}, "link_to": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Link To", "description": "Path of file targeted by symlink.", "default": ""}, "publish_id": {"type": "string", "format": "uuid", "title": "Publish Id", "description": "Unique ID of publish object containing this item."}}, "type": "object", "required": ["web_uri", "publish_id"], "title": "Item"}, "ItemBase": {"properties": {"web_uri": {"type": "string", "title": "Web Uri", "description": "URI, relative to CDN root, which shall be used to expose this object."}, "object_key": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Object Key", "description": "Key of blob to be exposed; should be the SHA256 checksum of a previously uploaded piece of content, in lowercase hex-digest form. \n\nAlternatively, the string 'absent' to indicate that no content shall be exposed at the given URI. Publishing an item with key 'absent' can be used to effectively delete formerly published content from the point of view of a CDN consumer.", "default": ""}, "content_type": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Content Type", "description": "Content type of the content associated with this object.", "default": ""}, "link_to": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Link To", "description": "Path of file targeted by symlink.", "default": ""}}, "type": "object", "required": ["web_uri"], "title": "ItemBase"}, "ListingItem": {"properties": {"var": {"$ref": "#/components/schemas/YumVariable", "description": "YUM variable name."}, "values": {"items": {"type": "string"}, "type": "array", "title": "Values", "description": "Allowed values for YUM variable replacement."}}, "type": "object", "required": ["var", "values"], "title": "ListingItem"}, "MessageResponse": {"properties": {"detail": {"type": "string", "title": "Detail", "description": "A human-readable message with additional info."}}, "type": "object", "required": ["detail"], "title": "MessageResponse"}, "Publish": {"properties": {"id": {"type": "string", "title": "Id", "description": "Unique ID of publish object."}, "env": {"type": "string", "title": "Env", "description": "Environment to which this publish belongs."}, "state": {"$ref": "#/components/schemas/PublishStates", "description": "Current state of this publish."}, "updated": {"anyOf": [{"type": "string", "format": "date-time"}, {"type": "null"}], "title": "Updated", "description": "DateTime of last update to this publish. None if never updated."}, "links": {"additionalProperties": {"type": "string"}, "type": "object", "title": "Links", "description": "URL links related to this publish.", "default": {}}, "items": {"items": {"$ref": "#/components/schemas/Item"}, "type": "array", "title": "Items", "description": "All items (pieces of content) included in this publish.", "default": []}}, "type": "object", "required": ["id", "env", "state"], "title": "Publish"}, "PublishStates": {"type": "string", "enum": ["PENDING", "COMMITTING", "COMMITTED", "FAILED"], "title": "PublishStates"}, "Task": {"properties": {"id": {"type": "string", "format": "uuid", "title": "Id", "description": "Unique ID of task object."}, "publish_id": {"anyOf": [{"type": "string", "format": "uuid"}, {"type": "null"}], "title": "Publish Id", "description": "Unique ID of publish object related to this task, if any."}, "state": {"$ref": "#/components/schemas/TaskStates", "description": "Current state of this task."}, "updated": {"anyOf": [{"type": "string", "format": "date-time"}, {"type": "null"}], "title": "Updated", "description": "DateTime of last update to this task. None if never updated.", "examples": ["2019-08-24T14:15:22Z"]}, "deadline": {"anyOf": [{"type": "string", "format": "date-time"}, {"type": "null"}], "title": "Deadline", "description": "DateTime at which this task should be abandoned.", "examples": ["2019-08-24T18:15:22Z"]}, "links": {"additionalProperties": {"type": "string"}, "type": "object", "title": "Links", "description": "URL links related to this task.", "default": {}, "examples": [{"self": "/task/497f6eca-6276-4993-bfeb-53cbbbba6f08"}]}}, "type": "object", "required": ["id", "state"], "title": "Task"}, "TaskStates": {"type": "string", "enum": ["NOT_STARTED", "IN_PROGRESS", "COMPLETE", "FAILED"], "title": "TaskStates"}, "UserContext": {"properties": {"roles": {"items": {"type": "string"}, "type": "array", "title": "Roles", "default": []}, "authenticated": {"type": "boolean", "title": "Authenticated", "default": false}, "internalUsername": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Internalusername"}}, "type": "object", "title": "UserContext", "description": "Call context data relating to human users."}, "ValidationError": {"properties": {"loc": {"items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, "type": "array", "title": "Location"}, "msg": {"type": "string", "title": "Message"}, "type": {"type": "string", "title": "Error Type"}}, "type": "object", "required": ["loc", "msg", "type"], "title": "ValidationError"}, "YumVariable": {"type": "string", "enum": ["releasever", "basearch"], "title": "YumVariable"}}}, "tags": [{"name": "service", "description": "APIs for inspecting the state of the exodus-gw service."}, {"name": "upload", "description": "An API for uploading binary data.\n\nThis API provides endpoints for uploading files into the data store\nused by the Exodus CDN. Uploading files does not immediately expose\nthem to clients of the CDN, but is a prerequisite of publishing files,\nwhich is achieved via the [publish](#tag/publish) APIs.\n\nThe upload API is a partially compatible subset of the S3 API.\nIt supports at least enough functionality such that the AWS SDK may be\nused when uploading to exodus-gw.\n\nDifferences from the AWS S3 API include:\n\n- Most optional arguments are not supported.\n\n- All `x-amz-*` headers, other than `x-amz-meta-*`, are omitted from responses.\n\n- The usual AWS authentication mechanism is unused; request signatures are ignored.\n Authentication is expected to be performed by other means.\n\n- Object keys should always be SHA256 checksums of the objects being stored,\n in lowercase hex digest form. This allows the object store to be used\n as content-addressable storage.\n\n- When uploading content, the Content-MD5 and Content-Length headers are mandatory;\n chunked encoding is not supported.\n\n- The API may enforce stricter limits or policies on uploads than those imposed\n by the AWS API.\n\n## Using boto3 with the upload API\n\nAs the upload API is partially compatible with S3, it is possible to use\nexisting S3 clients such as the AWS SDK to perform uploads. This is the\nrecommended method of using the API. Here we show an example using boto, the\nAWS SDK for Python.\n\nUse `endpoint_url` when creating a boto resource or client to point at exodus-gw.\nRegion and credentials will be ignored.\n\nNote that, as the upload API provides only a subset of the S3 API, many boto methods\nwill not work. Uploading objects and querying the existence of an object are\nsupported.\n\n```python\nimport boto3\nfrom botocore.config import Config\n\n# Prepare S3 resource pointing at exodus-gw\ns3 = boto3.resource('s3',\n endpoint_url='https://exodus-gw.example.com/upload',\n # In a typical setup using PKI auth, these values are not used during\n # auth to exodus-gw, but boto client always insists on having *some* keys.\n # Dummy values can be provided to prevent boto looking for credentials\n # in config files.\n aws_access_key_id=\"dummy\",\n aws_secret_access_key=\"dummy\",\n # If SSL needs to be configured:\n verify='/path/to/bundle.pem',\n config=Config(client_cert=('client.crt', 'client.key')))\n\n# Bucket name must match one of the environment names\nbucket = s3.Bucket('live')\n\n# Basic APIs such as upload_file now work as usual\nbucket.upload_file('/tmp/hello.txt',\n 'aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f')\n```\n"}, {"name": "publish", "description": "APIs for publishing blobs.\n\nIn the context of exodus-gw, \"publishing\" a blob means exposing it\nvia one or more user-accessible paths on the CDN. These blobs should first\nbe uploaded using the [upload](#tag/upload) APIs.\n\n\n## Atomicity\n\nexodus-gw aims to enable atomic semantics for publishes; i.e., for a set\nof published content, committing the publish will make either *all* of it\navailable (if commit succeeds) or *none* of it available (if commit fails),\nwith no partial updates becoming visible from the point of view of a CDN\nclient.\n\nIn practice, limitations in the storage backend mean that true atomicity\nis not achieved for all but the smallest publishes, so it is more accurate\nto refer to the commit operation as \"near-atomic\".\n\nHere are a few of the strategies used by exodus-gw in order to achieve as\nclose as possible to atomic behavior:\n\n- exodus-gw performs writes to the underlying database (DynamoDB) in batches,\n which themselves are committed atomically.\n\n - (True atomicity could be achieved if the entire publish fits within\n a single batch, but as batches have a maximum size of 25 items, this\n is rarely the case.)\n\n- All operations are aggressively retried in case of error.\n\n- exodus-gw keeps track of what has been committed and is able to roll\n back a partially committed publish in case of unrecoverable errors.\n\n- During commit, the items to be committed are prioritized intelligently\n with knowledge of the types of content being published. Files which serve\n as an index or entry point to a set of content are committed last, to ensure\n minimal impact in the case that a commit is interrupted.\n See \"two-phase commit\" below for a more in-depth explanation of this.\n\n## Two-phase commit\n\nAll published content is categorized into two phases, phase 1 and phase 2,\nand committed in that order. exodus-gw performs this categorization internally\nand clients cannot influence this.\n\nSimple clients do not need to worry about this, but in more complicated scenarios\nthe client may wish to control the commit of each phase independently. In such\ncases it is important to understand how the two phases are intended to work.\n\nPhase 1 content:\n\n- includes the majority of content within a publish\n- should be immutable\n- is usually not discoverable by CDN users without consulting some form of index\n- examples: RPM files within a yum repo; any generic file\n\nPhase 2 content:\n\n- includes a small minority of content within a publish\n- is usually mutable, perhaps changing at every publish\n- contains indexes, repository entry points or other references pointing at\n phase 1 content (and thus must be committed last)\n- examples: `repodata/repomd.xml` within a yum repo; `PULP_MANIFEST` within a\n Pulp file repository\n\nAs an example of this phased approach, consider the publish of a yum repository.\nA client consuming packages from a yum repository discovers available packages\nvia a series of fetches involving multiple files which are published together,\ne.g.\n\n`repodata/repomd.xml` => `repodata/-primary.xml.gz`\n => `Packages/.rpm`\n\nIf no ordering were to be applied to the publish of these files it would be\npossible for `repomd.xml` to be published prior to `-primary.xml.gz`,\nor for `-primary.xml.gz` to be published prior to\n`Packages/.rpm`, either of which could cause a CDN consumer to\nattempt to fetch content which has not yet been published, resulting in 404\nerrors.\n\nThis problem is avoided by exodus-gw internally categorizing `repomd.xml` as\nphase 2 content and ensuring it is committed only after the rest of the files\nin the repo, which are categorized as phase 1 content.\n\n## Expiry of publish objects\n\nPublish objects should be treated as ephemeral; they are not persisted indefinitely.\n\n- All publish objects which have reached a terminal state (failed or committed) will be\n deleted after some server-defined timeout, defaulting to two weeks.\n- Publish objects which have been created but not committed within a server-defined timeout,\n typically one day, will be marked as failed.\n\n\n## Expiry of task objects\n\nLike publish objects, task objects created when publishes are committed are not persisted\nindefinitely.\n\n- All task objects which have reached a terminal state (failed or complete) will be\n deleted after some server-defined timeout, defaulting to two weeks.\n- Task objects not picked up by a worker within a server-defined time limit, defaulting\n to two hours, will be marked as failed along with the associated publish object. This\n prevents system overload in the event of a worker outage.\n"}, {"name": "deploy", "description": "APIs for adjusting configuration used by the CDN."}, {"name": "cdn", "description": "Utilities for accessing the Exodus CDN."}, {"name": "config", "description": "APIs for retrieving and deploying configuration used by the CDN."}]} \ No newline at end of file