From fb4839b7352b63d41a85ee69b593ffbc74b38187 Mon Sep 17 00:00:00 2001 From: Monica Date: Tue, 27 Jun 2023 11:28:56 -0400 Subject: [PATCH 1/6] Add k8s-ts-wait --- k8s-ts-wait/Pulumi.yaml | 3 ++ k8s-ts-wait/README.md | 63 ++++++++++++++++++++++++++++++ k8s-ts-wait/index.ts | 80 +++++++++++++++++++++++++++++++++++++++ k8s-ts-wait/package.json | 14 +++++++ k8s-ts-wait/tsconfig.json | 18 +++++++++ 5 files changed, 178 insertions(+) create mode 100644 k8s-ts-wait/Pulumi.yaml create mode 100644 k8s-ts-wait/README.md create mode 100644 k8s-ts-wait/index.ts create mode 100644 k8s-ts-wait/package.json create mode 100644 k8s-ts-wait/tsconfig.json diff --git a/k8s-ts-wait/Pulumi.yaml b/k8s-ts-wait/Pulumi.yaml new file mode 100644 index 000000000..0c836e1b1 --- /dev/null +++ b/k8s-ts-wait/Pulumi.yaml @@ -0,0 +1,3 @@ +name: k8s-ts-wait +runtime: nodejs +description: A TypeScript program to deploy Kubernetes jobs with dependencies and wait for them to complete. diff --git a/k8s-ts-wait/README.md b/k8s-ts-wait/README.md new file mode 100644 index 000000000..9df4ee217 --- /dev/null +++ b/k8s-ts-wait/README.md @@ -0,0 +1,63 @@ +# Kubernetes Dependent Jobs and Waiter + +This program creates two Kubernetes Jobs that run the "hello-world" image. It uses an async waitForJob function to wait for the first Job to complete, then creates a second Job with a data dependency on the first Job's completion. The program exports the IDs and status details of both Jobs. + +## Deploying the Example + +### Prerequisites + +To follow this example, you will need: + +1. [Install Pulumi](https://www.pulumi.com/docs/get-started/install/) +1. A local [kubeconfig](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/) if available, or one can be passed as a [provider argument](https://www.pulumi.com/registry/packages/kubernetes/api-docs/provider#inputs) in the request. + +### Steps + +After cloning this repo, from this working directory, run these commands: + +1. Install the required Node.js packages: + + This installs the dependent packages [needed](https://www.pulumi.com/docs/intro/concepts/how-pulumi-works/) for our Pulumi program. + + ```bash + $ npm install + ``` + +1. Create a new Pulumi stack, which is an isolated deployment target for this example: + + ```bash + $ pulumi stack init dev + ``` + +1. Deploy the kubernetes jobs by running `pulumi up`. + + This command shows a preview of the resources that will be created and asks you + whether to proceed with the deployment. Select "yes" to perform the deployment. + + ```bash + $ pulumi up + Updating (dev): + + Type Name Status + + + Outputs: + + Resources: + + Duration: + + Permalink: https://app.pulumi.com/.../k8s-ts-wait/dev/updates/1 + ``` + + Note that the entire deployment will typically take between ## minutes. + + As part of the update, you'll see some .. + +1. From here, feel free to experiment a little bit. Once you've finished experimenting, + tear down your stack's resources by destroying and removing it: + + ```bash + $ pulumi destroy --yes + $ pulumi stack rm --yes + ``` diff --git a/k8s-ts-wait/index.ts b/k8s-ts-wait/index.ts new file mode 100644 index 000000000..d259b3df3 --- /dev/null +++ b/k8s-ts-wait/index.ts @@ -0,0 +1,80 @@ +import * as pulumi from "@pulumi/pulumi"; +import * as k8s from "@pulumi/kubernetes"; +import * as k8sOutput from "@pulumi/kubernetes/types/output"; +import * as k8sapi from '@kubernetes/client-node'; + +// Create a Kubernetes Job with a single container running the "hello-world" image +const job = new k8s.batch.v1.Job("job", { + spec: { + template: { + spec: { + containers: [{ + name: "helloworld", + image: "hello-world", + }], + restartPolicy: "Never", + }, + }, + }, +}); + +// Define an async function to wait for a Kubernetes Job to complete +async function waitForJob(jobMetadata: k8sOutput.meta.v1.ObjectMeta): Promise { + // Only run the waitForJob function during a non-dryRun + if (!pulumi.runtime.isDryRun()) { + + // Load the default KubeConfig from the local environment + const kc = new k8sapi.KubeConfig(); + kc.loadFromDefault(); + + // Initialize a Kubernetes API client using the loaded KubeConfig + const client = kc.makeApiClient(k8sapi.BatchV1Api); + + // Poll the Kubernetes Job status every 10 seconds for up to 10 minutes + for (let i = 0; i <60; i++) { + const jobDetails = (await client.readNamespacedJob(jobMetadata.name, jobMetadata.namespace)).response; + if (jobDetails.body && jobDetails.body.status && jobDetails.body.status.succeeded > 0) { + // Return the Job details once completed successfully + return jobDetails.body; + } + pulumi.log.info(`Waiting for Job to finish (${i})`, job) + // Wait for 10s between polls + await new Promise(r => setTimeout(r,10000)); + } + // Throw an error if the Job did not complete within the 10-minute timeout + throw new Error("timed out waiting for Job to complete"); + } +} + +// WaitForJob for the first Kubernetes Job to complete +const jobDone = job.metadata.apply(metadata => waitForJob(metadata)); + +// Create a second Kubernetes Job with data dependency on the completion of the first Kubernetes Job +const job2 = new k8s.batch.v1.Job("job2", { + metadata: { + annotations: { + // Assign the completionTime of the first Job to an annotation on the second Job + // This enforces a dependency relationship between the jobs + "pulumi-waited-on-completion": jobDone.apply(j => j.status.completionTime), + } + }, + spec: { + template: { + spec: { + containers: [{ + name: "helloworld", + image: "hello-world", + }], + restartPolicy: "Never", + }, + }, + }, +}); +// WaitForJob for the second Kubernetes Job to complete +const job2Done = job2.metadata.apply(metadata => waitForJob(metadata)); + +// Export resource IDs and status details +export const jobId = job.id; +export const jobStatus = job.status; +export const jobDoneDetails = jobDone; +export const job2DoneDetails = job2Done; diff --git a/k8s-ts-wait/package.json b/k8s-ts-wait/package.json new file mode 100644 index 000000000..101d721b5 --- /dev/null +++ b/k8s-ts-wait/package.json @@ -0,0 +1,14 @@ +{ + "name": "k8s-ts", + "main": "index.ts", + "devDependencies": { + "@types/node": "^16" + }, + "dependencies": { + "@kubernetes/client-node": "^0.18.1", + "@pulumi/kubernetes": "^3.0.0", + "@pulumi/pulumi": "^3.0.0", + "install": "^0.13.0", + "npm": "^9.7.2" + } +} diff --git a/k8s-ts-wait/tsconfig.json b/k8s-ts-wait/tsconfig.json new file mode 100644 index 000000000..ab65afa61 --- /dev/null +++ b/k8s-ts-wait/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "strict": true, + "outDir": "bin", + "target": "es2016", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "experimentalDecorators": true, + "pretty": true, + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.ts" + ] +} From 63923acda49501be6784746d23198a93a8c0819e Mon Sep 17 00:00:00 2001 From: Monica Date: Thu, 29 Jun 2023 16:07:34 -0400 Subject: [PATCH 2/6] Update title and name --- k8s-ts-wait/Pulumi.yaml | 2 +- k8s-ts-wait/README.md | 2 +- k8s-ts-wait/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/k8s-ts-wait/Pulumi.yaml b/k8s-ts-wait/Pulumi.yaml index 0c836e1b1..e5f90c8d7 100644 --- a/k8s-ts-wait/Pulumi.yaml +++ b/k8s-ts-wait/Pulumi.yaml @@ -1,3 +1,3 @@ name: k8s-ts-wait runtime: nodejs -description: A TypeScript program to deploy Kubernetes jobs with dependencies and wait for them to complete. +description: A TypeScript program which waits on jobs during a Kubernetes deployment. diff --git a/k8s-ts-wait/README.md b/k8s-ts-wait/README.md index 9df4ee217..922d11fc1 100644 --- a/k8s-ts-wait/README.md +++ b/k8s-ts-wait/README.md @@ -1,4 +1,4 @@ -# Kubernetes Dependent Jobs and Waiter +# Kubernetes Wait for Dependent Jobs This program creates two Kubernetes Jobs that run the "hello-world" image. It uses an async waitForJob function to wait for the first Job to complete, then creates a second Job with a data dependency on the first Job's completion. The program exports the IDs and status details of both Jobs. diff --git a/k8s-ts-wait/package.json b/k8s-ts-wait/package.json index 101d721b5..387320969 100644 --- a/k8s-ts-wait/package.json +++ b/k8s-ts-wait/package.json @@ -1,5 +1,5 @@ { - "name": "k8s-ts", + "name": "k8s-ts-wait", "main": "index.ts", "devDependencies": { "@types/node": "^16" From c70116f54156ab5a6542eddb6d2f9da46bbe8a4f Mon Sep 17 00:00:00 2001 From: Monica Date: Fri, 30 Jun 2023 11:24:46 -0400 Subject: [PATCH 3/6] Add unknown return for preview --- k8s-ts-wait/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/k8s-ts-wait/index.ts b/k8s-ts-wait/index.ts index d259b3df3..55446185a 100644 --- a/k8s-ts-wait/index.ts +++ b/k8s-ts-wait/index.ts @@ -2,6 +2,7 @@ import * as pulumi from "@pulumi/pulumi"; import * as k8s from "@pulumi/kubernetes"; import * as k8sOutput from "@pulumi/kubernetes/types/output"; import * as k8sapi from '@kubernetes/client-node'; +import { unknownValue } from "@pulumi/pulumi/runtime"; // Create a Kubernetes Job with a single container running the "hello-world" image const job = new k8s.batch.v1.Job("job", { @@ -44,6 +45,9 @@ async function waitForJob(jobMetadata: k8sOutput.meta.v1.ObjectMeta): Promise Date: Tue, 7 Nov 2023 14:58:34 -0500 Subject: [PATCH 4/6] Update README --- k8s-ts-wait/README.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/k8s-ts-wait/README.md b/k8s-ts-wait/README.md index 922d11fc1..a463abd36 100644 --- a/k8s-ts-wait/README.md +++ b/k8s-ts-wait/README.md @@ -38,21 +38,27 @@ After cloning this repo, from this working directory, run these commands: $ pulumi up Updating (dev): - Type Name Status + View in Browser (Ctrl+O): https://app.pulumi.com/../k8s-ts-wait/dev/updates/1 + Type Name Status Info + + pulumi:pulumi:Stack k8s-ts-wait-dev creating (8s). + + ├─ kubernetes:batch/v1:Job job created (4s) Waiting for Job "job-b002d656" to succeed (Active: 0 | Succeeded: 1 | Failed: 0) + + └─ kubernetes:batch/v1:Job job2 creating (2s)... Waiting for Job "job2-5f7b1d45" to succeed (Active: 1 | Succeeded: 0 | Failed: 0) Outputs: + job2DoneDetails: {..} + jobDoneDetails : {..} + jobId : {..} + jobStatus : {..} Resources: + + 3 created - Duration: + Duration: 12s - Permalink: https://app.pulumi.com/.../k8s-ts-wait/dev/updates/1 ``` - Note that the entire deployment will typically take between ## minutes. - - As part of the update, you'll see some .. + As part of the update, you'll see the outputs of the program, showing the Job IDs and status details. The second job will not be created until the first job has completed. 1. From here, feel free to experiment a little bit. Once you've finished experimenting, tear down your stack's resources by destroying and removing it: From a9119d036b42120fe4bb474345a5df0b953eec0f Mon Sep 17 00:00:00 2001 From: mnlumi Date: Wed, 8 Nov 2023 09:47:03 -0500 Subject: [PATCH 5/6] Lint corrections --- k8s-ts-wait/index.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/k8s-ts-wait/index.ts b/k8s-ts-wait/index.ts index 55446185a..a9edb0957 100644 --- a/k8s-ts-wait/index.ts +++ b/k8s-ts-wait/index.ts @@ -1,7 +1,9 @@ -import * as pulumi from "@pulumi/pulumi"; +// Copyright 2016-2023, Pulumi Corporation. All rights reserved. + +import * as k8sapi from "@kubernetes/client-node"; import * as k8s from "@pulumi/kubernetes"; import * as k8sOutput from "@pulumi/kubernetes/types/output"; -import * as k8sapi from '@kubernetes/client-node'; +import * as pulumi from "@pulumi/pulumi"; import { unknownValue } from "@pulumi/pulumi/runtime"; // Create a Kubernetes Job with a single container running the "hello-world" image @@ -34,13 +36,13 @@ async function waitForJob(jobMetadata: k8sOutput.meta.v1.ObjectMeta): Promise 0) { + if (jobDetails.body && jobDetails.body.status && jobDetails.body.status.succeeded > 0) { // Return the Job details once completed successfully return jobDetails.body; } - pulumi.log.info(`Waiting for Job to finish (${i})`, job) + pulumi.log.info(`Waiting for Job to finish (${i})`, job); // Wait for 10s between polls - await new Promise(r => setTimeout(r,10000)); + await new Promise(r => setTimeout(r, 10000)); } // Throw an error if the Job did not complete within the 10-minute timeout throw new Error("timed out waiting for Job to complete"); @@ -60,7 +62,7 @@ const job2 = new k8s.batch.v1.Job("job2", { // Assign the completionTime of the first Job to an annotation on the second Job // This enforces a dependency relationship between the jobs "pulumi-waited-on-completion": jobDone.apply(j => j.status.completionTime), - } + }, }, spec: { template: { From 8605f14666895ddbb7aced52e40e219f1f754806 Mon Sep 17 00:00:00 2001 From: mnlumi Date: Wed, 15 Nov 2023 10:52:21 -0500 Subject: [PATCH 6/6] Update packages versions and deps --- k8s-ts-wait/package.json | 12 ++++-------- k8s-ts-wait/tsconfig.json | 18 ------------------ 2 files changed, 4 insertions(+), 26 deletions(-) delete mode 100644 k8s-ts-wait/tsconfig.json diff --git a/k8s-ts-wait/package.json b/k8s-ts-wait/package.json index 387320969..9bd28fb19 100644 --- a/k8s-ts-wait/package.json +++ b/k8s-ts-wait/package.json @@ -1,14 +1,10 @@ { "name": "k8s-ts-wait", "main": "index.ts", - "devDependencies": { - "@types/node": "^16" - }, "dependencies": { - "@kubernetes/client-node": "^0.18.1", - "@pulumi/kubernetes": "^3.0.0", - "@pulumi/pulumi": "^3.0.0", - "install": "^0.13.0", - "npm": "^9.7.2" + "@kubernetes/client-node": "^0.20.0", + "@pulumi/kubernetes": "^4.5.4", + "@pulumi/pulumi": "^3.93.0", + "node": "^21.1.0" } } diff --git a/k8s-ts-wait/tsconfig.json b/k8s-ts-wait/tsconfig.json deleted file mode 100644 index ab65afa61..000000000 --- a/k8s-ts-wait/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "compilerOptions": { - "strict": true, - "outDir": "bin", - "target": "es2016", - "module": "commonjs", - "moduleResolution": "node", - "sourceMap": true, - "experimentalDecorators": true, - "pretty": true, - "noFallthroughCasesInSwitch": true, - "noImplicitReturns": true, - "forceConsistentCasingInFileNames": true - }, - "files": [ - "index.ts" - ] -}