@highstate/k8s 0.7.2 → 0.7.3

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 (44) hide show
  1. package/dist/{helm-wPTgVV1N.js → chunk-K4WKJ4L5.js} +89 -47
  2. package/dist/chunk-K4WKJ4L5.js.map +1 -0
  3. package/dist/{shared-Clzbl5K-.js → chunk-T5Z2M4JE.js} +21 -7
  4. package/dist/chunk-T5Z2M4JE.js.map +1 -0
  5. package/dist/highstate.manifest.json +9 -0
  6. package/dist/index.js +304 -154
  7. package/dist/index.js.map +1 -0
  8. package/dist/units/access-point/index.js +9 -7
  9. package/dist/units/access-point/index.js.map +1 -0
  10. package/dist/units/cert-manager/index.js +29 -29
  11. package/dist/units/cert-manager/index.js.map +1 -0
  12. package/dist/units/dns01-issuer/index.js +22 -14
  13. package/dist/units/dns01-issuer/index.js.map +1 -0
  14. package/dist/units/existing-cluster/index.js +49 -21
  15. package/dist/units/existing-cluster/index.js.map +1 -0
  16. package/package.json +15 -16
  17. package/src/access-point.ts +185 -0
  18. package/src/container.ts +271 -0
  19. package/src/cron-job.ts +77 -0
  20. package/src/deployment.ts +210 -0
  21. package/src/gateway/backend.ts +61 -0
  22. package/src/gateway/http-route.ts +139 -0
  23. package/src/gateway/index.ts +2 -0
  24. package/src/helm.ts +298 -0
  25. package/src/index.ts +61 -0
  26. package/src/job.ts +66 -0
  27. package/src/network-policy.ts +732 -0
  28. package/src/pod.ts +5 -0
  29. package/src/pvc.ts +178 -0
  30. package/src/scripting/bundle.ts +244 -0
  31. package/src/scripting/container.ts +44 -0
  32. package/src/scripting/environment.ts +79 -0
  33. package/src/scripting/index.ts +3 -0
  34. package/src/service.ts +279 -0
  35. package/src/shared.ts +150 -0
  36. package/src/stateful-set.ts +159 -0
  37. package/src/units/access-point/index.ts +12 -0
  38. package/src/units/cert-manager/index.ts +37 -0
  39. package/src/units/dns01-issuer/index.ts +41 -0
  40. package/src/units/dns01-issuer/solver.ts +23 -0
  41. package/src/units/existing-cluster/index.ts +107 -0
  42. package/src/workload.ts +150 -0
  43. package/assets/charts.json +0 -8
  44. package/dist/index.d.ts +0 -1036
