This is an automated email from the ASF dual-hosted git repository.

sciabarracom pushed a commit to branch build-local
in repository https://gitbox.apache.org/repos/asf/openserverless-build.git

commit dd9ddc0acbdf35f478e8a76f6d7e2fc52a2f5887
Author: Michele Sciabarra <[email protected]>
AuthorDate: Sat Jun 27 15:57:40 2026 +0000

    Add local kind deploy workflow
---
 Taskfile.yml          |  96 +++++++++++++++++++
 scripts/kind-local.ts | 255 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 351 insertions(+)

diff --git a/Taskfile.yml b/Taskfile.yml
index be4e4bb..ed78540 100644
--- a/Taskfile.yml
+++ b/Taskfile.yml
@@ -29,6 +29,27 @@ vars:
   WORKSPACE_ROOT:
     sh: git rev-parse --show-superproject-working-tree 2>/dev/null || git 
rev-parse --show-toplevel
   BUILD_CONTAINER_IMAGE: openserverless-build-devcontainer:local
+  BUN:
+    sh: |
+      if command -v bun >/dev/null 2>&1; then
+        command -v bun
+        exit 0
+      fi
+      os="$(uname -s | tr A-Z a-z)"
+      arch="$(uname -m)"
+      case "$arch" in
+        x86_64) arch=amd64 ;;
+        aarch64|arm64) arch=arm64 ;;
+      esac
+      if test -x "$HOME/.ops/${os}-${arch}/bin/bun"; then
+        echo "$HOME/.ops/${os}-${arch}/bin/bun"
+      else
+        echo bun
+      fi
+  OPSROOT_JSON:
+    sh: echo "$HOME/.ops/0.1.0/olaris/opsroot.json"
+  KIND_NODE: nuvolaris-control-plane
+  KUBE_NAMESPACE: nuvolaris
   DOCKER_REGISTRY: registry.hub.docker.com/apache
   DIST_DOCKER_REGISTRY: ""
   DIST_DOCKER_REGISTRY_ARG:
@@ -289,6 +310,81 @@ tasks:
       vars:
          ACT: "build"
 
+  deploy-kind-local:
+    desc: build local OpenWhisk images, update OPS root, load kind, and roll 
out controller
+    deps:
+    - build-all
+    cmds:
+    - task: kind:deploy
+
+  opsroot:update:
+    desc: update installed OPS root with local OpenWhisk image tags
+    silent: true
+    cmds:
+    - "{{.BUN}} scripts/kind-local.ts update-opsroot"
+    env:
+      TAG: "{{.TAG}}"
+      DOCKER_REGISTRY: "{{.DOCKER_REGISTRY}}"
+      CONTROLLER_IMAGE: "{{.CONTROLLER_IMAGE}}"
+      INVOKER_IMAGE: "{{.INVOKER_IMAGE}}"
+      STANDALONE_IMAGE: "{{.STANDALONE_IMAGE}}"
+      OPSROOT_JSON: "{{.OPSROOT_JSON}}"
+
+  kind:load:
+    desc: load local OpenWhisk images into the kind node
+    silent: true
+    cmds:
+    - "{{.BUN}} scripts/kind-local.ts load-kind"
+    env:
+      TAG: "{{.TAG}}"
+      DOCKER_REGISTRY: "{{.DOCKER_REGISTRY}}"
+      CONTROLLER_IMAGE: "{{.CONTROLLER_IMAGE}}"
+      INVOKER_IMAGE: "{{.INVOKER_IMAGE}}"
+      STANDALONE_IMAGE: "{{.STANDALONE_IMAGE}}"
+      KIND_NODE: "{{.KIND_NODE}}"
+
+  kind:rollout:
+    desc: update local kind OpenWhisk workloads to the local images
+    silent: true
+    cmds:
+    - "{{.BUN}} scripts/kind-local.ts rollout"
+    env:
+      TAG: "{{.TAG}}"
+      DOCKER_REGISTRY: "{{.DOCKER_REGISTRY}}"
+      CONTROLLER_IMAGE: "{{.CONTROLLER_IMAGE}}"
+      INVOKER_IMAGE: "{{.INVOKER_IMAGE}}"
+      STANDALONE_IMAGE: "{{.STANDALONE_IMAGE}}"
+      KUBE_NAMESPACE: "{{.KUBE_NAMESPACE}}"
+
+  kind:status:
+    desc: show local image, kind image, and pod image status
+    silent: true
+    cmds:
+    - "{{.BUN}} scripts/kind-local.ts status"
+    env:
+      TAG: "{{.TAG}}"
+      DOCKER_REGISTRY: "{{.DOCKER_REGISTRY}}"
+      CONTROLLER_IMAGE: "{{.CONTROLLER_IMAGE}}"
+      INVOKER_IMAGE: "{{.INVOKER_IMAGE}}"
+      STANDALONE_IMAGE: "{{.STANDALONE_IMAGE}}"
+      KIND_NODE: "{{.KIND_NODE}}"
+      KUBE_NAMESPACE: "{{.KUBE_NAMESPACE}}"
+
+  kind:deploy:
+    desc: update OPS root, load local images into kind, and roll out OpenWhisk
+    silent: true
+    cmds:
+    - "{{.BUN}} scripts/kind-local.ts deploy-kind"
+    env:
+      TAG: "{{.TAG}}"
+      DOCKER_REGISTRY: "{{.DOCKER_REGISTRY}}"
+      CONTROLLER_IMAGE: "{{.CONTROLLER_IMAGE}}"
+      INVOKER_IMAGE: "{{.INVOKER_IMAGE}}"
+      STANDALONE_IMAGE: "{{.STANDALONE_IMAGE}}"
+      OPSROOT_JSON: "{{.OPSROOT_JSON}}"
+      KIND_NODE: "{{.KIND_NODE}}"
+      KUBE_NAMESPACE: "{{.KUBE_NAMESPACE}}"
+
   buildx-and-push:
     - task: build:scala
       vars:
