@highstate/k8s 0.19.1 → 0.20.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.
- package/dist/{chunk-FE4SHRAJ.js → chunk-23X5SXQG.js} +22 -7
- package/dist/chunk-23X5SXQG.js.map +1 -0
- package/dist/{chunk-LGHFSXNT.js → chunk-ADHZK6V2.js} +14 -10
- package/dist/chunk-ADHZK6V2.js.map +1 -0
- package/dist/{chunk-VCXWCZ43.js → chunk-BTAEFJ5N.js} +27 -15
- package/dist/chunk-BTAEFJ5N.js.map +1 -0
- package/dist/{chunk-BR2CLUUD.js → chunk-IXE3OKB4.js} +27 -8
- package/dist/chunk-IXE3OKB4.js.map +1 -0
- package/dist/{chunk-TWBMG6TD.js → chunk-OG2OPX7B.js} +30 -12
- package/dist/chunk-OG2OPX7B.js.map +1 -0
- package/dist/{chunk-DCUMJSO6.js → chunk-P26SQ2ZB.js} +17 -51
- package/dist/chunk-P26SQ2ZB.js.map +1 -0
- package/dist/{chunk-MIC2BHGS.js → chunk-PG27ZY2H.js} +25 -7
- package/dist/chunk-PG27ZY2H.js.map +1 -0
- package/dist/chunk-PZYGZSN5.js +54 -0
- package/dist/{chunk-PZ5AY32C.js.map → chunk-PZYGZSN5.js.map} +1 -1
- package/dist/{chunk-YIJUVPU2.js → chunk-S77TE7UC.js} +27 -15
- package/dist/chunk-S77TE7UC.js.map +1 -0
- package/dist/{chunk-P2VOUU7E.js → chunk-SZKOAHNX.js} +383 -205
- package/dist/chunk-SZKOAHNX.js.map +1 -0
- package/dist/chunk-TOLFVF4S.js +889 -0
- package/dist/chunk-TOLFVF4S.js.map +1 -0
- package/dist/{chunk-RVB4WWZZ.js → chunk-TVKT3ZYX.js} +174 -18
- package/dist/chunk-TVKT3ZYX.js.map +1 -0
- package/dist/cron-job-RKB2HYTO.js +7 -0
- package/dist/{cron-job-NX4HD4FI.js.map → cron-job-RKB2HYTO.js.map} +1 -1
- package/dist/deployment-T35TUOL2.js +7 -0
- package/dist/{deployment-O2LJ5WR5.js.map → deployment-T35TUOL2.js.map} +1 -1
- package/dist/highstate.manifest.json +3 -2
- package/dist/impl/dynamic-endpoint-resolver.js +90 -0
- package/dist/impl/dynamic-endpoint-resolver.js.map +1 -0
- package/dist/impl/gateway-route.js +159 -62
- package/dist/impl/gateway-route.js.map +1 -1
- package/dist/impl/tls-certificate.js +6 -5
- package/dist/impl/tls-certificate.js.map +1 -1
- package/dist/index.js +106 -23
- package/dist/index.js.map +1 -1
- package/dist/job-PE4AKOHB.js +7 -0
- package/dist/job-PE4AKOHB.js.map +1 -0
- package/dist/stateful-set-LUIRHQJY.js +7 -0
- package/dist/{stateful-set-VJYKTQ72.js.map → stateful-set-LUIRHQJY.js.map} +1 -1
- package/dist/units/cert-manager/index.js +7 -8
- package/dist/units/cert-manager/index.js.map +1 -1
- package/dist/units/cluster-patch/index.js +6 -6
- package/dist/units/cluster-patch/index.js.map +1 -1
- package/dist/units/dns01-issuer/index.js +52 -15
- package/dist/units/dns01-issuer/index.js.map +1 -1
- package/dist/units/existing-cluster/index.js +39 -18
- package/dist/units/existing-cluster/index.js.map +1 -1
- package/dist/units/gateway-api/index.js +2 -2
- package/dist/units/reduced-access-cluster/index.js +8 -8
- package/dist/units/reduced-access-cluster/index.js.map +1 -1
- package/package.json +9 -7
- package/src/cluster.ts +12 -8
- package/src/config-map.ts +15 -5
- package/src/container.ts +4 -2
- package/src/cron-job.ts +25 -4
- package/src/deployment.ts +32 -17
- package/src/gateway/backend.ts +3 -3
- package/src/gateway/gateway.ts +12 -56
- package/src/helm.ts +354 -22
- package/src/impl/dynamic-endpoint-resolver.ts +109 -0
- package/src/impl/gateway-route.ts +231 -57
- package/src/impl/tls-certificate.ts +8 -3
- package/src/index.ts +1 -0
- package/src/job.ts +23 -5
- package/src/kubectl.ts +166 -0
- package/src/namespace.ts +47 -3
- package/src/network-policy.ts +1 -1
- package/src/pvc.ts +12 -2
- package/src/rbac.ts +28 -5
- package/src/scripting/environment.ts +3 -2
- package/src/secret.ts +15 -5
- package/src/service.ts +28 -6
- package/src/shared.ts +30 -2
- package/src/stateful-set.ts +32 -17
- package/src/tls.ts +31 -5
- package/src/units/cluster-patch/index.ts +5 -5
- package/src/units/dns01-issuer/index.ts +56 -12
- package/src/units/existing-cluster/index.ts +36 -15
- package/src/units/reduced-access-cluster/index.ts +6 -3
- package/src/worker.ts +4 -2
- package/src/workload.ts +453 -213
- package/dist/chunk-4G6LLC2X.js +0 -240
- package/dist/chunk-4G6LLC2X.js.map +0 -1
- package/dist/chunk-BR2CLUUD.js.map +0 -1
- package/dist/chunk-DCUMJSO6.js.map +0 -1
- package/dist/chunk-FE4SHRAJ.js.map +0 -1
- package/dist/chunk-KMLRI5UZ.js +0 -155
- package/dist/chunk-KMLRI5UZ.js.map +0 -1
- package/dist/chunk-LGHFSXNT.js.map +0 -1
- package/dist/chunk-MIC2BHGS.js.map +0 -1
- package/dist/chunk-OBDQONMV.js +0 -401
- package/dist/chunk-OBDQONMV.js.map +0 -1
- package/dist/chunk-P2VOUU7E.js.map +0 -1
- package/dist/chunk-PZ5AY32C.js +0 -9
- package/dist/chunk-RVB4WWZZ.js.map +0 -1
- package/dist/chunk-TWBMG6TD.js.map +0 -1
- package/dist/chunk-VCXWCZ43.js.map +0 -1
- package/dist/chunk-YIJUVPU2.js.map +0 -1
- package/dist/cron-job-NX4HD4FI.js +0 -8
- package/dist/deployment-O2LJ5WR5.js +0 -8
- package/dist/job-SYME6Y43.js +0 -8
- package/dist/job-SYME6Y43.js.map +0 -1
- package/dist/stateful-set-VJYKTQ72.js +0 -8
|
@@ -1,23 +1,20 @@
|
|
|
1
1
|
import type { Secret } from "../secret"
|
|
2
|
-
import {
|
|
3
|
-
import { k8s, type network } from "@highstate/library"
|
|
2
|
+
import { gatewayRouteMediator, type TlsCertificate } from "@highstate/common"
|
|
3
|
+
import { type common, k8s, type network } from "@highstate/library"
|
|
4
4
|
import { type ComponentResourceOptions, type Input, toPromise } from "@highstate/pulumi"
|
|
5
5
|
import { core } from "@pulumi/kubernetes"
|
|
6
6
|
import { Gateway, HttpRoute, TcpRoute, UdpRoute } from "../gateway"
|
|
7
7
|
import { Namespace } from "../namespace"
|
|
8
|
-
import { l4EndpointToServicePort, Service } from "../service"
|
|
8
|
+
import { isEndpointFromCluster, l4EndpointToServicePort, Service } from "../service"
|
|
9
9
|
import { getProvider, mapMetadata } from "../shared"
|
|
10
10
|
import { Certificate } from "../tls"
|
|
11
11
|
|
|
12
12
|
export const createGatewayRoute = gatewayRouteMediator.implement(
|
|
13
13
|
k8s.gatewayDataSchema,
|
|
14
|
-
async ({ name, spec, opts }, data) => {
|
|
15
|
-
const namespace =
|
|
16
|
-
spec.nativeData instanceof Service
|
|
17
|
-
? await toPromise(spec.nativeData.namespace)
|
|
18
|
-
: Namespace.for(data.namespace, data.cluster)
|
|
14
|
+
async ({ name, args: spec, opts }, data) => {
|
|
15
|
+
const namespace = resolveRouteNamespace(spec, data)
|
|
19
16
|
|
|
20
|
-
const certSecret = await getCertificateSecret(name, namespace, spec.
|
|
17
|
+
const certSecret = await getCertificateSecret(name, namespace, spec.certificate)
|
|
21
18
|
|
|
22
19
|
const certificateRef = certSecret
|
|
23
20
|
? {
|
|
@@ -27,7 +24,9 @@ export const createGatewayRoute = gatewayRouteMediator.implement(
|
|
|
27
24
|
}
|
|
28
25
|
: undefined
|
|
29
26
|
|
|
30
|
-
|
|
27
|
+
const routeProtocol = resolveRouteProtocol(spec)
|
|
28
|
+
|
|
29
|
+
if (routeProtocol === "http") {
|
|
31
30
|
return await createHttpGatewayRoute({
|
|
32
31
|
name,
|
|
33
32
|
spec,
|
|
@@ -38,25 +37,48 @@ export const createGatewayRoute = gatewayRouteMediator.implement(
|
|
|
38
37
|
})
|
|
39
38
|
}
|
|
40
39
|
|
|
41
|
-
const protocol = spec.type === "tcp" ? "TCP" : "UDP"
|
|
42
|
-
|
|
43
40
|
return await createL4GatewayRoute({
|
|
44
41
|
name,
|
|
45
42
|
spec,
|
|
46
43
|
opts,
|
|
47
44
|
data,
|
|
48
45
|
namespace,
|
|
49
|
-
protocol,
|
|
46
|
+
protocol: routeProtocol === "tcp" ? "TCP" : "UDP",
|
|
50
47
|
})
|
|
51
48
|
},
|
|
52
49
|
)
|
|
53
50
|
|
|
54
|
-
type
|
|
55
|
-
type
|
|
51
|
+
type GatewayRouteRuleSpec = {
|
|
52
|
+
type: "http" | "tcp" | "udp"
|
|
53
|
+
paths: string[]
|
|
54
|
+
backends: {
|
|
55
|
+
endpoints: network.L4Endpoint[]
|
|
56
|
+
weight?: number
|
|
57
|
+
}[]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function resolveRouteNamespace(spec: GatewayRouteSpec, data: k8s.GatewayData): Namespace {
|
|
61
|
+
const metadataNamespace = spec.metadata["k8s.namespace"]
|
|
62
|
+
|
|
63
|
+
if (metadataNamespace instanceof Namespace) {
|
|
64
|
+
return metadataNamespace
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return Namespace.for(data.namespace, data.cluster)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
type GatewayRouteSpec = {
|
|
71
|
+
gateway: common.Gateway
|
|
72
|
+
metadata: Record<string, unknown>
|
|
73
|
+
fqdns: string[]
|
|
74
|
+
certificate?: TlsCertificate
|
|
75
|
+
port?: number
|
|
76
|
+
rules: Record<string, GatewayRouteRuleSpec>
|
|
77
|
+
}
|
|
56
78
|
|
|
57
79
|
type CreateHttpGatewayRouteArgs = {
|
|
58
80
|
name: string
|
|
59
|
-
spec:
|
|
81
|
+
spec: GatewayRouteSpec
|
|
60
82
|
opts: ComponentResourceOptions | undefined
|
|
61
83
|
data: k8s.GatewayData
|
|
62
84
|
namespace: Namespace
|
|
@@ -77,17 +99,16 @@ async function createHttpGatewayRoute({
|
|
|
77
99
|
namespace,
|
|
78
100
|
certificateRef,
|
|
79
101
|
}: CreateHttpGatewayRouteArgs) {
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
: (await createServiceFromEndpoints(name, namespace, spec.endpoints, data.cluster, opts))
|
|
84
|
-
.service
|
|
102
|
+
const listenerPort = spec.port ?? (certificateRef ? data.httpsPort : data.httpPort)
|
|
103
|
+
const listenerProtocol = certificateRef ? "HTTPS" : "HTTP"
|
|
104
|
+
const listenerHostname = spec.fqdns[0]
|
|
85
105
|
|
|
86
106
|
const listeners = [
|
|
87
107
|
{
|
|
88
|
-
name:
|
|
89
|
-
port:
|
|
90
|
-
protocol:
|
|
108
|
+
name: `${listenerProtocol.toLowerCase()}-${listenerPort}`,
|
|
109
|
+
port: listenerPort,
|
|
110
|
+
protocol: listenerProtocol,
|
|
111
|
+
hostname: listenerHostname,
|
|
91
112
|
tls: {
|
|
92
113
|
mode: "Terminate",
|
|
93
114
|
certificateRefs: certificateRef ? [certificateRef] : undefined,
|
|
@@ -95,9 +116,9 @@ async function createHttpGatewayRoute({
|
|
|
95
116
|
},
|
|
96
117
|
]
|
|
97
118
|
|
|
98
|
-
const gateway =
|
|
119
|
+
const gateway = Gateway.create(
|
|
120
|
+
name,
|
|
99
121
|
{
|
|
100
|
-
name: data.className,
|
|
101
122
|
namespace,
|
|
102
123
|
gatewayClassName: data.className,
|
|
103
124
|
listeners,
|
|
@@ -105,13 +126,50 @@ async function createHttpGatewayRoute({
|
|
|
105
126
|
opts,
|
|
106
127
|
)
|
|
107
128
|
|
|
129
|
+
const ruleSpecs = Object.values(spec.rules)
|
|
130
|
+
|
|
131
|
+
const rules = await Promise.all(
|
|
132
|
+
ruleSpecs.map(async (ruleSpec, ruleIndex) => {
|
|
133
|
+
const backendRefs = await Promise.all(
|
|
134
|
+
ruleSpec.backends.map(async (backend, backendIndex) => {
|
|
135
|
+
const backendData = await resolveBackendFromEndpoints({
|
|
136
|
+
routeName: name,
|
|
137
|
+
backendName: `${ruleIndex}-${backendIndex}`,
|
|
138
|
+
endpoints: backend.endpoints,
|
|
139
|
+
namespace,
|
|
140
|
+
cluster: data.cluster,
|
|
141
|
+
opts,
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
const backendPort = await selectBackendPort({
|
|
145
|
+
ports: backendData.ports,
|
|
146
|
+
protocol: "TCP",
|
|
147
|
+
targetPort: backendData.preferredTargetPort,
|
|
148
|
+
serviceName: backendData.serviceName,
|
|
149
|
+
routeName: name,
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
name: backendData.serviceName,
|
|
154
|
+
namespace: backendData.serviceNamespace,
|
|
155
|
+
port: backendPort.port,
|
|
156
|
+
}
|
|
157
|
+
}),
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
matches: ruleSpec.paths,
|
|
162
|
+
backends: backendRefs,
|
|
163
|
+
}
|
|
164
|
+
}),
|
|
165
|
+
)
|
|
166
|
+
|
|
108
167
|
const httpRoute = new HttpRoute(
|
|
109
168
|
name,
|
|
110
169
|
{
|
|
111
170
|
gateway,
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
},
|
|
171
|
+
hostnames: spec.fqdns,
|
|
172
|
+
rules,
|
|
115
173
|
},
|
|
116
174
|
opts,
|
|
117
175
|
)
|
|
@@ -124,7 +182,7 @@ async function createHttpGatewayRoute({
|
|
|
124
182
|
|
|
125
183
|
type CreateL4GatewayRouteArgs = {
|
|
126
184
|
name: string
|
|
127
|
-
spec:
|
|
185
|
+
spec: GatewayRouteSpec
|
|
128
186
|
opts: ComponentResourceOptions | undefined
|
|
129
187
|
data: k8s.GatewayData
|
|
130
188
|
namespace: Namespace
|
|
@@ -139,21 +197,28 @@ async function createL4GatewayRoute({
|
|
|
139
197
|
namespace,
|
|
140
198
|
protocol,
|
|
141
199
|
}: CreateL4GatewayRouteArgs) {
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
: await createServiceFromEndpoints(name, namespace, spec.endpoints, data.cluster, opts)
|
|
200
|
+
const backends = Object.values(spec.rules).flatMap(rule => rule.backends)
|
|
201
|
+
|
|
202
|
+
if (backends.length === 0) {
|
|
203
|
+
throw new Error(`Gateway route "${name}" has no backends to expose.`)
|
|
204
|
+
}
|
|
149
205
|
|
|
150
|
-
const
|
|
206
|
+
const firstBackend = backends[0]
|
|
207
|
+
|
|
208
|
+
const backendData = await resolveBackendFromEndpoints({
|
|
209
|
+
routeName: name,
|
|
210
|
+
backendName: "0",
|
|
211
|
+
endpoints: firstBackend.endpoints,
|
|
212
|
+
namespace,
|
|
213
|
+
cluster: data.cluster,
|
|
214
|
+
opts,
|
|
215
|
+
})
|
|
151
216
|
|
|
152
217
|
const backendPort = await selectBackendPort({
|
|
153
|
-
ports:
|
|
218
|
+
ports: backendData.ports,
|
|
154
219
|
protocol,
|
|
155
|
-
targetPort:
|
|
156
|
-
serviceName,
|
|
220
|
+
targetPort: backendData.preferredTargetPort,
|
|
221
|
+
serviceName: backendData.serviceName,
|
|
157
222
|
routeName: name,
|
|
158
223
|
})
|
|
159
224
|
|
|
@@ -166,9 +231,13 @@ async function createL4GatewayRoute({
|
|
|
166
231
|
|
|
167
232
|
const listenerName = `${protocol.toLowerCase()}-${listenerPort}`
|
|
168
233
|
|
|
169
|
-
const
|
|
234
|
+
const gatewayName = name
|
|
235
|
+
const gatewayResourceName = name
|
|
236
|
+
|
|
237
|
+
const gateway = Gateway.create(
|
|
238
|
+
gatewayResourceName,
|
|
170
239
|
{
|
|
171
|
-
name:
|
|
240
|
+
name: gatewayName,
|
|
172
241
|
namespace,
|
|
173
242
|
gatewayClassName: data.className,
|
|
174
243
|
listeners: [
|
|
@@ -182,19 +251,11 @@ async function createL4GatewayRoute({
|
|
|
182
251
|
opts,
|
|
183
252
|
)
|
|
184
253
|
|
|
185
|
-
const backendRef =
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return {
|
|
193
|
-
name: metadata.name,
|
|
194
|
-
namespace: metadata.namespace,
|
|
195
|
-
port: backendPort.port,
|
|
196
|
-
}
|
|
197
|
-
})
|
|
254
|
+
const backendRef = {
|
|
255
|
+
name: backendData.serviceName,
|
|
256
|
+
namespace: backendData.serviceNamespace,
|
|
257
|
+
port: backendPort.port,
|
|
258
|
+
}
|
|
198
259
|
|
|
199
260
|
const routeOpts = { ...opts, parent: gateway }
|
|
200
261
|
|
|
@@ -254,6 +315,119 @@ async function getCertificateSecret(
|
|
|
254
315
|
)
|
|
255
316
|
}
|
|
256
317
|
|
|
318
|
+
function resolveRouteProtocol(spec: GatewayRouteSpec): GatewayRouteRuleSpec["type"] {
|
|
319
|
+
const rules = Object.values(spec.rules)
|
|
320
|
+
|
|
321
|
+
if (rules.length === 0) {
|
|
322
|
+
throw new Error("Gateway route must contain at least one rule")
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const type = rules[0].type
|
|
326
|
+
|
|
327
|
+
if (rules.some(rule => rule.type !== type)) {
|
|
328
|
+
throw new Error("Gateway route rules must use the same protocol type")
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return type
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async function resolveBackendFromEndpoints({
|
|
335
|
+
routeName,
|
|
336
|
+
backendName,
|
|
337
|
+
endpoints,
|
|
338
|
+
namespace,
|
|
339
|
+
cluster,
|
|
340
|
+
opts,
|
|
341
|
+
}: {
|
|
342
|
+
routeName: string
|
|
343
|
+
backendName: string
|
|
344
|
+
endpoints: network.L4Endpoint[]
|
|
345
|
+
namespace: Namespace
|
|
346
|
+
cluster: k8s.Cluster
|
|
347
|
+
opts: ComponentResourceOptions | undefined
|
|
348
|
+
}): Promise<{
|
|
349
|
+
serviceName: string
|
|
350
|
+
serviceNamespace: string
|
|
351
|
+
ports: ServicePortInfo[]
|
|
352
|
+
preferredTargetPort?: number | string
|
|
353
|
+
}> {
|
|
354
|
+
const serviceMeta = getServiceMetadataFromEndpoints(endpoints, cluster)
|
|
355
|
+
|
|
356
|
+
if (serviceMeta) {
|
|
357
|
+
const metadataPorts: ServicePortInfo[] = endpoints.map(endpoint => ({
|
|
358
|
+
name: typeof serviceMeta.targetPort === "string" ? serviceMeta.targetPort : undefined,
|
|
359
|
+
port: endpoint.port,
|
|
360
|
+
protocol: endpoint.protocol.toUpperCase() as "TCP" | "UDP",
|
|
361
|
+
targetPort: serviceMeta.targetPort,
|
|
362
|
+
}))
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
serviceName: serviceMeta.name,
|
|
366
|
+
serviceNamespace: serviceMeta.namespace,
|
|
367
|
+
ports: metadataPorts,
|
|
368
|
+
preferredTargetPort: serviceMeta.targetPort,
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const syntheticService = await createServiceFromEndpoints(
|
|
373
|
+
`${routeName}-${backendName}`,
|
|
374
|
+
namespace,
|
|
375
|
+
endpoints,
|
|
376
|
+
cluster,
|
|
377
|
+
opts,
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
const syntheticServiceName = await toPromise(syntheticService.service.metadata.name)
|
|
381
|
+
const syntheticServiceNamespace = await toPromise(syntheticService.service.metadata.namespace)
|
|
382
|
+
|
|
383
|
+
if (!syntheticServiceNamespace) {
|
|
384
|
+
throw new Error(
|
|
385
|
+
`Synthetic backend service "${syntheticServiceName}" for gateway route "${routeName}" has no namespace.`,
|
|
386
|
+
)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return {
|
|
390
|
+
serviceName: syntheticServiceName,
|
|
391
|
+
serviceNamespace: syntheticServiceNamespace,
|
|
392
|
+
ports: syntheticService.ports,
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function getServiceMetadataFromEndpoints(
|
|
397
|
+
endpoints: network.L4Endpoint[],
|
|
398
|
+
cluster: k8s.Cluster,
|
|
399
|
+
):
|
|
400
|
+
| {
|
|
401
|
+
name: string
|
|
402
|
+
namespace: string
|
|
403
|
+
targetPort?: number | string
|
|
404
|
+
}
|
|
405
|
+
| undefined {
|
|
406
|
+
const serviceEndpoints = endpoints.filter(endpoint => isEndpointFromCluster(endpoint, cluster))
|
|
407
|
+
|
|
408
|
+
if (serviceEndpoints.length === 0 || serviceEndpoints.length !== endpoints.length) {
|
|
409
|
+
return undefined
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const first = serviceEndpoints[0].metadata["k8s.service"]
|
|
413
|
+
|
|
414
|
+
if (
|
|
415
|
+
serviceEndpoints.some(
|
|
416
|
+
endpoint =>
|
|
417
|
+
endpoint.metadata["k8s.service"].name !== first.name ||
|
|
418
|
+
endpoint.metadata["k8s.service"].namespace !== first.namespace,
|
|
419
|
+
)
|
|
420
|
+
) {
|
|
421
|
+
return undefined
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return {
|
|
425
|
+
name: first.name,
|
|
426
|
+
namespace: first.namespace,
|
|
427
|
+
targetPort: first.targetPort,
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
257
431
|
type ServicePortInfo = {
|
|
258
432
|
name: string | undefined
|
|
259
433
|
port: number
|
|
@@ -331,7 +505,7 @@ async function createServiceFromEndpoints(
|
|
|
331
505
|
}
|
|
332
506
|
}
|
|
333
507
|
|
|
334
|
-
async function
|
|
508
|
+
async function _getServicePorts(service: Service): Promise<ServicePortInfo[]> {
|
|
335
509
|
const spec = await toPromise(service.spec)
|
|
336
510
|
const ports = spec.ports ?? []
|
|
337
511
|
|
|
@@ -9,10 +9,15 @@ export const createCertificate = tlsCertificateMediator.implement(
|
|
|
9
9
|
({ name, spec, opts }, data) => {
|
|
10
10
|
const provider = getProvider(data.cluster)
|
|
11
11
|
|
|
12
|
+
const metadata = spec.metadata as Record<string, unknown> | undefined
|
|
13
|
+
const metadataNamespace = metadata?.["k8s.namespace"]
|
|
14
|
+
|
|
12
15
|
const namespace =
|
|
13
|
-
|
|
14
|
-
?
|
|
15
|
-
:
|
|
16
|
+
metadataNamespace instanceof Namespace
|
|
17
|
+
? metadataNamespace
|
|
18
|
+
: metadataNamespace
|
|
19
|
+
? Namespace.for(metadataNamespace as k8s.Namespace, data.cluster)
|
|
20
|
+
: Namespace.get("cert-manager", { name: "cert-manager", cluster: data.cluster })
|
|
16
21
|
|
|
17
22
|
return Certificate.create(
|
|
18
23
|
name,
|
package/src/index.ts
CHANGED
package/src/job.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import type { k8s } from "@highstate/library"
|
|
2
1
|
import type { Container } from "./container"
|
|
3
2
|
import type { NetworkPolicy } from "./network-policy"
|
|
4
3
|
import { getOrCreate, type UnitTerminal } from "@highstate/contract"
|
|
4
|
+
import { k8s } from "@highstate/library"
|
|
5
5
|
import {
|
|
6
6
|
type ComponentResourceOptions,
|
|
7
7
|
type Input,
|
|
8
8
|
type Inputs,
|
|
9
9
|
interpolate,
|
|
10
|
+
makeEntityOutput,
|
|
10
11
|
type Output,
|
|
11
12
|
output,
|
|
12
13
|
toPromise,
|
|
@@ -18,6 +19,7 @@ import { omit } from "remeda"
|
|
|
18
19
|
import { Namespace } from "./namespace"
|
|
19
20
|
import { commonExtraArgs, getProvider, mapMetadata, type ScopedResourceArgs } from "./shared"
|
|
20
21
|
import {
|
|
22
|
+
filterPatchOwnedContainersInTemplate,
|
|
21
23
|
getWorkloadComponents,
|
|
22
24
|
Workload,
|
|
23
25
|
type WorkloadArgs,
|
|
@@ -90,7 +92,17 @@ export abstract class Job extends Workload {
|
|
|
90
92
|
* The Highstate job entity.
|
|
91
93
|
*/
|
|
92
94
|
get entity(): Output<k8s.Job> {
|
|
93
|
-
return
|
|
95
|
+
return makeEntityOutput({
|
|
96
|
+
entity: k8s.jobEntity,
|
|
97
|
+
identity: this.metadata.uid,
|
|
98
|
+
meta: {
|
|
99
|
+
title: this.metadata.name,
|
|
100
|
+
},
|
|
101
|
+
value: {
|
|
102
|
+
...this.entityBase,
|
|
103
|
+
spec: this.spec,
|
|
104
|
+
},
|
|
105
|
+
})
|
|
94
106
|
}
|
|
95
107
|
|
|
96
108
|
protected getTerminalMeta(): Output<UnitTerminal["meta"]> {
|
|
@@ -298,10 +310,16 @@ class JobPatch extends Job {
|
|
|
298
310
|
{
|
|
299
311
|
metadata: mapMetadata(args, name),
|
|
300
312
|
spec: output({ args, podTemplate }).apply(({ args, podTemplate }) => {
|
|
301
|
-
|
|
313
|
+
const spec = deepmerge(
|
|
302
314
|
{ template: podTemplate } satisfies types.input.batch.v1.JobSpec,
|
|
303
|
-
omit(args, jobExtraArgs)
|
|
304
|
-
)
|
|
315
|
+
omit(args, jobExtraArgs),
|
|
316
|
+
) as Unwrap<types.input.batch.v1.JobSpec>
|
|
317
|
+
|
|
318
|
+
if (spec.template) {
|
|
319
|
+
spec.template = filterPatchOwnedContainersInTemplate(spec.template, podTemplate)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return spec
|
|
305
323
|
}),
|
|
306
324
|
},
|
|
307
325
|
{ ...opts, parent: this, provider: getProvider(cluster) },
|
package/src/kubectl.ts
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import type { k8s } from "@highstate/library"
|
|
2
|
+
import type { InputOrArray } from "@highstate/pulumi"
|
|
3
|
+
import type { Namespace } from "./namespace"
|
|
4
|
+
import type { Workload } from "./workload"
|
|
5
|
+
import { Command, MaterializedFile } from "@highstate/common"
|
|
6
|
+
import {
|
|
7
|
+
ComponentResource,
|
|
8
|
+
type ComponentResourceOptions,
|
|
9
|
+
type Input,
|
|
10
|
+
type Output,
|
|
11
|
+
output,
|
|
12
|
+
} from "@pulumi/pulumi"
|
|
13
|
+
import { images } from "./shared"
|
|
14
|
+
|
|
15
|
+
export type KubeCommandArgs = {
|
|
16
|
+
/**
|
|
17
|
+
* The kubernetes cluster to run the command against.
|
|
18
|
+
*/
|
|
19
|
+
cluster: Input<k8s.Cluster>
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The namespace to run the command in, if any.
|
|
23
|
+
*/
|
|
24
|
+
namespace?: Input<string>
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The create command to run.
|
|
28
|
+
*/
|
|
29
|
+
create: InputOrArray<string>
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The update command to run.
|
|
33
|
+
*/
|
|
34
|
+
update?: InputOrArray<string>
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The delete command to run.
|
|
38
|
+
*/
|
|
39
|
+
delete?: InputOrArray<string>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type NamespaceKubeCommandArgs = Omit<KubeCommandArgs, "cluster" | "namespace"> & {
|
|
43
|
+
/**
|
|
44
|
+
* The namespace to run the command in.
|
|
45
|
+
*/
|
|
46
|
+
namespace: Input<Namespace>
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type ExecKubeCommandArgs = Omit<KubeCommandArgs, "cluster" | "namespace"> & {
|
|
50
|
+
/**
|
|
51
|
+
* The workload to exec into.
|
|
52
|
+
*/
|
|
53
|
+
workload: Input<Workload>
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function createCommand(command: string | string[]): string {
|
|
57
|
+
if (Array.isArray(command)) {
|
|
58
|
+
return command.join(" ")
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return command
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function buildKubeCommand(
|
|
65
|
+
command: InputOrArray<string>,
|
|
66
|
+
namespace?: Input<string>,
|
|
67
|
+
): Output<string> {
|
|
68
|
+
if (namespace) {
|
|
69
|
+
return output([command, namespace]).apply(
|
|
70
|
+
([cmd, ns]) => `kubectl -n ${ns} ${createCommand(cmd)}`,
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return output(command).apply(cmd => `kubectl ${createCommand(cmd)}`)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function buildWorkloadExecCommand(
|
|
78
|
+
command: InputOrArray<string>,
|
|
79
|
+
workload: Input<Workload>,
|
|
80
|
+
): Output<string> {
|
|
81
|
+
return output({
|
|
82
|
+
command,
|
|
83
|
+
kind: output(workload).kind,
|
|
84
|
+
name: output(workload).metadata.name,
|
|
85
|
+
}).apply(({ command, kind, name }) => {
|
|
86
|
+
const type = kind.toLowerCase()
|
|
87
|
+
|
|
88
|
+
return `exec -it ${type}/${name} -- ${createCommand(command)}`
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export class KubeCommand extends ComponentResource {
|
|
93
|
+
/**
|
|
94
|
+
* The underlying command that will be executed when this unit is invoked.
|
|
95
|
+
*/
|
|
96
|
+
readonly command: Output<Command>
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The standard output of the command.
|
|
100
|
+
*/
|
|
101
|
+
readonly stdout: Output<string>
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The standard error of the command.
|
|
105
|
+
*/
|
|
106
|
+
readonly stderr: Output<string>
|
|
107
|
+
|
|
108
|
+
constructor(name: string, args: KubeCommandArgs, opts?: ComponentResourceOptions) {
|
|
109
|
+
super("highstate:k8s:KubeCommand", name, args, opts)
|
|
110
|
+
|
|
111
|
+
this.command = output(args.cluster).apply(cluster => {
|
|
112
|
+
const kubeconfig = MaterializedFile.for(cluster.kubeconfig)
|
|
113
|
+
|
|
114
|
+
return new Command(`kubectl-${name}`, {
|
|
115
|
+
host: "local",
|
|
116
|
+
create: buildKubeCommand(args.create, args.namespace),
|
|
117
|
+
update: args.update ? buildKubeCommand(args.update, args.namespace) : undefined,
|
|
118
|
+
delete: args.delete ? buildKubeCommand(args.delete, args.namespace) : undefined,
|
|
119
|
+
files: [kubeconfig],
|
|
120
|
+
image: images["terminal-kubectl"].image,
|
|
121
|
+
containerShell: "bash",
|
|
122
|
+
environment: {
|
|
123
|
+
KUBECONFIG: kubeconfig.path,
|
|
124
|
+
},
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
this.stdout = this.command.stdout
|
|
129
|
+
this.stderr = this.command.stderr
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
static forNamespace(
|
|
133
|
+
name: string,
|
|
134
|
+
args: NamespaceKubeCommandArgs,
|
|
135
|
+
opts?: ComponentResourceOptions,
|
|
136
|
+
): KubeCommand {
|
|
137
|
+
return new KubeCommand(
|
|
138
|
+
name,
|
|
139
|
+
{
|
|
140
|
+
cluster: output(args.namespace).cluster,
|
|
141
|
+
create: args.create,
|
|
142
|
+
update: args.update,
|
|
143
|
+
delete: args.delete,
|
|
144
|
+
namespace: output(args.namespace).metadata.name,
|
|
145
|
+
},
|
|
146
|
+
opts,
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
static execInto(
|
|
151
|
+
name: string,
|
|
152
|
+
args: ExecKubeCommandArgs,
|
|
153
|
+
opts?: ComponentResourceOptions,
|
|
154
|
+
): KubeCommand {
|
|
155
|
+
return KubeCommand.forNamespace(
|
|
156
|
+
name,
|
|
157
|
+
{
|
|
158
|
+
namespace: output(args.workload).namespace,
|
|
159
|
+
create: buildWorkloadExecCommand(args.create, args.workload),
|
|
160
|
+
update: args.update ? buildWorkloadExecCommand(args.update, args.workload) : undefined,
|
|
161
|
+
delete: args.delete ? buildWorkloadExecCommand(args.delete, args.workload) : undefined,
|
|
162
|
+
},
|
|
163
|
+
opts,
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
}
|