@toa.io/operations 1.0.0-alpha.284 → 1.0.0-alpha.286

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 (50) hide show
  1. package/package.json +4 -4
  2. package/readme.md +43 -0
  3. package/src/deployment/.deployment/.describe/compositions.js +7 -5
  4. package/src/deployment/.deployment/.describe/events.js +7 -7
  5. package/src/deployment/.deployment/.describe/fold.js +10 -3
  6. package/src/deployment/.deployment/.describe/fold.test.js +30 -14
  7. package/src/deployment/.deployment/.describe/mounts.js +4 -7
  8. package/src/deployment/.deployment/.describe/resources.js +7 -5
  9. package/src/deployment/.deployment/.describe/resources.test.js +13 -6
  10. package/src/deployment/.deployment/.describe/services.js +10 -9
  11. package/src/deployment/.deployment/.describe/services.test.js +22 -5
  12. package/src/deployment/.deployment/.describe/variables.js +2 -3
  13. package/src/deployment/.deployment/describe.js +11 -7
  14. package/src/deployment/.deployment/merge.js +11 -6
  15. package/src/deployment/.deployment/merge.test.js +41 -10
  16. package/src/deployment/composition.js +1 -1
  17. package/src/deployment/deployment.js +35 -16
  18. package/src/deployment/factory.js +39 -19
  19. package/src/deployment/images/composition.Dockerfile +1 -1
  20. package/src/deployment/images/composition.js +10 -9
  21. package/src/deployment/images/factory.js +17 -6
  22. package/src/deployment/images/format.js +9 -5
  23. package/src/deployment/images/image.fixtures.js +2 -2
  24. package/src/deployment/images/image.js +38 -21
  25. package/src/deployment/images/image.test.js +7 -2
  26. package/src/deployment/images/mono.Dockerfile +1 -1
  27. package/src/deployment/images/mono.js +10 -9
  28. package/src/deployment/images/publish.js +98 -0
  29. package/src/deployment/images/runtime-base.test.js +12 -6
  30. package/src/deployment/images/service.Dockerfile +1 -1
  31. package/src/deployment/images/service.js +4 -4
  32. package/src/deployment/operator.js +8 -8
  33. package/src/deployment/registry.js +89 -61
  34. package/src/deployment/registry.test.js +117 -43
  35. package/src/deployment/service.js +3 -1
  36. package/src/deployment/workspace.js +1 -1
  37. package/src/process.js +1 -1
  38. package/src/process.test.js +8 -4
  39. package/types/_deployment/composition.d.ts +0 -2
  40. package/types/_deployment/dependency.d.ts +10 -6
  41. package/types/_deployment/deployment.d.ts +0 -4
  42. package/types/_deployment/factory.d.ts +0 -2
  43. package/types/_deployment/images/factory.d.ts +4 -6
  44. package/types/_deployment/images/image.d.ts +2 -4
  45. package/types/_deployment/images/registry.d.ts +7 -10
  46. package/types/_deployment/operator.d.ts +5 -7
  47. package/types/_deployment/registry.d.ts +6 -8
  48. package/types/_deployment/service.d.ts +0 -2
  49. package/types/dependency.ts +3 -0
  50. package/CHANGELOG.md +0 -231
