@highstate/k8s 0.15.0 → 0.16.0

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 (96) hide show
  1. package/dist/chunk-22GOWZQP.js +286 -0
  2. package/dist/chunk-22GOWZQP.js.map +1 -0
  3. package/dist/{chunk-VJL2BFKO.js → chunk-4G6LLC2X.js} +13 -24
  4. package/dist/chunk-4G6LLC2X.js.map +1 -0
  5. package/dist/{chunk-C6WHUOC3.js → chunk-BR2CLUUD.js} +15 -26
  6. package/dist/chunk-BR2CLUUD.js.map +1 -0
  7. package/dist/{chunk-EACAK6W4.js → chunk-DCUMJSO6.js} +17 -28
  8. package/dist/chunk-DCUMJSO6.js.map +1 -0
  9. package/dist/{chunk-NWXKLVBC.js → chunk-HJKJHTJM.js} +20 -30
  10. package/dist/chunk-HJKJHTJM.js.map +1 -0
  11. package/dist/{chunk-7H4L3DFC.js → chunk-KMLRI5UZ.js} +57 -70
  12. package/dist/chunk-KMLRI5UZ.js.map +1 -0
  13. package/dist/{chunk-6ACIPGW4.js → chunk-LGHFSXNT.js} +12 -13
  14. package/dist/chunk-LGHFSXNT.js.map +1 -0
  15. package/dist/{chunk-SEWB4FUB.js → chunk-OBDQONMV.js} +64 -23
  16. package/dist/chunk-OBDQONMV.js.map +1 -0
  17. package/dist/chunk-SL5CBM3A.js +301 -0
  18. package/dist/chunk-SL5CBM3A.js.map +1 -0
  19. package/dist/{chunk-3CKMDTYK.js → chunk-TWBMG6TD.js} +68 -91
  20. package/dist/chunk-TWBMG6TD.js.map +1 -0
  21. package/dist/{chunk-YTRQ6JRU.js → chunk-XRIC6EJ3.js} +159 -94
  22. package/dist/chunk-XRIC6EJ3.js.map +1 -0
  23. package/dist/{chunk-O64YZLA4.js → chunk-ZBFWQHE4.js} +21 -30
  24. package/dist/chunk-ZBFWQHE4.js.map +1 -0
  25. package/dist/{chunk-4VGISFL4.js → chunk-ZHVKK2U6.js} +12 -11
  26. package/dist/chunk-ZHVKK2U6.js.map +1 -0
  27. package/dist/cron-job-LX35I6HG.js +8 -0
  28. package/dist/cron-job-LX35I6HG.js.map +1 -0
  29. package/dist/deployment-HRJGAEJR.js +8 -0
  30. package/dist/{deployment-THUD5QUH.js.map → deployment-HRJGAEJR.js.map} +1 -1
  31. package/dist/highstate.manifest.json +2 -3
  32. package/dist/impl/gateway-route.js +10 -10
  33. package/dist/impl/gateway-route.js.map +1 -1
  34. package/dist/impl/tls-certificate.js +3 -3
  35. package/dist/index.js +39 -627
  36. package/dist/index.js.map +1 -1
  37. package/dist/job-J4BKBVQD.js +8 -0
  38. package/dist/job-J4BKBVQD.js.map +1 -0
  39. package/dist/stateful-set-LAJR5RL4.js +8 -0
  40. package/dist/{stateful-set-ABCZML4L.js.map → stateful-set-LAJR5RL4.js.map} +1 -1
  41. package/dist/units/cert-manager/index.js +7 -7
  42. package/dist/units/cluster-patch/index.js +9 -18
  43. package/dist/units/cluster-patch/index.js.map +1 -1
  44. package/dist/units/dns01-issuer/index.js +6 -6
  45. package/dist/units/dns01-issuer/index.js.map +1 -1
  46. package/dist/units/existing-cluster/index.js +19 -9
  47. package/dist/units/existing-cluster/index.js.map +1 -1
  48. package/dist/units/gateway-api/index.js +2 -2
  49. package/dist/units/gateway-api/index.js.map +1 -1
  50. package/dist/units/reduced-access-cluster/index.js +18 -25
  51. package/dist/units/reduced-access-cluster/index.js.map +1 -1
  52. package/package.json +6 -11
  53. package/src/cluster.ts +14 -14
  54. package/src/config-map.ts +16 -30
  55. package/src/container.ts +1 -1
  56. package/src/cron-job.ts +23 -41
  57. package/src/deployment.ts +23 -30
  58. package/src/gateway/gateway.ts +21 -29
  59. package/src/helm.ts +7 -8
  60. package/src/impl/gateway-route.ts +5 -13
  61. package/src/job.ts +20 -38
  62. package/src/namespace.ts +18 -22
  63. package/src/network-policy.ts +37 -36
  64. package/src/network.ts +18 -10
  65. package/src/pvc.ts +12 -28
  66. package/src/rbac.ts +75 -97
  67. package/src/scripting/bundle.ts +3 -3
  68. package/src/scripting/environment.ts +3 -3
  69. package/src/secret.ts +16 -30
  70. package/src/service.ts +86 -105
  71. package/src/shared.ts +82 -20
  72. package/src/stateful-set.ts +17 -28
  73. package/src/tls.ts +20 -31
  74. package/src/units/cluster-patch/index.ts +9 -19
  75. package/src/units/dns01-issuer/index.ts +6 -6
  76. package/src/units/existing-cluster/index.ts +28 -10
  77. package/src/units/gateway-api/index.ts +1 -1
  78. package/src/units/reduced-access-cluster/index.ts +16 -24
  79. package/src/worker.ts +7 -5
  80. package/src/workload.ts +172 -28
  81. package/dist/chunk-3CKMDTYK.js.map +0 -1
  82. package/dist/chunk-4VGISFL4.js.map +0 -1
  83. package/dist/chunk-6ACIPGW4.js.map +0 -1
  84. package/dist/chunk-7H4L3DFC.js.map +0 -1
  85. package/dist/chunk-C6WHUOC3.js.map +0 -1
  86. package/dist/chunk-EACAK6W4.js.map +0 -1
  87. package/dist/chunk-NWXKLVBC.js.map +0 -1
  88. package/dist/chunk-O64YZLA4.js.map +0 -1
  89. package/dist/chunk-SEWB4FUB.js.map +0 -1
  90. package/dist/chunk-VJL2BFKO.js.map +0 -1
  91. package/dist/chunk-YTRQ6JRU.js.map +0 -1
  92. package/dist/deployment-THUD5QUH.js +0 -8
  93. package/dist/stateful-set-ABCZML4L.js +0 -8
  94. package/dist/units/cluster-dns/index.js +0 -37
  95. package/dist/units/cluster-dns/index.js.map +0 -1
  96. package/src/units/cluster-dns/index.ts +0 -37
