From dbdb089d5e235f8927ae4b609b847c78ab0ce3e5 Mon Sep 17 00:00:00 2001 From: snehajais22 Date: Thu, 18 Jul 2024 15:24:57 -0400 Subject: [PATCH 1/2] feature: using resolvers to specify remote tasks in taskRef field Signed-off-by: snehajais22 --- API.md | 272 ++++++++++++++++-- src/builders.ts | 111 +++++-- src/pipelines.ts | 4 +- src/tasks.ts | 23 ++ .../pipelinebuilder.test.ts.snap | 62 ++++ test/pipelinebuilder.test.ts | 38 +++ 6 files changed, 466 insertions(+), 44 deletions(-) diff --git a/API.md b/API.md index 6be3a65..ea13cd9 100644 --- a/API.md +++ b/API.md @@ -1806,7 +1806,7 @@ const pipelineTask: PipelineTask = { ... } | name | string | *No description.* | | params | TaskParam[] | *No description.* | | runAfter | string[] | *No description.* | -| taskRef | TaskRef | *No description.* | +| taskRef | TaskRef \| RemoteTaskRef | *No description.* | | workspaces | PipelineTaskWorkspace[] | *No description.* | --- @@ -1844,10 +1844,10 @@ public readonly runAfter: string[]; ##### `taskRef`Optional ```typescript -public readonly taskRef: TaskRef; +public readonly taskRef: TaskRef | RemoteTaskRef; ``` -- *Type:* TaskRef +- *Type:* TaskRef | RemoteTaskRef --- @@ -1880,7 +1880,7 @@ const pipelineTaskDef: PipelineTaskDef = { ... } | name | string | *No description.* | | params | TaskParam[] | *No description.* | | runAfter | string[] | *No description.* | -| taskRef | TaskRef | *No description.* | +| taskRef | TaskRef \| RemoteTaskRef | *No description.* | | workspaces | PipelineTaskWorkspace[] | *No description.* | | refParams | PipelineParam[] | *No description.* | | refWorkspaces | PipelineTaskWorkspace[] | *No description.* | @@ -1920,10 +1920,10 @@ public readonly runAfter: string[]; ##### `taskRef`Optional ```typescript -public readonly taskRef: TaskRef; +public readonly taskRef: TaskRef | RemoteTaskRef; ``` -- *Type:* TaskRef +- *Type:* TaskRef | RemoteTaskRef --- @@ -2042,6 +2042,49 @@ The description of the workspace. --- +### ResolverParam + +A Resolver parameter value. + +#### Initializer + +```typescript +import { ResolverParam } from 'cdk8s-pipelines' + +const resolverParam: ResolverParam = { ... } +``` + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| name | string | *No description.* | +| value | string | The value of the resolver parameter. | + +--- + +##### `name`Optional + +```typescript +public readonly name: string; +``` + +- *Type:* string + +--- + +##### `value`Optional + +```typescript +public readonly value: string; +``` + +- *Type:* string + +The value of the resolver parameter. + +--- + ### TaskEnvValueSource The source for a `env` `valueFrom`. @@ -2652,6 +2695,88 @@ public readonly logicalID: string; ## Classes +### ClusterTaskResolver + +- *Implements:* IRemoteTaskResolver + +Resolves the provided cluster-scoped task into yaml for the taskRef field. + +#### Initializers + +```typescript +import { ClusterTaskResolver } from 'cdk8s-pipelines' + +new ClusterTaskResolver(name: string, namespace: string) +``` + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| name | string | The name of the cluster-scoped task. | +| namespace | string | The namespace of the cluster-scoped task. | + +--- + +##### `name`Required + +- *Type:* string + +The name of the cluster-scoped task. + +--- + +##### `namespace`Required + +- *Type:* string + +The namespace of the cluster-scoped task. + +--- + + + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| taskRef | RemoteTaskRef | Gets the YAML representation of cluster-scoped task. | +| params | ResolverParam[] | *No description.* | +| resolver | string | *No description.* | + +--- + +##### `taskRef`Required + +```typescript +public readonly taskRef: RemoteTaskRef; +``` + +- *Type:* RemoteTaskRef + +Gets the YAML representation of cluster-scoped task. + +--- + +##### `params`Optional + +```typescript +public readonly params: ResolverParam[]; +``` + +- *Type:* ResolverParam[] + +--- + +##### `resolver`Optional + +```typescript +public readonly resolver: string; +``` + +- *Type:* string + +--- + + ### ConstantStringValueResolver - *Implements:* IValueResolver @@ -3282,6 +3407,71 @@ The sub path on the `persistentVolumeClaim` to use for the `workspace`. +### RemoteTaskRef + +A remote `Task` reference. + +Will be generated as a `taskRef`. + +#### Initializers + +```typescript +import { RemoteTaskRef } from 'cdk8s-pipelines' + +new RemoteTaskRef(resolver: string, params: ResolverParam[]) +``` + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| resolver | string | *No description.* | +| params | ResolverParam[] | *No description.* | + +--- + +##### `resolver`Required + +- *Type:* string + +--- + +##### `params`Required + +- *Type:* ResolverParam[] + +--- + + + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| params | ResolverParam[] | *No description.* | +| resolver | string | *No description.* | + +--- + +##### `params`Optional + +```typescript +public readonly params: ResolverParam[]; +``` + +- *Type:* ResolverParam[] + +--- + +##### `resolver`Optional + +```typescript +public readonly resolver: string; +``` + +- *Type:* string + +--- + + ### TaskBuilder Builds Tekton `Task` objects that are independent of a `Pipeline`. @@ -3321,7 +3511,7 @@ new TaskBuilder(scope: Construct, id: string) | **Name** | **Description** | | --- | --- | | buildTask | Builds the `Task`. | -| referencingTask | Sets the taskRef field of the 'Task'. | +| referencingTask | TODO. | | specifyRunAfter | Allows you to specify the names of which task(s), if any, the 'Task' should run after in a pipeline. | | withAnnotation | Adds an annotation to the `Task` `metadata` with the provided key and value. | | withDescription | Sets the `description` of the `Task` being built. | @@ -3345,17 +3535,16 @@ Builds the `Task`. ##### `referencingTask` ```typescript -public referencingTask(taskRef: string): TaskBuilder +public referencingTask(task: string | IRemoteTaskResolver): TaskBuilder ``` -Sets the taskRef field of the 'Task'. +TODO. -Use only for tasks within pipelines: -overrides logicalID as the name of the 'Task' in its individual yaml. +###### `task`Required -###### `taskRef`Required +- *Type:* string | IRemoteTaskResolver -- *Type:* string +TODO. --- @@ -3528,7 +3717,7 @@ Adds the specified workspace to the `Task`. | --- | --- | --- | | logicalID | string | *No description.* | | name | string | Gets the name of the `Task` in the context of a pipeline. | -| taskRef | string | Gets the taskRef field of the `Task` for use within a pipeline. | +| taskRef | TaskRef \| RemoteTaskRef | TODO. | | description | string | Gets the `description` of the `Task`. | | parameters | ParameterBuilder[] | *No description.* | | runAfter | string[] | Gets the list of task names for the runAfter value of the `Task`. | @@ -3563,14 +3752,12 @@ If not set, the 'Task' id is used. ##### `taskRef`Required ```typescript -public readonly taskRef: string; +public readonly taskRef: TaskRef | RemoteTaskRef; ``` -- *Type:* string +- *Type:* TaskRef | RemoteTaskRef -Gets the taskRef field of the `Task` for use within a pipeline. - -If not set, the 'Task' id is used. +TODO. --- @@ -4112,6 +4299,53 @@ Gets the name of the workspace. ## Protocols +### IRemoteTaskResolver + +- *Implemented By:* ClusterTaskResolver, IRemoteTaskResolver + + +#### Properties + +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| taskRef | RemoteTaskRef | Gets the taskRef yaml for a remote Task. | +| params | ResolverParam[] | *No description.* | +| resolver | string | *No description.* | + +--- + +##### `taskRef`Required + +```typescript +public readonly taskRef: RemoteTaskRef; +``` + +- *Type:* RemoteTaskRef + +Gets the taskRef yaml for a remote Task. + +--- + +##### `params`Optional + +```typescript +public readonly params: ResolverParam[]; +``` + +- *Type:* ResolverParam[] + +--- + +##### `resolver`Optional + +```typescript +public readonly resolver: string; +``` + +- *Type:* string + +--- + ### IValueResolver - *Implemented By:* ConstantStringValueResolver, PipelineParameterValueResolver, IValueResolver diff --git a/src/builders.ts b/src/builders.ts index 72abcf8..0d5f7db 100644 --- a/src/builders.ts +++ b/src/builders.ts @@ -17,7 +17,20 @@ import { PipelineTaskWorkspace, PipelineWorkspace, } from './pipelines'; -import { Task, TaskEnvValueSource, TaskParam, TaskProps, TaskSpecParam, TaskSpecResult, TaskStep, TaskStepEnv, TaskWorkspace } from './tasks'; +import { + Task, + TaskEnvValueSource, + TaskParam, + TaskProps, + TaskSpecParam, + TaskSpecResult, + TaskStep, + TaskStepEnv, + TaskWorkspace, + ResolverParam, + RemoteTaskRef, + TaskRef, +} from './tasks'; const DefaultPipelineServiceAccountName = 'default:pipeline'; @@ -640,6 +653,56 @@ export class TaskStepBuilder { } } +export interface IRemoteTaskResolver { + resolver?: string; + params?: ResolverParam[]; + /** + * Gets the taskRef yaml for a remote Task + * @returns RemoteTaskRef The yaml as an API Object + */ + get taskRef(): RemoteTaskRef; +} + +/** + * Resolves the provided cluster-scoped task into yaml for the taskRef field. + */ +export class ClusterTaskResolver implements IRemoteTaskResolver { + resolver?: string; + params?: ResolverParam[]; + + /** + * Creates an instance of the `ClusterTaskResolver`. + * @param name The name of the cluster-scoped task. + * @param namespace The namespace of the cluster-scoped task. + */ + constructor(name: string, namespace: string) { + this.resolver = 'cluster'; + this.params = new Array(); + this.params.push({ + name: 'name', + value: name, + }); + this.params.push({ + name: 'namespace', + value: namespace, + }); + this.params.push({ + name: 'kind', + value: 'task', + }); + } + + /** + * Gets the YAML representation of cluster-scoped task. + */ + get taskRef(): RemoteTaskRef { + return { + resolver: this.resolver, + params: this.params, + }; + } +} + /** * Builds Tekton `Task` objects that are independent of a `Pipeline`. * @@ -653,7 +716,7 @@ export class TaskBuilder { private _steps?: TaskStepBuilder[]; private _name?: string; private _description?: string; - private _taskref?: string; + private _taskref?: TaskRef | RemoteTaskRef; // These were initially arrays, but converted them to maps so that if // multiple values are added that the last one will win. private _workspaces = new Map; @@ -830,21 +893,23 @@ export class TaskBuilder { } /** - * Sets the taskRef field of the 'Task'. Use only for tasks within pipelines: - * overrides logicalID as the name of the 'Task' in its individual yaml. - * @param taskRef + * TODO + * @param task TODO */ - public referencingTask(taskRef: string): TaskBuilder { - this._taskref = taskRef; + public referencingTask(task: string | IRemoteTaskResolver): TaskBuilder { + if (typeof(task) == 'string') { + this._taskref = { name: task }; + } else { + this._taskref = task.taskRef; + } return this; } /** - * Gets the taskRef field of the `Task` for use within a pipeline. - * If not set, the 'Task' id is used. + * TODO */ - public get taskRef(): string { - return this._taskref || this._id; + public get taskRef(): TaskRef | RemoteTaskRef { + return this._taskref || { name: this._id }; } /** @@ -878,9 +943,12 @@ export class TaskBuilder { }); }); + // Note: buildTask called for this TaskBuilder object only if this.taskRef is a TaskRef + const taskName = ('name' in this.taskRef) ? this.taskRef.name : this.logicalID; + const props: TaskProps = { metadata: { - name: this.taskRef, + name: taskName, labels: this._labels, annotations: this._annotations, }, @@ -1100,16 +1168,17 @@ export class PipelineBuilder { pipelineTasks.push(pt); - if (opts.includeDependencies) { + if (opts.includeDependencies && 'name' in t.taskRef) { // Build the task if the user has asked for the dependencies to be - // built along with the pipeline, but only if we haven't already - // built the taskRef yet. + // built along with the pipeline, but only if the task does not + // reference a remote location and we haven't already built the taskRef. + const buildName = t.taskRef.name!; if (!taskList.find(it => { - return it == t.taskRef; + return it == buildName; })) { t.buildTask(); } - taskList.push(t.taskRef); + taskList.push(buildName); } }); @@ -1132,9 +1201,7 @@ function createOrderedPipelineTask(t: TaskBuilder, after: string[], params: Task if (after.length) { return { name: t.name, - taskRef: { - name: t.taskRef, - }, + taskRef: t.taskRef, runAfter: after, params: params, workspaces: ws, @@ -1142,9 +1209,7 @@ function createOrderedPipelineTask(t: TaskBuilder, after: string[], params: Task } return { name: t.name, - taskRef: { - name: t.taskRef, - }, + taskRef: t.taskRef, params: params, workspaces: ws, }; diff --git a/src/pipelines.ts b/src/pipelines.ts index 473cfc6..5023153 100644 --- a/src/pipelines.ts +++ b/src/pipelines.ts @@ -1,7 +1,7 @@ import { ApiObject, ApiObjectMetadata, GroupVersionKind } from 'cdk8s'; import { Construct } from 'constructs'; import { NamedResource, TektonV1ApiVersion } from './common'; -import { TaskParam, TaskRef } from './tasks'; +import { RemoteTaskRef, TaskParam, TaskRef } from './tasks'; // The following interfaces and classes are strictly for generating the YAML or // JSON in the proper format for the Tekton pipelines. See the builders to use @@ -23,7 +23,7 @@ export interface PipelineTaskWorkspace extends NamedResource { * A task in a pipeline. See https://tekton.dev/docs/pipelines/pipelines/#adding-tasks-to-the-pipeline */ export interface PipelineTask extends NamedResource { - readonly taskRef?: TaskRef; + readonly taskRef?: TaskRef | RemoteTaskRef; readonly params?: TaskParam[]; readonly runAfter?: string[]; readonly workspaces?: PipelineTaskWorkspace[]; diff --git a/src/tasks.ts b/src/tasks.ts index a7c7e43..f19d209 100644 --- a/src/tasks.ts +++ b/src/tasks.ts @@ -68,6 +68,29 @@ export class TaskRef { } } +/** + * A Resolver parameter value. + */ +export interface ResolverParam extends NamedResource { + /** + * The value of the resolver parameter. + */ + readonly value?: string; +} + +/** + * A remote `Task` reference. Will be generated as a `taskRef`. + */ +export class RemoteTaskRef { + resolver?: string; + params?: ResolverParam[]; + + constructor(resolver: string, params: ResolverParam[]) { + this.resolver = resolver; + this.params = params; + } +} + /** * A Task parameter value. */ diff --git a/test/__snapshots__/pipelinebuilder.test.ts.snap b/test/__snapshots__/pipelinebuilder.test.ts.snap index fa3c47d..72a7643 100644 --- a/test/__snapshots__/pipelinebuilder.test.ts.snap +++ b/test/__snapshots__/pipelinebuilder.test.ts.snap @@ -229,6 +229,68 @@ exports[`PipelineBuilderTest PipelineBuilderWithParameters 1`] = ` ] `; +exports[`PipelineBuilderTest PipelineBuilderWithResolver 1`] = ` +[ + { + "apiVersion": "tekton.dev/v1", + "kind": "Pipeline", + "metadata": { + "name": "clone-build-push", + }, + "spec": { + "description": "This pipeline closes a repository, builds a Docker image, etc.", + "params": [ + { + "default": "", + "name": "repo-url", + "type": "string", + }, + ], + "tasks": [ + { + "name": "fetch-source", + "params": [ + { + "name": "url", + "value": "$(params.repo-url)", + }, + ], + "taskRef": { + "params": [ + { + "name": "name", + "value": "git-clone", + }, + { + "name": "namespace", + "value": "default", + }, + { + "name": "kind", + "value": "task", + }, + ], + "resolver": "cluster", + }, + "workspaces": [ + { + "name": "output", + "workspace": "shared-data", + }, + ], + }, + ], + "workspaces": [ + { + "description": "The files cloned by the task", + "name": "shared-data", + }, + ], + }, + }, +] +`; + exports[`PipelineBuilderTest PipelineBuilderWithRunAfter 1`] = ` [ { diff --git a/test/pipelinebuilder.test.ts b/test/pipelinebuilder.test.ts index e64761b..6b76706 100644 --- a/test/pipelinebuilder.test.ts +++ b/test/pipelinebuilder.test.ts @@ -10,6 +10,7 @@ import { WorkspaceBuilder, fromPipelineParam, constant, + ClusterTaskResolver, } from '../src'; class PipelineRunTest extends Chart { @@ -349,6 +350,36 @@ class MyTestChartWithRunAfterError extends Chart { } } +class PipelineTestWithResolver extends Chart { + constructor(scope: Construct, id: string, props?: ChartProps) { + super(scope, id, props); + + const myWorkspace = new WorkspaceBuilder('output') + .withDescription('The files cloned by the task') + .withBinding('shared-data'); + + const pipelineParam = new ParameterBuilder('repo-url') + .withDefaultValue(''); + + const urlParam = new ParameterBuilder('url') + .withValue(fromPipelineParam(pipelineParam)); + + const resolver = new ClusterTaskResolver('git-clone', 'default'); + + const myTask = new TaskBuilder(this, 'fetch-source') + .referencingTask(resolver) + .withWorkspace(myWorkspace) + .withStringParam(urlParam) + ; + + new PipelineBuilder(this, 'clone-build-push') + .withDescription('This pipeline closes a repository, builds a Docker image, etc.') + .withTask(myTask) + .withStringParam(pipelineParam) + .buildPipeline({ includeDependencies: true }); + } +} + describe('PipelineBuilderTest', () => { test('PipelineRunBuilder', () => { const app = Testing.app(); @@ -438,4 +469,11 @@ describe('PipelineBuilderTest', () => { }; expect(f).toThrowError('\'fetch-source\' supplied as value for runAfter but no such task found in pipeline.'); }); + + test('PipelineBuilderWithResolver', () => { + const app = Testing.app(); + const chart = new PipelineTestWithResolver(app, 'test-chart'); + const results = Testing.synth(chart); + expect(results).toMatchSnapshot(); + }); }); From 0f960e0752e075a3c5ed88614da2b7751397bdb3 Mon Sep 17 00:00:00 2001 From: snehajais22 Date: Fri, 19 Jul 2024 10:58:31 -0400 Subject: [PATCH 2/2] Update documentation for new feature Signed-off-by: snehajais22 --- API.md | 15 ++++++++++----- src/builders.ts | 12 ++++++++---- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/API.md b/API.md index ea13cd9..da0cffb 100644 --- a/API.md +++ b/API.md @@ -3511,7 +3511,7 @@ new TaskBuilder(scope: Construct, id: string) | **Name** | **Description** | | --- | --- | | buildTask | Builds the `Task`. | -| referencingTask | TODO. | +| referencingTask | Sets the taskRef field of the `Task`. | | specifyRunAfter | Allows you to specify the names of which task(s), if any, the 'Task' should run after in a pipeline. | | withAnnotation | Adds an annotation to the `Task` `metadata` with the provided key and value. | | withDescription | Sets the `description` of the `Task` being built. | @@ -3538,13 +3538,16 @@ Builds the `Task`. public referencingTask(task: string | IRemoteTaskResolver): TaskBuilder ``` -TODO. +Sets the taskRef field of the `Task`. + +Use only for tasks within pipelines: +overrides `logicalID as the name of the `Task` in its individual yaml. ###### `task`Required - *Type:* string | IRemoteTaskResolver -TODO. +as string: name of the local task being referenced as IRemoteTaskResolver: resolver for a task in remote location. --- @@ -3717,7 +3720,7 @@ Adds the specified workspace to the `Task`. | --- | --- | --- | | logicalID | string | *No description.* | | name | string | Gets the name of the `Task` in the context of a pipeline. | -| taskRef | TaskRef \| RemoteTaskRef | TODO. | +| taskRef | TaskRef \| RemoteTaskRef | Gets the taskRef field of the `Task` for use within a pipeline. | | description | string | Gets the `description` of the `Task`. | | parameters | ParameterBuilder[] | *No description.* | | runAfter | string[] | Gets the list of task names for the runAfter value of the `Task`. | @@ -3757,7 +3760,9 @@ public readonly taskRef: TaskRef | RemoteTaskRef; - *Type:* TaskRef | RemoteTaskRef -TODO. +Gets the taskRef field of the `Task` for use within a pipeline. + +If not set, a locally-scoped task named with the `logicalID` is used. --- diff --git a/src/builders.ts b/src/builders.ts index 0d5f7db..c3f26a6 100644 --- a/src/builders.ts +++ b/src/builders.ts @@ -893,8 +893,10 @@ export class TaskBuilder { } /** - * TODO - * @param task TODO + * Sets the taskRef field of the `Task`. Use only for tasks within pipelines: + * overrides `logicalID as the name of the `Task` in its individual yaml. + * @param task as string: name of the local task being referenced + * as IRemoteTaskResolver: resolver for a task in remote location */ public referencingTask(task: string | IRemoteTaskResolver): TaskBuilder { if (typeof(task) == 'string') { @@ -906,7 +908,8 @@ export class TaskBuilder { } /** - * TODO + * Gets the taskRef field of the `Task` for use within a pipeline. + * If not set, a locally-scoped task named with the `logicalID` is used. */ public get taskRef(): TaskRef | RemoteTaskRef { return this._taskref || { name: this._id }; @@ -943,7 +946,8 @@ export class TaskBuilder { }); }); - // Note: buildTask called for this TaskBuilder object only if this.taskRef is a TaskRef + // Note: buildTask called for this TaskBuilder object only if this.taskRef is a TaskRef, + // not if it is a RemoteTaskRef const taskName = ('name' in this.taskRef) ? this.taskRef.name : this.logicalID; const props: TaskProps = {