@highstate/k8s 0.9.18 → 0.9.20

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 (93) hide show
  1. package/dist/chunk-2EEHJZPD.js +13 -0
  2. package/dist/chunk-2EEHJZPD.js.map +1 -0
  3. package/dist/{chunk-OFFSHGC6.js → chunk-4JGXGN2L.js} +66 -48
  4. package/dist/chunk-4JGXGN2L.js.map +1 -0
  5. package/dist/chunk-A3XGSDIW.js +306 -0
  6. package/dist/chunk-A3XGSDIW.js.map +1 -0
  7. package/dist/chunk-IMTXUK2U.js +244 -0
  8. package/dist/chunk-IMTXUK2U.js.map +1 -0
  9. package/dist/chunk-JYNXQ3I3.js +287 -0
  10. package/dist/chunk-JYNXQ3I3.js.map +1 -0
  11. package/dist/{chunk-5C2BJGES.js → chunk-KDD6XUWM.js} +30 -23
  12. package/dist/chunk-KDD6XUWM.js.map +1 -0
  13. package/dist/chunk-NOFJC3EM.js +236 -0
  14. package/dist/chunk-NOFJC3EM.js.map +1 -0
  15. package/dist/chunk-NXSYCA3V.js +337 -0
  16. package/dist/chunk-NXSYCA3V.js.map +1 -0
  17. package/dist/chunk-SBC3TUIN.js +1513 -0
  18. package/dist/chunk-SBC3TUIN.js.map +1 -0
  19. package/dist/chunk-SI7X6N46.js +338 -0
  20. package/dist/chunk-SI7X6N46.js.map +1 -0
  21. package/dist/chunk-WGMJCZSK.js +360 -0
  22. package/dist/chunk-WGMJCZSK.js.map +1 -0
  23. package/dist/deployment-752P6JIT.js +8 -0
  24. package/dist/{deployment-XK3CDJOE.js.map → deployment-752P6JIT.js.map} +1 -1
  25. package/dist/highstate.manifest.json +8 -7
  26. package/dist/impl/gateway-route.js +123 -0
  27. package/dist/impl/gateway-route.js.map +1 -0
  28. package/dist/impl/tls-certificate.js +32 -0
  29. package/dist/impl/tls-certificate.js.map +1 -0
  30. package/dist/index.js +736 -208
  31. package/dist/index.js.map +1 -1
  32. package/dist/stateful-set-N64YVKR7.js +8 -0
  33. package/dist/{stateful-set-7CAQWTV2.js.map → stateful-set-N64YVKR7.js.map} +1 -1
  34. package/dist/units/cert-manager/index.js +11 -10
  35. package/dist/units/cert-manager/index.js.map +1 -1
  36. package/dist/units/dns01-issuer/index.js +27 -23
  37. package/dist/units/dns01-issuer/index.js.map +1 -1
  38. package/dist/units/existing-cluster/index.js +11 -8
  39. package/dist/units/existing-cluster/index.js.map +1 -1
  40. package/dist/units/gateway-api/index.js +2 -2
  41. package/dist/units/gateway-api/index.js.map +1 -1
  42. package/package.json +39 -13
  43. package/src/cluster.ts +30 -22
  44. package/src/config-map.ts +195 -57
  45. package/src/container.ts +5 -5
  46. package/src/cron-job.ts +403 -31
  47. package/src/deployment.ts +260 -120
  48. package/src/dns01-solver.ts +10 -0
  49. package/src/gateway/backend.ts +2 -2
  50. package/src/gateway/gateway.ts +383 -0
  51. package/src/gateway/http-route.ts +17 -24
  52. package/src/gateway/index.ts +1 -0
  53. package/src/helm.ts +83 -53
  54. package/src/impl/gateway-route.ts +155 -0
  55. package/src/impl/tls-certificate.ts +33 -0
  56. package/src/index.ts +22 -67
  57. package/src/job.ts +393 -28
  58. package/src/namespace.ts +236 -99
  59. package/src/network-policy.ts +216 -165
  60. package/src/network.ts +2 -2
  61. package/src/pvc.ts +266 -65
  62. package/src/rbac.ts +218 -0
  63. package/src/scripting/bundle.ts +9 -20
  64. package/src/scripting/container.ts +1 -1
  65. package/src/scripting/environment.ts +5 -5
  66. package/src/secret.ts +200 -62
  67. package/src/service.ts +288 -158
  68. package/src/shared.ts +94 -67
  69. package/src/stateful-set.ts +270 -117
  70. package/src/tls.ts +344 -0
  71. package/src/units/cert-manager/index.ts +2 -3
  72. package/src/units/dns01-issuer/index.ts +30 -14
  73. package/src/units/existing-cluster/index.ts +10 -7
  74. package/src/units/gateway-api/index.ts +2 -2
  75. package/src/worker.ts +26 -0
  76. package/src/workload.ts +275 -171
  77. package/dist/chunk-5C2BJGES.js.map +0 -1
  78. package/dist/chunk-5TLC5BXR.js +0 -256
  79. package/dist/chunk-5TLC5BXR.js.map +0 -1
  80. package/dist/chunk-BBIY3KUN.js +0 -1557
  81. package/dist/chunk-BBIY3KUN.js.map +0 -1
  82. package/dist/chunk-OFFSHGC6.js.map +0 -1
  83. package/dist/chunk-TZHOUJRC.js +0 -202
  84. package/dist/chunk-TZHOUJRC.js.map +0 -1
  85. package/dist/chunk-YWRJ4EZM.js +0 -192
  86. package/dist/chunk-YWRJ4EZM.js.map +0 -1
  87. package/dist/deployment-XK3CDJOE.js +0 -6
  88. package/dist/stateful-set-7CAQWTV2.js +0 -6
  89. package/dist/units/access-point/index.js +0 -21
  90. package/dist/units/access-point/index.js.map +0 -1
  91. package/src/access-point.ts +0 -191
  92. package/src/units/access-point/index.ts +0 -19
  93. package/src/units/dns01-issuer/solver.ts +0 -23
