diff --git a/API.md b/API.md index 98cfafa..c32d32f 100644 --- a/API.md +++ b/API.md @@ -1216,6 +1216,36 @@ public readonly key: string; --- +### PersistentVolumeClaimRef + +A reference to a PersistentVolumeClaim. + +#### Initializer + +```typescript +import { PersistentVolumeClaimRef } from 'cdk8s-pipelines' + +const persistentVolumeClaimRef: PersistentVolumeClaimRef = { ... } +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| claimName | string | *No description.* | + +--- + +##### `claimName`Required + +```typescript +public readonly claimName: string; +``` + +- *Type:* string + +--- + ### PipelineParam A Pipeline parameter. @@ -1462,6 +1492,7 @@ const pipelineRunSpec: PipelineRunSpec = { ... } | --- | --- | --- | | pipelineRef | PipelineRef | Required `Pipeline` reference. | | params | PipelineRunParam[] | *No description.* | +| workspaces | PipelineRunWorkspace[] | *No description.* | --- @@ -1487,6 +1518,70 @@ public readonly params: PipelineRunParam[]; --- +##### `workspaces`Optional + +```typescript +public readonly workspaces: PipelineRunWorkspace[]; +``` + +- *Type:* PipelineRunWorkspace[] + +--- + +### PipelineRunWorkspace + +The `Workspace` configuration for a `PipelineRun`. + +> [https://tekton.dev/docs/pipelines/pipelineruns/#specifying-workspaces](https://tekton.dev/docs/pipelines/pipelineruns/#specifying-workspaces) + +#### Initializer + +```typescript +import { PipelineRunWorkspace } from 'cdk8s-pipelines' + +const pipelineRunWorkspace: PipelineRunWorkspace = { ... } +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| name | string | *No description.* | +| persistentVolumeClaim | PersistentVolumeClaimRef | *No description.* | +| subPath | string | *No description.* | + +--- + +##### `name`Optional + +```typescript +public readonly name: string; +``` + +- *Type:* string + +--- + +##### `persistentVolumeClaim`Required + +```typescript +public readonly persistentVolumeClaim: PersistentVolumeClaimRef; +``` + +- *Type:* PersistentVolumeClaimRef + +--- + +##### `subPath`Required + +```typescript +public readonly subPath: string; +``` + +- *Type:* string + +--- + ### PipelineSpec The `spec` part of the Pipeline. @@ -2361,6 +2456,8 @@ public readonly logicalID: string; ### ParameterBuilder +Builds the parameters for use by Tasks and Pipelines. + #### Initializers ```typescript @@ -2492,7 +2589,7 @@ Sets the value for the parameter. | description | string | *No description.* | | requiresPipelineParameter | boolean | Returns true if this parameter expects input at the pipeline level. | | defaultValue | string | *No description.* | -| logicalID | string | *No description.* | +| logicalID | string | Gets the logicalID for the `ParameterBuilder`, which is used by the underlying construct. | | name | string | *No description.* | | type | string | Gets the type of the parameter. | | value | string | Gets the value of the parameter. | @@ -2539,6 +2636,8 @@ public readonly logicalID: string; - *Type:* string +Gets the logicalID for the `ParameterBuilder`, which is used by the underlying construct. + --- ##### `name`Optional @@ -2676,7 +2775,8 @@ public withTask(taskB: TaskBuilder): PipelineBuilder | **Name** | **Type** | **Description** | | --- | --- | --- | | name | string | Gets the name of the pipeline. | -| params | PipelineParam[] | Returns the array of `PipelineParam` objects. | +| params | PipelineParam[] | Returns the array of `PipelineParam` objects that represent the parameters configured for the `Pipeline`. | +| workspaces | PipelineWorkspace[] | Returns the array of `PipelineWorkspace` objects that represent the workspaces configured for the `Pipeline`. | --- @@ -2700,7 +2800,7 @@ public readonly params: PipelineParam[]; - *Type:* PipelineParam[] -Returns the array of `PipelineParam` objects. +Returns the array of `PipelineParam` objects that represent the parameters configured for the `Pipeline`. Note this is an "expensive" get because it loops through the tasks in the pipeline and checks for duplicates in the pipeline parameters for each task @@ -2709,6 +2809,23 @@ a local variable before the loop and reference that instead. --- +##### `workspaces`Required + +```typescript +public readonly workspaces: PipelineWorkspace[]; +``` + +- *Type:* PipelineWorkspace[] + +Returns the array of `PipelineWorkspace` objects that represent the workspaces configured for the `Pipeline`. + +This is an "expensive" get because it loops through the workspaces in the +pipeline and checks for duplicates in the pipeline workspaces for each task +workspace found. You should avoid calling this in a loop--instead, declare +a local variable before the loop and reference that instead. + +--- + ### PipelineRunBuilder @@ -2764,6 +2881,7 @@ The `Pipeline` for which to create this run, using the `PipelineBuilder`. | withClusterRoleBindingProps | *No description.* | | withRunParam | Adds a run parameter to the `PipelineRun`. | | withServiceAccount | Uses the provided role name for the `serviceAccountName` on the `PipelineRun`. | +| withWorkspace | Allows you to specify the name of a `PersistentVolumeClaim` but does not do any compile-time validation on the volume claim's name or existence. | --- @@ -2839,6 +2957,40 @@ The name of the service account (`serviceAccountName`) to use. --- +##### `withWorkspace` + +```typescript +public withWorkspace(name: string, claimName: string, subPath: string): PipelineRunBuilder +``` + +Allows you to specify the name of a `PersistentVolumeClaim` but does not do any compile-time validation on the volume claim's name or existence. + +> [https://kubernetes.io/docs/tasks/configure-pod-container/configure-persistent-volume-storage/#create-a-persistentvolumeclaim](https://kubernetes.io/docs/tasks/configure-pod-container/configure-persistent-volume-storage/#create-a-persistentvolumeclaim) + +###### `name`Required + +- *Type:* string + +The name of the workspace in the `PipelineRun` that will be used by the `Pipeline`. + +--- + +###### `claimName`Required + +- *Type:* string + +The name of the `PersistentVolumeClaim` to use for the `workspace`. + +--- + +###### `subPath`Required + +- *Type:* string + +The sub path on the `persistentVolumeClaim` to use for the `workspace`. + +--- + diff --git a/src/builders.ts b/src/builders.ts index 1dad501..4586cb7 100644 --- a/src/builders.ts +++ b/src/builders.ts @@ -7,7 +7,16 @@ import * as fs from 'fs'; import { ApiObject, ApiObjectProps, Yaml } from 'cdk8s'; import { Construct } from 'constructs'; import { buildParam } from './common'; -import { Pipeline, PipelineParam, PipelineRun, PipelineRunParam, PipelineTask, PipelineTaskWorkspace, PipelineWorkspace } from './pipelines'; +import { + Pipeline, + PipelineParam, + PipelineRun, + PipelineRunParam, + PipelineRunWorkspace, + PipelineTask, + PipelineTaskWorkspace, + PipelineWorkspace, +} from './pipelines'; import { Task, TaskEnvValueSource, TaskParam, TaskProps, TaskSpecParam, TaskStep, TaskStepEnv, TaskWorkspace } from './tasks'; const DefaultPipelineServiceAccountName = 'default:pipeline'; @@ -118,6 +127,9 @@ export class WorkspaceBuilder { } +/** + * Builds the parameters for use by Tasks and Pipelines. + */ export class ParameterBuilder { private readonly _logicalID: string; private _name?: string; @@ -132,6 +144,10 @@ export class ParameterBuilder { this._requiresPipelineParam = false; } + /** + * Gets the logicalID for the `ParameterBuilder`, which is used by the underlying + * construct. + */ public get logicalID(): string | undefined { return this._logicalID; } @@ -687,7 +703,8 @@ export class PipelineBuilder { } /** - * Returns the array of `PipelineParam` objects. + * Returns the array of `PipelineParam` objects that represent the parameters + * configured for the `Pipeline`. * * Note this is an "expensive" get because it loops through the tasks in the * pipeline and checks for duplicates in the pipeline parameters for each task @@ -718,6 +735,35 @@ export class PipelineBuilder { return Array.from(pipelineParams.values()); } + /** + * Returns the array of `PipelineWorkspace` objects that represent the workspaces + * configured for the `Pipeline`. + * + * This is an "expensive" get because it loops through the workspaces in the + * pipeline and checks for duplicates in the pipeline workspaces for each task + * workspace found. You should avoid calling this in a loop--instead, declare + * a local variable before the loop and reference that instead. + * + * @returns PipelineWorkspace[] An array of the pipeline workspaces. + */ + public get workspaces(): PipelineWorkspace[] { + const pipelineWorkspaces = new Map(); + this._tasks?.forEach((t) => { + t.workspaces?.forEach((w) => { + // Only add the workspace on the pipeline level if it is not already + // there... + const ws = pipelineWorkspaces.get(w.name!); + if (!ws) { + pipelineWorkspaces.set(w.name!, { + name: w.name, + description: w.description, + }); + } + }); + }); + return Array.from(pipelineWorkspaces.values()); + } + /** * Builds the actual [Pipeline](https://tekton.dev/docs/getting-started/pipelines/) * from the settings configured using the fluid syntax. @@ -725,8 +771,6 @@ export class PipelineBuilder { public buildPipeline(opts: BuilderOptions = DefaultBuilderOptions): void { // TODO: validate the object - const pipelineParams = new Map(); - const pipelineWorkspaces = new Map(); const pipelineTasks: PipelineTask[] = new Array(); // For making a list to make sure that tasks aren't duplicated when doing // the build. Not that it really hurts anything, but it makes the multidoc @@ -739,17 +783,6 @@ export class PipelineBuilder { const taskWorkspaces: PipelineTaskWorkspace[] = new Array(); t.parameters?.forEach(p => { - const pp = pipelineParams.get(p.name!); - if (!pp) { - // Do not add it to the pipeline if there is no need to add it... - if (p.requiresPipelineParameter) { - pipelineParams.set(p.name!, { - name: p.name, - type: p.type, - }); - } - } - taskParams.push({ name: p.logicalID, value: p.value, @@ -757,16 +790,6 @@ export class PipelineBuilder { }); t.workspaces?.forEach((w) => { - // Only add the workspace on the pipeline level if it is not already - // there... - const ws = pipelineWorkspaces.get(w.name!); - if (!ws) { - pipelineWorkspaces.set(w.name!, { - name: w.name, - description: w.description, - }); - } - taskWorkspaces.push({ name: w.logicalID, workspace: w.name, @@ -797,8 +820,8 @@ export class PipelineBuilder { }, spec: { description: this._description, - params: Array.from(pipelineParams.values()), - workspaces: Array.from(pipelineWorkspaces.values()), + params: this.params, + workspaces: this.workspaces, tasks: pipelineTasks, }, }); @@ -837,6 +860,7 @@ export class PipelineRunBuilder { private readonly _id: string; private readonly _pipeline: PipelineBuilder; private readonly _runParams: PipelineRunParam[]; + private readonly _runWorkspaces: PipelineRunWorkspace[]; private _sa: string; private _crbProps: ApiObjectProps; @@ -859,6 +883,7 @@ export class PipelineRunBuilder { this._sa = DefaultPipelineServiceAccountName; this._crbProps = DefaultClusterRoleBindingProps; this._runParams = new Array(); + this._runWorkspaces = new Array(); } /** @@ -882,6 +907,27 @@ export class PipelineRunBuilder { return this; } + /** + * Allows you to specify the name of a `PersistentVolumeClaim` but does not + * do any compile-time validation on the volume claim's name or existence. + * + * @see https://kubernetes.io/docs/tasks/configure-pod-container/configure-persistent-volume-storage/#create-a-persistentvolumeclaim + * + * @param name The name of the workspace in the `PipelineRun` that will be used by the `Pipeline`. + * @param claimName The name of the `PersistentVolumeClaim` to use for the `workspace`. + * @param subPath The sub path on the `persistentVolumeClaim` to use for the `workspace`. + */ + public withWorkspace(name: string, claimName: string, subPath: string): PipelineRunBuilder { + this._runWorkspaces.push({ + name: name, + persistentVolumeClaim: { + claimName: claimName, + }, + subPath: subPath, + }); + return this; + } + public withClusterRoleBindingProps(props: ApiObjectProps): PipelineRunBuilder { this._crbProps = props; return this; @@ -919,6 +965,16 @@ export class PipelineRunBuilder { } }); + // Do the same thing for workspaces. Check to make sure that the workspaces + // expected by the Pipeline are defined in the PipelineRun. + const workspaces: PipelineWorkspace[] = this._pipeline.workspaces; + workspaces.forEach((ws) => { + const pws = this._runWorkspaces.find((obj) => obj.name == ws.name); + if (! pws) { + throw new Error(`Pipeline workspace '${ws.name}' is not defined in PipelineRun '${this._id}'`); + } + }); + new PipelineRun(this._scope, this._id, { metadata: { name: this._id, diff --git a/src/pipelines.ts b/src/pipelines.ts index 637e042..473cfc6 100644 --- a/src/pipelines.ts +++ b/src/pipelines.ts @@ -172,6 +172,23 @@ export interface PipelineRunParam extends NamedResource { readonly value: string; } +/** + * A reference to a PersistentVolumeClaim + */ +export interface PersistentVolumeClaimRef { + readonly claimName: string; +} + +/** + * The `Workspace` configuration for a `PipelineRun`. + * + * @see https://tekton.dev/docs/pipelines/pipelineruns/#specifying-workspaces + */ +export interface PipelineRunWorkspace extends NamedResource { + readonly persistentVolumeClaim: PersistentVolumeClaimRef; + readonly subPath: string; +} + /** * The details for the `PipelineRun`. * @see https://tekton.dev/docs/pipelines/pipelineruns/#configuring-a-pipelinerun @@ -182,6 +199,7 @@ export interface PipelineRunSpec { */ readonly pipelineRef: PipelineRef; readonly params?: PipelineRunParam[]; + readonly workspaces?: PipelineRunWorkspace[]; } export interface PipelineRunProps { diff --git a/test/pipelinebuilder.test.ts b/test/pipelinebuilder.test.ts index fc5f74e..4e3bdb5 100644 --- a/test/pipelinebuilder.test.ts +++ b/test/pipelinebuilder.test.ts @@ -11,6 +11,30 @@ import { } from '../src'; class PipelineRunTest extends Chart { + constructor(scope: Construct, id: string, props?: ChartProps) { + super(scope, id, props); + + const myTask = new TaskBuilder(this, 'fetch-source') + .withName('git-clone') + .withWorkspace(new WorkspaceBuilder('output') + .withName('shared-data') + .withDescription('The files cloned by the task')) + .withStringParam(new ParameterBuilder('url').withPiplineParameter('repo-url', '')); + + const pipeline = new PipelineBuilder(this, 'my-pipeline') + .withName('clone-build-push') + .withDescription('This pipeline closes a repository, builds a Docker image, etc.') + .withTask(myTask); + pipeline.buildPipeline({ includeDependencies: true }); + + new PipelineRunBuilder(this, 'my-pipeline-run', pipeline) + .withRunParam('repo-url', 'https://github.com/exmaple/my-repo') + .withWorkspace('shared-data', 'dataPVC', 'my-shared-data') + .buildPipelineRun({ includeDependencies: true }); + } +} + +class PipelineRunTestWithUndefinedWorkspaceError extends Chart { constructor(scope: Construct, id: string, props?: ChartProps) { super(scope, id, props); @@ -215,6 +239,14 @@ describe('PipelineBuilderTest', () => { expect(f).toThrowError('PipelineRun parameter \'theundefinedparam\' does not exist in pipeline \'clone-build-push\''); }); + test('PipelineRunBuilderWithWorkspaceError', () => { + const app = Testing.app(); + const f = () => { + new PipelineRunTestWithUndefinedWorkspaceError(app, 'test-chart'); + }; + expect(f).toThrowError('Pipeline workspace \'shared-data\' is not defined in PipelineRun \'my-pipeline-run\''); + }); + test('PipelineBuilderWithComplexTasks', () => { const app = Testing.app(); const chart = new MySecondTestChart(app, 'my-second-test-chart');