@gapi/gcli 1.8.248 → 1.8.249

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README-CLI.md CHANGED
@@ -33,7 +33,7 @@ Commands:
33
33
  mq-trigger:create [options] Run a lambda for every message on a RabbitMQ queue
34
34
  mq-trigger:update [options] Change a message-queue trigger (same flags as create)
35
35
  mq-trigger:delete [options] Delete a message-queue trigger
36
- cluster:create [options] Provision a private Talos cluster for a project (runs in the background - takes several minutes; use --wait to block)
36
+ cluster:create [options] Provision a private Talos cluster, or with --virtual a virtual cluster, for a project (runs in the background; use --wait to block)
37
37
  cluster:apply [options] Make a private cluster match the `cluster:` section of lambforge.yaml: creates it if missing, then applies topology, workers, control planes, load balancer and autoscaling
38
38
  cluster:list [options] List every private cluster this project has (it may have several - dev/stage/prod, ...)
39
39
  cluster:teardown [options] Tear down one of a project's clusters (runs in the background - use --wait to block)
@@ -47,7 +47,7 @@ Commands:
47
47
  cluster:autoscale [options] Change the worker autoscaling policy - only the flags you pass change
48
48
  cluster:multi-node [options] Convert a single-node cluster to dedicated worker nodes (required for autoscaling)
49
49
  cluster:single-node [options] Convert a multi-node cluster back to a single node (removes the worker nodes)
50
- cluster:load-balancer [options] Add or remove the cluster's load balancer (billed per hour while enabled)
50
+ cluster:load-balancer [options] Add or remove the cluster's load balancer (billed per hour while enabled). On a virtual cluster this is its dedicated LB - its own IP; the platform subdomain keeps working either way
51
51
  rabbitmq:create [options] Provision a RabbitMQ broker for a project (a project may have several)
52
52
  rabbitmq:list [options] List every RabbitMQ broker for a project
53
53
  rabbitmq:delete [options] Delete a RabbitMQ broker
@@ -594,7 +594,7 @@ Options:
594
594
  --- Subcommand: cluster:create ---
595
595
  Usage: gcli cluster:create [options]
596
596
 
597
- Provision a private Talos cluster for a project (runs in the background - takes several minutes; use --wait to block)
597
+ Provision a private Talos cluster, or with --virtual a virtual cluster, for a project (runs in the background; use --wait to block)
598
598
 
599
599
  Options:
600
600
  -p, --project <project>
@@ -605,6 +605,14 @@ Options:
605
605
  -l, --location <location>
606
606
  --workers <workers> Worker count (ignored when --singleNode)
607
607
  --singleNode Control plane carries the workload too
608
+ --virtual A virtual cluster (own Kubernetes API + Fission,
609
+ no servers, ~2 minutes) instead of a private Talos
610
+ cluster
611
+ --host <host> With --virtual: name of a private cluster of this
612
+ project to run on (default: the shared cluster)
613
+ --dedicated-lb With --virtual: give its router its own load
614
+ balancer IP (billed per hour); without it lambdas
615
+ are served on the platform subdomain
608
616
  --wait Block until the cluster is ready (or errors)
609
617
  -h, --help display help for command
610
618
  
@@ -770,7 +778,7 @@ Options:
770
778
  --- Subcommand: cluster:load-balancer ---
771
779
  Usage: gcli cluster:load-balancer [options]
772
780
 
773
- Add or remove the cluster's load balancer (billed per hour while enabled)
781
+ Add or remove the cluster's load balancer (billed per hour while enabled). On a virtual cluster this is its dedicated LB - its own IP; the platform subdomain keeps working either way
774
782
 
775
783
  Options:
776
784
  -p, --project <project>
package/README.md CHANGED
@@ -217,12 +217,22 @@ A virtual cluster has its own Kubernetes API and Fission but no servers: it
217
217
  runs in one namespace of the shared Lambforge cluster, or of one of your
218
218
  private clusters, and is ready in about two minutes. Everything else
219
219
  (environments, lambdas, `--clusterName`, `cluster:kubeconfig`,
220
- `cluster:teardown`) works the same as for a private cluster; scaling and
221
- load balancer commands don't apply.
220
+ `cluster:teardown`) works the same as for a private cluster; scaling
221
+ commands don't apply.
222
+
223
+ Its lambdas are served on a platform subdomain (`https://vc-<id>.lambforge.com`,
224
+ shown in the `router` column of `cluster:list`) at no extra cost. A
225
+ **dedicated load balancer** gives its router an IP of its own, billed per
226
+ started hour like any load balancer; the subdomain keeps working.
227
+ `cluster:kubeconfig` gives you admin access through
228
+ `https://k-<id>.lambforge.com` (the `api` column).
222
229
 
