diff --git a/.changeset/snapshot-client-schedule-endpoints.md b/.changeset/snapshot-client-schedule-endpoints.md new file mode 100644 index 00000000..62bb8148 --- /dev/null +++ b/.changeset/snapshot-client-schedule-endpoints.md @@ -0,0 +1,13 @@ +--- +"@epilot/snapshot-client": minor +--- + +Add scheduled-snapshot endpoints, regenerated from the published Snapshot API +spec: + +- `getOrgSnapshotSchedule` +- `putOrgSnapshotSchedule` +- `deleteOrgSnapshotSchedule` + +Consumers can now call these as typed client methods instead of raw axios +requests. `captureOrgSnapshot` was already present. diff --git a/clients/snapshot-client/src/openapi-runtime.json b/clients/snapshot-client/src/openapi-runtime.json index 1a3e43cd..5b2f7985 100644 --- a/clients/snapshot-client/src/openapi-runtime.json +++ b/clients/snapshot-client/src/openapi-runtime.json @@ -123,6 +123,26 @@ "responses": {} } }, + "/v1/org-snapshot-schedule": { + "get": { + "operationId": "getOrgSnapshotSchedule", + "responses": {} + }, + "put": { + "operationId": "putOrgSnapshotSchedule", + "requestBody": { + "required": false, + "content": { + "application/json": {} + } + }, + "responses": {} + }, + "delete": { + "operationId": "deleteOrgSnapshotSchedule", + "responses": {} + } + }, "/v1/snapshots:list-dependencies": { "post": { "operationId": "listDependencies", @@ -152,11 +172,6 @@ "content": { "application/json": {} } - }, - "UnprocessableEntity": { - "content": { - "application/json": {} - } } } } diff --git a/clients/snapshot-client/src/openapi.d.ts b/clients/snapshot-client/src/openapi.d.ts index 0ff32e7d..cec0b913 100644 --- a/clients/snapshot-client/src/openapi.d.ts +++ b/clients/snapshot-client/src/openapi.d.ts @@ -11,13 +11,6 @@ declare namespace Components { export type BadRequest = Schemas.Error; export type NotFound = Schemas.Error; export type Unauthorized = Schemas.Error; - export type UnprocessableEntity = /** - * Returned (422) when the org inventory contains no capturable resources - * after filtering out sensitive, unsupported, and excluded types. The - * `skipped_types` array explains why every type was dropped. - * - */ - Schemas.EmptyInventoryError; } namespace Schemas { export interface CallerIdentity { @@ -86,23 +79,6 @@ declare namespace Components { status: "creating"; created_at: string; // date-time } - /** - * Returned (422) when the org inventory contains no capturable resources - * after filtering out sensitive, unsupported, and excluded types. The - * `skipped_types` array explains why every type was dropped. - * - */ - export interface EmptyInventoryError { - /** - * example: - * No capturable resources in the org inventory - */ - message: string; - skipped_types: { - type: string; - reason: string; - }[]; - } export interface Error { status: number; error: string; @@ -121,6 +97,89 @@ declare namespace Components { error?: string; triggered_by: CallerIdentity; } + /** + * Enrollment record for a scheduled org snapshot. One row per org. + * This table — not EventBridge — is the source of truth; the EventBridge + * schedule entry is the materialization of this row (reconciled on write + * by Task 6). + * + */ + export interface OrgSnapshotSchedule { + org_id: string; + enabled: boolean; + /** + * Validated 6-field EventBridge cron. Default `cron(0 2 * * ? *)`. + * example: + * cron(0 2 * * ? *) + */ + cron_expression: string; + /** + * IANA timezone. Default `Europe/Berlin`. + */ + timezone: string; + retention: /** + * Flat retention window for a scheduled snapshot. + * Converted to a `ttl` epoch at capture time. Capped at ~24 months. + * + */ + RetentionConfig; + excluded_types?: string[]; + /** + * Name of the EventBridge Scheduler entry this row owns. + * Set at enrollment time as `org-snapshot-{orgId}`. + * + */ + schedule_name: string; + last_started_at?: string; // date-time + last_completed_at?: string; // date-time + last_status?: "completed" | "partial" | "failed"; + created_by: string; + created_at: string; // date-time + updated_at: string; // date-time + } + /** + * Body for `putOrgSnapshotSchedule`. All fields optional; unset fields + * receive defaults on first create and are left unchanged on updates + * (except `updated_at`). + * + */ + export interface PutOrgSnapshotScheduleRequest { + /** + * Whether the schedule is active. + */ + enabled?: boolean; + /** + * 6-field EventBridge cron expression, e.g. `cron(0 2 * * ? *)`. + * Validated server-side: minute + hour must be concrete single integers + * (no `*`, lists, ranges, or steps) to cap cadence at ≤ once/day. + * Exactly one of day-of-month / day-of-week must be `?`. + * + * example: + * cron(0 2 * * ? *) + */ + cron_expression?: string; + /** + * IANA timezone string passed to EventBridge `ScheduleExpressionTimezone`, + * e.g. `Europe/Berlin`. AWS handles DST natively. + * + * example: + * Europe/Berlin + */ + timezone?: string; + retention?: /** + * Flat retention window for a scheduled snapshot. + * Converted to a `ttl` epoch at capture time. Capped at ~24 months. + * + */ + RetentionConfig; + /** + * Resource types to exclude from the scheduled capture, in addition + * to the always-excluded sensitive types (`access_token`, + * `environment_variable`). + * + */ + excluded_types?: string[]; + } export interface ResourceRef { /** * Resource type (e.g., custom_variable, journey, automation_flow) @@ -149,6 +208,18 @@ declare namespace Components { id: string; status: "restoring"; } + /** + * Flat retention window for a scheduled snapshot. + * Converted to a `ttl` epoch at capture time. Capped at ~24 months. + * + */ + export interface RetentionConfig { + /** + * Numeric quantity of retention (e.g. 90 for "90 days"). + */ + value: number; + unit: "days" | "weeks" | "months"; + } export interface Snapshot { id: string; org_id: string; @@ -287,7 +358,6 @@ declare namespace Paths { namespace Responses { export type $202 = Components.Schemas.CreateSnapshotResponse; export type $401 = Components.Responses.Unauthorized; - export type $422 = Components.Responses.UnprocessableEntity; } } namespace CreateSnapshot { @@ -298,6 +368,14 @@ declare namespace Paths { export type $401 = Components.Responses.Unauthorized; } } + namespace DeleteOrgSnapshotSchedule { + namespace Responses { + export interface $204 { + } + export type $401 = Components.Responses.Unauthorized; + export type $404 = Components.Responses.NotFound; + } + } namespace DeleteSnapshot { namespace Responses { export interface $204 { @@ -306,6 +384,20 @@ declare namespace Paths { export type $404 = Components.Responses.NotFound; } } + namespace GetOrgSnapshotSchedule { + namespace Responses { + export type $200 = /** + * Enrollment record for a scheduled org snapshot. One row per org. + * This table — not EventBridge — is the source of truth; the EventBridge + * schedule entry is the materialization of this row (reconciled on write + * by Task 6). + * + */ + Components.Schemas.OrgSnapshotSchedule; + export type $401 = Components.Responses.Unauthorized; + export type $404 = Components.Responses.NotFound; + } + } namespace GetSnapshot { namespace Responses { export type $200 = Components.Schemas.Snapshot; @@ -373,6 +465,27 @@ declare namespace Paths { export type $401 = Components.Responses.Unauthorized; } } + namespace PutOrgSnapshotSchedule { + export type RequestBody = /** + * Body for `putOrgSnapshotSchedule`. All fields optional; unset fields + * receive defaults on first create and are left unchanged on updates + * (except `updated_at`). + * + */ + Components.Schemas.PutOrgSnapshotScheduleRequest; + namespace Responses { + export type $200 = /** + * Enrollment record for a scheduled org snapshot. One row per org. + * This table — not EventBridge — is the source of truth; the EventBridge + * schedule entry is the materialization of this row (reconciled on write + * by Task 6). + * + */ + Components.Schemas.OrgSnapshotSchedule; + export type $400 = Components.Responses.BadRequest; + export type $401 = Components.Responses.Unauthorized; + } + } namespace RestoreSnapshot { export type RequestBody = /** * Apply a captured snapshot to its source org. snapshot-api applies the @@ -460,12 +573,14 @@ export interface OperationMethods { /** * captureOrgSnapshot - captureOrgSnapshot * - * Snapshot the caller's whole organization now. Fetches a fresh inventory - * of the org's configuration resources from configuration-hub-api, persists - * it as an inventory artifact, and starts a `scope: "org"` chunked capture. - * Async — returns immediately with a snapshot ID; client polls `getSnapshot` - * and watches `capture_summary` fill in until `create.status` moves from - * `in_progress` to `completed` or `failed`. + * Snapshot the caller's whole organization now. Creates a `scope: "org"` + * snapshot row and starts a chunked capture Step Function, then returns + * immediately. The capture asynchronously fetches a fresh inventory of the + * org's configuration resources from configuration-hub-api, persists it as + * an inventory artifact, and captures each resource. Client polls + * `getSnapshot` and watches `capture_summary` fill in until `create.status` + * moves from `in_progress` to `completed` or `failed`. An org with no + * capturable resources finalizes as a completed 0-resource snapshot. * * Sensitive types (`access_token`, `environment_variable`), types with no * engine adapter, and any `excluded_types` are dropped from the capture and @@ -542,6 +657,55 @@ export interface OperationMethods { data?: any, config?: AxiosRequestConfig ): OperationResponse + /** + * getOrgSnapshotSchedule - getOrgSnapshotSchedule + * + * Return the scheduled-snapshot enrollment config for the caller's org. + * Returns 404 when the org has not yet enrolled. + * + */ + 'getOrgSnapshotSchedule'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * putOrgSnapshotSchedule - putOrgSnapshotSchedule + * + * Create or update the scheduled-snapshot enrollment config for the + * caller's org (upsert). The cron expression and retention window are + * validated server-side; invalid values are rejected with 400. + * + * Defaults applied when a field is omitted on first create: + * - `cron_expression`: `cron(0 2 * * ? *)` (daily at 02:00) + * - `timezone`: `Europe/Berlin` + * - `retention`: `{ value: 90, unit: "days" }` + * - `enabled`: `true` + * + * **Note:** this call persists the row only. EventBridge schedule + * materialization is performed by a subsequent reconcile step (Task 6). + * The row is the authoritative source of truth for the UI. + * + */ + 'putOrgSnapshotSchedule'( + parameters?: Parameters | null, + data?: Paths.PutOrgSnapshotSchedule.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * deleteOrgSnapshotSchedule - deleteOrgSnapshotSchedule + * + * Remove the scheduled-snapshot enrollment for the caller's org. + * Returns 404 when no schedule exists. + * The corresponding EventBridge schedule is removed by a reconcile + * step (Task 6). + * + */ + 'deleteOrgSnapshotSchedule'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse /** * listDependencies - listDependencies * @@ -596,12 +760,14 @@ export interface PathsDictionary { /** * captureOrgSnapshot - captureOrgSnapshot * - * Snapshot the caller's whole organization now. Fetches a fresh inventory - * of the org's configuration resources from configuration-hub-api, persists - * it as an inventory artifact, and starts a `scope: "org"` chunked capture. - * Async — returns immediately with a snapshot ID; client polls `getSnapshot` - * and watches `capture_summary` fill in until `create.status` moves from - * `in_progress` to `completed` or `failed`. + * Snapshot the caller's whole organization now. Creates a `scope: "org"` + * snapshot row and starts a chunked capture Step Function, then returns + * immediately. The capture asynchronously fetches a fresh inventory of the + * org's configuration resources from configuration-hub-api, persists it as + * an inventory artifact, and captures each resource. Client polls + * `getSnapshot` and watches `capture_summary` fill in until `create.status` + * moves from `in_progress` to `completed` or `failed`. An org with no + * capturable resources finalizes as a completed 0-resource snapshot. * * Sensitive types (`access_token`, `environment_variable`), types with no * engine adapter, and any `excluded_types` are dropped from the capture and @@ -687,6 +853,57 @@ export interface PathsDictionary { config?: AxiosRequestConfig ): OperationResponse } + ['/v1/org-snapshot-schedule']: { + /** + * getOrgSnapshotSchedule - getOrgSnapshotSchedule + * + * Return the scheduled-snapshot enrollment config for the caller's org. + * Returns 404 when the org has not yet enrolled. + * + */ + 'get'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * putOrgSnapshotSchedule - putOrgSnapshotSchedule + * + * Create or update the scheduled-snapshot enrollment config for the + * caller's org (upsert). The cron expression and retention window are + * validated server-side; invalid values are rejected with 400. + * + * Defaults applied when a field is omitted on first create: + * - `cron_expression`: `cron(0 2 * * ? *)` (daily at 02:00) + * - `timezone`: `Europe/Berlin` + * - `retention`: `{ value: 90, unit: "days" }` + * - `enabled`: `true` + * + * **Note:** this call persists the row only. EventBridge schedule + * materialization is performed by a subsequent reconcile step (Task 6). + * The row is the authoritative source of truth for the UI. + * + */ + 'put'( + parameters?: Parameters | null, + data?: Paths.PutOrgSnapshotSchedule.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * deleteOrgSnapshotSchedule - deleteOrgSnapshotSchedule + * + * Remove the scheduled-snapshot enrollment for the caller's org. + * Returns 404 when no schedule exists. + * The corresponding EventBridge schedule is removed by a reconcile + * step (Task 6). + * + */ + 'delete'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + } ['/v1/snapshots:list-dependencies']: { /** * listDependencies - listDependencies @@ -713,12 +930,14 @@ export type CallerIdentity = Components.Schemas.CallerIdentity; export type CreateOrgSnapshotRequest = Components.Schemas.CreateOrgSnapshotRequest; export type CreateSnapshotRequest = Components.Schemas.CreateSnapshotRequest; export type CreateSnapshotResponse = Components.Schemas.CreateSnapshotResponse; -export type EmptyInventoryError = Components.Schemas.EmptyInventoryError; export type Error = Components.Schemas.Error; export type Operation = Components.Schemas.Operation; +export type OrgSnapshotSchedule = Components.Schemas.OrgSnapshotSchedule; +export type PutOrgSnapshotScheduleRequest = Components.Schemas.PutOrgSnapshotScheduleRequest; export type ResourceRef = Components.Schemas.ResourceRef; export type RestoreSnapshotRequest = Components.Schemas.RestoreSnapshotRequest; export type RestoreSnapshotResponse = Components.Schemas.RestoreSnapshotResponse; +export type RetentionConfig = Components.Schemas.RetentionConfig; export type Snapshot = Components.Schemas.Snapshot; export type SnapshotResourceDetail = Components.Schemas.SnapshotResourceDetail; export type SnapshotResourceList = Components.Schemas.SnapshotResourceList; diff --git a/clients/snapshot-client/src/openapi.json b/clients/snapshot-client/src/openapi.json index 5cdf0f29..6ca15728 100644 --- a/clients/snapshot-client/src/openapi.json +++ b/clients/snapshot-client/src/openapi.json @@ -27,6 +27,10 @@ { "name": "Snapshots", "description": "Snapshot CRUD and restore operations" + }, + { + "name": "ScheduledSnapshots", + "description": "Enrollment and configuration for scheduled org snapshots" } ], "paths": { @@ -160,7 +164,7 @@ "post": { "operationId": "captureOrgSnapshot", "summary": "captureOrgSnapshot", - "description": "Snapshot the caller's whole organization now. Fetches a fresh inventory\nof the org's configuration resources from configuration-hub-api, persists\nit as an inventory artifact, and starts a `scope: \"org\"` chunked capture.\nAsync — returns immediately with a snapshot ID; client polls `getSnapshot`\nand watches `capture_summary` fill in until `create.status` moves from\n`in_progress` to `completed` or `failed`.\n\nSensitive types (`access_token`, `environment_variable`), types with no\nengine adapter, and any `excluded_types` are dropped from the capture and\nrecorded in the snapshot's coverage report.\n", + "description": "Snapshot the caller's whole organization now. Creates a `scope: \"org\"`\nsnapshot row and starts a chunked capture Step Function, then returns\nimmediately. The capture asynchronously fetches a fresh inventory of the\norg's configuration resources from configuration-hub-api, persists it as\nan inventory artifact, and captures each resource. Client polls\n`getSnapshot` and watches `capture_summary` fill in until `create.status`\nmoves from `in_progress` to `completed` or `failed`. An org with no\ncapturable resources finalizes as a completed 0-resource snapshot.\n\nSensitive types (`access_token`, `environment_variable`), types with no\nengine adapter, and any `excluded_types` are dropped from the capture and\nrecorded in the snapshot's coverage report.\n", "tags": [ "Snapshots" ], @@ -187,9 +191,6 @@ }, "401": { "$ref": "#/components/responses/Unauthorized" - }, - "422": { - "$ref": "#/components/responses/UnprocessableEntity" } } } @@ -383,6 +384,89 @@ } } }, + "/v1/org-snapshot-schedule": { + "get": { + "operationId": "getOrgSnapshotSchedule", + "summary": "getOrgSnapshotSchedule", + "description": "Return the scheduled-snapshot enrollment config for the caller's org.\nReturns 404 when the org has not yet enrolled.\n", + "tags": [ + "ScheduledSnapshots" + ], + "responses": { + "200": { + "description": "Org snapshot schedule config", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgSnapshotSchedule" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + }, + "put": { + "operationId": "putOrgSnapshotSchedule", + "summary": "putOrgSnapshotSchedule", + "description": "Create or update the scheduled-snapshot enrollment config for the\ncaller's org (upsert). The cron expression and retention window are\nvalidated server-side; invalid values are rejected with 400.\n\nDefaults applied when a field is omitted on first create:\n - `cron_expression`: `cron(0 2 * * ? *)` (daily at 02:00)\n - `timezone`: `Europe/Berlin`\n - `retention`: `{ value: 90, unit: \"days\" }`\n - `enabled`: `true`\n\n**Note:** this call persists the row only. EventBridge schedule\nmaterialization is performed by a subsequent reconcile step (Task 6).\nThe row is the authoritative source of truth for the UI.\n", + "tags": [ + "ScheduledSnapshots" + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PutOrgSnapshotScheduleRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Stored config (after upsert)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgSnapshotSchedule" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + } + }, + "delete": { + "operationId": "deleteOrgSnapshotSchedule", + "summary": "deleteOrgSnapshotSchedule", + "description": "Remove the scheduled-snapshot enrollment for the caller's org.\nReturns 404 when no schedule exists.\nThe corresponding EventBridge schedule is removed by a reconcile\nstep (Task 6).\n", + "tags": [ + "ScheduledSnapshots" + ], + "responses": { + "204": { + "description": "Deleted" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, "/v1/snapshots:list-dependencies": { "post": { "operationId": "listDependencies", @@ -489,16 +573,6 @@ } } } - }, - "UnprocessableEntity": { - "description": "Unprocessable entity", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EmptyInventoryError" - } - } - } } }, "schemas": { @@ -517,38 +591,6 @@ } } }, - "EmptyInventoryError": { - "type": "object", - "required": [ - "message", - "skipped_types" - ], - "description": "Returned (422) when the org inventory contains no capturable resources\nafter filtering out sensitive, unsupported, and excluded types. The\n`skipped_types` array explains why every type was dropped.\n", - "properties": { - "message": { - "type": "string", - "example": "No capturable resources in the org inventory" - }, - "skipped_types": { - "type": "array", - "items": { - "type": "object", - "required": [ - "type", - "reason" - ], - "properties": { - "type": { - "type": "string" - }, - "reason": { - "type": "string" - } - } - } - } - } - }, "ResourceRef": { "type": "object", "required": [ @@ -929,6 +971,132 @@ "type": "string" } } + }, + "RetentionConfig": { + "type": "object", + "required": [ + "value", + "unit" + ], + "description": "Flat retention window for a scheduled snapshot.\nConverted to a `ttl` epoch at capture time. Capped at ~24 months.\n", + "properties": { + "value": { + "type": "integer", + "minimum": 1, + "description": "Numeric quantity of retention (e.g. 90 for \"90 days\")." + }, + "unit": { + "type": "string", + "enum": [ + "days", + "weeks", + "months" + ] + } + } + }, + "PutOrgSnapshotScheduleRequest": { + "type": "object", + "description": "Body for `putOrgSnapshotSchedule`. All fields optional; unset fields\nreceive defaults on first create and are left unchanged on updates\n(except `updated_at`).\n", + "properties": { + "enabled": { + "type": "boolean", + "default": true, + "description": "Whether the schedule is active." + }, + "cron_expression": { + "type": "string", + "description": "6-field EventBridge cron expression, e.g. `cron(0 2 * * ? *)`.\nValidated server-side: minute + hour must be concrete single integers\n(no `*`, lists, ranges, or steps) to cap cadence at ≤ once/day.\nExactly one of day-of-month / day-of-week must be `?`.\n", + "example": "cron(0 2 * * ? *)" + }, + "timezone": { + "type": "string", + "description": "IANA timezone string passed to EventBridge `ScheduleExpressionTimezone`,\ne.g. `Europe/Berlin`. AWS handles DST natively.\n", + "example": "Europe/Berlin" + }, + "retention": { + "$ref": "#/components/schemas/RetentionConfig" + }, + "excluded_types": { + "type": "array", + "description": "Resource types to exclude from the scheduled capture, in addition\nto the always-excluded sensitive types (`access_token`,\n`environment_variable`).\n", + "items": { + "type": "string" + } + } + } + }, + "OrgSnapshotSchedule": { + "type": "object", + "required": [ + "org_id", + "enabled", + "cron_expression", + "timezone", + "retention", + "schedule_name", + "created_by", + "created_at", + "updated_at" + ], + "description": "Enrollment record for a scheduled org snapshot. One row per org.\nThis table — not EventBridge — is the source of truth; the EventBridge\nschedule entry is the materialization of this row (reconciled on write\nby Task 6).\n", + "properties": { + "org_id": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "cron_expression": { + "type": "string", + "description": "Validated 6-field EventBridge cron. Default `cron(0 2 * * ? *)`.", + "example": "cron(0 2 * * ? *)" + }, + "timezone": { + "type": "string", + "description": "IANA timezone. Default `Europe/Berlin`." + }, + "retention": { + "$ref": "#/components/schemas/RetentionConfig" + }, + "excluded_types": { + "type": "array", + "items": { + "type": "string" + } + }, + "schedule_name": { + "type": "string", + "description": "Name of the EventBridge Scheduler entry this row owns.\nSet at enrollment time as `org-snapshot-{orgId}`.\n" + }, + "last_started_at": { + "type": "string", + "format": "date-time" + }, + "last_completed_at": { + "type": "string", + "format": "date-time" + }, + "last_status": { + "type": "string", + "enum": [ + "completed", + "partial", + "failed" + ] + }, + "created_by": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } } } }