@highstate/k8s 0.9.4 → 0.9.5

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 (63) hide show
  1. package/dist/chunk-DQSCJM5S.js +183 -0
  2. package/dist/chunk-DQSCJM5S.js.map +1 -0
  3. package/dist/chunk-FKNHHKOL.js +260 -0
  4. package/dist/chunk-FKNHHKOL.js.map +1 -0
  5. package/dist/chunk-HW3NS3MC.js +347 -0
  6. package/dist/chunk-HW3NS3MC.js.map +1 -0
  7. package/dist/chunk-OQ7UXASD.js +193 -0
  8. package/dist/chunk-OQ7UXASD.js.map +1 -0
  9. package/dist/chunk-QGHMLKTW.js +1123 -0
  10. package/dist/chunk-QGHMLKTW.js.map +1 -0
  11. package/dist/chunk-UNVSWG6D.js +214 -0
  12. package/dist/chunk-UNVSWG6D.js.map +1 -0
  13. package/dist/deployment-ZP3ASKPT.js +10 -0
  14. package/dist/deployment-ZP3ASKPT.js.map +1 -0
  15. package/dist/highstate.manifest.json +8 -6
  16. package/dist/index.js +291 -954
  17. package/dist/index.js.map +1 -1
  18. package/dist/stateful-set-2AH7RAF7.js +10 -0
  19. package/dist/stateful-set-2AH7RAF7.js.map +1 -0
  20. package/dist/units/access-point/index.js +6 -1
  21. package/dist/units/access-point/index.js.map +1 -1
  22. package/dist/units/cert-manager/index.js +19 -24
  23. package/dist/units/cert-manager/index.js.map +1 -1
  24. package/dist/units/cluster-dns/index.js +36 -0
  25. package/dist/units/cluster-dns/index.js.map +1 -0
  26. package/dist/units/cluster-patch/index.js +34 -0
  27. package/dist/units/cluster-patch/index.js.map +1 -0
  28. package/dist/units/dns01-issuer/index.js +2 -2
  29. package/dist/units/dns01-issuer/index.js.map +1 -1
  30. package/dist/units/existing-cluster/index.js +22 -14
  31. package/dist/units/existing-cluster/index.js.map +1 -1
  32. package/dist/units/gateway-api/index.js +1 -1
  33. package/package.json +12 -10
  34. package/src/access-point.ts +44 -39
  35. package/src/container.ts +54 -5
  36. package/src/cron-job.ts +14 -30
  37. package/src/deployment.ts +170 -127
  38. package/src/gateway/http-route.ts +7 -5
  39. package/src/helm.ts +57 -8
  40. package/src/index.ts +11 -4
  41. package/src/job.ts +14 -32
  42. package/src/namespace.ts +241 -0
  43. package/src/network-policy.ts +371 -87
  44. package/src/network.ts +41 -0
  45. package/src/pvc.ts +43 -25
  46. package/src/scripting/bundle.ts +125 -22
  47. package/src/scripting/container.ts +16 -11
  48. package/src/scripting/environment.ts +56 -6
  49. package/src/secret.ts +195 -0
  50. package/src/service.ts +209 -89
  51. package/src/shared.ts +42 -51
  52. package/src/stateful-set.ts +193 -88
  53. package/src/units/access-point/index.ts +8 -1
  54. package/src/units/cert-manager/index.ts +15 -20
  55. package/src/units/cluster-dns/index.ts +37 -0
  56. package/src/units/cluster-patch/index.ts +35 -0
  57. package/src/units/dns01-issuer/index.ts +1 -1
  58. package/src/units/existing-cluster/index.ts +24 -14
  59. package/src/workload.ts +342 -44
  60. package/dist/chunk-K4WKJ4L5.js +0 -455
  61. package/dist/chunk-K4WKJ4L5.js.map +0 -1
  62. package/dist/chunk-T5Z2M4JE.js +0 -103
  63. package/dist/chunk-T5Z2M4JE.js.map +0 -1