diff --git a/scripts/kind-local.ts b/scripts/kind-local.ts
new file mode 100644
index 0000000..214200e
--- /dev/null
+++ b/scripts/kind-local.ts
@@ -0,0 +1,255 @@
+#!/usr/bin/env bun
+
+import { existsSync, readFileSync, writeFileSync } from "fs";
+import { homedir } from "os";
+import { join } from "path";
+
+type ImageMap = {
+  controller: string;
+  invoker: string;
+  standalone: string;
+};
+
+function die(message: string): never {
+  console.error(`ERROR: ${message}`);
+  process.exit(1);
+}
+
+function shellQuote(value: string): string {
+  return `'${value.replace(/'/g, `'\\''`)}'`;
+}
+
+function run(cmd: string[], allowFailure = false): string {
+  const proc = Bun.spawnSync(cmd, {
+    stdout: "pipe",
+    stderr: "pipe",
+  });
+  const stdout = new TextDecoder().decode(proc.stdout);
+  const stderr = new TextDecoder().decode(proc.stderr);
+
+  if (proc.exitCode !== 0 && !allowFailure) {
+    if (stdout.trim()) console.log(stdout.trimEnd());
+    if (stderr.trim()) console.error(stderr.trimEnd());
+    die(`${cmd.join(" ")} failed with exit code ${proc.exitCode}`);
+  }
+
+  return stdout;
+}
+
+function runStreaming(cmd: string[]): void {
+  const proc = Bun.spawnSync(cmd, {
+    stdout: "inherit",
+    stderr: "inherit",
+  });
+  if (proc.exitCode !== 0) {
+    die(`${cmd.join(" ")} failed with exit code ${proc.exitCode}`);
+  }
+}
+
+function env(name: string, fallback = ""): string {
+  return process.env[name] || fallback;
+}
+
+function normalizedRegistry(): string {
+  return env("DOCKER_REGISTRY", 
"registry.hub.docker.com/apache").replace(/\/+$/, "");
+}
+
+function buildTag(): string {
+  const tag = env("TAG");
+  if (!tag) die("TAG is required");
+  return tag;
+}
+
+function images(): ImageMap {
+  const registry = normalizedRegistry();
+  const tag = buildTag();
+  return {
+    controller: `${registry}/${env("CONTROLLER_IMAGE", 
"openserverless-wsk-controller")}:${tag}`,
+    invoker: `${registry}/${env("INVOKER_IMAGE", 
"openserverless-wsk-invoker")}:${tag}`,
+    standalone: `${registry}/${env("STANDALONE_IMAGE", 
"openserverless-wsk-standalone")}:${tag}`,
+  };
+}
+
+function opsrootPath(): string {
+  return env("OPSROOT_JSON", join(homedir(), 
".ops/0.1.0/olaris/opsroot.json"));
+}
+
+function updateOpsroot(dryRun = false): void {
+  const file = opsrootPath();
+  if (!existsSync(file)) die(`opsroot.json not found: ${file}`);
+
+  const data = JSON.parse(readFileSync(file, "utf8"));
+  data.config ??= {};
+  data.config.images ??= {};
+
+  const img = images();
+  const before = JSON.stringify(data.config.images, null, 2);
+  data.config.images.standalone = img.standalone;
+  data.config.images.controller = img.controller;
+  data.config.images.invoker = img.invoker;
+  const after = JSON.stringify(data.config.images, null, 2);
+
+  if (before === after) {
+    console.log(`opsroot unchanged: ${file}`);
+    return;
+  }
+
+  console.log(`updating ${file}`);
+  console.log(`  standalone: ${img.standalone}`);
+  console.log(`  controller:  ${img.controller}`);
+  console.log(`  invoker:     ${img.invoker}`);
+
+  if (dryRun) return;
+
+  const backup = `${file}.bak.${new Date().toISOString().replace(/[-:TZ.]/g, 
"").slice(0, 14)}`;
+  writeFileSync(backup, readFileSync(file));
+  writeFileSync(file, `${JSON.stringify(data, null, 2)}\n`);
+  console.log(`backup: ${backup}`);
+}
+
+function dockerImageExists(image: string): boolean {
+  const proc = Bun.spawnSync(["docker", "image", "inspect", image], {
+    stdout: "ignore",
+    stderr: "ignore",
+  });
+  return proc.exitCode === 0;
+}
+
+function kindNode(): string {
+  return env("KIND_NODE", "nuvolaris-control-plane");
+}
+
+function loadKind(): void {
+  const node = kindNode();
+  const img = images();
+
+  for (const image of [img.controller, img.invoker, img.standalone]) {
+    if (!dockerImageExists(image)) die(`local Docker image not found: 
${image}`);
+    console.log(`loading ${image} into ${node}`);
+    runStreaming([
+      "bash",
+      "-lc",
+      `docker save ${shellQuote(image)} | docker exec -i ${shellQuote(node)} 
ctr --namespace=k8s.io images import -`,
+    ]);
+  }
+}
+
+function kubectlExists(kind: string, name: string): boolean {
+  const ns = env("KUBE_NAMESPACE", "nuvolaris");
+  const proc = Bun.spawnSync(["kubectl", "-n", ns, "get", kind, name], {
+    stdout: "ignore",
+    stderr: "ignore",
+  });
+  return proc.exitCode === 0;
+}
+
+function rollout(): void {
+  const ns = env("KUBE_NAMESPACE", "nuvolaris");
+  const img = images();
+
+  if (kubectlExists("statefulset", "controller")) {
+    runStreaming([
+      "kubectl",
+      "-n",
+      ns,
+      "set",
+      "image",
+      "statefulset/controller",
+      `controller=${img.controller}`,
+    ]);
+    runStreaming(["kubectl", "-n", ns, "rollout", "status", 
"statefulset/controller", "--timeout=180s"]);
+  } else {
+    console.log(`statefulset/controller not found in namespace ${ns}`);
+  }
+
+  if (kubectlExists("statefulset", "invoker")) {
+    runStreaming([
+      "kubectl",
+      "-n",
+      ns,
+      "set",
+      "image",
+      "statefulset/invoker",
+      `invoker=${img.invoker}`,
+    ]);
+    runStreaming(["kubectl", "-n", ns, "rollout", "status", 
"statefulset/invoker", "--timeout=180s"]);
+  } else {
+    console.log(`statefulset/invoker not found in namespace ${ns}; skipping`);
+  }
+}
+
+function status(): void {
+  const ns = env("KUBE_NAMESPACE", "nuvolaris");
+  const img = images();
+  console.log("expected images:");
+  console.log(`  controller: ${img.controller}`);
+  console.log(`  invoker:    ${img.invoker}`);
+  console.log(`  standalone: ${img.standalone}`);
+
+  console.log("\nkind images:");
+  const crictl = run([
+    "bash",
+    "-lc",
+    `docker exec ${shellQuote(kindNode())} crictl images 2>/dev/null | rg 
'openserverless-wsk-(controller|invoker|standalone)' || true`,
+  ], true);
+  console.log(crictl.trim() || "  none");
+
+  console.log("\npods:");
+  const pods = run([
+    "bash",
+    "-lc",
+    `kubectl -n ${shellQuote(ns)} get pods -o jsonpath='{range 
.items[*]}{.metadata.name}{"\\t"}{range .spec.containers[*]}{.image}{" 
"}{end}{"\\n"}{end}' 2>/dev/null | sort || true`,
+  ], true);
+  console.log(pods.trim() || "  none");
+}
+
+function help(): void {
+  console.log(`Usage: bun scripts/kind-local.ts <command>
+
+Commands:
+  update-opsroot      Update OPSROOT_JSON with local OpenWhisk images
+  load-kind           Import local OpenWhisk images into the kind node
+  rollout             Update controller/invoker StatefulSets when present
+  deploy-kind         update-opsroot, load-kind, rollout, status
+  status              Show expected images, kind images, and pod images
+  help                Show this help
+
+Environment:
+  TAG                 Image tag to use
+  DOCKER_REGISTRY     Default: registry.hub.docker.com/apache
+  OPSROOT_JSON        Default: ~/.ops/0.1.0/olaris/opsroot.json
+  KIND_NODE           Default: nuvolaris-control-plane
+  KUBE_NAMESPACE      Default: nuvolaris
+`);
+}
+
+const command = process.argv[2] || "help";
+
+switch (command) {
+  case "update-opsroot":
+    updateOpsroot();
+    break;
+  case "load-kind":
+    loadKind();
+    break;
+  case "rollout":
+    rollout();
+    break;
+  case "deploy-kind":
+    updateOpsroot();
+    loadKind();
+    rollout();
+    status();
+    break;
+  case "status":
+    status();
+    break;
+  case "help":
+  case "--help":
+  case "-h":
+    help();
+    break;
+  default:
+    help();
+    die(`unknown command: ${command}`);
+}

Reply via email to