package/src/rbac.ts CHANGED
@@ -10,17 +10,24 @@ import {
10
10
  type Output,
11
11
  output,
12
12
  toPromise,
13
+ type Unwrap,
13
14
  } from "@highstate/pulumi"
14
15
  import { KubeConfig } from "@kubernetes/client-node"
15
16
  import { core, rbac, type types } from "@pulumi/kubernetes"
16
17
  import { map, unique } from "remeda"
17
18
  import { stringify } from "yaml"
18
19
  import { Secret } from "./secret"
19
- import { getNamespaceName, getProvider, type NamespaceLike, type ScopedResource } from "./shared"
20
+ import {
21
+ getNamespaceName,
22
+ getProvider,
23
+ NamespacedResource,
24
+ type NamespaceLike,
25
+ type Resource,
26
+ } from "./shared"
20
27
 
21
28
  export type ClusterAccessScopeArgs = {
22
29
  /**
23
- * The namespace to locate the ServiceAccount in.
30
+ * The namespace to create the ServiceAccount in.
24
31
  */
25
32
  namespace: Input<Namespace>
26
33
 
@@ -52,35 +59,19 @@ export type ClusterAccessScopeArgs = {
52
59
  extraNamespaces?: InputArray<NamespaceLike>
53
60
 
54
61
  /**
55
- * Whether to create `ClusterRoleBinding` to bind the `ServiceAccount` to the `ClusterRole`.
62
+ * Whether to create `ClusterRoleBinding` instead of `RoleBinding` to allow cluster-wide access.
56
63
  *
57
64
  * This will allow the `ServiceAccount` to access all namespaces and cluster resources.
58
65
  */
59
66
  clusterWide?: boolean
60
- }
61
-
62
- export type ClusterAccessScopeForResourcesArgs = {
63
- /**
64
- * The namespace to locate the `ServiceAccount` in.
65
- */
66
- namespace: Input<Namespace>
67
-
68
- /**
69
- * The verbs to allow on the resources.
70
- */
71
- verbs: string[]
72
-
73
- /**
74
- * The resources to allow verbs on.
75
- */
76
- resources: InputArray<ScopedResource>
77
67
 
78
68
  /**
79
- * Whether to allow access on the whole collection rather than specific resources.
69
+ * The extra resources to merge into passed rules.
80
70
  *
81
- * The provided resources in this case will be used to determine the types and api groups only.
71
+ * Resources will be merged into rule `resourceNames` if they exactly match rule's `apiGroups` and `resources`.
72
+ * If rule specifies multiple apiGroups or resources, resources will not be merged into it.
82
73
  */
83
- collectionAccess?: boolean
74
+ resources?: InputArray<Resource | k8s.Resource>
84
75
  }
85
76
 
86
77
  export class ClusterAccessScope extends ComponentResource {
@@ -111,12 +102,15 @@ export class ClusterAccessScope extends ComponentResource {
111
102
  name,
112
103
  {
113
104
  metadata: {
114
- name: interpolate`highstate.${namespaceName}.${name}`,
105
+ name: interpolate`hs.${namespaceName}.${name}`,
115
106
  annotations: {
116
107
  "kubernetes.io/description": interpolate`Created by Highstate for the ServiceAccount "${name}" in the namespace "${namespaceName}".`,
117
108
  },
118
109
  },
119
- rules: normalizeInputs(args.rule, args.rules),
110
+ rules: output({
111
+ rules: normalizeInputs(args.rule, args.rules),
112
+ resources: args.resources ?? [],
113
+ }).apply(({ rules, resources }) => mergeResources(rules, resources)),
120
114
  },
121
115
  { provider },
122
116
  )
@@ -143,14 +137,36 @@ export class ClusterAccessScope extends ComponentResource {
143
137
  )
144
138
  }
145
139
 
146
- if (args.allowOriginNamespace ?? true) {
147
- createRoleBinding(namespaceName)
140
+ if (args.clusterWide) {
141
+ new rbac.v1.ClusterRoleBinding(
142
+ name,
143
+ {
144
+ metadata: { name },
145
+ roleRef: {
146
+ kind: "ClusterRole",
147
+ name: clusterRole.metadata.name,
148
+ apiGroup: "rbac.authorization.k8s.io",
149
+ },
150
+ subjects: [
151
+ {
152
+ kind: "ServiceAccount",
153
+ name: serviceAccount.metadata.name,
154
+ namespace: namespaceName,
155
+ },
156
+ ],
157
+ },
158
+ { provider },
159
+ )
160
+ } else {
161
+ if (args.allowOriginNamespace !== false) {
162
+ createRoleBinding(namespaceName)
163
+ }
164
+
165
+ output(args.extraNamespaces ?? [])
166
+ .apply(map(getNamespaceName))
167
+ .apply(map(createRoleBinding))
148
168
  }
149
169
 
150
- output(args.extraNamespaces ?? [])
151
- .apply(map(getNamespaceName))
152
- .apply(map(createRoleBinding))
153
-
154
170
  return { serviceAccount, kubeconfig: cluster.kubeconfig }
155
171
  })