package/src/secret.ts ADDED
@@ -0,0 +1,195 @@
1
+ import type { k8s } from "@highstate/library"
2
+ import { core, type types } from "@pulumi/kubernetes"
3
+ import {
4
+ ComponentResource,
5
+ output,
6
+ Output,
7
+ type ComponentResourceOptions,
8
+ type Input,
9
+ type Inputs,
10
+ } from "@pulumi/pulumi"
11
+ import { getProvider, mapMetadata, withPatchName, type CommonArgs } from "./shared"
12
+
13
+ export type SecretArgs = CommonArgs &
14
+ Omit<types.input.core.v1.Secret, "kind" | "metadata" | "apiVersion">
15
+
16
+ export type CreateOrPatchSecretArgs = SecretArgs & {
17
+ /**
18
+ * The resource to use to determine the name of the secret.
19
+ *
20
+ * If not provided, the secret will be created, otherwise it will be retrieved/patched.
21
+ */
22
+ existing: Input<k8s.Resource> | undefined
23
+ }
24
+
25
+ export abstract class Secret extends ComponentResource {
26
+ protected constructor(
27
+ type: string,
28
+ name: string,
29
+ args: Inputs,
30
+ opts: ComponentResourceOptions | undefined,
31
+
32
+ /**
33
+ * The cluster where the secret is created.
34
+ */
35
+ readonly cluster: Output<k8s.Cluster>,
36
+
37
+ /**
38
+ * The metadata of the underlying Kubernetes secret.
39
+ */
40
+ readonly metadata: Output<types.output.meta.v1.ObjectMeta>,
41
+
42
+ /**
43
+ * The data of the underlying Kubernetes secret.
44
+ */
45
+ readonly data: Output<Record<string, string>>,
46
+
47
+ /**
48
+ * The stringData of the underlying Kubernetes secret.
49
+ */
50
+ readonly stringData: Output<Record<string, string>>,
51
+ ) {
52
+ super(type, name, args, opts)
53
+ }
54
+
55
+ /**
56
+ * Creates a new secret.
57
+ */
58
+ static create(name: string, args: SecretArgs, opts?: ComponentResourceOptions): Secret {
59
+ return new CreatedSecret(name, args, opts)
60
+ }
61
+
62
+ /**
63
+ * Creates a new secret or patches an existing one.
64
+ *
65
+ * Will throw an error if the secret does not exist when `args.resource` is provided.
66
+ */
67
+ static createOrPatch(
68
+ name: string,
69
+ args: CreateOrPatchSecretArgs,
70
+ opts?: ComponentResourceOptions,
71
+ ): Secret {
72
+ if (!args.existing) {
73
+ return new CreatedSecret(name, args, opts)
74
+ }
75
+
76
+ return new SecretPatch(
77
+ name,
78
+ {
79
+ ...args,
80
+ name: withPatchName("secret", args.existing, args.cluster),
81
+ namespace: output(args.existing).metadata.namespace,
82
+ },
83
+ opts,
84
+ )
85
+ }
86
+
87
+ /**
88
+ * Gets an existing secret.
89
+ *
90
+ * Will throw an error if the secret does not exist.
91
+ */
92
+ static get(
93
+ name: string,
94
+ id: Input<string>,
95
+ cluster: Input<k8s.Cluster>,
96
+ opts?: ComponentResourceOptions,
97
+ ): Secret {
98
+ return new ExternalSecret(name, id, cluster, opts)
99
+ }
100
+ }
101
+
102
+ class CreatedSecret extends Secret {
103
+ constructor(name: string, args: SecretArgs, opts?: ComponentResourceOptions) {
104
+ const secret = output(args).apply(async args => {
105
+ return new core.v1.Secret(
106
+ name,
107
+ {
108
+ metadata: mapMetadata(args, name),
109
+ data: args.data,
110
+ stringData: args.stringData,
111
+ },
112
+ {
113
+ ...opts,
114
+ parent: this,
115
+ provider: await getProvider(args.cluster),
116
+ },
117
+ )
118
+ })
119
+
120
+ super(
121
+ "highstate:k8s:Secret",
122
+ name,
123
+ args,
124
+ opts,
125
+ output(args.cluster),
126
+ secret.metadata,
127
+ secret.data,
128
+ secret.stringData,
129
+ )
130
+ }
131
+ }
132
+
133
+ class SecretPatch extends Secret {
134
+ constructor(name: string, args: SecretArgs, opts?: ComponentResourceOptions) {
135
+ const secret = output(args).apply(async args => {
136
+ return new core.v1.SecretPatch(
137
+ name,
138
+ {
139
+ metadata: mapMetadata(args, name),
140
+ data: args.data,
141
+ stringData: args.stringData,
142
+ },
143
+ {
144
+ ...opts,
145
+ parent: this,
146
+ provider: await getProvider(args.cluster),
147
+ },
148
+ )
149
+ })
150
+
151
+ super(
152
+ "highstate:k8s:SecretPatch",
153
+ name,
154
+ args,
155
+ opts,
156
+ output(args.cluster),
157
+ secret.metadata,
158
+ secret.data,
159
+ secret.stringData,
160
+ )
161
+ }
162
+ }
163
+
164
+ class ExternalSecret extends Secret {
165
+ constructor(
166
+ name: string,
167
+ id: Input<string>,
168
+ cluster: Input<k8s.Cluster>,
169
+ opts?: ComponentResourceOptions,
170
+ ) {
171
+ const secret = output(id).apply(async realName => {
172
+ return core.v1.Secret.get(
173
+ //
174
+ name,
175
+ realName,
176
+ {
177
+ ...opts,
178
+ parent: this,
179
+ provider: await getProvider(cluster),
180
+ },
181
+ )
182
+ })
183
+
184
+ super(
185
+ "highstate:k8s:ExternalSecret",
186
+ name,
187
+ { id, cluster },
188
+ opts,
189
+ output(cluster),
190
+ secret.metadata,
191
+ secret.data,
192
+ secret.stringData,
193
+ )
194
+ }
195
+ }
package/src/service.ts CHANGED
@@ -1,43 +1,109 @@
1
- import type { k8s } from "@highstate/library"
1
+ import type { k8s, network } from "@highstate/library"
2
2
  import { core, types } from "@pulumi/kubernetes"