@@ -17,8 +17,7 @@ beforeEach(() => {
17
17
  images = []
18
18
  process = /** @type {toa.operations.Process} */ {
19
19
  execute: mock.fn(async (cmd, args) => {
20
- if (args[0] === 'manifest')
21
- throw new Error('manifest unknown')
20
+ if (args[0] === 'manifest') throw new Error('manifest unknown')
22
21
 
23
22
  if (args[0] === 'buildx' && args[1] === 'inspect')
24
23
  throw new Error('builder not found')
@@ -29,30 +28,45 @@ beforeEach(() => {
29
28
 
30
29
  factory = /** @type {toa.deployment.images.Factory} */ {
31
30
  composition: () => createImage('composition-mono'),
32
- service: () => createImage('extension-realtime')
31
+ service: () => createImage('extension-realtime'),
32
+ mono: () => createImage('mono')
33
33
  }
34
34
  })
35
35
 
36
36
  it('should reuse named builder across images', async () => {
37
- const registry = createRegistry({ base: 'example.com/reg', platforms: ['linux/amd64'] })
37
+ const registry = createRegistry({
38
+ base: 'example.com/reg',
39
+ platforms: ['linux/amd64', 'linux/arm64']
40
+ })
38
41
 
39
42
  registry.composition(/** @type {any} */ ({}))
40
43
  registry.service('.', /** @type {any} */ ({}))
41
44
 
42
45
  await registry.build()
43
46
 
44
- const creates = process.execute.mock.calls.filter(({ arguments: [, args] }) =>
45
- args[0] === 'buildx' && args[1] === 'create')
47
+ const creates = process.execute.mock.calls.filter(
48
+ ({ arguments: [, args] }) => args[0] === 'buildx' && args[1] === 'create'
49
+ )
46
50
 
47
51
  assert.strictEqual(creates.length, 1)
48
- assert.deepStrictEqual(creates[0].arguments[1], ['buildx', 'create', '--name', 'toa', '--bootstrap'])
49
-
50
- const builds = process.execute.mock.calls.filter(({ arguments: [, args] }) =>
51
- args[0] === '--context=default' && args[1] === 'buildx' && args[2] === 'build')
52
+ assert.deepStrictEqual(creates[0].arguments[1], [
53
+ 'buildx',
54
+ 'create',
55
+ '--name',
56
+ 'toa',
57
+ '--bootstrap'
58
+ ])
59
+
60
+ const builds = process.execute.mock.calls.filter(
61
+ ({ arguments: [, args] }) =>
62
+ args[0] === '--context=default' && args[1] === 'buildx' && args[2] === 'build'
63
+ )
52
64
 
53
65
  assert.strictEqual(builds.length, 2)
54
66
 
55
- for (const { arguments: [, args] } of builds) {
67
+ for (const {
68
+ arguments: [, args]
69
+ } of builds) {
56
70
  assert.ok(args.includes('--builder'))
57
71
  assert.strictEqual(args[args.indexOf('--builder') + 1], 'toa')
58
72
  }
@@ -60,25 +74,28 @@ it('should reuse named builder across images', async () => {
60
74
 
61
75
  it('should not create builder when it already exists', async () => {
62
76
  process.execute = mock.fn(async (cmd, args) => {
63
- if (args[0] === 'manifest')
64
- throw new Error('manifest unknown')
77
+ if (args[0] === 'manifest') throw new Error('manifest unknown')
65
78
 
66
79
  return ''
67
80
  })
68
81
 
69
- const registry = createRegistry({ base: 'example.com/reg', platforms: ['linux/amd64'] })
82
+ const registry = createRegistry({
83
+ base: 'example.com/reg',
84
+ platforms: ['linux/amd64', 'linux/arm64']
85
+ })
70
86
 
71
87
  registry.composition(/** @type {any} */ ({}))
72
88
 
73
89
  await registry.build()
74
90
 
75
- const creates = process.execute.mock.calls.filter(({ arguments: [, args] }) =>
76
- args[0] === 'buildx' && args[1] === 'create')
91
+ const creates = process.execute.mock.calls.filter(
92
+ ({ arguments: [, args] }) => args[0] === 'buildx' && args[1] === 'create'
93
+ )
77
94
 
78
95
  assert.strictEqual(creates.length, 0)
79
96
  })
80
97
 
81
- it('should add shared registry cache flags when base is set', async () => {
98
+ it('should not export a build cache', async () => {
82
99
  const registry = createRegistry({ base: 'example.com/reg', platforms: ['linux/amd64'] })
83
100
 
84
101
  registry.composition(/** @type {any} */ ({}))
@@ -86,35 +103,77 @@ it('should add shared registry cache flags when base is set', async () => {
86
103
 
87
104
  await registry.build()
88
105
 
89
- const builds = process.execute.mock.calls.filter(({ arguments: [, args] }) =>
90
- args[0] === '--context=default' && args[2] === 'build')
106
+ const builds = process.execute.mock.calls.filter(
107
+ ({ arguments: [, args] }) => args[0] === '--context=default' && args[2] === 'build'
108
+ )
91
109
 
92
110
  assert.strictEqual(builds.length, 2)
93
111
 
94
- for (const { arguments: [, args] } of builds) {
95
- assert.ok(args.includes('--cache-from'))
96
- assert.ok(args.includes('type=registry,ref=example.com/reg/acme/buildcache'))
97
- assert.ok(args.includes('--cache-to'))
98
- assert.ok(args.includes('type=registry,ref=example.com/reg/acme/buildcache,mode=max,image-manifest=true'))
112
+ for (const {
113
+ arguments: [, args]
114
+ } of builds) {
115
+ assert.ok(!args.includes('--cache-from'))
116
+ assert.ok(!args.includes('--cache-to'))
99
117
  }
100
118
  })
101
119
 
102
- it('should omit cache flags when base is not set', async () => {
103
- const registry = createRegistry({ platforms: ['linux/amd64'] })
120
+ it('should use the default builder for a single platform', async () => {
121
+ const registry = createRegistry({ base: 'example.com/reg', platforms: ['linux/amd64'] })
104
122
 
105
123
  registry.composition(/** @type {any} */ ({}))
106
124
 
107
125
  await registry.build()
108
126
 
109
- const builds = process.execute.mock.calls.filter(({ arguments: [, args] }) =>
110
- args[0] === '--context=default' && args[2] === 'build')
127
+ const creates = process.execute.mock.calls.filter(
128
+ ({ arguments: [, args] }) => args[0] === 'buildx' && args[1] === 'create'
129
+ )
111
130
 
112
- assert.strictEqual(builds.length, 1)
131
+ assert.strictEqual(creates.length, 0)
132
+
133
+ const {
134
+ arguments: [, args]
135
+ } = process.execute.mock.calls.find(
136
+ ({ arguments: [, args] }) => args[0] === '--context=default' && args[2] === 'build'
137
+ )
138
+
139
+ assert.strictEqual(args[args.indexOf('--builder') + 1], 'default')
140
+ assert.ok(args.includes('--platform'))
141
+ assert.strictEqual(args[args.indexOf('--platform') + 1], 'linux/amd64')
142
+ })
143
+
144
+ it('should create the builder once when images build concurrently', async () => {
145
+ const registry = createRegistry({
146
+ base: 'example.com/reg',
147
+ platforms: ['linux/amd64', 'linux/arm64']
148
+ })
149
+
150
+ registry.composition(/** @type {any} */ ({}))
151
+ registry.service('.', /** @type {any} */ ({}))
152
+ registry.mono(/** @type {any} */ ({}))
153
+
154
+ await registry.build()
155
+
156
+ const creates = process.execute.mock.calls.filter(
157
+ ({ arguments: [, args] }) => args[0] === 'buildx' && args[1] === 'create'
158
+ )
159
+
160
+ assert.strictEqual(creates.length, 1)
161
+ })
162
+
163
+ it('should probe every image before building any', async () => {
164
+ const registry = createRegistry({ base: 'example.com/reg', platforms: ['linux/amd64'] })
165
+
166
+ registry.composition(/** @type {any} */ ({}))
167
+ registry.service('.', /** @type {any} */ ({}))
168
+
169
+ await registry.build()
113
170
 
114
- const { arguments: [, args] } = builds[0]
171
+ const calls = process.execute.mock.calls.map(({ arguments: [, args] }) => args[0])
172
+ const build = calls.indexOf('--context=default')
173
+ const probed = calls.slice(0, build).filter((argument) => argument === 'manifest')
115
174
 
116
- assert.ok(!(args.includes('--cache-from')))
117
- assert.ok(!(args.includes('--cache-to')))
175
+ // both probes are spent before the first build starts, rather than one before each
176
+ assert.strictEqual(probed.length, 2)
118
177
  })
119
178
 
120
179
  it('should use default builder when platforms is null', async () => {
@@ -124,16 +183,18 @@ it('should use default builder when platforms is null', async () => {
124
183
 
125
184
  await registry.build()
126
185
 
127
- const builds = process.execute.mock.calls.filter(({ arguments: [, args] }) =>
128
- args[0] === '--context=default' && args[2] === 'build')
186
+ const builds = process.execute.mock.calls.filter(
187
+ ({ arguments: [, args] }) => args[0] === '--context=default' && args[2] === 'build'
188
+ )
129
189
 
130
190
  assert.strictEqual(builds.length, 1)
131
191
 
132
- const { arguments: [, args] } = builds[0]
192
+ const {
193
+ arguments: [, args]
194
+ } = builds[0]
133
195
 
134
196
  assert.strictEqual(args[args.indexOf('--builder') + 1], 'default')
135
- assert.ok(!(args.includes('--cache-from')))
136
- assert.ok(!(args.includes('--platform')))
197
+ assert.ok(!args.includes('--platform'))
137
198
  })
138
199
 
139
200
  it('should skip build when image already exists', async () => {
@@ -145,26 +206,39 @@ it('should skip build when image already exists', async () => {
145
206
 
146
207
  await registry.build()
147
208
 
148
- const builds = process.execute.mock.calls.filter(({ arguments: [, args] }) =>
149
- args[0] === '--context=default' && args[2] === 'build')
209
+ const builds = process.execute.mock.calls.filter(
210
+ ({ arguments: [, args] }) => args[0] === '--context=default' && args[2] === 'build'
211
+ )
150
212
 
151
213
  assert.strictEqual(builds.length, 0)
152
- assert.ok(process.execute.mock.calls.some((call) => call.arguments.length === 3 && isDeepStrictEqual(call.arguments[0], 'docker') && isDeepStrictEqual(call.arguments[1], ['manifest', 'inspect', images[0].reference]) && isDeepStrictEqual(call.arguments[2], { silently: true })))
214
+ assert.ok(
215
+ process.execute.mock.calls.some(
216
+ (call) =>
217
+ call.arguments.length === 3 &&
218
+ isDeepStrictEqual(call.arguments[0], 'docker') &&
219
+ isDeepStrictEqual(call.arguments[1], [
220
+ 'manifest',
221
+ 'inspect',
222
+ images[0].reference
223
+ ]) &&
224
+ isDeepStrictEqual(call.arguments[2], { silently: true })
225
+ )
226
+ )
153
227
  })
154
228
 
155
229
  /**
156
230
  * @param {object} [registry]
157
231
  * @returns {Registry}
158
232
  */
159
- function createRegistry (registry = {}) {
160
- return new Registry('acme', registry, factory, process)
233
+ function createRegistry(registry = {}) {
234
+ return new Registry(registry, factory, process)
161
235
  }
162
236
 
163
237
  /**
164
238
  * @param {string} name
165
239
  * @returns {toa.deployment.images.Image}
166
240
  */
167
- function createImage (name) {
241
+ function createImage(name) {
168
242
  const image = /** @type {toa.deployment.images.Image} */ {
169
243
  reference: `example.com/reg/acme/${name}:abcdef12`,
170
244
  context: `/tmp/${name}`,
@@ -1,5 +1,7 @@
1
+ // a service is what a deployment carries, and it is constructed
2
+ // oxlint-disable-next-line typescript/no-extraneous-class
1
3
  export class Service {
2
- constructor (service, image) {
4
+ constructor(service, image) {
3
5
  Object.assign(this, service)
4
6
 
5
7
  this.name = service.group + '-' + service.name
@@ -7,7 +7,7 @@ import { tmpdir } from 'node:os'
7
7
  * @param {string} [path]
8
8
  * @return {Promise<string>}
9
9
  */
10
- export async function create (type, path) {
10
+ export async function create(type, path) {
11
11
  if (path === undefined) return await mkdtemp(join(tmpdir(), 'toa-' + type))
12
12
 
13
13
  path = resolve(path)
package/src/process.js CHANGED
@@ -4,7 +4,7 @@ import { execa } from 'execa'
4
4
  * @implements {toa.operations.Process}
5
5
  */
6
6
  export class Process {
7
- async execute (cmd, args, options = {}) {
7
+ async execute(cmd, args, options = {}) {
8
8
  console.log('toa>', cmd, args.join(' '))
9
9
 
10
10
  const command = execa(cmd, args)
@@ -7,14 +7,18 @@ describe('execute', () => {
7
7
  it('should answer what the command wrote', async () => {
8
8
  // `execa` is a named export, and calling the module namespace instead answers
9
9
  // `execa is not a function` — where only a deployment would have found out
10
- const output = await new Process().execute('node', ['-e', 'process.stdout.write("ok")'],
11
- { silently: true })
10
+ const output = await new Process().execute(
11
+ 'node',
12
+ ['-e', 'process.stdout.write("ok")'],
13
+ { silently: true }
14
+ )
12
15
 
13
16
  assert.strictEqual(output, 'ok')
14
17
  })
15
18
 
16
19
  it('should reject what the command failed at', async () => {
17
- await assert.rejects(new Process().execute('node', ['-e', 'process.exit(1)'],
18
- { silently: true }))
20
+ await assert.rejects(
21
+ new Process().execute('node', ['-e', 'process.exit(1)'], { silently: true })
22
+ )
19
23
  })
20
24
  })
@@ -1,11 +1,9 @@
1
1
  import type * as _deployment from './deployment.js'
2
2
 
3
3
  declare namespace toa.deployment {
4
-
5
4
  interface Composition extends _deployment.Deployable {
6
5
  components: Array<string>
7
6
  }
8
-
9
7
  }
10
8
 
11
9
  export type Composition = toa.deployment.Composition
@@ -2,10 +2,11 @@ import * as _service from './service.js'
2
2
  import { dependencies } from '@toa.io/norm/types/context'
3
3
 
4
4
  declare namespace toa.deployment {
5
-
6
5
  namespace dependency {
7
-
8
- type Constructor = (instances: dependencies.Instance[], annotation: any) => Declaration
6
+ type Constructor = (
7
+ instances: dependencies.Instance[],
8
+ annotation: any
9
+ ) => Declaration
9
10
 
10
11
  type Reference = {
11
12
  name: string
@@ -19,6 +20,11 @@ declare namespace toa.deployment {
19
20
  group: string
20
21
  name: string
21
22
  version: string
23
+
24
+ /** The repository the extension publishes this service's image to, without a tag.
25
+ * Stating it lets an application take the published image instead of building one. */
26
+ image?: string
27
+
22
28
  port: number
23
29
  ingress?: {
24
30
  host: string
@@ -36,7 +42,7 @@ declare namespace toa.deployment {
36
42
  name: string
37
43
  value?: string | number
38
44
  secret?: {
39
- name: string,
45
+ name: string
40
46
  key: string
41
47
  }
42
48
  }
@@ -52,7 +58,6 @@ declare namespace toa.deployment {
52
58
  variables?: Variables
53
59
  events?: string[]
54
60
  }
55
-
56
61
  }
57
62
 
58
63
  type Dependency = {
@@ -62,7 +67,6 @@ declare namespace toa.deployment {
62
67
  variables?: dependency.Variables
63
68
  events?: string[]
64
69
  }
65
-
66
70
  }
67
71
 
68
72
  export type Declaration = toa.deployment.dependency.Declaration
@@ -3,7 +3,6 @@ import type * as _service from './service.js'
3
3
  import type * as _dependency from './dependency.js'
4
4
 
5
5
  declare namespace toa.deployment {
6
-
7
6
  interface Declaration {
8
7
  apiVersion: string
9
8
  type: string
@@ -25,13 +24,11 @@ declare namespace toa.deployment {
25
24
  }
26
25
 
27
26
  namespace installation {
28
-
29
27
  interface Options {
30
28
  wait?: boolean
31
29
  target?: string
32
30
  namespace?: string
33
31
  }
34
-
35
32
  }
36
33
 
37
34
  namespace template {
@@ -54,7 +51,6 @@ declare namespace toa.deployment {
54
51
 
55
52
  variables(): _dependency.Variables
56
53
  }
57
-
58
54
  }
59
55
 
60
56
  export namespace installation {
@@ -2,13 +2,11 @@ import type * as _operator from './operator.js'
2
2
  import * as _registry from './registry.js'
3
3
 
4
4
  declare namespace toa.deployment {
5
-
6
5
  interface Factory {
7
6
  operator(): _operator.Operator
8
7
 
9
8
  registry(): _registry.Registry
10
9
  }
11
-
12
10
  }
13
11
 
14
12
  export type Factory = toa.deployment.Factory
@@ -5,13 +5,11 @@ import type { Image } from './image.js'
5
5
  import type { dependency } from '../dependency.js'
6
6
 
7
7
  declare namespace toa.deployment.images {
8
+ interface Factory {
9
+ composition(composition: Composition): Image
8
10
 
9
- interface Factory {
10
- composition(composition: Composition): Image
11
-
12
- service(path: string, service: dependency.Service): Image
13
- }
14
-
11
+ service(path: string, service: dependency.Service): Image
12
+ }
15
13
  }
16
14
 
17
15
  export type Factory = toa.deployment.images.Factory
@@ -1,16 +1,14 @@
1
1
  declare namespace toa.deployment.images {
2
-
3
2
  export interface Image {
4
3
  readonly reference: string
5
4
  readonly context: string
6
5
 
7
6
  name: string
8
7
 
9
- tag (): void
8
+ tag(): void
10
9
 
11
- prepare (root: string): Promise<string>
10
+ prepare(root: string): Promise<string>
12
11
  }
13
-
14
12
  }
15
13
 
16
14
  export type Image = toa.deployment.images.Image
@@ -2,19 +2,16 @@
2
2
 
3
3
  import type { Composition } from '@toa.io/norm'
4
4
  import type { dependency } from '../dependency.js'
5
- import type { Image } from "./image.js"
5
+ import type { Image } from './image.js'
6
6
 
7
7
  declare namespace toa.deployment.images {
8
+ interface Registry {
9
+ composition(composition: Composition): Image
8
10
 
9
- interface Registry {
10
- composition(composition: Composition): Image
11
+ service(path: string, service: dependency.Service): Image
11
12
 
12
- service(path: string, service: dependency.Service): Image
13
-
14
- prepare(target: string): Promise<void>
15
-
16
- push(): Promise<void>
17
- }
13
+ prepare(target: string): Promise<void>
18
14
 
15
+ push(): Promise<void>
16
+ }
19
17
  }
20
-
@@ -2,19 +2,17 @@ import * as _deployment from './deployment.js'
2
2
  import * as _dependency from './dependency.js'
3
3
 
4
4
  declare namespace toa.deployment {
5
-
6
5
  interface Operator {
7
- export (path?: string): Promise<string>
6
+ export(path?: string): Promise<string>
8
7
 
9
- install (options?: _deployment.installation.Options): Promise<void>
8
+ install(options?: _deployment.installation.Options): Promise<void>
10
9
 
11
- template (options?: _deployment.template.Options): Promise<string>
10
+ template(options?: _deployment.template.Options): Promise<string>
12
11
 
13
- variables (): _dependency.Variables
12
+ variables(): _dependency.Variables
14
13
 
15
- listVariables (): _dependency.Variable[]
14
+ listVariables(): _dependency.Variable[]
16
15
  }
17
-
18
16
  }
19
17
 
20
18
  export type Operator = toa.deployment.Operator
@@ -3,21 +3,19 @@ import type * as _dependency from './dependency.js'
3
3
  import type * as _image from './images/image.js'
4
4
 
5
5
  declare namespace toa.deployment {
6
-
7
6
  interface Registry {
8
- composition (composition: _norm.Composition): _image.Image
7
+ composition(composition: _norm.Composition): _image.Image
9
8
 
10
- service (path: string, service: _dependency.Service): _image.Image
9
+ service(path: string, service: _dependency.Service): _image.Image
11
10
 
12
- prepare (path: string): Promise<string>
11
+ prepare(path: string): Promise<string>
13
12
 
14
- build (): Promise<void>
13
+ build(): Promise<void>
15
14
 
16
- push (): Promise<void>
15
+ push(): Promise<void>
17
16
 
18
- tags (): string[]
17
+ tags(): string[]
19
18
  }
20
-
21
19
  }
22
20
 
23
21
  export type Registry = toa.deployment.Registry
@@ -1,7 +1,6 @@
1
1
  import type * as _deployment from './deployment.js'
2
2
 
3
3
  declare namespace toa.deployment {
4
-
5
4
  interface Ingress {
6
5
  host: string
7
6
  class: string
@@ -15,7 +14,6 @@ declare namespace toa.deployment {
15
14
  /** Annotations for the Service itself, as opposed to `ingress.annotations`. */
16
15
  annotations?: object
17
16
  }
18
-
19
17
  }
20
18
 
21
19
  export type Service = toa.deployment.Service
@@ -5,6 +5,9 @@ export interface Service {
5
5
  group: string
6
6
  name: string
7
7
  version: string
8
+ /** The repository the extension publishes this service's image to, without a tag.
9
+ * Stating it lets an application take the published image instead of building one. */
10
+ image?: string
8
11
  port?: number
9
12
  ingress?: Ingress
10
13
  /** Annotations for the Service itself, as opposed to `ingress.annotations`. */