156
172
 
@@ -193,81 +209,43 @@ export class ClusterAccessScope extends ComponentResource {
193
209
  }
194
210
  })
195
211
  }
212
+ }
196
213
 
197
- /**
198
- * Creates `ClusterAccessScope` for the given resources with the specified verbs.
199
- *
200
- * All resources must belong to the same namespace in the same cluster.
201
- *
202
- * @param name The name of the resource and the ServiceAccount.
203
- * @param resources The resources to create access scope for.
204
- * @param verbs The verbs to allow on the resources.
205
- */
206
- static async forResources(
207
- name: string,
208
- args: ClusterAccessScopeForResourcesArgs,
209
- opts?: ComponentResourceOptions,
210
- ): Promise<ClusterAccessScope> {
211
- const resolved = await toPromise(
212
- output(args.resources).apply(resources =>
213
- resources.map(r => ({
214
- namespaceId: r.namespace.metadata.uid,
215
- namespace: r.namespace,
216
- metadata: r.metadata,
217
- apiVersion: r.apiVersion,
218
- kind: r.kind,
219
- })),
220
- ),
214
+ async function mergeResources(
215
+ rules: Unwrap<types.input.rbac.v1.PolicyRule>[],
216
+ resources: (Resource | k8s.Resource)[],
217
+ ): Promise<types.input.rbac.v1.PolicyRule[]> {
218
+ for (const resource of resources) {
219
+ const entity = await toPromise(
220
+ resource instanceof NamespacedResource ? resource.entity : resource,
221
221
  )
222
222
 
223
- if (resolved.length === 0) {
224
- throw new Error("No resources provided to forResources.")
225
- }
223
+ const apiGroup = entity.apiVersion.includes("/") // e.g., "apps/v1"
224
+ ? entity.apiVersion.split("/")[0]
225
+ : ""
226
226
 
227
- if (unique(resolved.map(r => r.namespaceId)).length > 1) {
228
- throw new Error("All resources must belong to the same namespace.")
229
- }
230
-
231
- const saNamespaceId = await toPromise(output(args.namespace).metadata.uid)
232
-
233
- if (resolved[0].namespaceId !== saNamespaceId) {
234
- throw new Error("The resources must belong to the same namespace as the ServiceAccount.")
235
- }
227
+ const resourceCollection = `${entity.kind.toLowerCase()}s`
236
228
 
237
- if (args.collectionAccess) {
238
- // when collection access is requested, we only need to know the types and api groups
239
- const uniqueTypes = unique(resolved.map(r => `${r.apiVersion}::${r.kind}`))
229
+ const matchingRule = rules.find(rule => {
230
+ const apiGroupsMatch = rule.apiGroups?.length === 1 && rule.apiGroups[0] === apiGroup
231
+ const resourcesMatch =
232
+ rule.resources?.length === 1 && rule.resources[0] === resourceCollection
240
233
 
241
- return new ClusterAccessScope(
242
- name,
243
- {
244
- namespace: args.namespace,
245
- rules: uniqueTypes.map(t => {
246
- const [apiVersion, kind] = t.split("::")
234
+ return apiGroupsMatch && resourcesMatch
235
+ })
247
236
 
248
- return {
249
- apiGroups: apiVersion === "v1" ? [""] : [apiVersion.split("/")[0]],
250
- resources: [`${kind.toLowerCase()}s`],
251
- verbs: args.verbs,
252
- }
253
- }),
254
- },
255
- opts,
256
- )
237
+ if (!matchingRule) {
238
+ continue
257
239
  }
258
240
 
259
- return new ClusterAccessScope(
260
- name,
261
- {
262
- namespace: args.namespace,
263
- rules: resolved.map(r => ({
264
- apiGroups: r.apiVersion === "v1" ? [""] : [r.apiVersion.split("/")[0]],
265
- resources: [r.kind.toLowerCase() + (r.metadata?.name ? "s" : "")],
266
- resourceNames: r.metadata?.name ? [r.metadata.name] : undefined,
267
- verbs: args.verbs,
268
- })),
269
- },
270
- opts,
241
+ matchingRule.resourceNames = await toPromise(
242
+ unique([
243
+ //
244
+ ...(matchingRule.resourceNames ?? []),
245
+ entity.metadata.name,
246
+ ]),
271
247
  )
272
248
  }
249
+
250
+ return rules
273
251
  }
@@ -1,7 +1,7 @@
1
1
  import type { network } from "@highstate/library"
2
2
  import type { ContainerEnvironment, ContainerVolumeMount, WorkloadVolume } from "../container"
3
3
  import type { ScopedResourceArgs } from "../shared"
4
- import { parseL34Endpoint } from "@highstate/common"
4
+ import { parseEndpoint } from "@highstate/common"
5
5
  import { text, trimIndentation } from "@highstate/contract"
6
6
  import { type InputArray, normalize } from "@highstate/pulumi"
7
7
  import {
@@ -76,7 +76,7 @@ export class ScriptBundle extends ComponentResource {
76
76
  /**
77
77
  * The list of endpoints that the script is allowed to access.
78
78
  */
79
- readonly allowedEndpoints: Output<network.L34Endpoint[]>
79
+ readonly allowedEndpoints: Output<network.L3Endpoint[]>
80
80
 
81
81
  constructor(name: string, args: ScriptBundleArgs, opts?: ComponentResourceOptions) {
82
82
  super("highstate:k8s:ScriptBundle", name, args, opts)
@@ -113,7 +113,7 @@ export class ScriptBundle extends ComponentResource {
113
113
  allowedEndpoints.push("tcp://registry.npmjs.org:443")
114
114
  }
115
115
 
116
- return allowedEndpoints.map(parseL34Endpoint)
116
+ return allowedEndpoints.map(endpoint => parseEndpoint(endpoint))
117
117
  },
118
118
  )
119
119
 
@@ -1,4 +1,4 @@
1
- import type { InputL34Endpoint } from "@highstate/common"
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
4
 
@@ -35,7 +35,7 @@ export type DistributionEnvironment = {
35
35
  *
36
36
  * Will be used to generate a network policy.
37
37
  */
38
- allowedEndpoints?: InputArray<InputL34Endpoint>
38
+ allowedEndpoints?: InputArray<InputEndpoint>
39
39
  }
40
40
 
41
41
  export type ScriptProgram = () => unknown
@@ -78,7 +78,7 @@ export type ScriptEnvironment = {
78
78
  *
79
79
  * Will be used to generate a network policy.
80
80
  */
81
- allowedEndpoints?: InputArray<InputL34Endpoint>
81
+ allowedEndpoints?: InputArray<InputEndpoint>
82
82
  }
83
83
 
84
84
  export type ResolvedScriptEnvironment = Omit<Required<ScriptEnvironment>, ScriptDistribution> & {
package/src/secret.ts CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  output,
12
12
  } from "@pulumi/pulumi"
13
13
  import { Namespace } from "./namespace"
14
- import { getProvider, mapMetadata, ScopedResource, type ScopedResourceArgs } from "./shared"
14
+ import { getProvider, mapMetadata, NamespacedResource, type ScopedResourceArgs } from "./shared"
15
15
 
16
16
  export type SecretArgs = ScopedResourceArgs &
17
17
  Omit<types.input.core.v1.Secret, "kind" | "metadata" | "apiVersion">
@@ -20,23 +20,24 @@ export type CreateOrGetSecretArgs = SecretArgs & {
20
20
  /**
21
21
  * The secret entity to patch/retrieve.
22
22
  */
23
- existing: Input<k8s.ScopedResource> | undefined
23
+ existing: Input<k8s.NamespacedResource> | undefined
24
24
  }
25
25
 
26
26
  /**
27
27
  * Represents a Kubernetes Secret resource with metadata and data.
28
28
  */
29
- export abstract class Secret extends ScopedResource {
29
+ export abstract class Secret extends NamespacedResource {
30
+ static apiVersion = "v1"
31
+ static kind = "Secret"
32
+
30
33
  protected constructor(
31
34
  type: string,
32
35
  name: string,
33
36
  args: Inputs,
34
37
  opts: ComponentResourceOptions | undefined,
35
38
 
36
- apiVersion: Output<string>,
37
- kind: Output<string>,
38
- namespace: Output<Namespace>,
39
39
  metadata: Output<types.output.meta.v1.ObjectMeta>,
40
+ namespace: Output<Namespace>,
40
41
 
41
42
  /**
42
43
  * The data of the underlying Kubernetes secret.
@@ -48,19 +49,14 @@ export abstract class Secret extends ScopedResource {
48
49
  */
49
50
  readonly stringData: Output<Record<string, string>>,
50
51
  ) {
51
- super(type, name, args, opts, apiVersion, kind, namespace, metadata)
52
+ super(type, name, args, opts, metadata, namespace)
52
53
  }
53
54
 
54
55
  /**
55
56
  * The Highstate secret entity.
56
57
  */
57
- get entity(): Output<k8s.ScopedResource> {
58
- return output({
59
- type: "secret",
60
- clusterId: this.cluster.id,
61
- clusterName: this.cluster.name,
62
- metadata: this.metadata,
63
- })
58
+ get entity(): Output<k8s.Secret> {
59
+ return output(this.entityBase)
64
60
  }
65
61
 
66
62
  /**
@@ -165,7 +161,7 @@ export abstract class Secret extends ScopedResource {
165
161
  * @param entity The entity to get the secret for.
166
162
  * @param cluster The cluster where the secret is located.
167
163
  */
168
- static for(entity: k8s.ScopedResource, cluster: Input<k8s.Cluster>): Secret {
164
+ static for(entity: k8s.NamespacedResource, cluster: Input<k8s.Cluster>): Secret {
169
165
  return getOrCreate(
170
166
  Secret.secretCache,
171
167
  `${entity.clusterName}.${entity.metadata.namespace}.${entity.metadata.name}.${entity.clusterId}`,
@@ -190,7 +186,7 @@ export abstract class Secret extends ScopedResource {
190
186
  * @param cluster The cluster where the secret is located.
191
187
  */
192
188
  static async forAsync(
193
- entity: Input<k8s.ScopedResource>,
189
+ entity: Input<k8s.NamespacedResource>,
194
190
  cluster: Input<k8s.Cluster>,
195
191
  ): Promise<Secret> {
196
192
  const resolvedEntity = await toPromise(entity)
@@ -223,10 +219,8 @@ class CreatedSecret extends Secret {
223
219
  name,
224
220
  args,
225
221
  opts,
226
- secret.apiVersion,
227
- secret.kind,
228
- output(args.namespace),
229
222
  secret.metadata,
223
+ output(args.namespace),
230
224
  secret.data,
231
225
  secret.stringData,
232
226
  )
@@ -258,10 +252,8 @@ class SecretPatch extends Secret {
258
252
  name,
259
253
  args,
260
254
  opts,
261
- secret.apiVersion,
262
- secret.kind,
263
- output(args.namespace),
264
255
  secret.metadata,
256
+ output(args.namespace),
265
257
  secret.data,
266
258
  secret.stringData,
267
259
  )
@@ -287,11 +279,8 @@ class WrappedSecret extends Secret {
287
279
  name,
288
280
  args,
289
281
  opts,
290
-
291
- output(args.secret).apiVersion,
292
- output(args.secret).kind,
293
- output(args.namespace),
294
282
  output(args.secret).metadata,
283
+ output(args.namespace),
295
284
  output(args.secret).data,
296
285
  output(args.secret).stringData,
297
286
  )
@@ -335,11 +324,8 @@ class ExternalSecret extends Secret {
335
324
  name,
336
325
  args,
337
326
  opts,
338
-
339
- secret.apiVersion,
340
- secret.kind,
341
- output(args.namespace),
342
327
  secret.metadata,
328
+ output(args.namespace),
343
329
  secret.data,
344
330
  secret.stringData,
345
331
  )