@@ -0,0 +1,185 @@
1
+ import type { k8s } from "@highstate/library"
2
+ import type { core, Provider } from "@pulumi/kubernetes"
3
+ import { DnsRecord } from "@highstate/common"
4
+ import { gateway } from "@highstate/gateway-api"
5
+ import {
6
+ normalize,
7
+ Output,
8
+ output,
9
+ toPromise,
10
+ type Input,
11
+ type InputArray,
12
+ } from "@highstate/pulumi"
13
+ import { NetworkPolicy } from "./network-policy"
14
+ import { getAppDisplayName, getAppName, mapNamespaceLikeToNamespaceName } from "./shared"
15
+
16
+ export type UseAccessPointResult = {
17
+ /**
18
+ * The gateway instance created according to the access point.
19
+ */
20
+ gateway: gateway.v1.Gateway
21
+
22
+ /**
23
+ * The DNS record associated created according to the access point and gateway.
24
+ */
25
+ dnsRecords: DnsRecord[]
26
+
27
+ /**
28
+ * The network policies associated with the access point.
29
+ */
30
+ networkPolicies: NetworkPolicy[]
31
+ }
32
+
33
+ export type UseAccessPointArgs = Omit<CreateGatewayArgs, "gateway"> & {
34
+ accessPoint: Input<k8s.AccessPoint>
35
+ }
36
+
37
+ export function useAccessPoint(args: UseAccessPointArgs): Promise<UseAccessPointResult> {
38
+ const result = output({ args, namespaceName: output(args.namespace).metadata.name }).apply(
39
+ ({ args, namespaceName }) => {
40
+ const gateway = createGateway({
41
+ ...args,
42
+ annotations: {
43
+ "cert-manager.io/cluster-issuer": args.accessPoint.tlsIssuer.clusterIssuerName,
44
+ },
45
+ gateway: args.accessPoint.gateway,
46
+ })
47
+
48
+ const dnsRecords = normalize(args.fqdn, args.fqdns).map(fqdn => {
49
+ return DnsRecord.create(fqdn, {
50
+ provider: args.accessPoint.dnsProvider,
51
+ type: "A",
52
+ value: args.accessPoint.gateway.ip,
53
+ })
54
+ })
55
+
56
+ const networkPolicies: Output<NetworkPolicy>[] = []
57
+
58
+ if (args.accessPoint.gateway.service) {
59
+ const displayName = getAppDisplayName(args.accessPoint.gateway.service.metadata)
60
+
61
+ networkPolicies.push(
62
+ NetworkPolicy.create(
63
+ `allow-ingress-from-${getAppName(args.accessPoint.gateway.service.metadata)}`,
64
+ {
65
+ cni: args.accessPoint.gateway.service.clusterInfo.cni,
66
+ namespace: args.namespace,
67
+
68
+ description: `Allow ingress traffic from the gateway "${displayName}".`,
69
+
70
+ ingressRule: {
71
+ fromNamespace: args.accessPoint.gateway.service.metadata.namespace,
72
+ fromSelector: args.accessPoint.gateway.service.spec.selector,
73
+ },
74
+ },
75
+ { provider: args.provider },
76
+ ),
77
+
78
+ NetworkPolicy.create(
79
+ `allow-egress-to-${namespaceName}`,
80
+ {
81
+ cni: args.accessPoint.gateway.service.clusterInfo.cni,
82
+ namespace: args.accessPoint.gateway.service.metadata.namespace,
83
+ selector: args.accessPoint.gateway.service.spec.selector,
84
+
85
+ description: `Allow egress traffic to the namespace "${namespaceName}".`,
86
+
87
+ egressRule: {
88
+ toNamespace: args.namespace,
89
+ },
90
+ },
91
+ { provider: args.provider },
92
+ ),
93
+ )
94
+ }
95
+
96
+ return output({
97
+ gateway,
98
+ dnsRecords,
99
+ networkPolicies,
100
+ })
101
+ },
102
+ )
103
+
104
+ return toPromise(result)
105
+ }
106
+
107
+ export type StandardAccessPointArgs = {
108
+ fqdn: string
109
+ }
110
+
111
+ export type StandardAccessPointInputs = {
112
+ accessPoint: Output<k8s.AccessPoint>
113
+ k8sCluster: Output<k8s.Cluster>
114
+ }
115
+
116
+ export function useStandardAcessPoint(
117
+ appName: string,
118
+ namespace: core.v1.Namespace,
119
+ args: StandardAccessPointArgs,
120
+ inputs: StandardAccessPointInputs,
121
+ provider: Provider,
122
+ ): Promise<UseAccessPointResult> {
123
+ return useAccessPoint({
124
+ name: appName,
125
+ namespace,
126
+
127
+ fqdn: args.fqdn,
128
+
129
+ accessPoint: inputs.accessPoint,
130
+ clusterInfo: inputs.k8sCluster.info,
131
+ provider,
132
+ })
133
+ }
134
+
135
+ export type CreateGatewayArgs = {
136
+ name: string
137
+ namespace: Input<core.v1.Namespace>
138
+ annotations?: Input<Record<string, string>>
139
+
140
+ fqdn?: Input<string>
141
+ fqdns?: InputArray<string>
142
+
143
+ gateway: Input<k8s.Gateway>
144
+ clusterInfo: Input<k8s.ClusterInfo>
145
+ provider: Provider
146
+ }
147
+
148
+ export function createGateway(args: CreateGatewayArgs): Output<gateway.v1.Gateway> {
149
+ return output(args).apply(args => {
150
+ if (args.clusterInfo.id !== args.gateway.clusterInfo.id) {
151
+ throw new Error(
152
+ "The provided Kubernetes cluster is different from the one where the gateway controller is deployed.",
153
+ )
154
+ }
155
+
156
+ return new gateway.v1.Gateway(
157
+ args.name,
158
+ {
159
+ metadata: {
160
+ name: args.name,
161
+ namespace: mapNamespaceLikeToNamespaceName(args.namespace),
162
+ annotations: args.annotations,
163
+ },
164
+ spec: {
165
+ gatewayClassName: output(args.gateway).gatewayClassName,
166
+ listeners: normalize(args.fqdn, args.fqdns).map(fqdn => {
167
+ const normalizedName = fqdn.replace(/\*/g, "wildcard")
168
+
169
+ return {
170
+ name: `https-${normalizedName}`,
171
+ port: output(args.gateway).httpsListenerPort,
172
+ protocol: "HTTPS",
173
+ hostname: fqdn,
174
+ tls: {
175
+ mode: "Terminate",
176
+ certificateRefs: [{ name: normalizedName }],
177
+ },
178
+ }
179
+ }),
180
+ },
181
+ },
182
+ { provider: args.provider, deletedWith: args.namespace },
183
+ )
184
+ })
185
+ }
@@ -0,0 +1,271 @@
1
+ import type { PartialKeys } from "@highstate/contract"
2
+ import { core, type types } from "@pulumi/kubernetes"
3
+ import { normalize, output, type Input, type InputArray, type Unwrap } from "@highstate/pulumi"
4
+ import { concat, map, omit } from "remeda"
5
+ import { PersistentVolumeClaim } from "./pvc"
6
+
7
+ export type Container = Omit<PartialKeys<types.input.core.v1.Container, "name">, "volumeMounts"> & {
8
+ /**
9
+ * The single port to add to the container.
10
+ */
11
+ port?: Input<types.input.core.v1.ContainerPort>
12
+
13
+ /**
14
+ * The volume mount to attach to the container.
15
+ */
16
+ volumeMount?: Input<ContainerVolumeMount>
17
+
18
+ /**
19
+ * The volume mounts to attach to the container.
20
+ */
21
+ volumeMounts?: InputArray<ContainerVolumeMount>
22
+
23
+ /**
24
+ * The volume to include in the parent workload.
25
+ * It is like the `volumes` property, but defined at the container level.
26
+ * It will be defined as a volume mount in the parent workload automatically.
27
+ */
28
+ volume?: Input<WorkloadVolume>
29
+
30
+ /**
31
+ * The volumes to include in the parent workload.
32
+ * It is like the `volumes` property, but defined at the container level.
33
+ * It will be defined as a volume mount in the parent workload automatically.
34
+ */
35
+ volumes?: InputArray<WorkloadVolume>
36
+
37
+ /**
38
+ * The map of environment variables to set in the container.
39
+ * It is like the `env` property, but more convenient to use.
40
+ */
41
+ environment?: Input<ContainerEnvironment>
42
+
43
+ /**
44
+ * The source of environment variables to set in the container.
45
+ * It is like the `envFrom` property, but more convenient to use.
46
+ */
47
+ environmentSource?: Input<ContainerEnvironmentSource>
48
+
49
+ /**
50
+ * The sources of environment variables to set in the container.
51
+ * It is like the `envFrom` property, but more convenient to use.
52
+ */
53
+ environmentSources?: InputArray<ContainerEnvironmentSource>
54
+ }
55
+
56
+ const containerExtraArgs = [
57
+ "port",
58
+ "volumeMount",
59
+ "volume",
60
+ "environment",
61
+ "environmentSource",
62
+ "environmentSources",
63
+ ] as const
64
+
65
+ export type ContainerEnvironment = Record<
66
+ string,
67
+ Input<string | undefined | null | ContainerEnvironmentVariable>
68
+ >
69
+
70
+ export type ContainerEnvironmentVariable =
71
+ | types.input.core.v1.EnvVarSource
72
+ | {
73
+ /**
74
+ * The secret to select from.
75
+ */
76
+ secret: Input<core.v1.Secret>
77
+
78
+ /**
79
+ * The key of the secret to select from.
80
+ */
81
+ key: string
82
+ }
83
+ | {
84
+ /**
85
+ * The config map to select from.
86
+ */
87
+ configMap: Input<core.v1.ConfigMap>
88
+
89
+ /**
90
+ * The key of the config map to select from.
91
+ */
92
+ key: string
93
+ }
94
+
95
+ export type ContainerEnvironmentSource =
96
+ | types.input.core.v1.EnvFromSource
97
+ | core.v1.ConfigMap
98
+ | core.v1.Secret
99
+
100
+ export type ContainerVolumeMount =
101
+ | types.input.core.v1.VolumeMount
102
+ | (Omit<types.input.core.v1.VolumeMount, "name"> & {
103
+ /**
104
+ * The volume to mount.
105
+ */
106
+ volume: Input<WorkloadVolume>
107
+ })
108
+
109
+ export type WorkloadVolume =
110
+ | types.input.core.v1.Volume
111
+ | core.v1.PersistentVolumeClaim
112
+ | PersistentVolumeClaim
113
+ | core.v1.ConfigMap
114
+ | core.v1.Secret
115
+
116
+ export function mapContainerToRaw(
117
+ container: Unwrap<Container>,
118
+ fallbackName: string,
119
+ ): types.input.core.v1.Container {
120
+ const containerName = container.name ?? fallbackName
121
+
122
+ return {
123
+ ...omit(container, containerExtraArgs),
124
+
125
+ name: containerName,
126
+ ports: normalize(container.port, container.ports),
127
+
128
+ volumeMounts: map(normalize(container.volumeMount, container.volumeMounts), mapVolumeMount),
129
+
130
+ env: concat(
131
+ container.environment ? mapContainerEnvironment(container.environment) : [],
132
+ container.env ?? [],
133
+ ),
134
+
135
+ envFrom: concat(
136
+ map(
137
+ normalize(container.environmentSource, container.environmentSources),
138
+ mapEnvironmentSource,
139
+ ),
140
+ container.envFrom ?? [],
141
+ ),
142
+ }
143
+ }
144
+
145
+ export function mapContainerEnvironment(
146
+ environment: Unwrap<ContainerEnvironment>,
147
+ ): types.input.core.v1.EnvVar[] {
148
+ const envVars: types.input.core.v1.EnvVar[] = []
149
+
150
+ for (const [name, value] of Object.entries(environment)) {
151
+ if (!value) {
152
+ continue
153
+ }
154
+
155
+ if (typeof value === "string") {
156
+ envVars.push({ name, value })
157
+ continue
158
+ }
159
+
160
+ if ("secret" in value) {
161
+ envVars.push({
162
+ name,
163
+ valueFrom: {
164
+ secretKeyRef: {
165
+ name: value.secret.metadata.name,
166
+ key: value.key,
167
+ },
168
+ },
169
+ })
170
+ continue
171
+ }
172
+
173
+ if ("configMap" in value) {
174
+ envVars.push({
175
+ name,
176
+ valueFrom: {
177
+ configMapKeyRef: {
178
+ name: value.configMap.metadata.name,
179
+ key: value.key,
180
+ },
181
+ },
182
+ })
183
+ continue
184
+ }
185
+
186
+ envVars.push({ name, valueFrom: value })
187
+ }
188
+
189
+ return envVars
190
+ }
191
+
192
+ export function mapVolumeMount(volumeMount: ContainerVolumeMount): types.input.core.v1.VolumeMount {
193
+ if ("volume" in volumeMount) {
194
+ return omit(
195
+ {
196
+ ...volumeMount,
197
+ name: output(volumeMount.volume)
198
+ .apply(mapWorkloadVolume)
199
+ .apply(volume => output(volume.name)),
200
+ },
201
+ ["volume"],
202
+ )
203
+ }
204
+
205
+ return {
206
+ ...volumeMount,
207
+ name: volumeMount.name,
208
+ }
209
+ }
210
+
211
+ export function mapEnvironmentSource(
212
+ envFrom: ContainerEnvironmentSource,
213
+ ): types.input.core.v1.EnvFromSource {
214
+ if (envFrom instanceof core.v1.ConfigMap) {
215
+ return {
216
+ configMapRef: {
217
+ name: envFrom.metadata.name,
218
+ },
219
+ }
220
+ }
221
+
222
+ if (envFrom instanceof core.v1.Secret) {
223
+ return {
224
+ secretRef: {
225
+ name: envFrom.metadata.name,
226
+ },
227
+ }
228
+ }
229
+
230
+ return envFrom
231
+ }
232
+
233
+ export function mapWorkloadVolume(volume: WorkloadVolume) {
234
+ if (volume instanceof PersistentVolumeClaim) {
235
+ return {
236
+ name: volume.metadata.name,
237
+ persistentVolumeClaim: {
238
+ claimName: volume.metadata.name,
239
+ },
240
+ }
241
+ }
242
+
243
+ if (volume instanceof core.v1.PersistentVolumeClaim) {
244
+ return {
245
+ name: volume.metadata.name,
246
+ persistentVolumeClaim: {
247
+ claimName: volume.metadata.name,
248
+ },
249
+ }
250
+ }
251
+
252
+ if (volume instanceof core.v1.ConfigMap) {
253
+ return {
254
+ name: volume.metadata.name,
255
+ configMap: {
256
+ name: volume.metadata.name,
257
+ },
258
+ }
259
+ }
260
+
261
+ if (volume instanceof core.v1.Secret) {
262
+ return {
263
+ name: volume.metadata.name,
264
+ secret: {
265
+ secretName: volume.metadata.name,
266
+ },
267
+ }
268
+ }
269
+
270
+ return volume
271
+ }
@@ -0,0 +1,77 @@
1
+ import type { RequiredKeys } from "@highstate/contract"
2
+ import { batch, type types } from "@pulumi/kubernetes"
3
+ import {
4
+ ComponentResource,
5
+ normalize,
6
+ Output,
7
+ output,
8
+ type ComponentResourceOptions,
9
+ type Input,
10
+ type InputArray,
11
+ } from "@highstate/pulumi"
12
+ import { mergeDeep, omit } from "remeda"
13
+ import { mapContainerToRaw, mapWorkloadVolume, type Container } from "./container"
14
+ import { commonExtraArgs, mapMetadata, type CommonArgs } from "./shared"
15
+
16
+ export type CronJobArgs = CommonArgs & {
17
+ container?: Input<Container>
18
+ containers?: InputArray<Container>
19
+ } & Omit<RequiredKeys<Partial<types.input.batch.v1.CronJobSpec>, "schedule">, "jobTemplate"> & {
20
+ jobTemplate?: {
21
+ metadata?: types.input.meta.v1.ObjectMeta
22
+ spec?: Omit<types.input.batch.v1.JobSpec, "template"> & {
23
+ template?: {
24
+ metadata?: types.input.meta.v1.ObjectMeta
25
+ spec?: Partial<types.input.core.v1.PodSpec>
26
+ }
27
+ }
28
+ }
29
+ }
30
+
31
+ const cronJobExtraArgs = [...commonExtraArgs, "container", "containers"] as const
32
+
33
+ export class CronJob extends ComponentResource {
34
+ /**
35
+ * The underlying Kubernetes job.
36
+ */
37
+ public readonly cronJob: Output<batch.v1.CronJob>
38
+
39
+ constructor(name: string, args: CronJobArgs, opts?: ComponentResourceOptions) {
40
+ super("highstate:k8s:CronJob", name, args, opts)
41
+
42
+ this.cronJob = output(args).apply(args => {
43
+ const containers = normalize(args.container, args.containers)
44
+
45
+ return new batch.v1.CronJob(
46
+ name,
47
+ {
48
+ metadata: mapMetadata(args, name),
49
+
50
+ spec: mergeDeep(
51
+ {
52
+ jobTemplate: {
53
+ spec: {
54
+ template: {
55
+ spec: {
56
+ containers: containers.map(container => mapContainerToRaw(container, name)),
57
+
58
+ volumes: containers
59
+ .flatMap(container => normalize(container.volume, container.volumes))
60
+ .map(mapWorkloadVolume),
61
+ },
62
+ },
63
+ },
64
+ },
65
+
66
+ schedule: args.schedule,
67
+ } satisfies types.input.batch.v1.CronJobSpec,
68
+ omit(args, cronJobExtraArgs) as types.input.batch.v1.CronJobSpec,
69
+ ),
70
+ },
71
+ { parent: this, ...opts },
72
+ )
73
+ })
74
+
75
+ this.registerOutputs({ cronJob: this.cronJob })
76
+ }
77
+ }