3
3
  import {
4
4
  ComponentResource,
5
- interpolate,
6
5
  normalize,
7
6
  output,
8
7
  Output,
9
- Resource,
10
8
  type ComponentResourceOptions,
11
9
  type Input,
12
- type InputArray,
13
10
  type Inputs,
14
11
  } from "@highstate/pulumi"
15
- import { omit } from "remeda"
12
+ import { omit, uniqueBy } from "remeda"
16
13
  import { deepmerge } from "deepmerge-ts"
14
+ import { filterEndpoints, l4EndpointToString, parseL3Endpoint } from "@highstate/common"
17
15
  import {
18
16
  commonExtraArgs,
19
17
  mapMetadata,
20
18
  resourceIdToString,
21
- verifyProvider,
22
19
  type CommonArgs,
23
20
  type ResourceId,
21
+ type SelectorLike,
24
22
  } from "./shared"
25
23
 
26
24
  export type ServiceArgs = CommonArgs & {
27
- port?: Input<types.input.core.v1.ServicePort>
28
-
29
25
  /**
30
- * The service to patch instead of creating a new one.
26
+ * The port to expose the service on.
31
27
  */
32
- patch?: Input<k8s.Service>
28
+ port?: Input<types.input.core.v1.ServicePort>
33
29
 
34
30
  /**
35
- * The cluster to create the resource in.
31
+ * Whether the service should be exposed by `NodePort` or `LoadBalancer`.
32
+ *
33
+ * The type of the service will be determined automatically based on the cluster.
36
34
  */
37
- cluster: Input<k8s.Cluster>
35
+ external?: boolean
38
36
  } & types.input.core.v1.ServiceSpec