package/src/pvc.ts CHANGED
@@ -1,26 +1,27 @@
1
1
  import type { k8s } from "@highstate/library"
2
2
  import { core, type types } from "@pulumi/kubernetes"
3
3
  import {
4
- ComponentResource,
5
- Output,
4
+ interpolate,
5
+ type Output,
6
6
  output,
7
7
  type ComponentResourceOptions,
8
- type CustomResourceOptions,
9
8
  type Input,
10
9
  type Inputs,
11
10
  } from "@highstate/pulumi"
11
+ import { toPromise } from "@highstate/pulumi"
12
12
  import { deepmerge } from "deepmerge-ts"
13
13
  import { omit } from "remeda"
14
+ import { getOrCreate } from "@highstate/contract"
14
15
  import {
15
16
  commonExtraArgs,
16
17
  getProvider,
17
18
  mapMetadata,
18
- resourceIdToString,
19
- type CommonArgs,
20
- type ResourceId,
19
+ type ScopedResourceArgs,
20
+ ScopedResource,
21
21
  } from "./shared"
22
+ import { Namespace } from "./namespace"
22
23
 
23
- export type PersistentVolumeClaimArgs = CommonArgs &
24
+ export type PersistentVolumeClaimArgs = ScopedResourceArgs &
24
25
  types.input.core.v1.PersistentVolumeClaimSpec & {
25
26
  /**
26
27
  * The size of the volume to request.
@@ -31,27 +32,28 @@ export type PersistentVolumeClaimArgs = CommonArgs &
31
32
  }
32
33
 
33
34
  export type CreateOrGetPersistentVolumeClaimArgs = PersistentVolumeClaimArgs & {
35
+ /**
36
+ * The PVC entity to patch/retrieve.
37
+ */
34
38
  existing: Input<k8s.PersistentVolumeClaim> | undefined
35
39
  }