223
230
  ```bash
224
231
  gcli cluster:create --virtual --name dev # on the shared cluster
225
232
  gcli cluster:create --virtual --name stage --host prod # on your private cluster "prod"
233
+ gcli cluster:create --virtual --name demo --dedicated-lb # with its own IP
234
+ gcli cluster:load-balancer --cluster <id> --enable # add the dedicated LB later
235
+ gcli cluster:load-balancer --cluster <id> --disable # release it (subdomain stays)
226
236
  ```
227
237
 
228
238
  ```yaml
@@ -230,8 +240,13 @@ cluster:
230
240
  name: dev
231
241
  provider: vcluster
232
242
  host: prod # optional - omitted = the shared cluster
243
+ loadBalancer: true # optional - dedicated LB; cluster:apply adds/removes it
233
244
  ```
234
245
 
246
+ `cluster:apply` creates the virtual cluster (with the load balancer if set)
247
+ and later adds or removes the dedicated load balancer when `loadBalancer`
248
+ changes; removing it is skipped unless you pass `--yes`.
249
+
235
250
  ### RabbitMQ Brokers
236
251
 
237
252
  A project may have several RabbitMQ brokers, each optionally scoped to one of its private clusters.
@@ -0,0 +1,23 @@
1
+ ## 2026-10-28 - virtual-clusters: cluster:create --dedicated-lb, loadBalancer for virtual clusters, cluster:list URLs
2
+
3
+ **Feature:** virtual-clusters · **Type:** feat
4
+
5
+ - `cluster:create --virtual --dedicated-lb` -> provisionProjectVirtualCluster
6
+ `dedicatedLoadBalancer`; `lambforge.yaml` `loadBalancer: true` on a
7
+ `provider: vcluster` cluster is now accepted (no longer in
8
+ VIRTUAL_UNSUPPORTED): created with it, later added/removed by
9
+ `cluster:apply` (removal = destructive, `--yes`).
10
+ - `cluster:load-balancer` help explains the virtual case (dedicated LB, the
11
+ subdomain stays) - the backend already handles vClusters.
12
+ - `cluster:list`: type, router (lambda URL), api, loadBalancer columns;
13
+ `ClusterFragment` + `routerUrl withLoadBalancer loadBalancerIp`.
14
+ - README virtual-clusters section, README-CLI regenerated (it was also
15
+ missing --virtual/--host), public docs gcli page + What's new.
16
+ - **Test:** `npx jest --coverage=false test/cluster-apply` (plan: create
17
+ with LB in one step, add, destructive remove; command: spec ->
18
+ `dedicatedLoadBalancer: true`).
19
+ - **Debug:** `gcli cluster:apply --dry-run` prints the planned LB step;
20
+ `cluster:list` shows `loadBalancer` pending while the re-apply runs.
21
+ - **Verification:** 119/119 tests, tsc clean, eslint clean; built CLI shows
22
+ `--dedicated-lb` in `cluster:create --help`. Not run against a live
23
+ server (gcli is logged into production).
@@ -1,6 +1,6 @@
1
1
  # Virtual clusters (slice)
2
2
 
3
- **Status:** built and tested end to end on minikube 2026-10-28, not deployed. Feature slug `virtual-clusters`.
3
+ **Status:** in production since 2026-10-28 (shared cluster host, subdomain routing, dedicated LB add-on, token kubeconfig). Feature slug `virtual-clusters`.
4
4
  Canonical doc (how it works, test, debug, limits):
5
5
  [`graphql-server-functions/docs/features/virtual-clusters.md`](../../../../../graphql-server-functions/docs/features/virtual-clusters.md).
6
6
 
@@ -9,4 +9,18 @@ cluster or one of the project's private clusters, `provider: 'vcluster'`.
9
9
 
10
10
  ## This repo
11
11
 
12
- `gcli cluster:create --virtual [--host <private-cluster>]`; in `lambforge.yaml`: `cluster: { name: dev, provider: vcluster, host: prod }` for `cluster:create` / `cluster:apply` (no host = shared cluster; node settings are rejected). Code: `src/commands/cluster/helpers/virtual-cluster.ts`, `helpers/plan.ts`. Test: `npx jest --coverage=false test/cluster-apply`.
12
+ - `gcli cluster:create --virtual [--host <private-cluster>] [--dedicated-lb]`;
13
+ in `lambforge.yaml`: `cluster: { name: dev, provider: vcluster, host: prod,
14
+ loadBalancer: true }` for `cluster:create` / `cluster:apply` (no host =
15
+ shared cluster; node settings are rejected).
16
+ - `loadBalancer` on a virtual cluster = its **dedicated** LB (own IP,
17
+ billed). `cluster:apply` creates with it in one step, later adds/removes it
18
+ (removal is destructive -> needs `--yes`); `cluster:load-balancer
19
+ --enable|--disable` does the same by hand (the backend routes
20
+ enable/disableLoadBalancerProjectCluster to the vCluster re-apply).
21
+ - `cluster:list` columns: `type`, `router` (lambda base URL, the
22
+ `vc-<id>` subdomain), `api` (endpoint - `k-<id>` for vClusters on the
23
+ shared host), `loadBalancer` (IP / pending / off). `ClusterFragment`
24
+ selects `routerUrl withLoadBalancer loadBalancerIp`.
25
+ - Code: `src/commands/cluster/helpers/virtual-cluster.ts`, `helpers/plan.ts`,
26
+ `create.ts`, `apply.ts`, `list.ts`. Test: `npx jest --coverage=false test/cluster-apply`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gapi/gcli",
3
- "version": "1.8.248",
3
+ "version": "1.8.249",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/Stradivario/gapi.git"
package/release/index.js CHANGED
@@ -36155,6 +36155,9 @@ topology {
36155
36155
  singleNode
36156
36156
  }