39
37
 
40
- const serviceExtraArgs = [...commonExtraArgs, "port", "ports", "patch"] as const
38
+ const serviceExtraArgs = [...commonExtraArgs, "port", "ports", "external"] as const
39
+
40
+ export type ServiceEndpointMetadata = {
41
+ clusterId: string
42
+ name: string
43
+ namespace: string
44
+ selector: SelectorLike
45
+ targetPort: string | number
46
+ }
47
+
48
+ /**
49
+ * Checks if the endpoint has service metadata.
50
+ *
51
+ * Alters the type of the endpoint to include the service metadata if it exists.
52
+ *
53
+ * @param endpoint The endpoint to check.
54
+ * @returns True if the endpoint has service metadata, false otherwise.
55
+ */
56
+ export function hasServiceMetadata(
57
+ endpoint: network.L3Endpoint,
58
+ ): endpoint is network.L3Endpoint & { metadata: { k8sService: ServiceEndpointMetadata } } {
59
+ return endpoint.metadata?.k8sService !== undefined
60
+ }
61
+
62
+ /**
63
+ * Returns the service metadata of the endpoint.
64
+ *
65
+ * @param endpoint The endpoint to get the service metadata from.
66
+ * @returns The service metadata of the endpoint, or undefined if it doesn't exist.
67
+ */
68
+ export function getServiceMetadata(
69
+ endpoint: network.L3Endpoint,
70
+ ): ServiceEndpointMetadata | undefined {
71
+ return endpoint.metadata?.k8sService as ServiceEndpointMetadata
72
+ }
73
+
74
+ /**
75
+ * Adds service metadata to the endpoint.
76
+ *
77
+ * @param endpoint The endpoint to add the metadata to.
78
+ * @param metadata The metadata to add.
79
+ * @returns The endpoint with the added metadata.
80
+ */
81
+ export function withServiceMetadata<TEdnpoint extends network.L34Endpoint>(
82
+ endpoint: TEdnpoint,
83
+ metadata: ServiceEndpointMetadata,
84
+ ): TEdnpoint & { metadata: { k8sService: ServiceEndpointMetadata } } {
85
+ return {
86
+ ...endpoint,
87
+ metadata: {
88
+ ...endpoint.metadata,
89
+ k8sService: metadata,
90
+ },
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Checks if the endpoint is from the given cluster.
96
+ *
97
+ * @param endpoint The endpoint to check.
98
+ * @param cluster The cluster to check against.
99
+ * @returns True if the endpoint is from the cluster, false otherwise.
100
+ */
101
+ export function isFromCluster(
102
+ endpoint: network.L3Endpoint,
103
+ cluster: k8s.Cluster,
104
+ ): endpoint is network.L3Endpoint & { metadata: { k8sService: ServiceEndpointMetadata } } {
105
+ return getServiceMetadata(endpoint)?.clusterId === cluster.id
106
+ }
41
107
 
42
108
  export abstract class Service extends ComponentResource {
43
109
  protected constructor(
@@ -49,7 +115,7 @@ export abstract class Service extends ComponentResource {
49
115
  /**
50
116
  * The cluster info associated with the service.
51
117
  */
52
- readonly clusterInfo: Output<k8s.ClusterInfo>,
118
+ readonly cluster: Output<k8s.Cluster>,
53
119
 
54
120
  /**
55
121
  * The metadata of the underlying Kubernetes service.
@@ -65,11 +131,6 @@ export abstract class Service extends ComponentResource {
65
131
  * The status of the underlying Kubernetes service.
66
132
  */
67
133
  readonly status: Output<types.output.core.v1.ServiceStatus>,
68
-
69
- /**
70
- * The resources associated with the service.
71
- */
72
- readonly resources: InputArray<Resource>,
73
134
  ) {
74
135
  super(type, name, args, opts)
75
136
  }
@@ -80,9 +141,9 @@ export abstract class Service extends ComponentResource {
80
141
  get entity(): Output<k8s.Service> {
81
142
  return output({
82
143
  type: "k8s.service",
83
- clusterInfo: this.clusterInfo,
144
+ clusterId: this.cluster.id,
84
145
  metadata: this.metadata,
85
- spec: this.spec,
146
+ endpoints: this.endpoints,
86
147
  })
87
148
  }
88
149
 
@@ -93,94 +154,143 @@ export abstract class Service extends ComponentResource {
93
154
  static wrap(
94
155
  name: string,
95
156
  service: Input<core.v1.Service>,
96
- clusterInfo: Input<k8s.ClusterInfo>,
157
+ cluster: Input<k8s.Cluster>,
97
158
  opts: ComponentResourceOptions,
98
159
  ): Service {
99
- return new WrappedService(name, service, clusterInfo, opts)
160
+ return new WrappedService(name, service, cluster, opts)
100
161
  }
101
162
 
102
163
  static external(
103
164
  name: string,
104
165
  id: ResourceId,
105
- clusterInfo: Input<k8s.ClusterInfo>,
166
+ cluster: Input<k8s.Cluster>,
106
167
  opts: ComponentResourceOptions,
107
168
  ): Service {
108
- return new ExternalService(name, id, clusterInfo, opts)
169
+ return new ExternalService(name, id, cluster, opts)
109
170
  }
110
171
 
111
- static of(name: string, entity: Input<k8s.Service>, opts: ComponentResourceOptions): Service {
112
- return new ExternalService(name, output(entity).metadata, output(entity).clusterInfo, opts)
113
- }
172
+ static of(
173
+ name: string,
174
+ entity: Input<k8s.Service>,
175
+ cluster: Input<k8s.Cluster>,
176
+ opts: ComponentResourceOptions,
177
+ ): Service {
178
+ return new ExternalService(
179
+ name,
180
+ output(entity).metadata,
181
+ output({ cluster, entity }).apply(({ cluster, entity }) => {
182
+ if (cluster.id !== entity.clusterId) {
183
+ throw new Error(
184
+ `Cluster mismatch when wrapping service "${name}": "${cluster.id}" != "${entity.clusterId}"`,
185
+ )
186
+ }
114
187
 
115
- /**
116
- * Returns the host of the service accessible from the cluster.
117
- *
118
- * The format is `name.namespace.svc`.
119
- */
120
- get clusterHost(): Output<string> {
121
- return interpolate`${this.metadata.name}.${this.metadata.namespace}.svc`
188
+ return cluster
189
+ }),
190
+ opts,
191
+ )
122
192
  }
123
193
 
124
194
  /**
125
- * Returns the external IP of the service.
195
+ * Returns the endpoints of the service applying the given filter.
126
196
  *
127
- * If the load balancer is available, the external IP is returned, otherwise the IP of the node.
197
+ * If no filter is specified, the default behavior of `filterEndpoints` is used.
128
198
  *
129
- * If the service has no external IP, `undefined` is returned.
199
+ * @param filter If specified, the endpoints are filtered based on the given filter.
200
+ * @returns The endpoints of the service.
130
201
  */
131
- get externalIp(): Output<string | undefined> {
132
- return output({ spec: this.spec, status: this.status }).apply(({ spec, status }) => {
133
- const loadBalancerIp = status.loadBalancer?.ingress?.[0]?.ip
134
- if (loadBalancerIp) {
135
- return loadBalancerIp
136
- }
137
-
138
- const nodeIp = spec.externalIPs?.[0]
139
- if (nodeIp) {
140
- return nodeIp
141
- }
142
-
143
- return undefined
202
+ filterEndpoints(filter?: network.EndpointFilter): Output<network.L4Endpoint[]> {
203
+ return output({ endpoints: this.endpoints }).apply(({ endpoints }) => {
204
+ return filterEndpoints(endpoints, filter)
144
205
  })
145
206
  }
146
207
 
147
208
  /**
148
- * The "most public" IP of the service.
149
- *
150
- * Resolves to the external IP if available, otherwise the cluster IP.
151
- *
152
- * If the service is headless, an error is thrown.
209
+ * Returns the endpoints of the service including both internal and external endpoints.
153
210
  */
154
- get ip(): Output<string> {
155
- return output({ spec: this.spec, externalIp: this.externalIp }).apply(
156
- ({ spec, externalIp }) => {
157
- if (externalIp) {
158
- return externalIp
159
- }
211
+ get endpoints(): Output<network.L4Endpoint[]> {
212
+ return output({
213
+ clusterId: this.cluster.id,
214
+ metadata: this.metadata,
215
+ spec: this.spec,
216
+ status: this.status,
217
+ }).apply(({ clusterId, metadata, spec, status }) => {
218
+ const endpointMetadata = {
219
+ k8sService: {
220
+ clusterId,
221
+ name: metadata.name,
222
+ namespace: metadata.namespace,
223
+ selector: spec.selector,
224
+ targetPort: spec.ports[0].targetPort ?? spec.ports[0].port,
225
+ },
226
+ }
160
227
 
161
- const clusterIp = spec.clusterIP
162
- if (clusterIp && clusterIp !== "None") {
163
- return clusterIp
164
- }
228
+ const clusterIpEndpoints = spec.clusterIPs?.map(ip => ({
229
+ ...parseL3Endpoint(ip),
230
+ port: spec.ports[0].port,
231
+ protocol: spec.ports[0].protocol?.toLowerCase() as network.L4Protocol,
232
+ metadata: endpointMetadata,
233
+ }))
234
+
235
+ if (clusterIpEndpoints.length > 0) {
236
+ clusterIpEndpoints.unshift({
237
+ type: "hostname",
238
+ visibility: "internal",
239
+ hostname: `${metadata.name}.${metadata.namespace}.svc.cluster.local`,
240
+ port: spec.ports[0].port,
241
+ protocol: spec.ports[0].protocol?.toLowerCase() as network.L4Protocol,
242
+ metadata: endpointMetadata,
243
+ })
244
+ }
165
245
 
166
- throw new Error("The service does not have neither an external IP nor a cluster IP.")
167
- },
168
- )
246
+ const nodePortEndpoints =
247
+ spec.type === "NodePort"
248
+ ? spec.externalIPs?.map(ip => ({
249
+ ...parseL3Endpoint(ip),
250
+ port: spec.ports[0].nodePort,
251
+ protocol: spec.ports[0].protocol?.toLowerCase() as network.L4Protocol,
252
+ metadata: endpointMetadata,
253
+ }))
254
+ : []
255
+
256
+ const loadBalancerEndpoints =
257
+ spec.type === "LoadBalancer"
258
+ ? status.loadBalancer?.ingress?.map(endpoint => ({
259
+ ...parseL3Endpoint(endpoint.ip ?? endpoint.hostname),
260
+ port: spec.ports[0].port,
261
+ protocol: spec.ports[0].protocol?.toLowerCase() as network.L4Protocol,
262
+ metadata: endpointMetadata,
263
+ }))
264
+ : []
265
+
266
+ return uniqueBy(
267
+ [
268
+ ...(clusterIpEndpoints ?? []),
269
+ ...(loadBalancerEndpoints ?? []),
270
+ ...(nodePortEndpoints ?? []),
271
+ ],
272
+ endpoint => l4EndpointToString(endpoint),
273
+ )
274
+ })
169
275
  }
170
276
  }
171
277
 
172
278
  class CreatedService extends Service {
173
279
  constructor(name: string, args: ServiceArgs, opts: ComponentResourceOptions) {
174
- const service = output(args).apply(async args => {
175
- await verifyProvider(opts.provider, args.cluster.info)
176
-
177
- return new (args.patch ? core.v1.ServicePatch : core.v1.Service)(
280
+ const service = output(args).apply(args => {
281
+ return new core.v1.Service(
178
282
  name,
179
283
  {
180
- metadata: mapMetadata(args.patch?.metadata ?? args, name),
284
+ metadata: mapMetadata(args, name),
181
285
  spec: deepmerge(
182
286
  {
183
287
  ports: normalize(args.port, args.ports),
288
+
289
+ externalIPs: args.external
290
+ ? (args.externalIPs ?? args.cluster.externalIps)
291
+ : args.cluster.externalIps,
292
+
293
+ type: getServiceType(args, args.cluster),
184
294
  },
185
295
  omit(args, serviceExtraArgs),
186
296
  ),
@@ -195,11 +305,10 @@ class CreatedService extends Service {
195
305
  args,
196
306
  opts,
197
307
 
198
- output(args.cluster).info,
308
+ output(args.cluster),
199
309
  service.metadata,
200
310
  service.spec,
201
311
  service.status,
202
- [service],
203
312
  )
204
313
  }
205
314
  }
@@ -208,20 +317,19 @@ class WrappedService extends Service {
208
317
  constructor(
209
318
  name: string,
210
319
  service: Input<core.v1.Service>,
211
- clusterInfo: Input<k8s.ClusterInfo>,
320
+ cluster: Input<k8s.Cluster>,
212
321
  opts: ComponentResourceOptions,
213
322
  ) {
214
323
  super(
215
324
  "highstate:k8s:WrappedService",
216
325
  name,
217
- { service, clusterInfo },
326
+ { service, clusterInfo: cluster },
218
327
  opts,
219
328
 
220
- output(clusterInfo),
329
+ output(cluster),
221
330
  output(service).metadata,
222
331
  output(service).spec,
223
332
  output(service).status,
224
- [service],
225
333
  )
226
334
  }
227
335
  }
@@ -229,32 +337,29 @@ class WrappedService extends Service {
229
337
  class ExternalService extends Service {
230
338
  constructor(
231
339
  name: string,
232
- id: ResourceId,
233
- clusterInfo: Input<k8s.ClusterInfo>,
340
+ id: Input<ResourceId>,
341
+ cluster: Input<k8s.Cluster>,
234
342
  opts: ComponentResourceOptions,
235
343
  ) {
236
- const service = output(id).apply(async id => {
237
- await verifyProvider(opts.provider, this.clusterInfo)
238
-
344
+ const service = output(id).apply(id => {
239
345
  return core.v1.Service.get(
240
346
  //
241
347
  name,
242
348
  resourceIdToString(id),
243
- { parent: this, provider: opts.provider },
349
+ { ...opts, parent: this },
244
350
  )
245
351
  })
246
352
 
247
353
  super(
248
354
  "highstate:k8s:ExternalService",
249
355
  name,
250
- { id, clusterInfo },
356
+ { id, cluster },
251
357
  opts,
252
358
 
253
- output(clusterInfo),
359
+ output(cluster),
254
360
  service.metadata,
255
361
  service.spec,
256
362
  service.status,
257
- [service],
258
363
  )
259
364
  }
260
365
  }
@@ -277,3 +382,18 @@ export function mapServiceToLabelSelector(
277
382
  matchLabels: service.spec.selector,
278
383
  }
279
384
  }
385
+
386
+ export function getServiceType(
387
+ service: Pick<ServiceArgs, "type" | "external"> | undefined,
388
+ cluster: k8s.Cluster,
389
+ ): Input<string> {
390
+ if (service?.type) {
391
+ return service.type
392
+ }
393
+
394
+ if (!service?.external) {
395
+ return "ClusterIP"
396
+ }
397
+
398
+ return cluster.quirks?.externalServiceType === "LoadBalancer" ? "LoadBalancer" : "NodePort"
399
+ }