@highstate/k8s 0.19.1 → 0.21.1

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.
Files changed (107) hide show
  1. package/dist/chunk-23vn2rdc.js +11 -0
  2. package/dist/chunk-2pfx13ay.js +11 -0
  3. package/dist/chunk-46ntav0c.js +299 -0
  4. package/dist/chunk-556pc9e6.js +155 -0
  5. package/dist/chunk-7kgjgcft.js +170 -0
  6. package/dist/{chunk-LGHFSXNT.js → chunk-9hs97f1q.js} +23 -17
  7. package/dist/chunk-aame3x1b.js +11 -0
  8. package/dist/chunk-b05q6fm2.js +37 -0
  9. package/dist/chunk-bmvc9d2d.js +11 -0
  10. package/dist/chunk-de82bbp2.js +7 -0
  11. package/dist/chunk-facs31cb.js +624 -0
  12. package/dist/chunk-h1b79v66.js +1425 -0
  13. package/dist/chunk-k4w9zpn5.js +215 -0
  14. package/dist/chunk-pqc6w52f.js +352 -0
  15. package/dist/chunk-qyshvz32.js +176 -0
  16. package/dist/chunk-tpfyj6fe.js +199 -0
  17. package/dist/chunk-z6bmpnm7.js +180 -0
  18. package/dist/highstate.manifest.json +3 -2
  19. package/dist/impl/dynamic-endpoint-resolver.js +91 -0
  20. package/dist/impl/gateway-route.js +226 -166
  21. package/dist/impl/tls-certificate.js +31 -31
  22. package/dist/index.js +293 -166
  23. package/dist/units/cert-manager/index.js +19 -14
  24. package/dist/units/cluster-patch/index.js +14 -13
  25. package/dist/units/dns01-issuer/index.js +82 -42
  26. package/dist/units/existing-cluster/index.js +59 -26
  27. package/dist/units/gateway-api/index.js +15 -16
  28. package/dist/units/reduced-access-cluster/index.js +32 -36
  29. package/package.json +23 -21
  30. package/src/cluster.ts +12 -8
  31. package/src/config-map.ts +15 -5
  32. package/src/container.ts +4 -2
  33. package/src/cron-job.ts +51 -5
  34. package/src/deployment.ts +49 -18
  35. package/src/gateway/backend.ts +3 -3
  36. package/src/gateway/gateway.ts +12 -56
  37. package/src/helm.ts +354 -22
  38. package/src/impl/dynamic-endpoint-resolver.ts +109 -0
  39. package/src/impl/gateway-route.ts +231 -57
  40. package/src/impl/tls-certificate.ts +8 -3
  41. package/src/index.ts +1 -0
  42. package/src/job.ts +38 -6
  43. package/src/kubectl.ts +166 -0
  44. package/src/namespace.ts +47 -3
  45. package/src/network-policy.ts +1 -1
  46. package/src/pvc.ts +12 -2
  47. package/src/rbac.ts +28 -5
  48. package/src/scripting/bundle.ts +21 -98
  49. package/src/scripting/environment.ts +4 -10
  50. package/src/secret.ts +15 -5
  51. package/src/service.ts +28 -6
  52. package/src/shared.ts +31 -3
  53. package/src/stateful-set.ts +49 -18
  54. package/src/tls.ts +31 -5
  55. package/src/units/cluster-patch/index.ts +5 -5
  56. package/src/units/dns01-issuer/index.ts +56 -12
  57. package/src/units/existing-cluster/index.ts +36 -15
  58. package/src/units/reduced-access-cluster/index.ts +6 -3
  59. package/src/worker.ts +4 -2
  60. package/src/workload.ts +474 -217
  61. package/LICENSE +0 -21
  62. package/dist/chunk-4G6LLC2X.js +0 -240
  63. package/dist/chunk-4G6LLC2X.js.map +0 -1
  64. package/dist/chunk-BR2CLUUD.js +0 -230
  65. package/dist/chunk-BR2CLUUD.js.map +0 -1
  66. package/dist/chunk-DCUMJSO6.js +0 -427
  67. package/dist/chunk-DCUMJSO6.js.map +0 -1
  68. package/dist/chunk-FE4SHRAJ.js +0 -286
  69. package/dist/chunk-FE4SHRAJ.js.map +0 -1
  70. package/dist/chunk-HH2JJELM.js +0 -13
  71. package/dist/chunk-HH2JJELM.js.map +0 -1
  72. package/dist/chunk-KMLRI5UZ.js +0 -155
  73. package/dist/chunk-KMLRI5UZ.js.map +0 -1
  74. package/dist/chunk-LGHFSXNT.js.map +0 -1
  75. package/dist/chunk-MIC2BHGS.js +0 -301
  76. package/dist/chunk-MIC2BHGS.js.map +0 -1
  77. package/dist/chunk-OBDQONMV.js +0 -401
  78. package/dist/chunk-OBDQONMV.js.map +0 -1
  79. package/dist/chunk-P2VOUU7E.js +0 -1626
  80. package/dist/chunk-P2VOUU7E.js.map +0 -1
  81. package/dist/chunk-PZ5AY32C.js +0 -9
  82. package/dist/chunk-PZ5AY32C.js.map +0 -1
  83. package/dist/chunk-RVB4WWZZ.js +0 -267
  84. package/dist/chunk-RVB4WWZZ.js.map +0 -1
  85. package/dist/chunk-TWBMG6TD.js +0 -315
  86. package/dist/chunk-TWBMG6TD.js.map +0 -1
  87. package/dist/chunk-VCXWCZ43.js +0 -279
  88. package/dist/chunk-VCXWCZ43.js.map +0 -1
  89. package/dist/chunk-YIJUVPU2.js +0 -297
  90. package/dist/chunk-YIJUVPU2.js.map +0 -1
  91. package/dist/cron-job-NX4HD4FI.js +0 -8
  92. package/dist/cron-job-NX4HD4FI.js.map +0 -1
  93. package/dist/deployment-O2LJ5WR5.js +0 -8
  94. package/dist/deployment-O2LJ5WR5.js.map +0 -1
  95. package/dist/impl/gateway-route.js.map +0 -1
  96. package/dist/impl/tls-certificate.js.map +0 -1
  97. package/dist/index.js.map +0 -1
  98. package/dist/job-SYME6Y43.js +0 -8
  99. package/dist/job-SYME6Y43.js.map +0 -1
  100. package/dist/stateful-set-VJYKTQ72.js +0 -8
  101. package/dist/stateful-set-VJYKTQ72.js.map +0 -1
  102. package/dist/units/cert-manager/index.js.map +0 -1
  103. package/dist/units/cluster-patch/index.js.map +0 -1
  104. package/dist/units/dns01-issuer/index.js.map +0 -1
  105. package/dist/units/existing-cluster/index.js.map +0 -1
  106. package/dist/units/gateway-api/index.js.map +0 -1
  107. package/dist/units/reduced-access-cluster/index.js.map +0 -1