36157
36157
  endpoint
36158
+ routerUrl
36159
+ withLoadBalancer
36160
+ loadBalancerIp
36158
36161
  error
36159
36162
  `;
36160
36163
  }
@@ -36946,8 +36949,8 @@ var init_gql_client = __esm({
36946
36949
  }).pipe((0, import_operators.map)((res) => res.provisionProjectCluster));
36947
36950
  }
36948
36951
  /** A virtual cluster (vCluster) on the shared cluster or on one of the
36949
- project's private clusters. routerUrl/hostNamespace are selected here
36950
- only, not in ClusterFragment, so older servers keep working. */
36952
+ project's private clusters. `dedicatedLoadBalancer` gives its router
36953
+ its own IP (billed); the platform subdomain works either way. */
36951
36954
  static provisionProjectVirtualCluster(projectId, input) {
36952
36955
  return this.query({
36953
36956
  query: gql`
@@ -36955,14 +36958,15 @@ var init_gql_client = __esm({
36955
36958
  $projectId: String!
36956
36959
  $name: String!
36957
36960
  $hostClusterId: String
36961
+ $dedicatedLoadBalancer: Boolean
36958
36962
  ) {
36959
36963
  provisionProjectVirtualCluster(
36960
36964
  projectId: $projectId
36961
36965
  name: $name
36962
36966
  hostClusterId: $hostClusterId
36967
+ dedicatedLoadBalancer: $dedicatedLoadBalancer
36963
36968
  ) {
36964
36969
  ${ClusterFragment}
36965
- routerUrl
36966
36970
  hostNamespace
36967
36971
  }
36968
36972
  }
@@ -254759,7 +254763,7 @@ var init_virtual_cluster = __esm({
254759
254763
  "src/commands/cluster/helpers/virtual-cluster.ts"() {
254760
254764
  import_rxjs8 = __toESM(require_cjs());
254761
254765
  init_gql_client();
254762
- provisionVirtualCluster = async (projectId, name2, host) => {
254766
+ provisionVirtualCluster = async (projectId, name2, host, dedicatedLoadBalancer) => {
254763
254767
  let hostClusterId;
254764
254768
  if (host) {
254765
254769
  const clusters = await (0, import_rxjs8.lastValueFrom)(
@@ -254777,7 +254781,8 @@ var init_virtual_cluster = __esm({
254777
254781
  return (0, import_rxjs8.lastValueFrom)(
254778
254782
  GraphqlClienAPI.provisionProjectVirtualCluster(projectId, {
254779
254783
  name: name2,
254780
- hostClusterId
254784
+ hostClusterId,
254785
+ ...dedicatedLoadBalancer === void 0 ? {} : { dedicatedLoadBalancer }
254781
254786
  })
254782
254787
  );
254783
254788
  };
@@ -254819,7 +254824,8 @@ var init_create = __esm({
254819
254824
  provisionVirtualCluster(
254820
254825
  projectId,
254821
254826
  cmd.name || data.name,
254822
- cmd.host ?? data.host
254827
+ cmd.host ?? data.host,
254828
+ cmd.dedicatedLb ?? data.loadBalancer
254823
254829
  )
254824
254830
  ) : GraphqlClienAPI.provisionProjectCluster(projectId, {
254825
254831
  name: cmd.name || data.name,
@@ -254960,7 +254966,6 @@ var init_plan = __esm({
254960
254966
  "singleNode",
254961
254967
  "workers",
254962
254968
  "controlPlanes",
254963
- "loadBalancer",
254964
254969
  "autoscale"
254965
254970
  ];
254966
254971
  intIn = (v, min, max) => Number.isInteger(v) && v >= min && v <= max;
@@ -255022,8 +255027,21 @@ var init_plan = __esm({
255022
255027
  if (!current) {
255023
255028
  steps.push({
255024
255029
  kind: "create",
255025
- description: `create virtual cluster "${desired.name}" on ${desired.host ? `"${desired.host}"` : "the shared cluster"}`
255030
+ description: `create virtual cluster "${desired.name}" on ${desired.host ? `"${desired.host}"` : "the shared cluster"}${desired.loadBalancer ? " with a dedicated load balancer" : ""}`
255026
255031
  });
255032
+ } else if (desired.loadBalancer !== void 0 && !!current.withLoadBalancer !== desired.loadBalancer) {
255033
+ steps.push(
255034
+ desired.loadBalancer ? {
255035
+ kind: "load-balancer",
255036
+ enabled: true,
255037
+ description: "add a dedicated load balancer (its own IP, billed; the subdomain stays)"
255038
+ } : {
255039
+ kind: "load-balancer",
255040
+ enabled: false,
255041
+ description: "remove the dedicated load balancer (its IP is released; the subdomain stays)",
255042
+ destructive: true
255043
+ }
255044
+ );
255027
255045
  }
255028
255046
  return { steps, notes };
255029
255047
  }
@@ -255208,7 +255226,12 @@ var init_apply = __esm({
255208
255226
  }
255209
255227
  Logger.info(`-> ${step.description}`);
255210
255228
  if (step.kind === "create") {
255211
- const created = desired.provider === "vcluster" ? await provisionVirtualCluster(projectId, desired.name, desired.host) : await (0, import_rxjs12.lastValueFrom)(
255229
+ const created = desired.provider === "vcluster" ? await provisionVirtualCluster(
255230
+ projectId,
255231
+ desired.name,
255232
+ desired.host,
255233
+ desired.loadBalancer
255234
+ ) : await (0, import_rxjs12.lastValueFrom)(
255212
255235
  GraphqlClienAPI.provisionProjectCluster(projectId, {
255213
255236
  name: desired.name,
255214
255237
  imageId: desired.imageId,
@@ -255293,14 +255316,20 @@ var init_list = __esm({
255293
255316
  "No private clusters for this project yet - this project runs on the shared cluster. See cluster:create."
255294
255317
  );
255295
255318
  } else {
255296
- Logger.table(clusters, [
255297
- "id",
255298
- "name",
255299
- "status",
255300
- "region",
255301
- "serverType",
255302
- "endpoint"
255303
- ]);
255319
+ Logger.table(
255320
+ clusters.map((c) => {
255321
+ const x2 = c;
255322
+ return {
255323
+ id: x2.id,
255324
+ name: x2.name,
255325
+ type: x2.provider === "vcluster" ? "virtual" : x2.provider,
255326
+ status: x2.status,
255327
+ router: x2.routerUrl ?? "-",
255328
+ api: x2.endpoint ?? "-",
255329
+ loadBalancer: x2.withLoadBalancer ? x2.loadBalancerIp ?? "pending" : "off"
255330
+ };
255331
+ })
255332
+ );
255304
255333
  }
255305
255334
  Logger.log("-------------------");
255306
255335
  })
@@ -323006,6 +323035,9 @@ function registerClusterCommands(program2) {
323006
323035
  ).option(
323007
323036
  "--host <host>",
323008
323037
  "With --virtual: name of a private cluster of this project to run on (default: the shared cluster)"
323038
+ ).option(
323039
+ "--dedicated-lb",
323040
+ "With --virtual: give its router its own load balancer IP (billed per hour); without it lambdas are served on the platform subdomain"
323009
323041
  ).option("--wait", "Block until the cluster is ready (or errors)").action(lazy(() => Promise.resolve().then(() => (init_create(), create_exports)).then((m2) => m2.default)));
323010
323042
  program2.command("cluster:apply").description(
323011
323043
  "Make a private cluster match the `cluster:` section of lambforge.yaml: creates it if missing, then applies topology, workers, control planes, load balancer and autoscaling"
@@ -323072,7 +323104,7 @@ function registerClusterCommands(program2) {
323072
323104
  ).option("--wait", "Block until the cluster settles").action(lazy(() => Promise.resolve().then(() => (init_single_node(), single_node_exports)).then((m2) => m2.default)));
323073
323105
  target(
323074
323106
  program2.command("cluster:load-balancer").description(
323075
- "Add or remove the cluster's load balancer (billed per hour while enabled)"
323107
+ "Add or remove the cluster's load balancer (billed per hour while enabled). On a virtual cluster this is its dedicated LB - its own IP; the platform subdomain keeps working either way"
323076
323108
  )
323077
323109
  ).option("--enable", "Add the load balancer").option("--disable", "Remove the load balancer").option("--wait", "Block until the cluster settles").action(lazy(() => Promise.resolve().then(() => (init_load_balancer(), load_balancer_exports)).then((m2) => m2.default)));
323078
323110
  }