diff --git a/apps/webservice/src/app/api/v1/jobs/[jobId]/route.ts b/apps/webservice/src/app/api/v1/jobs/[jobId]/route.ts index 7c6cdba07..570301641 100644 --- a/apps/webservice/src/app/api/v1/jobs/[jobId]/route.ts +++ b/apps/webservice/src/app/api/v1/jobs/[jobId]/route.ts @@ -6,6 +6,7 @@ import { db } from "@ctrlplane/db/client"; import { deployment, environment, + environmentPolicyApproval, job, jobVariable, release, @@ -15,6 +16,7 @@ import { target, targetMetadata, updateJob, + user, } from "@ctrlplane/db/schema"; import { onJobCompletion } from "@ctrlplane/job-dispatch"; import { variablesAES256 } from "@ctrlplane/secrets"; @@ -22,12 +24,46 @@ import { JobStatus } from "@ctrlplane/validators/jobs"; import { getUser } from "~/app/api/v1/auth"; +type ApprovalJoinResult = { + environment_policy_approval: typeof environmentPolicyApproval.$inferSelect; + user: typeof user.$inferSelect | null; +}; + +const getApprovalDetails = async (releaseId: string, policyId: string) => + db + .select() + .from(environmentPolicyApproval) + .leftJoin(user, eq(environmentPolicyApproval.userId, user.id)) + .where( + and( + eq(environmentPolicyApproval.releaseId, releaseId), + eq(environmentPolicyApproval.policyId, policyId), + ), + ) + .then(takeFirstOrNull) + .then(mapApprovalResponse); + +const mapApprovalResponse = (row: ApprovalJoinResult | null) => + !row + ? null + : { + id: row.environment_policy_approval.id, + status: row.environment_policy_approval.status, + approver: + row.user && row.environment_policy_approval.status !== "pending" + ? { + id: row.user.id, + name: row.user.name, + } + : null, + }; + export const GET = async ( req: NextRequest, { params }: { params: { jobId: string } }, ) => { - const user = await getUser(req); - if (!user) + const caller = await getUser(req); + if (!caller) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); const je = await db @@ -51,6 +87,13 @@ export const GET = async ( release: row.release, })); + const policyId = je.environment?.policyId; + + const approval = + je.release?.id && policyId + ? await getApprovalDetails(je.release.id, policyId) + : null; + const jobVariableRows = await db .select() .from(jobVariable) @@ -80,6 +123,7 @@ export const GET = async ( ...je, variables, target: targetWithMetadata, + approval, }); }; diff --git a/github/get-job-inputs/index.js b/github/get-job-inputs/index.js index 221c50e61..44b14342e 100644 --- a/github/get-job-inputs/index.js +++ b/github/get-job-inputs/index.js @@ -485,6 +485,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true })); exports.prepareKeyValueMessage = exports.issueFileCommand = void 0; // We use any as a valid input type /* eslint-disable @typescript-eslint/no-explicit-any */ +const crypto = __importStar(__nccwpck_require__(6982)); const fs = __importStar(__nccwpck_require__(9896)); const os = __importStar(__nccwpck_require__(857)); const utils_1 = __nccwpck_require__(274); @@ -27853,6 +27854,69 @@ function GetAgentRunningJob200ResponseInnerToJSON(value) { }; } +// src/models/GetJob200ResponseApprovalApprover.ts +function instanceOfGetJob200ResponseApprovalApprover(value) { + if (!("id" in value) || value["id"] === void 0) return false; + if (!("name" in value) || value["name"] === void 0) return false; + return true; +} +function GetJob200ResponseApprovalApproverFromJSON(json) { + return GetJob200ResponseApprovalApproverFromJSONTyped(json, false); +} +function GetJob200ResponseApprovalApproverFromJSONTyped(json, ignoreDiscriminator) { + if (json == null) { + return json; + } + return { + id: json["id"], + name: json["name"] + }; +} +function GetJob200ResponseApprovalApproverToJSON(value) { + if (value == null) { + return value; + } + return { + id: value["id"], + name: value["name"] + }; +} + +// src/models/GetJob200ResponseApproval.ts +var GetJob200ResponseApprovalStatusEnum = { + Pending: "pending", + Approved: "approved", + Rejected: "rejected" +}; +function instanceOfGetJob200ResponseApproval(value) { + if (!("id" in value) || value["id"] === void 0) return false; + if (!("status" in value) || value["status"] === void 0) return false; + return true; +} +function GetJob200ResponseApprovalFromJSON(json) { + return GetJob200ResponseApprovalFromJSONTyped(json, false); +} +function GetJob200ResponseApprovalFromJSONTyped(json, ignoreDiscriminator) { + if (json == null) { + return json; + } + return { + id: json["id"], + status: json["status"], + approver: json["approver"] == null ? void 0 : GetJob200ResponseApprovalApproverFromJSON(json["approver"]) + }; +} +function GetJob200ResponseApprovalToJSON(value) { + if (value == null) { + return value; + } + return { + id: value["id"], + status: value["status"], + approver: GetJob200ResponseApprovalApproverToJSON(value["approver"]) + }; +} + // src/models/GetJob200ResponseDeployment.ts function instanceOfGetJob200ResponseDeployment(value) { if (!("id" in value) || value["id"] === void 0) return false; @@ -28054,6 +28118,8 @@ var GetJob200ResponseStatusEnum = { function instanceOfGetJob200Response(value) { if (!("id" in value) || value["id"] === void 0) return false; if (!("status" in value) || value["status"] === void 0) return false; + if (!("variables" in value) || value["variables"] === void 0) return false; + if (!("approval" in value) || value["approval"] === void 0) return false; return true; } function GetJob200ResponseFromJSON(json) { @@ -28071,7 +28137,8 @@ function GetJob200ResponseFromJSONTyped(json, ignoreDiscriminator) { runbook: json["runbook"] == null ? void 0 : GetJob200ResponseRunbookFromJSON(json["runbook"]), target: json["target"] == null ? void 0 : GetJob200ResponseTargetFromJSON(json["target"]), environment: json["environment"] == null ? void 0 : GetJob200ResponseEnvironmentFromJSON(json["environment"]), - variables: json["variables"] == null ? void 0 : json["variables"] + variables: json["variables"], + approval: GetJob200ResponseApprovalFromJSON(json["approval"]) }; } function GetJob200ResponseToJSON(value) { @@ -28086,7 +28153,8 @@ function GetJob200ResponseToJSON(value) { runbook: GetJob200ResponseRunbookToJSON(value["runbook"]), target: GetJob200ResponseTargetToJSON(value["target"]), environment: GetJob200ResponseEnvironmentToJSON(value["environment"]), - variables: value["variables"] + variables: value["variables"], + approval: GetJob200ResponseApprovalToJSON(value["approval"]) }; } @@ -28838,7 +28906,7 @@ async function run() { .getJob({ jobId }) .then((response) => { core.info(JSON.stringify(response, null, 2)); - const { variables, target, release, environment, runbook, deployment } = response; + const { variables, target, release, environment, runbook, deployment, approval, } = response; setOutputAndLog("base_url", baseUrl); setOutputAndLog("target", target); setOutputAndLog("target_id", target?.id); @@ -28855,10 +28923,13 @@ async function run() { setOutputAndLog("release_version", release?.version); setOutputsRecursively("release_config", release?.config); setOutputsRecursively("release_metadata", release?.metadata); + setOutputAndLog("approval_status", approval.status); + setOutputAndLog("approval_approver_id", approval.approver?.id); + setOutputAndLog("approval_approver_name", approval.approver?.name); setOutputAndLog("deployment_id", deployment?.id); setOutputAndLog("deployment_name", deployment?.name); setOutputAndLog("deployment_slug", deployment?.slug); - for (const [key, value] of Object.entries(variables ?? {})) { + for (const [key, value] of Object.entries(variables)) { const sanitizedKey = key.replace(/[.\-/\s\t]+/g, "_"); setOutputAndLog(`variable_${sanitizedKey}`, value); } diff --git a/integrations/github-get-job-inputs/src/index.ts b/integrations/github-get-job-inputs/src/index.ts index 7374704fd..fc7c42e08 100644 --- a/integrations/github-get-job-inputs/src/index.ts +++ b/integrations/github-get-job-inputs/src/index.ts @@ -51,8 +51,15 @@ async function run() { .then((response) => { core.info(JSON.stringify(response, null, 2)); - const { variables, target, release, environment, runbook, deployment } = - response; + const { + variables, + target, + release, + environment, + runbook, + deployment, + approval, + } = response; setOutputAndLog("base_url", baseUrl); @@ -75,11 +82,15 @@ async function run() { setOutputsRecursively("release_config", release?.config); setOutputsRecursively("release_metadata", release?.metadata); + setOutputAndLog("approval_status", approval.status); + setOutputAndLog("approval_approver_id", approval.approver?.id); + setOutputAndLog("approval_approver_name", approval.approver?.name); + setOutputAndLog("deployment_id", deployment?.id); setOutputAndLog("deployment_name", deployment?.name); setOutputAndLog("deployment_slug", deployment?.slug); - for (const [key, value] of Object.entries(variables ?? {})) { + for (const [key, value] of Object.entries(variables)) { const sanitizedKey = key.replace(/[.\-/\s\t]+/g, "_"); setOutputAndLog(`variable_${sanitizedKey}`, value); } diff --git a/openapi.v1.yaml b/openapi.v1.yaml index 8f57beee6..c9893dc25 100644 --- a/openapi.v1.yaml +++ b/openapi.v1.yaml @@ -310,7 +310,6 @@ paths: invalid_integration, external_run_not_found, ] - release: type: object properties: @@ -323,7 +322,6 @@ paths: config: type: object required: [id, version, metadata, config] - deployment: type: object properties: @@ -338,7 +336,6 @@ paths: jobAgentId: type: string required: [id, version, slug, systemId, jobAgentId] - runbook: type: object properties: @@ -351,7 +348,6 @@ paths: jobAgentId: type: string required: [id, name, systemId, jobAgentId] - target: type: object properties: @@ -380,7 +376,6 @@ paths: - workspaceId - config - metadata - environment: type: object properties: @@ -391,15 +386,39 @@ paths: systemId: type: string required: [id, name, systemId] - variables: type: object + approval: + type: object + nullable: true + properties: + id: + type: string + status: + type: string + enum: [pending, approved, rejected] + approver: + type: object + nullable: true + description: Null when status is pending, contains approver details when approved or rejected + properties: + id: + type: string + name: + type: string + required: + - id + - name + required: + - id + - status required: - id - status - createdAt - updatedAt - - variable + - variables + - approval patch: summary: Update a job operationId: updateJob diff --git a/packages/node-sdk/openapitools.json b/packages/node-sdk/openapitools.json index 8113dc7f0..4f0ad37e8 100644 --- a/packages/node-sdk/openapitools.json +++ b/packages/node-sdk/openapitools.json @@ -7,7 +7,7 @@ "v1": { "generatorName": "typescript-fetch", "output": "#{cwd}/src", - "glob": "../../openapi.yaml" + "glob": "../../openapi.v1.yaml" } } } diff --git a/packages/node-sdk/src/.openapi-generator/FILES b/packages/node-sdk/src/.openapi-generator/FILES index a032348a4..91a871bd7 100644 --- a/packages/node-sdk/src/.openapi-generator/FILES +++ b/packages/node-sdk/src/.openapi-generator/FILES @@ -6,6 +6,8 @@ models/CreateRelease200Response.ts models/CreateReleaseRequest.ts models/GetAgentRunningJob200ResponseInner.ts models/GetJob200Response.ts +models/GetJob200ResponseApproval.ts +models/GetJob200ResponseApprovalApprover.ts models/GetJob200ResponseDeployment.ts models/GetJob200ResponseEnvironment.ts models/GetJob200ResponseRelease.ts diff --git a/packages/node-sdk/src/models/GetJob200Response.ts b/packages/node-sdk/src/models/GetJob200Response.ts index 7079fe022..ac754531f 100644 --- a/packages/node-sdk/src/models/GetJob200Response.ts +++ b/packages/node-sdk/src/models/GetJob200Response.ts @@ -12,12 +12,18 @@ * Do not edit the class manually. */ +import type { GetJob200ResponseApproval } from "./GetJob200ResponseApproval"; import type { GetJob200ResponseDeployment } from "./GetJob200ResponseDeployment"; import type { GetJob200ResponseEnvironment } from "./GetJob200ResponseEnvironment"; import type { GetJob200ResponseRelease } from "./GetJob200ResponseRelease"; import type { GetJob200ResponseRunbook } from "./GetJob200ResponseRunbook"; import type { GetJob200ResponseTarget } from "./GetJob200ResponseTarget"; import { mapValues } from "../runtime"; +import { + GetJob200ResponseApprovalFromJSON, + GetJob200ResponseApprovalFromJSONTyped, + GetJob200ResponseApprovalToJSON, +} from "./GetJob200ResponseApproval"; import { GetJob200ResponseDeploymentFromJSON, GetJob200ResponseDeploymentFromJSONTyped, @@ -97,7 +103,13 @@ export interface GetJob200Response { * @type {object} * @memberof GetJob200Response */ - variables?: object; + variables: object; + /** + * + * @type {GetJob200ResponseApproval} + * @memberof GetJob200Response + */ + approval: GetJob200ResponseApproval; } /** @@ -126,6 +138,8 @@ export function instanceOfGetJob200Response( ): value is GetJob200Response { if (!("id" in value) || value["id"] === undefined) return false; if (!("status" in value) || value["status"] === undefined) return false; + if (!("variables" in value) || value["variables"] === undefined) return false; + if (!("approval" in value) || value["approval"] === undefined) return false; return true; } @@ -163,7 +177,8 @@ export function GetJob200ResponseFromJSONTyped( json["environment"] == null ? undefined : GetJob200ResponseEnvironmentFromJSON(json["environment"]), - variables: json["variables"] == null ? undefined : json["variables"], + variables: json["variables"], + approval: GetJob200ResponseApprovalFromJSON(json["approval"]), }; } @@ -180,5 +195,6 @@ export function GetJob200ResponseToJSON(value?: GetJob200Response | null): any { target: GetJob200ResponseTargetToJSON(value["target"]), environment: GetJob200ResponseEnvironmentToJSON(value["environment"]), variables: value["variables"], + approval: GetJob200ResponseApprovalToJSON(value["approval"]), }; } diff --git a/packages/node-sdk/src/models/GetJob200ResponseApproval.ts b/packages/node-sdk/src/models/GetJob200ResponseApproval.ts new file mode 100644 index 000000000..5b93d73fa --- /dev/null +++ b/packages/node-sdk/src/models/GetJob200ResponseApproval.ts @@ -0,0 +1,105 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Ctrlplane API + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import type { GetJob200ResponseApprovalApprover } from "./GetJob200ResponseApprovalApprover"; +import { mapValues } from "../runtime"; +import { + GetJob200ResponseApprovalApproverFromJSON, + GetJob200ResponseApprovalApproverFromJSONTyped, + GetJob200ResponseApprovalApproverToJSON, +} from "./GetJob200ResponseApprovalApprover"; + +/** + * + * @export + * @interface GetJob200ResponseApproval + */ +export interface GetJob200ResponseApproval { + /** + * + * @type {string} + * @memberof GetJob200ResponseApproval + */ + id: string; + /** + * + * @type {string} + * @memberof GetJob200ResponseApproval + */ + status: GetJob200ResponseApprovalStatusEnum; + /** + * + * @type {GetJob200ResponseApprovalApprover} + * @memberof GetJob200ResponseApproval + */ + approver?: GetJob200ResponseApprovalApprover; +} + +/** + * @export + */ +export const GetJob200ResponseApprovalStatusEnum = { + Pending: "pending", + Approved: "approved", + Rejected: "rejected", +} as const; +export type GetJob200ResponseApprovalStatusEnum = + (typeof GetJob200ResponseApprovalStatusEnum)[keyof typeof GetJob200ResponseApprovalStatusEnum]; + +/** + * Check if a given object implements the GetJob200ResponseApproval interface. + */ +export function instanceOfGetJob200ResponseApproval( + value: object, +): value is GetJob200ResponseApproval { + if (!("id" in value) || value["id"] === undefined) return false; + if (!("status" in value) || value["status"] === undefined) return false; + return true; +} + +export function GetJob200ResponseApprovalFromJSON( + json: any, +): GetJob200ResponseApproval { + return GetJob200ResponseApprovalFromJSONTyped(json, false); +} + +export function GetJob200ResponseApprovalFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): GetJob200ResponseApproval { + if (json == null) { + return json; + } + return { + id: json["id"], + status: json["status"], + approver: + json["approver"] == null + ? undefined + : GetJob200ResponseApprovalApproverFromJSON(json["approver"]), + }; +} + +export function GetJob200ResponseApprovalToJSON( + value?: GetJob200ResponseApproval | null, +): any { + if (value == null) { + return value; + } + return { + id: value["id"], + status: value["status"], + approver: GetJob200ResponseApprovalApproverToJSON(value["approver"]), + }; +} diff --git a/packages/node-sdk/src/models/GetJob200ResponseApprovalApprover.ts b/packages/node-sdk/src/models/GetJob200ResponseApprovalApprover.ts new file mode 100644 index 000000000..4978dfdc3 --- /dev/null +++ b/packages/node-sdk/src/models/GetJob200ResponseApprovalApprover.ts @@ -0,0 +1,77 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Ctrlplane API + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from "../runtime"; + +/** + * Null when status is pending, contains approver details when approved or rejected + * @export + * @interface GetJob200ResponseApprovalApprover + */ +export interface GetJob200ResponseApprovalApprover { + /** + * + * @type {string} + * @memberof GetJob200ResponseApprovalApprover + */ + id: string; + /** + * + * @type {string} + * @memberof GetJob200ResponseApprovalApprover + */ + name: string; +} + +/** + * Check if a given object implements the GetJob200ResponseApprovalApprover interface. + */ +export function instanceOfGetJob200ResponseApprovalApprover( + value: object, +): value is GetJob200ResponseApprovalApprover { + if (!("id" in value) || value["id"] === undefined) return false; + if (!("name" in value) || value["name"] === undefined) return false; + return true; +} + +export function GetJob200ResponseApprovalApproverFromJSON( + json: any, +): GetJob200ResponseApprovalApprover { + return GetJob200ResponseApprovalApproverFromJSONTyped(json, false); +} + +export function GetJob200ResponseApprovalApproverFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): GetJob200ResponseApprovalApprover { + if (json == null) { + return json; + } + return { + id: json["id"], + name: json["name"], + }; +} + +export function GetJob200ResponseApprovalApproverToJSON( + value?: GetJob200ResponseApprovalApprover | null, +): any { + if (value == null) { + return value; + } + return { + id: value["id"], + name: value["name"], + }; +} diff --git a/packages/node-sdk/src/models/GetJob200ResponseCausedBy.ts b/packages/node-sdk/src/models/GetJob200ResponseCausedBy.ts new file mode 100644 index 000000000..da440969c --- /dev/null +++ b/packages/node-sdk/src/models/GetJob200ResponseCausedBy.ts @@ -0,0 +1,86 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Ctrlplane API + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from "../runtime"; + +/** + * + * @export + * @interface GetJob200ResponseCausedBy + */ +export interface GetJob200ResponseCausedBy { + /** + * + * @type {string} + * @memberof GetJob200ResponseCausedBy + */ + id: string; + /** + * + * @type {string} + * @memberof GetJob200ResponseCausedBy + */ + name: string; + /** + * + * @type {string} + * @memberof GetJob200ResponseCausedBy + */ + email: string; +} + +/** + * Check if a given object implements the GetJob200ResponseCausedBy interface. + */ +export function instanceOfGetJob200ResponseCausedBy( + value: object, +): value is GetJob200ResponseCausedBy { + if (!("id" in value) || value["id"] === undefined) return false; + if (!("name" in value) || value["name"] === undefined) return false; + if (!("email" in value) || value["email"] === undefined) return false; + return true; +} + +export function GetJob200ResponseCausedByFromJSON( + json: any, +): GetJob200ResponseCausedBy { + return GetJob200ResponseCausedByFromJSONTyped(json, false); +} + +export function GetJob200ResponseCausedByFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): GetJob200ResponseCausedBy { + if (json == null) { + return json; + } + return { + id: json["id"], + name: json["name"], + email: json["email"], + }; +} + +export function GetJob200ResponseCausedByToJSON( + value?: GetJob200ResponseCausedBy | null, +): any { + if (value == null) { + return value; + } + return { + id: value["id"], + name: value["name"], + email: value["email"], + }; +} diff --git a/packages/node-sdk/src/models/index.ts b/packages/node-sdk/src/models/index.ts index 837448176..fc8ae4bc3 100644 --- a/packages/node-sdk/src/models/index.ts +++ b/packages/node-sdk/src/models/index.ts @@ -5,6 +5,8 @@ export * from "./CreateRelease200Response"; export * from "./CreateReleaseRequest"; export * from "./GetAgentRunningJob200ResponseInner"; export * from "./GetJob200Response"; +export * from "./GetJob200ResponseApproval"; +export * from "./GetJob200ResponseApprovalApprover"; export * from "./GetJob200ResponseDeployment"; export * from "./GetJob200ResponseEnvironment"; export * from "./GetJob200ResponseRelease";