package/src/namespace.ts CHANGED
@@ -1,6 +1,6 @@
1
- import type { k8s } from "@highstate/library"
2
1
  import { getOrCreate } from "@highstate/contract"
3
- import { toPromise } from "@highstate/pulumi"
2
+ import { k8s } from "@highstate/library"
3
+ import { makeEntityOutput, toPromise } from "@highstate/pulumi"
4
4
  import { core, type types } from "@pulumi/kubernetes"
5
5
  import {
6
6
  type ComponentResourceOptions,
@@ -10,6 +10,7 @@ import {
10
10
  output,
11
11
  type Unwrap,
12
12
  } from "@pulumi/pulumi"
13
+ import { ClusterAccessScope } from "./rbac"
13
14
  import {
14
15
  getProvider,
15
16
  mapMetadata,
@@ -48,6 +49,13 @@ export abstract class Namespace extends Resource {
48
49
  static readonly apiVersion = "v1"
49
50
  static readonly kind = "Namespace"
50
51
 
52
+ /**
53
+ * The cluster entity authorized to port-forward into the namespace.
54
+ *
55
+ * Only created for namespaces created with `create` or `createOrGet` methods, not for wrapped or external namespaces.
56
+ */
57
+ portForwardCluster?: Output<k8s.Cluster>
58
+
51
59
  constructor(
52
60
  type: string,
53
61
  name: string,
@@ -74,7 +82,16 @@ export abstract class Namespace extends Resource {
74
82
  * The Highstate namespace entity.
75
83
  */
76
84
  get entity(): Output<k8s.Namespace> {
77
- return output(this.entityBase) as Output<k8s.Namespace>
85
+ return makeEntityOutput({
86
+ entity: k8s.namespaceEntity,
87
+ identity: this.metadata.uid,
88
+ meta: {
89
+ title: this.metadata.name,
90
+ },
91
+ value: {
92
+ ...this.entityBase,
93
+ },
94
+ })
78
95
  }
79
96
 
80
97
  /**
@@ -298,6 +315,33 @@ class CreatedNamespace extends Namespace {
298
315
  namespace.spec,
299
316
  namespace.status,
300
317
  )
318
+
319
+ const scope = new ClusterAccessScope(
320
+ `${name}-port-forward`,
321
+ {
322
+ namespace: this,
323
+ rules: [
324
+ {
325
+ apiGroups: [""],
326
+ resources: ["services"],
327
+ verbs: ["get"],
328
+ },
329
+ {
330
+ apiGroups: [""],
331
+ resources: ["pods"],
332
+ verbs: ["get", "list"],
333
+ },
334
+ {
335
+ apiGroups: [""],
336
+ resources: ["pods/portforward"],
337
+ verbs: ["create"],
338
+ },
339
+ ],
340
+ },
341
+ { parent: this },
342
+ )
343
+
344
+ this.portForwardCluster = scope.cluster
301
345
  }
302
346
  }
303
347
 
@@ -360,7 +360,7 @@ export type NetworkPolicyArgs = ScopedResourceArgs & {
360
360
  * The pod selector for this network policy.
361
361
  * If not provided, it will select all pods in the namespace.
362
362
  */
363
- selector?: SelectorLike
363
+ selector?: Input<SelectorLike>
364
364
 
365
365
  /**
366
366
  * The rule for incoming traffic.
package/src/pvc.ts CHANGED
@@ -1,10 +1,11 @@
1
- import type { k8s } from "@highstate/library"
2
1
  import { getOrCreate } from "@highstate/contract"
2
+ import { k8s } from "@highstate/library"
3
3
  import {
4
4
  type ComponentResourceOptions,
5
5
  type Input,
6
6
  type Inputs,
7
7
  interpolate,
8
+ makeEntityOutput,
8
9
  type Output,
9
10
  output,
10
11
  toPromise,
@@ -73,7 +74,16 @@ export abstract class PersistentVolumeClaim extends NamespacedResource {
73
74
  * The Highstate PVC entity.
74
75
  */
75
76
  get entity(): Output<k8s.PersistentVolumeClaim> {
76
- return output(this.entityBase)
77
+ return makeEntityOutput({
78
+ entity: k8s.persistentVolumeClaimEntity,
79
+ identity: this.metadata.uid,
80
+ meta: {
81
+ title: this.metadata.name,
82
+ },
83
+ value: {
84
+ ...this.entityBase,
85
+ },
86
+ })
77
87
  }
78
88
 
79
89
  /**
package/src/rbac.ts CHANGED
@@ -1,11 +1,12 @@
1
- import type { k8s } from "@highstate/library"
2
1
  import type { Namespace } from "./namespace"
2
+ import { common, type k8s } from "@highstate/library"
3
3
  import {
4
4
  ComponentResource,
5
5
  type ComponentResourceOptions,
6
6
  type Input,
7
7
  type InputArray,
8
8
  interpolate,
9
+ makeEntity,
9
10
  normalizeInputs,
10
11
  type Output,
11
12
  output,
@@ -15,7 +16,6 @@ import {
15
16
  import { KubeConfig } from "@kubernetes/client-node"
16
17
  import { core, rbac, type types } from "@pulumi/kubernetes"
17
18
  import { map, unique } from "remeda"
18
- import { stringify } from "yaml"
19
19
  import { Secret } from "./secret"
20
20
  import {
21
21
  getNamespaceName,
@@ -185,9 +185,14 @@ export class ClusterAccessScope extends ComponentResource {
185
185
  kubeconfig,
186
186
  newToken: accessTokenSecret.getValue("token"),
187
187
  serviceAccount: serviceAccount.metadata.name,
188
- }).apply(({ cluster, kubeconfig, newToken, serviceAccount }) => {
188
+ serviceAccountId: serviceAccount.metadata.uid,
189
+ }).apply(({ cluster, kubeconfig, newToken, serviceAccount, serviceAccountId }) => {
190
+ if (kubeconfig.content.type !== "embedded-secret") {
191
+ throw new Error("Only embedded secrets are supported for cluster kubeconfig for now")
192
+ }
193
+
189
194
  const config = new KubeConfig()
190
- config.loadFromString(kubeconfig)
195
+ config.loadFromString(kubeconfig.content.value.value)
191
196
 
192
197
  // clear all existing contexts and users
193
198
  config.users = []
@@ -205,7 +210,25 @@ export class ClusterAccessScope extends ComponentResource {
205
210
 
206
211
  return {
207
212
  ...cluster,
208
- kubeconfig: stringify(JSON.parse(config.exportConfig())),
213
+ connectionId: serviceAccountId,
214
+ kubeconfig: makeEntity({
215
+ entity: common.fileEntity,
216
+ identity: `${serviceAccountId}:kubeconfig`,
217
+ meta: {
218
+ title: `kubeconfig for SA ${serviceAccount}`,
219
+ },
220
+ value: {
221
+ content: {
222
+ type: "embedded-secret",
223
+ value: config.exportConfig(),
224
+ },
225
+ meta: {
226
+ name: "kubeconfig",
227
+ contentType: "text/yaml",
228
+ mode: 0o600,
229
+ },
230
+ },
231
+ }),
209
232
  }
210
233
  })
211
234
  }
@@ -12,14 +12,10 @@ import {
12
12
  output,
13
13
  type Unwrap,
14
14
  } from "@pulumi/pulumi"
15
- import { serializeFunction } from "@pulumi/pulumi/runtime/index.js"
16
15
  import { deepmerge } from "deepmerge-ts"
17
- import { readPackageJSON } from "pkg-types"
18
- import { mapValues, omitBy } from "remeda"
19
16
  import { ConfigMap } from "../config-map"
20
17
  import {
21
18
  emptyScriptEnvironment,
22
- functionScriptImages,
23
19
  type ResolvedScriptEnvironment,
24
20
  type ScriptDistribution,
25
21
  type ScriptEnvironment,
@@ -87,35 +83,19 @@ export class ScriptBundle extends ComponentResource {
87
83
  Unwrap<ResolvedScriptEnvironment>
88
84
  >
89
85
 
90
- const hasFunctionScripts = scriptEnvironment.apply(scriptEnvironment => {
91
- return Object.values(scriptEnvironment.files).some(file => typeof file === "function")
92
- })
93
-
94
86
  this.distribution = args.distribution
95
87
  this.environment = scriptEnvironment.environment
96
88
 
97
- this.image = hasFunctionScripts.apply(hasFunctionScripts =>
98
- output(
99
- hasFunctionScripts
100
- ? functionScriptImages[args.distribution]
101
- : scriptEnvironment[args.distribution].image,
102
- ),
103
- )
89
+ this.image = scriptEnvironment[args.distribution].image
104
90
 
105
- this.allowedEndpoints = output({ scriptEnvironment, hasFunctionScripts }).apply(
106
- ({ scriptEnvironment, hasFunctionScripts }) => {
107
- const allowedEndpoints = [
108
- ...scriptEnvironment.allowedEndpoints,
109
- ...scriptEnvironment[args.distribution].allowedEndpoints,
110
- ]
111
-
112
- if (hasFunctionScripts) {
113
- allowedEndpoints.push("tcp://registry.npmjs.org:443")
114
- }
91
+ this.allowedEndpoints = scriptEnvironment.apply(scriptEnvironment => {
92
+ const allowedEndpoints = [
93
+ ...scriptEnvironment.allowedEndpoints,
94
+ ...scriptEnvironment[args.distribution].allowedEndpoints,
95
+ ]
115
96
 
116
- return allowedEndpoints.map(endpoint => parseEndpoint(endpoint))
117
- },
118
- )
97
+ return allowedEndpoints.map(endpoint => parseEndpoint(endpoint))
98
+ })
119
99
 
120
100
  this.configMap = output({ scriptEnvironment, args }).apply(({ scriptEnvironment, args }) => {
121
101
  return ConfigMap.create(
@@ -129,49 +109,32 @@ export class ScriptBundle extends ComponentResource {
129
109
  )
130
110
  })
131
111
 
132
- this.volumes = output({ hasFunctionScripts, volumes: scriptEnvironment.volumes }).apply(
133
- ({ hasFunctionScripts, volumes }) => {
134
- return [
135
- ...volumes,
136
- {
137
- name: this.configMap.metadata.name,
112
+ this.volumes = scriptEnvironment.volumes.apply(volumes => {
113
+ return [
114
+ ...volumes,
115
+ {
116
+ name: this.configMap.metadata.name,
138
117
 
139
- configMap: {
140
- name: this.configMap.metadata.name,
141
- defaultMode: 0o550, // read and execute permissions
142
- },
118
+ configMap: {
119
+ name: this.configMap.metadata.name,
120
+ defaultMode: 0o550, // read and execute permissions
143
121
  },
144
- ...(hasFunctionScripts ? [{ name: "node-modules", emptyDir: {} }] : []),
145
- ]
146
- },
147
- )
122
+ },
123
+ ]
124
+ })
148
125
 
149
- this.volumeMounts = output({
150
- hasFunctionScripts,
151
- volumeMounts: scriptEnvironment.volumeMounts,
152
- }).apply(({ hasFunctionScripts, volumeMounts }) => {
126
+ this.volumeMounts = scriptEnvironment.volumeMounts.apply(volumeMounts => {
153
127
  return [
154
128
  ...volumeMounts,
155
129
  {
156
130
  volume: this.configMap,
157
131
  mountPath: "/scripts",
158
132
  },
159
- ...(hasFunctionScripts
160
- ? [{ name: "node-modules", mountPath: "/scripts/node_modules" }]
161
- : []),
162
133
  ]
163
134
  })
164
135
  }
165
136
  }
166
137
 
167
- function stripWorkspacePrefix(value: string): string {
168
- if (value.startsWith("workspace:")) {
169
- return value.replace("workspace:", "")
170
- }
171
-
172
- return value
173
- }
174
-
175
138
  async function createScriptData(
176
139
  distribution: ScriptDistribution,
177
140
  environment: Unwrap<ResolvedScriptEnvironment>,
@@ -182,48 +145,8 @@ async function createScriptData(
182
145
  const distributionEnvironment = environment[distribution]
183
146
  const setupScripts = { ...environment.setupScripts }
184
147
 
185
- let hasFunctionScripts = false
186
-
187
148
  for (const key in environment.files) {
188
- if (typeof environment.files[key] === "function") {
189
- const serialized = await serializeFunction(environment.files[key])
190
-
191
- scriptData[key] = text`
192
- #!/usr/local/bin/bun
193
-
194
- ${serialized.text}
195
-
196
- exports.${serialized.exportName}()
197
- `
198
-
199
- hasFunctionScripts = true
200
- } else {
201
- scriptData[key] = environment.files[key]
202
- }
203
- }
204
-
205
- if (hasFunctionScripts) {
206
- const packageJson = await readPackageJSON()
207
-
208
- packageJson.dependencies = omitBy(
209
- mapValues(packageJson.dependencies ?? {}, stripWorkspacePrefix),
210
- (_, key) => key.startsWith("@highstate/"),
211
- )
212
-
213
- packageJson.devDependencies = omitBy(
214
- mapValues(packageJson.devDependencies ?? {}, stripWorkspacePrefix),
215
- (_, key) => key.startsWith("@highstate/"),
216
- )
217
-
218
- scriptData["package.json"] = JSON.stringify(packageJson, null, 2)
219
-
220
- setupScripts["resolve-dependencies.sh"] = text`
221
- #!/usr/local/bin/bun
222
- set -e
223
-
224
- cd /scripts
225
- bun install --production
226
- `
149
+ scriptData[key] = environment.files[key]
227
150
  }
228
151
 
229
152
  if (distributionEnvironment.preInstallPackages.length > 0) {
@@ -1,6 +1,7 @@
1
1
  import type { InputEndpoint } from "@highstate/common"
2
2
  import type { Input, InputArray, InputRecord } from "@highstate/pulumi"
3
3
  import type { ContainerEnvironment, ContainerVolumeMount, WorkloadVolume } from "../container"
4
+ import images from "../../assets/images.json"
4
5
 
5
6
  export type ScriptDistribution = "alpine" | "ubuntu"
6
7
 
@@ -38,8 +39,6 @@ export type DistributionEnvironment = {
38
39
  allowedEndpoints?: InputArray<InputEndpoint>
39
40
  }
40
41
 
41
- export type ScriptProgram = () => unknown
42
-
43
42
  export type ScriptEnvironment = {
44
43
  [distribution in ScriptDistribution]?: DistributionEnvironment
45
44
  } & {
@@ -56,7 +55,7 @@ export type ScriptEnvironment = {
56
55
  /**
57
56
  * The arbitrary files available in the environment including scripts.
58
57
  */
59
- files?: InputRecord<string | ScriptProgram>
58
+ files?: InputRecord<string>
60
59
 
61
60
  /**
62
61
  * The volumes that should be defined in the environment.
@@ -94,7 +93,7 @@ const emptyDistributionEnvironment = {
94
93
  export const emptyScriptEnvironment: ResolvedScriptEnvironment = {
95
94
  alpine: {
96
95
  ...emptyDistributionEnvironment,
97
- image: "alpine@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c",
96
+ image: images.alpine.image,
98
97
  allowedEndpoints: [
99
98
  //
100
99
  "tcp://dl-cdn.alpinelinux.org:443",
@@ -104,7 +103,7 @@ export const emptyScriptEnvironment: ResolvedScriptEnvironment = {
104
103
 
105
104
  ubuntu: {
106
105
  ...emptyDistributionEnvironment,
107
- image: "ubuntu@sha256:72297848456d5d37d1262630108ab308d3e9ec7ed1c3286a32fe09856619a782",
106
+ image: images.ubuntu.image,
108
107
  allowedEndpoints: [
109
108
  //
110
109
  "tcp://archive.ubuntu.com:80",
@@ -122,8 +121,3 @@ export const emptyScriptEnvironment: ResolvedScriptEnvironment = {
122
121
  environment: {},
123
122
  allowedEndpoints: [],
124
123
  }
125
-
126
- export const functionScriptImages: Record<ScriptDistribution, string> = {
127
- alpine: "oven/bun@sha256:6b14922b0885c3890cdb0b396090af1da486ba941df5ee94391eef64f7113c61",
128
- ubuntu: "oven/bun@sha256:66b431441dc4c36d7e8164bfc61e6348ec1d7ce2862fc3a29f5dc9856e8205e4",
129
- }
package/src/secret.ts CHANGED
@@ -1,15 +1,16 @@
1
- import type { k8s } from "@highstate/library"
2
1
  import { getOrCreate } from "@highstate/contract"
3
- import { toPromise } from "@highstate/pulumi"
4
- import { core, type types } from "@pulumi/kubernetes"
2
+ import { k8s } from "@highstate/library"
5
3
  import {
6
4
  type ComponentResourceOptions,
7
5
  type Input,
8
6
  type Inputs,
9
7
  interpolate,
8
+ makeEntityOutput,
10
9
  type Output,
11
10
  output,
12
- } from "@pulumi/pulumi"
11
+ toPromise,
12
+ } from "@highstate/pulumi"
13
+ import { core, type types } from "@pulumi/kubernetes"
13
14
  import { Namespace } from "./namespace"
14
15
  import { getProvider, mapMetadata, NamespacedResource, type ScopedResourceArgs } from "./shared"
15
16
 
@@ -56,7 +57,16 @@ export abstract class Secret extends NamespacedResource {
56
57
  * The Highstate secret entity.
57
58
  */
58
59
  get entity(): Output<k8s.Secret> {
59
- return output(this.entityBase)
60
+ return makeEntityOutput({
61
+ entity: k8s.secretEntity,
62
+ identity: this.metadata.uid,
63
+ meta: {
64
+ title: this.metadata.name,
65
+ },
66
+ value: {
67
+ ...this.entityBase,
68
+ },
69
+ })
60
70
  }
61
71
 
62
72
  /**
package/src/service.ts CHANGED
@@ -6,12 +6,13 @@ import {
6
6
  parseL4Protocol,
7
7
  } from "@highstate/common"
8
8
  import { check, getOrCreate } from "@highstate/contract"
9
- import { k8s, type network } from "@highstate/library"
9
+ import { type ImplementationReference, k8s, type network } from "@highstate/library"
10
10
  import {
11
11
  type ComponentResourceOptions,
12
12
  type Input,
13
13
  type Inputs,
14
14
  interpolate,
15
+ makeEntityOutput,
15
16
  normalize,
16
17
  type Output,
17
18
  output,
@@ -101,9 +102,16 @@ export abstract class Service extends NamespacedResource {
101
102
  * The Highstate service entity.
102
103
  */
103
104
  get entity(): Output<k8s.Service> {
104
- return output({
105
- ...this.entityBase,
106
- endpoints: this.endpoints,
105
+ return makeEntityOutput({
106
+ entity: k8s.serviceEntity,
107
+ identity: this.metadata.uid,
108
+ meta: {
109
+ title: this.metadata.name,
110
+ },
111
+ value: {
112
+ ...this.entityBase,
113
+ endpoints: this.endpoints,
114
+ },
107
115
  })
108
116
  }
109
117
 
@@ -230,6 +238,8 @@ export abstract class Service extends NamespacedResource {
230
238
 
231
239
  /**
232
240
  * Returns the endpoints of the service including both internal and external endpoints.
241
+ *
242
+ * Also includes an `implRef` for port-forwarding if the namespace was created with port-forwarding enabled.
233
243
  */
234
244
  get endpoints(): Output<k8s.ServiceEndpoint[]> {
235
245
  return output({
@@ -237,7 +247,8 @@ export abstract class Service extends NamespacedResource {
237
247
  metadata: this.metadata,
238
248
  spec: this.spec,
239
249
  status: this.status,
240
- }).apply(({ cluster, metadata, spec, status }) => {
250
+ portForwardCluster: this.namespace.portForwardCluster,
251
+ }).apply(({ cluster, metadata, spec, status, portForwardCluster }) => {
241
252
  function createMetadata(isInternal: boolean): k8s.EndpointServiceMetadata {
242
253
  return {
243
254
  "k8s.service": {
@@ -252,6 +263,15 @@ export abstract class Service extends NamespacedResource {
252
263
  }
253
264
  }
254
265
 
266
+ const implRef: ImplementationReference | undefined = portForwardCluster
267
+ ? {
268
+ package: "@highstate/k8s",
269
+ data: {
270
+ cluster: portForwardCluster,
271
+ },
272
+ }
273
+ : undefined
274
+
255
275
  const internalHosts = spec.clusterIPs.length
256
276
  ? [...spec.clusterIPs, `${metadata.name}.${metadata.namespace}.svc.cluster.local`]
257
277
  : []
@@ -263,6 +283,7 @@ export abstract class Service extends NamespacedResource {
263
283
  parseEndpoint,
264
284
  endpoint => l3EndpointToL4(endpoint, port.port, parseL4Protocol(port.protocol)),
265
285
  endpoint => addEndpointMetadata(endpoint, createMetadata(true)),
286
+ endpoint => (implRef ? { ...endpoint, implRef } : endpoint),
266
287
  ),
267
288
  ),
268
289
  )
@@ -282,8 +303,9 @@ export abstract class Service extends NamespacedResource {
282
303
  pipe(
283
304
  ip,
284
305
  parseEndpoint,
285
- endpoint => l3EndpointToL4(endpoint, port.port, parseL4Protocol(port.protocol)),
306
+ endpoint => l3EndpointToL4(endpoint, port.nodePort, parseL4Protocol(port.protocol)),
286
307
  endpoint => addEndpointMetadata(endpoint, createMetadata(false)),
308
+ endpoint => (implRef ? { ...endpoint, implRef } : endpoint),
287
309
  ),
288
310
  ),
289
311
  )
package/src/shared.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { PartialKeys } from "@highstate/contract"
2
- import type { k8s } from "@highstate/library"
2
+ import type { common, k8s } from "@highstate/library"
3
3
  import {
4
4
  ComponentResource,
5
5
  type ComponentResourceOptions,
@@ -12,7 +12,7 @@ import {
12
12
  type Unwrap,
13
13
  } from "@highstate/pulumi"
14
14
  import { core, Provider, type types } from "@pulumi/kubernetes"
15
- import * as images from "../assets/images.json"
15
+ import images from "../assets/images.json"
16
16
  import { Namespace } from "./namespace"
17
17
 
18
18
  const providers = new Map<`${string}.${string}`, Provider>()
@@ -24,7 +24,13 @@ export function getProvider(cluster: k8s.Cluster): Provider {
24
24
  return existing
25
25
  }
26
26
 
27
- const provider = new Provider(name, { kubeconfig: secret(cluster.kubeconfig) })
27
+ if (cluster.kubeconfig.content.type !== "embedded-secret") {
28
+ throw new Error("Only embedded secrets are supported for cluster kubeconfig for now")
29
+ }
30
+
31
+ const provider = new Provider(name, {
32
+ kubeconfig: secret(cluster.kubeconfig.content.value.value),
33
+ })
28
34
  providers.set(name, provider)
29
35
 
30
36
  return provider
@@ -36,6 +42,28 @@ export async function getProviderAsync(cluster: Input<k8s.Cluster>): Promise<Pro
36
42
  return getProvider(resolvedCluster)
37
43
  }
38
44
 
45
+ export function getEmbeddedSecretFileContent(file: Input<common.File>): Output<string> {
46
+ return output(file).apply(file => {
47
+ if (file.content.type !== "embedded-secret") {
48
+ throw new Error("Only embedded-secret file contents are supported for kubeconfig for now")
49
+ }
50
+
51
+ return file.content.value.value
52
+ })
53
+ }
54
+
55
+ export function getClusterKubeconfigContent(cluster: Input<k8s.Cluster>): Output<string> {
56
+ return output(cluster).apply(cluster => {
57
+ if (cluster.kubeconfig.content.type !== "embedded-secret") {
58
+ throw new Error(
59
+ "Only embedded-secret file contents are supported for cluster kubeconfig for now",
60
+ )
61
+ }
62
+
63
+ return cluster.kubeconfig.content.value.value
64
+ })
65
+ }
66
+
39
67
  export type NamespaceLike = core.v1.Namespace | Namespace | string
40
68
 
41
69
  export type ScopedResourceArgs = {