36
40
 
37
41
  const extraPersistentVolumeClaimArgs = [...commonExtraArgs, "size"] as const
38
42
 
39
- export abstract class PersistentVolumeClaim extends ComponentResource {
43
+ /**
44
+ * Represents a Kubernetes PersistentVolumeClaim resource with metadata and spec.
45
+ */
46
+ export abstract class PersistentVolumeClaim extends ScopedResource {
40
47
  protected constructor(
41
48
  type: string,
42
49
  name: string,
43
50
  args: Inputs,
44
- opts: ComponentResourceOptions,
51
+ opts: ComponentResourceOptions | undefined,
45
52
 
46
- /**
47
- * The cluster where the PVC is created.
48
- */
49
- readonly cluster: Output<k8s.Cluster>,
50
-
51
- /**
52
- * The metadata of the underlying Kubernetes PVC.
53
- */
54
- readonly metadata: Output<types.output.meta.v1.ObjectMeta>,
53
+ apiVersion: Output<string>,
54
+ kind: Output<string>,
55
+ namespace: Output<Namespace>,
56
+ metadata: Output<types.output.meta.v1.ObjectMeta>,
55
57
 
56
58
  /**
57
59
  * The spec of the underlying Kubernetes PVC.
@@ -63,7 +65,7 @@ export abstract class PersistentVolumeClaim extends ComponentResource {
63
65
  */
64
66
  readonly status: Output<types.output.core.v1.PersistentVolumeClaimStatus>,
65
67
  ) {
66
- super(type, name, args, opts)
68
+ super(type, name, args, opts, apiVersion, kind, namespace, metadata)
67
69
  }
68
70
 
69
71
  /**
@@ -71,81 +73,238 @@ export abstract class PersistentVolumeClaim extends ComponentResource {
71
73
  */
72
74
  get entity(): Output<k8s.PersistentVolumeClaim> {
73
75
  return output({
74
- type: "k8s.persistent-volume-claim",
76
+ type: "persistent-volume-claim",
75
77
  clusterId: this.cluster.id,
78
+ clusterName: this.cluster.name,
76
79
  metadata: this.metadata,
77
80
  })
78
81
  }
79
82
 
83
+ /**
84
+ * Creates a new PVC.
85
+ */
80
86
  static create(
81
87
  name: string,
82
88
  args: PersistentVolumeClaimArgs,
83
- opts: ComponentResourceOptions,
89
+ opts?: ComponentResourceOptions,
84
90
  ): PersistentVolumeClaim {
85
91
  return new CreatedPersistentVolumeClaim(name, args, opts)
86
92
  }
87
93
 
88
- static of(
94
+ /**
95
+ * Creates a new PVC or patches an existing one.
96
+ *
97
+ * @param name The name of the resource. May not be the same as the PVC name.
98
+ * @param args The arguments to create or patch the PVC with.
99
+ * @param opts Optional resource options.
100
+ */
101
+ static createOrPatch(
89
102
  name: string,
90
- entity: Input<k8s.PersistentVolumeClaim>,
91
- cluster: Input<k8s.Cluster>,
92
- opts: ComponentResourceOptions,
103
+ args: CreateOrGetPersistentVolumeClaimArgs,
104
+ opts?: ComponentResourceOptions,
93
105
  ): PersistentVolumeClaim {
94
- return new ExternalPersistentVolumeClaim(name, output(entity).metadata, cluster, opts)
106
+ if (args.existing) {
107
+ return new PersistentVolumeClaimPatch(name, {
108
+ ...args,
109
+ name: output(args.existing).metadata.name,
110
+ })
111
+ }
112
+
113
+ return new CreatedPersistentVolumeClaim(name, args, opts)
95
114
  }
96
115
 
97
- static createOrGet(
116
+ /**
117
+ * Creates a new PVC or gets an existing one.
118
+ *
119
+ * @param name The name of the resource. May not be the same as the PVC name. Will not be used when existing PVC is retrieved.
120
+ * @param args The arguments to create or get the PVC with.
121
+ * @param opts Optional resource options.
122
+ */
123
+ static async createOrGet(
98
124
  name: string,
99
125
  args: CreateOrGetPersistentVolumeClaimArgs,
100
- opts: ComponentResourceOptions,
101
- ): PersistentVolumeClaim {
102
- if (!args.existing) {
103
- return new CreatedPersistentVolumeClaim(name, args, opts)
126
+ opts?: ComponentResourceOptions,
127
+ ): Promise<PersistentVolumeClaim> {
128
+ if (args.existing) {
129
+ return await PersistentVolumeClaim.forAsync(args.existing, output(args.namespace).cluster)
104
130
  }
105
131
 
106
- return new ExternalPersistentVolumeClaim(
132
+ return new CreatedPersistentVolumeClaim(name, args, opts)
133
+ }
134
+
135
+ /**
136
+ * Patches an existing PVC.
137
+ *
138
+ * Will throw an error if the PVC does not exist.
139
+ *
140
+ * @param name The name of the resource. May not be the same as the PVC name.
141
+ * @param args The arguments to patch the PVC with.
142
+ * @param opts Optional resource options.
143
+ */
144
+ static patch(
145
+ name: string,
146
+ args: PersistentVolumeClaimArgs,
147
+ opts?: ComponentResourceOptions,
148
+ ): PersistentVolumeClaim {
149
+ return new PersistentVolumeClaimPatch(name, args, opts)
150
+ }
151
+
152
+ /**
153
+ * Wraps an existing Kubernetes PVC.
154
+ */
155
+ static wrap(
156
+ name: string,
157
+ args: WrappedPersistentVolumeClaimArgs,
158
+ opts?: ComponentResourceOptions,
159
+ ): PersistentVolumeClaim {
160
+ return new WrappedPersistentVolumeClaim(name, args, opts)
161
+ }
162
+
163
+ /**
164
+ * Gets an existing PVC.
165
+ *
166
+ * Will throw an error if the PVC does not exist.
167
+ */
168
+ static get(
169
+ name: string,
170
+ args: ExternalPersistentVolumeClaimArgs,
171
+ opts?: ComponentResourceOptions,
172
+ ): PersistentVolumeClaim {
173
+ return new ExternalPersistentVolumeClaim(name, args, opts)
174
+ }
175
+
176
+ private static readonly pvcCache = new Map<string, PersistentVolumeClaim>()
177
+
178
+ /**
179
+ * Gets an existing PVC for a given entity.
180
+ * Prefer this method over `get` when possible.
181
+ *
182
+ * It automatically names the resource with the following format: `{clusterName}.{namespace}.{name}.{clusterId}`.
183
+ *
184
+ * This method is idempotent and will return the same instance for the same entity.
185
+ *
186
+ * @param entity The entity to get the PVC for.
187
+ * @param cluster The cluster where the PVC is located.
188
+ */
189
+ static for(
190
+ entity: k8s.PersistentVolumeClaim,
191
+ cluster: Input<k8s.Cluster>,
192
+ ): PersistentVolumeClaim {
193
+ return getOrCreate(
194
+ PersistentVolumeClaim.pvcCache,
195
+ `${entity.clusterName}.${entity.metadata.namespace}.${entity.metadata.name}.${entity.clusterId}`,
196
+ name => {
197
+ return PersistentVolumeClaim.get(name, {
198
+ name: entity.metadata.name,
199
+ namespace: Namespace.forResource(entity, cluster),
200
+ })
201
+ },
202
+ )
203
+ }
204
+
205
+ /**
206
+ * Gets an existing PVC for a given entity.
207
+ * Prefer this method over `get` when possible.
208
+ *
209
+ * It automatically names the resource with the following format: `{clusterName}.{namespace}.{name}.{clusterId}`.
210
+ *
211
+ * This method is idempotent and will return the same instance for the same entity.
212
+ *
213
+ * @param entity The entity to get the PVC for.
214
+ * @param cluster The cluster where the PVC is located.
215
+ */
216
+ static async forAsync(
217
+ entity: Input<k8s.PersistentVolumeClaim>,
218
+ cluster: Input<k8s.Cluster>,
219
+ ): Promise<PersistentVolumeClaim> {
220
+ const resolvedEntity = await toPromise(entity)
221
+ return PersistentVolumeClaim.for(resolvedEntity, cluster)
222
+ }
223
+ }
224
+
225
+ class CreatedPersistentVolumeClaim extends PersistentVolumeClaim {
226
+ constructor(name: string, args: PersistentVolumeClaimArgs, opts?: ComponentResourceOptions) {
227
+ const pvc = output(args.namespace).cluster.apply(cluster => {
228
+ return new core.v1.PersistentVolumeClaim(
229
+ name,
230
+ {
231
+ metadata: mapMetadata(args, name),
232
+ spec: output(args).apply(args => {
233
+ return deepmerge(
234
+ {
235
+ accessModes: ["ReadWriteOnce"],
236
+ resources: {
237
+ requests: {
238
+ storage: args.size ?? "100Mi",
239
+ },
240
+ },
241
+ } satisfies types.input.core.v1.PersistentVolumeClaimSpec,
242
+ omit(args, extraPersistentVolumeClaimArgs),
243
+ )
244
+ }),
245
+ },
246
+ {
247
+ ...opts,
248
+ parent: this,
249
+ provider: getProvider(cluster),
250
+ },
251
+ )
252
+ })
253
+
254
+ super(
255
+ "highstate:k8s:PersistentVolumeClaim",
107
256
  name,
108
- output(args.existing).metadata,
109
- args.cluster,
257
+ args,
110
258
  opts,
259
+
260
+ pvc.apiVersion,
261
+ pvc.kind,
262
+ output(args.namespace),
263
+ pvc.metadata,
264
+ pvc.spec,
265
+ pvc.status,
111
266
  )
112
267
  }
113
268
  }
114
269
 
115
- export class CreatedPersistentVolumeClaim extends PersistentVolumeClaim {
116
- constructor(name: string, args: PersistentVolumeClaimArgs, opts: CustomResourceOptions) {
117
- const pvc = output(args).apply(async args => {
118
- return new core.v1.PersistentVolumeClaim(
270
+ class PersistentVolumeClaimPatch extends PersistentVolumeClaim {
271
+ constructor(name: string, args: PersistentVolumeClaimArgs, opts?: ComponentResourceOptions) {
272
+ const pvc = output(args.namespace).cluster.apply(cluster => {
273
+ return new core.v1.PersistentVolumeClaimPatch(
119
274
  name,
120
275
  {
121
276
  metadata: mapMetadata(args, name),
122
- spec: deepmerge(
123
- {
124
- accessModes: ["ReadWriteOnce"],
125
- resources: {
126
- requests: {
127
- storage: args.size ?? "100Mi",
277
+ spec: output(args).apply(args => {
278
+ return deepmerge(
279
+ {
280
+ accessModes: ["ReadWriteOnce"],
281
+ resources: {
282
+ requests: {
283
+ storage: args.size ?? "100Mi",
284
+ },
128
285
  },
129
- },
130
- } satisfies types.input.core.v1.PersistentVolumeClaimSpec,
131
- omit(args, extraPersistentVolumeClaimArgs),
132
- ),
286
+ } satisfies types.input.core.v1.PersistentVolumeClaimSpec,
287
+ omit(args, extraPersistentVolumeClaimArgs),
288
+ )
289
+ }),
133
290
  },
134
291
  {
135
292
  ...opts,
136
293
  parent: this,
137
- provider: await getProvider(args.cluster),
294
+ provider: getProvider(cluster),
138
295
  },
139
296
  )
140
297
  })
141
298
 
142
299
  super(
143
- "k8s:PersistentVolumeClaim",
300
+ "highstate:k8s:PersistentVolumeClaimPatch",
144
301
  name,
145
302
  args,
146
303
  opts,
147
304
 
148
- output(args.cluster),
305
+ pvc.apiVersion,
306
+ pvc.kind,
307
+ output(args.namespace),
149
308
  pvc.metadata,
150
309
  pvc.spec,
151
310
  pvc.status,
@@ -153,33 +312,75 @@ export class CreatedPersistentVolumeClaim extends PersistentVolumeClaim {
153
312
  }
154
313
  }
155
314
 
156
- export class ExternalPersistentVolumeClaim extends PersistentVolumeClaim {
315
+ export type WrappedPersistentVolumeClaimArgs = {
316
+ /**
317
+ * The underlying Kubernetes PVC to wrap.
318
+ */
319
+ pvc: Input<core.v1.PersistentVolumeClaim>
320
+
321
+ /**
322
+ * The namespace where the PVC is located.
323
+ */
324
+ namespace: Input<Namespace>
325
+ }
326
+
327
+ class WrappedPersistentVolumeClaim extends PersistentVolumeClaim {
328
+ constructor(
329
+ name: string,
330
+ args: WrappedPersistentVolumeClaimArgs,
331
+ opts?: ComponentResourceOptions,
332
+ ) {
333
+ super(
334
+ "highstate:k8s:WrappedPersistentVolumeClaim",
335
+ name,
336
+ args,
337
+ opts,
338
+
339
+ output(args.pvc).apiVersion,
340
+ output(args.pvc).kind,
341
+ output(args.namespace),
342
+ output(args.pvc).metadata,
343
+ output(args.pvc).spec,
344
+ output(args.pvc).status,
345
+ )
346
+ }
347
+ }
348
+
349
+ export type ExternalPersistentVolumeClaimArgs = {
350
+ /**
351
+ * The name of the PVC to get.
352
+ */
353
+ name: Input<string>
354
+
355
+ /**
356
+ * The namespace where the PVC is located.
357
+ */
358
+ namespace: Input<Namespace>
359
+ }
360
+
361
+ class ExternalPersistentVolumeClaim extends PersistentVolumeClaim {
157
362
  constructor(
158
363
  name: string,
159
- id: Input<ResourceId>,
160
- cluster: Input<k8s.Cluster>,
161
- opts: ComponentResourceOptions,
364
+ args: ExternalPersistentVolumeClaimArgs,
365
+ opts?: ComponentResourceOptions,
162
366
  ) {
163
- const pvc = output(id).apply(async id => {
367
+ const pvc = output(args.namespace).cluster.apply(cluster => {
164
368
  return core.v1.PersistentVolumeClaim.get(
165
- //
166
369
  name,
167
- resourceIdToString(id),
168
- {
169
- ...opts,
170
- parent: this,
171
- provider: await getProvider(cluster),
172
- },
370
+ interpolate`${output(args.namespace).metadata.name}/${args.name}`,
371
+ { ...opts, parent: this, provider: getProvider(cluster) },
173
372
  )
174
373
  })
175
374
 
176
375
  super(
177
376
  "highstate:k8s:ExternalPersistentVolumeClaim",
178
377
  name,
179
- { id, cluster },
378
+ args,
180
379
  opts,
181
380
 
182
- output(cluster),
381
+ pvc.apiVersion,
382
+ pvc.kind,
383
+ output(args.namespace),
183
384
  pvc.metadata,
184
385
  pvc.spec,
185
386
  pvc.status,
package/src/rbac.ts ADDED
@@ -0,0 +1,218 @@
1
+ import type { k8s } from "@highstate/library"
2
+ import type { Namespace } from "./namespace"
3
+ import {
4
+ ComponentResource,
5
+ type ComponentResourceOptions,
6
+ type Input,
7
+ type InputArray,
8
+ interpolate,
9
+ normalizeInputs,
10
+ type Output,
11
+ output,
12
+ toPromise,
13
+ } from "@highstate/pulumi"
14
+ import { KubeConfig } from "@kubernetes/client-node"
15
+ import { core, rbac, type types } from "@pulumi/kubernetes"
16
+ import { map, unique } from "remeda"
17
+ import { Secret } from "./secret"
18
+ import { getNamespaceName, getProvider, type NamespaceLike, type ScopedResource } from "./shared"
19
+
20
+ export type ClusterAccessScopeArgs = {
21
+ /**
22
+ * The namespace to locate the ServiceAccount in.
23
+ */
24
+ namespace: Input<Namespace>
25
+
26
+ /**
27
+ * The RBAC rule to apply to the `ServiceAccount`.
28
+ *
29
+ * It will be used to create ClusterRole.
30
+ */
31
+ rule?: Input<types.input.rbac.v1.PolicyRule>
32
+
33
+ /**
34
+ * The RBAC rules to apply to the `ServiceAccount`.
35
+ *
36
+ * It will be used to create `ClusterRole`.
37
+ */
38
+ rules?: InputArray<types.input.rbac.v1.PolicyRule>
39
+
40
+ /**
41
+ * Whether to allow the `ServiceAccount` to access resources in the namespace where it is created.
42
+ *
43
+ * By default, it is set to `true`.
44
+ */
45
+ allowOriginNamespace?: boolean
46
+
47
+ /**
48
+ * The extra namespaces to bind to the `ClusterRole` and allow `ServiceAccount` to access them
49
+ * with specified `rules`.
50
+ */
51
+ extraNamespaces?: InputArray<NamespaceLike>
52
+
53
+ /**
54
+ * Whether to create `ClusterRoleBinding` to bind the `ServiceAccount` to the `ClusterRole`.
55
+ *
56
+ * This will allow the `ServiceAccount` to access all namespaces and cluster resources.
57
+ */
58
+ clusterWide?: boolean
59
+ }
60
+
61
+ export class ClusterAccessScope extends ComponentResource {
62
+ /**
63
+ * The cluster entity with the reduced access.
64
+ */
65
+ readonly cluster: Output<k8s.Cluster>
66
+
67
+ constructor(name: string, args: ClusterAccessScopeArgs, opts?: ComponentResourceOptions) {
68
+ super("highstate:k8s:ClusterAccessScope", name, args, opts)
69
+
70
+ const { serviceAccount, kubeconfig } = output(args.namespace).cluster.apply(cluster => {
71
+ const provider = getProvider(cluster)
72
+ const namespaceName = output(args.namespace).metadata.name
73
+
74
+ const serviceAccount = new core.v1.ServiceAccount(
75
+ name,
76
+ {
77
+ metadata: {
78
+ name,
79
+ namespace: namespaceName,
80
+ },
81
+ },
82
+ { provider },
83
+ )
84
+
85
+ const clusterRole = new rbac.v1.ClusterRole(
86
+ name,
87
+ {
88
+ metadata: {
89
+ name: interpolate`highstate.${namespaceName}.${name}`,
90
+ annotations: {
91
+ "kubernetes.io/description": interpolate`Created by Highstate for the ServiceAccount "${name}" in the namespace "${namespaceName}".`,
92
+ },
93
+ },
94
+ rules: normalizeInputs(args.rule, args.rules),
95
+ },
96
+ { provider },
97
+ )
98
+
99
+ const createRoleBinding = (namespace: Input<string>) => {
100
+ return new rbac.v1.RoleBinding(
101
+ name,
102
+ {
103
+ metadata: { name, namespace },
104
+ roleRef: {
105
+ kind: "ClusterRole",
106
+ name: clusterRole.metadata.name,
107
+ apiGroup: "rbac.authorization.k8s.io",
108
+ },
109
+ subjects: [
110
+ {
111
+ kind: "ServiceAccount",
112
+ name: serviceAccount.metadata.name,
113
+ namespace: namespaceName,
114
+ },
115
+ ],
116
+ },
117
+ { provider },
118
+ )
119
+ }
120
+
121
+ if (args.allowOriginNamespace ?? true) {
122
+ createRoleBinding(namespaceName)
123
+ }
124
+
125
+ output(args.extraNamespaces ?? [])
126
+ .apply(map(getNamespaceName))
127
+ .apply(map(createRoleBinding))
128
+
129
+ return { serviceAccount, kubeconfig: cluster.kubeconfig }
130
+ })
131
+
132
+ const accessTokenSecret = Secret.create(`${name}-token`, {
133
+ namespace: args.namespace,
134
+ type: "kubernetes.io/service-account-token",
135
+ metadata: {
136
+ annotations: {
137
+ "kubernetes.io/service-account.name": serviceAccount.metadata.name,
138
+ },
139
+ },
140
+ })
141
+
142
+ this.cluster = output({
143
+ cluster: output(args.namespace).cluster,
144
+ kubeconfig,
145
+ newToken: accessTokenSecret.getValue("token"),
146
+ serviceAccount: serviceAccount.metadata.name,
147
+ }).apply(({ cluster, kubeconfig, newToken, serviceAccount }) => {
148
+ const config = new KubeConfig()
149
+ config.loadFromString(kubeconfig)
150
+
151
+ // clear all existing contexts and users
152
+ config.users = []
153
+ config.contexts = []
154
+
155
+ config.addUser({ name: serviceAccount, token: newToken })
156
+
157
+ config.addContext({
158
+ name: config.clusters[0].name,
159
+ cluster: config.clusters[0].name,
160
+ user: serviceAccount,
161
+ })
162
+
163
+ config.setCurrentContext(config.clusters[0].name)
164
+
165
+ return {
166
+ ...cluster,
167
+ kubeconfig: config.exportConfig(),
168
+ }
169
+ })
170
+ }
171
+
172
+ /**
173
+ * Creates `ClusterAccessScope` for the given resources with the specified verbs.
174
+ *
175
+ * All resources must belong to the same namespace in the same cluster.
176
+ *
177
+ * @param name The name of the resource and the ServiceAccount.
178
+ * @param resources The resources to create access scope for.
179
+ * @param verbs The verbs to allow on the resources.
180
+ */
181
+ static async forResources(
182
+ name: string,
183
+ resources: InputArray<ScopedResource>,
184
+ verbs: string[],
185
+ ): Promise<ClusterAccessScope> {
186
+ const resolved = await toPromise(
187
+ output(resources).apply(resources =>
188
+ resources.map(r => ({
189
+ namespaceId: r.namespace.metadata.uid,
190
+ namespace: r.namespace,
191
+ metadata: r.metadata,
192
+ apiVersion: r.apiVersion,
193
+ kind: r.kind,
194
+ })),
195
+ ),
196
+ )
197
+
198
+ if (resolved.length === 0) {
199
+ throw new Error("No resources provided to forResources.")
200
+ }
201
+
202
+ // verify all resources belong to the same namespace
203
+ if (unique(resolved.map(r => r.namespaceId)).length !== 1) {
204
+ throw new Error("All resources must belong to the same namespace.")
205
+ }
206
+
207
+ return new ClusterAccessScope(name, {
208
+ namespace: resolved[0].namespace,
209
+ rules: resolved.map(r => ({
210
+ apiGroups: r.apiVersion === "v1" ? [""] : [r.apiVersion.split("/")[0]],
211
+ resources: [r.kind.toLowerCase() + (r.metadata?.name ? "s" : "")],
212
+ // TODO: critical
213
+ // resourceNames: r.metadata?.name ? [r.metadata.name] : undefined,
214
+ verbs,
215
+ })),
216
+ })
217
+ }
218
+ }