@toa.io/operations 0.2.0-dev.3 → 0.2.1-dev.4

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 (58) hide show
  1. package/package.json +5 -3
  2. package/src/deployment/.deployment/.describe/components.js +19 -0
  3. package/src/deployment/.deployment/.describe/dependencies.js +18 -0
  4. package/src/deployment/.deployment/.describe/index.js +9 -0
  5. package/src/deployment/.deployment/.describe/variables.js +27 -0
  6. package/src/deployment/.deployment/declare.js +29 -0
  7. package/src/deployment/.deployment/describe.js +28 -0
  8. package/src/deployment/.deployment/index.js +9 -0
  9. package/src/deployment/.deployment/merge.js +42 -0
  10. package/src/deployment/{assets/chart → chart}/Chart.yaml +1 -1
  11. package/src/deployment/chart/templates/compositions.yaml +38 -0
  12. package/src/deployment/chart/templates/proxies.yaml +10 -0
  13. package/src/deployment/chart/templates/services.yaml +63 -0
  14. package/src/deployment/chart/templates/variables.yaml +12 -0
  15. package/src/deployment/chart/values.yaml +47 -0
  16. package/src/deployment/composition.js +18 -5
  17. package/src/deployment/deployment.js +55 -49
  18. package/src/deployment/factory.js +78 -15
  19. package/src/deployment/images/composition.Dockerfile +14 -0
  20. package/src/deployment/images/composition.js +50 -0
  21. package/src/deployment/images/factory.js +39 -0
  22. package/src/deployment/images/image.js +91 -0
  23. package/src/deployment/images/index.js +7 -0
  24. package/src/deployment/images/registry.js +102 -0
  25. package/src/deployment/images/service.Dockerfile +12 -0
  26. package/src/deployment/images/service.js +70 -0
  27. package/src/deployment/operator.js +85 -0
  28. package/src/deployment/service.js +25 -0
  29. package/src/process.js +25 -0
  30. package/test/deployment/deployment.fixtures.js +18 -0
  31. package/test/deployment/deployment.test.js +41 -0
  32. package/test/deployment/images/image.fixtures.js +33 -0
  33. package/test/deployment/images/image.test.js +25 -0
  34. package/types/deployment/composition.d.ts +13 -0
  35. package/types/deployment/dependency.d.ts +77 -0
  36. package/types/deployment/deployment.d.ts +67 -0
  37. package/types/deployment/factory.d.ts +12 -0
  38. package/types/deployment/images/factory.d.ts +17 -0
  39. package/types/deployment/images/image.d.ts +14 -0
  40. package/types/deployment/images/registry.d.ts +20 -0
  41. package/types/deployment/index.d.ts +1 -0
  42. package/types/deployment/operator.d.ts +21 -0
  43. package/types/deployment/service.d.ts +20 -0
  44. package/types/process.d.ts +13 -0
  45. package/LICENSE +0 -22
  46. package/src/deployment/assets/Dockerfile +0 -10
  47. package/src/deployment/assets/chart/templates/deployment.yaml +0 -23
  48. package/src/deployment/assets/chart/values.yaml +0 -18
  49. package/src/deployment/chart.js +0 -79
  50. package/src/deployment/compositions.js +0 -60
  51. package/src/deployment/copy.js +0 -7
  52. package/src/deployment/directory.js +0 -19
  53. package/src/deployment/image.js +0 -67
  54. package/test/deployment/compositions.test.js +0 -31
  55. package/test/deployment/copositions.fixtures.js +0 -42
  56. package/test/deployment/image.fixtures.js +0 -26
  57. package/test/deployment/image.test.js +0 -22
  58. /package/src/deployment/{assets/chart/templates/service.yaml → chart/templates/components.yaml} +0 -0
@@ -0,0 +1,77 @@
1
+ // noinspection JSUnusedGlobalSymbols,ES6UnusedImports
2
+
3
+ import { Service } from './service'
4
+ import { dependencies } from '@toa.io/norm/types/context'
5
+
6
+ declare namespace toa.deployment {
7
+
8
+ namespace dependency {
9
+
10
+ type Constructor = (instances: dependencies.Instance[], annotation: any) => Declaration
11
+
12
+ type Reference = {
13
+ name: string
14
+ version: string
15
+ repository?: string
16
+ alias?: string
17
+ values?: Object
18
+ }
19
+
20
+ type Service = {
21
+ group: string
22
+ name: string
23
+ version: string
24
+ port: number
25
+ ingress?: {
26
+ host: string
27
+ class: string
28
+ annotations?: object
29
+ }
30
+ }
31
+
32
+ type Proxy = {
33
+ name: string
34
+ target: string
35
+ }
36
+
37
+ type Variable = {
38
+ name: string
39
+ value?: string | number
40
+ secret?: {
41
+ name: string,
42
+ key: string
43
+ }
44
+ }
45
+
46
+ type Variables = {
47
+ [key: string]: Variable[]
48
+ }
49
+
50
+ type Declaration = {
51
+ references?: Reference[]
52
+ services?: Service[] // dependency.Service
53
+ proxies?: Proxy[]
54
+ variables?: Variables
55
+ }
56
+
57
+ }
58
+
59
+ type Dependency = {
60
+ references?: dependency.Reference[]
61
+ services?: Service[] // deployment.Service
62
+ proxies?: dependency.Proxy[]
63
+ variables?: dependency.Variables
64
+ }
65
+
66
+ }
67
+
68
+ export namespace dependency {
69
+ export type Declaration = toa.deployment.dependency.Declaration
70
+ export type Reference = toa.deployment.dependency.Reference
71
+ export type Service = toa.deployment.dependency.Service
72
+ export type Proxy = toa.deployment.dependency.Proxy
73
+ export type Variables = toa.deployment.dependency.Variables
74
+ export type Variable = toa.deployment.dependency.Variable
75
+ }
76
+
77
+ export type Dependency = toa.deployment.Dependency
@@ -0,0 +1,67 @@
1
+ // noinspection ES6UnusedImports,JSUnusedGlobalSymbols
2
+
3
+ import type { Composition } from './composition'
4
+ import type { Service } from './service'
5
+ import type { dependency } from './dependency'
6
+
7
+ declare namespace toa.deployment {
8
+
9
+ interface Declaration {
10
+ apiVersion: string
11
+ type: string
12
+ name: string
13
+ description?: string
14
+ version: string
15
+ appVersion: string
16
+ dependencies: dependency.Reference[]
17
+ }
18
+
19
+ interface Contents {
20
+ compositions?: Composition[]
21
+ components?: string[]
22
+ services?: Service[]
23
+ proxies?: dependency.Proxy[]
24
+ variables?: dependency.Variables
25
+ [key: string]: Object
26
+ }
27
+
28
+ namespace installation {
29
+
30
+ interface Options {
31
+ wait?: boolean
32
+ target?: string
33
+ namespace?: string
34
+ }
35
+
36
+ }
37
+
38
+ namespace template {
39
+ interface Options {
40
+ namespace: string
41
+ }
42
+ }
43
+
44
+ interface Deployable {
45
+ name: string
46
+ image: string
47
+ }
48
+
49
+ interface Deployment {
50
+ export(target: string): Promise<void>
51
+
52
+ install(options: installation.Options): Promise<void>
53
+
54
+ template(options: template.Options): Promise<string>
55
+ }
56
+
57
+ }
58
+
59
+ export namespace installation {
60
+ export type Options = toa.deployment.installation.Options
61
+ }
62
+
63
+ export namespace template {
64
+ export type Options = toa.deployment.template.Options
65
+ }
66
+
67
+ export type Deployable = toa.deployment.Deployable
@@ -0,0 +1,12 @@
1
+ // noinspection ES6UnusedImports
2
+ import type { Operator } from './operator'
3
+
4
+ declare namespace toa.deployment {
5
+
6
+ interface Factory {
7
+ operator(): Operator
8
+ }
9
+
10
+ }
11
+
12
+ export type Factory = toa.deployment.Factory
@@ -0,0 +1,17 @@
1
+ // noinspection ES6UnusedImports
2
+
3
+ import type { Composition } from '@toa.io/norm/types'
4
+ import type { Image } from './image'
5
+ import type { dependency } from '../dependency'
6
+
7
+ declare namespace toa.deployment.images {
8
+
9
+ interface Factory {
10
+ composition(composition: Composition): Image
11
+
12
+ service(path: string, service: dependency.Service): Image
13
+ }
14
+
15
+ }
16
+
17
+ export type Factory = toa.deployment.images.Factory
@@ -0,0 +1,14 @@
1
+ declare namespace toa.deployment.images {
2
+
3
+ export interface Image {
4
+ readonly reference: string
5
+ readonly context: string
6
+
7
+ tag(base: string): void
8
+
9
+ prepare(root: string): Promise<string>
10
+ }
11
+
12
+ }
13
+
14
+ export type Image = toa.deployment.images.Image
@@ -0,0 +1,20 @@
1
+ // noinspection ES6UnusedImports
2
+
3
+ import type { Composition } from '@toa.io/norm'
4
+ import type { dependency } from '../dependency'
5
+ import type { Image } from "./image"
6
+
7
+ declare namespace toa.deployment.images {
8
+
9
+ interface Registry {
10
+ composition(composition: Composition): Image
11
+
12
+ service(path: string, service: dependency.Service): Image
13
+
14
+ prepare(target: string): Promise<void>
15
+
16
+ push(): Promise<void>
17
+ }
18
+
19
+ }
20
+
@@ -0,0 +1 @@
1
+ export { Dependency, dependency } from './dependency'
@@ -0,0 +1,21 @@
1
+ // noinspection ES6UnusedImports
2
+
3
+ import { installation, template } from './deployment'
4
+
5
+ declare namespace toa.deployment {
6
+
7
+ interface Operator {
8
+ export(path?: string): Promise<string>
9
+
10
+ prepare(path?: string): Promise<string>
11
+
12
+ build(): Promise<void>
13
+
14
+ install(options?: installation.Options): Promise<void>
15
+
16
+ template(options?: template.Options): Promise<string>
17
+ }
18
+
19
+ }
20
+
21
+ export type Operator = toa.deployment.Operator
@@ -0,0 +1,20 @@
1
+ // noinspection ES6UnusedImports
2
+
3
+ import type { Deployable } from './deployment'
4
+
5
+ declare namespace toa.deployment {
6
+
7
+ interface Ingress {
8
+ host: string
9
+ class: string
10
+ annotations?: object
11
+ }
12
+
13
+ interface Service extends Deployable {
14
+ port: number
15
+ ingress?: Ingress
16
+ }
17
+
18
+ }
19
+
20
+ export type Service = toa.deployment.Service
@@ -0,0 +1,13 @@
1
+ declare namespace toa.operations {
2
+
3
+ namespace process {
4
+ interface Options {
5
+ silently?: boolean
6
+ }
7
+ }
8
+
9
+ interface Process {
10
+ execute(cmd: string, args: Array<string>, options?: process.Options): Promise<string>
11
+ }
12
+
13
+ }
package/LICENSE DELETED
@@ -1,22 +0,0 @@
1
- Copyright (c) 2020-present Artem Gurtovoi
2
-
3
- MIT License
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining
6
- a copy of this software and associated documentation files (the
7
- "Software"), to deal in the Software without restriction, including
8
- without limitation the rights to use, copy, modify, merge, publish,
9
- distribute, sublicense, and/or sell copies of the Software, and to
10
- permit persons to whom the Software is furnished to do so, subject to
11
- the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be
14
- included in all copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -1,10 +0,0 @@
1
- # TODO: bridge specifics
2
-
3
- FROM node:alpine
4
-
5
- RUN npm i -g @toa.io/runtime@{{version}}
6
-
7
- WORKDIR /composition
8
- ADD . .
9
-
10
- CMD toa compose *
@@ -1,23 +0,0 @@
1
- {{- range .Values.compositions }}
2
- apiVersion: apps/v1
3
- kind: Deployment
4
- metadata:
5
- name: composition-{{ required "deployment name is required" .name }}
6
- spec:
7
- replicas: {{ .replicas | default 2 }}
8
- selector:
9
- matchLabels:
10
- toa.io/composition: {{ .name }}
11
- template:
12
- metadata:
13
- labels:
14
- toa.io/composition: {{ .name }}
15
- {{- range .components }}
16
- {{ . }}: "1"
17
- {{- end }}
18
- spec:
19
- containers:
20
- - name: {{ .name }}
21
- image: {{ .image }}
22
- ---
23
- {{- end }}
@@ -1,18 +0,0 @@
1
- # This file just helps with IDE autocompletion and helm debugging. Actually dynamically replaced by ../values.js.
2
- compositions:
3
- - name: todos
4
- image: localhost:5000/todos:0.0.0
5
- replicas: 2
6
- components:
7
- - todos-tasks
8
- - todos-stats
9
- - name: messages
10
- image: localhost:5000/messages:0.0.0
11
- replicas: 2
12
- components:
13
- - users-users
14
- # TODO: create services only if sync binding is being used by a component
15
- components:
16
- - todos-tasks
17
- - todos-stats
18
- - users-users
@@ -1,79 +0,0 @@
1
- 'use strict'
2
-
3
- class Chart {
4
- name
5
-
6
- #context
7
- #compositions
8
- #dependencies
9
-
10
- constructor (context, compositions) {
11
- this.name = context.name
12
-
13
- this.#context = context
14
- this.#compositions = compositions
15
- this.#dependencies = Chart.dependencies(context)
16
- }
17
-
18
- get declaration () {
19
- const { name, description, version } = this.#context
20
- const dependencies = this.#dependencies.map((dependency) => dependency.chart)
21
-
22
- return {
23
- apiVersion: 'v2',
24
- type: 'application',
25
- name,
26
- description,
27
- version,
28
- appVersion: version,
29
- dependencies
30
- }
31
- }
32
-
33
- get values () {
34
- const result = {}
35
-
36
- result.compositions = this.#deployments()
37
- result.components = this.#context.components.map((component) => component.domain + '-' + component.name)
38
-
39
- for (const { chart, values } of this.#dependencies) result[chart.alias || chart.name] = values
40
-
41
- return result
42
- }
43
-
44
- #deployments () {
45
- return Array.from(this.#compositions).map((composition) => {
46
- const { name, components, replicas, image: { tag: image } } = composition
47
-
48
- return {
49
- name,
50
- components: components.map((component) => component.locator.label),
51
- replicas,
52
- image
53
- }
54
- })
55
- }
56
-
57
- static dependencies (context) {
58
- const map = (map) => {
59
- const list = []
60
-
61
- for (const [key, values] of Object.entries(map)) {
62
- const dependency = require(key)
63
-
64
- if (dependency.deployments !== undefined) list.push(...dependency.deployments(values))
65
- }
66
-
67
- return list
68
- }
69
-
70
- const dependencies = []
71
-
72
- if (context.connectors !== undefined) dependencies.push(...map(context.connectors))
73
- if (context.extensions !== undefined) dependencies.push(...map(context.extensions))
74
-
75
- return dependencies
76
- }
77
- }
78
-
79
- exports.Chart = Chart
@@ -1,60 +0,0 @@
1
- 'use strict'
2
-
3
- class Compositions {
4
- #compositions
5
-
6
- constructor (context, instantiate) {
7
- if (context.compositions === undefined) context.compositions = []
8
-
9
- Compositions.#resolve(context)
10
- Compositions.#complete(context)
11
-
12
- this.#compositions = context.compositions.map(instantiate)
13
- }
14
-
15
- [Symbol.iterator] = () => this.#compositions.values()
16
-
17
- static #resolve (context) {
18
- const map = {}
19
-
20
- for (const component of context.components) {
21
- const id = component.locator.id
22
-
23
- map[id] = component
24
- }
25
-
26
- for (const composition of context.compositions) {
27
- composition.components = composition.components.map((id) => {
28
- const component = map[id]
29
-
30
- if (component === undefined) {
31
- throw new Error(`Unknown component '${id}' within composition '${composition.name}'`)
32
- }
33
-
34
- return component
35
- })
36
- }
37
- }
38
-
39
- static #complete (context) {
40
- const composed = new Set(context.compositions.map((composition) =>
41
- composition.components.map((component) => component.locator.id)
42
- ).flat())
43
-
44
- const names = new Set(context.compositions.map((composition) => composition.name))
45
-
46
- for (const component of context.components) {
47
- const { id, label } = component.locator
48
-
49
- if (composed.has(id)) continue
50
- if (names.has(label)) throw new Error(`Duplicate composition name '${label}'`)
51
-
52
- context.compositions.push({
53
- name: label,
54
- components: [component]
55
- })
56
- }
57
- }
58
- }
59
-
60
- exports.Compositions = Compositions
@@ -1,7 +0,0 @@
1
- 'use strict'
2
-
3
- const execa = require('execa')
4
-
5
- const copy = async (source, target) => await execa('cp', ['-r', source, target])
6
-
7
- exports.copy = copy
@@ -1,19 +0,0 @@
1
- 'use strict'
2
-
3
- const fs = require('node:fs/promises')
4
- const { join } = require('node:path')
5
- const { tmpdir } = require('node:os')
6
-
7
- const directory = async (path) => {
8
- await fs.mkdir(path, { recursive: true })
9
-
10
- const entries = await fs.readdir(path)
11
-
12
- if (entries.length > 0) throw new Error('Target directory must be empty')
13
-
14
- return path
15
- }
16
-
17
- directory.temp = async (type) => await fs.mkdtemp(join(tmpdir(), `toa-${type}-`))
18
-
19
- exports.directory = directory
@@ -1,67 +0,0 @@
1
- 'use strict'
2
-
3
- const { join } = require('node:path')
4
- const { readFile: read, writeFile: write, rm: remove } = require('node:fs/promises')
5
- const execa = require('execa')
6
-
7
- const { hash } = require('@toa.io/gears')
8
- const { directory } = require('./directory')
9
- const { copy } = require('./copy')
10
-
11
- class Image {
12
- tag
13
-
14
- #composition
15
- #registry
16
- #runtime
17
-
18
- constructor (composition, context) {
19
- this.#composition = composition
20
- this.#registry = context.registry
21
- this.#runtime = context.runtime
22
-
23
- this.tag = context.registry + '/' + composition.name + ':' + Image.#tag(composition)
24
- }
25
-
26
- async build () {
27
- const path = await this.#context()
28
- const build = execa('docker', ['build', path, '-t', this.tag])
29
-
30
- build.stdout.pipe(process.stdout)
31
-
32
- await build
33
-
34
- await remove(path, { recursive: true })
35
- }
36
-
37
- async push () {
38
- const push = execa('docker', ['push', this.tag])
39
-
40
- push.stdout.pipe(process.stdout)
41
-
42
- await push
43
- }
44
-
45
- async #context () {
46
- const path = await directory.temp('composition')
47
- const dockerfile = (await read(DOCKERFILE, 'utf-8')).replace('{{version}}', this.#runtime)
48
-
49
- for (const component of this.#composition.components) {
50
- await copy(component.path, join(path, component.locator.id))
51
- }
52
-
53
- await write(join(path, 'Dockerfile'), dockerfile)
54
-
55
- return path
56
- }
57
-
58
- static #tag (composition) {
59
- const components = composition.components.map((component) => component.locator.id).join(';')
60
-
61
- return hash(components)
62
- }
63
- }
64
-
65
- const DOCKERFILE = join(__dirname, 'assets/Dockerfile')
66
-
67
- exports.Image = Image
@@ -1,31 +0,0 @@
1
- 'use strict'
2
-
3
- const fixtures = require('./copositions.fixtures')
4
- const { Compositions } = require('../../src/deployment/compositions')
5
-
6
- const clone = require('clone-deep')
7
-
8
- let context, compositions
9
-
10
- beforeEach(() => {
11
- context = clone(fixtures.context)
12
- compositions = new Compositions(context, fixtures.instantiate)
13
- })
14
-
15
- it('should instantiate', () => {
16
- expect(fixtures.instantiate).toHaveBeenCalledTimes(context.compositions.length)
17
- })
18
-
19
- it('should be iterable over instances', () => {
20
- expect(Symbol.iterator in compositions).toEqual(true)
21
-
22
- for (const composition of compositions) expect(fixtures.instantiate).toHaveReturnedWith(composition)
23
- })
24
-
25
- it('should complete compositions', () => {
26
- expect(context.compositions.length).toBeGreaterThan(fixtures.context.compositions.length)
27
-
28
- const used = new Set(context.compositions.map((composition) => composition.components).flat())
29
-
30
- expect(used.size).toEqual(context.components.length)
31
- })
@@ -1,42 +0,0 @@
1
- 'use strict'
2
-
3
- const { repeat, random } = require('@toa.io/gears')
4
- const { generate } = require('randomstring')
5
-
6
- const components = repeat(() => ({
7
- domain: generate(),
8
- name: generate(),
9
- locator: {
10
- id: generate(),
11
- label: generate()
12
- },
13
- version: random(10) + '.' + random(20) + '.' + random(30)
14
- }), random(10) + 5)
15
-
16
- const context = {
17
- registry: generate(),
18
- components,
19
- compositions: []
20
- }
21
-
22
- let unused = components.length
23
-
24
- while (1) {
25
- const use = random(3) + 1
26
- const index = components.length - unused
27
-
28
- if (use >= unused) break
29
-
30
- context.compositions.push({
31
- name: generate(),
32
- components: components.slice(index, index + use)
33
- .map((component) => component.locator.id)
34
- })
35
-
36
- unused -= use
37
- }
38
-
39
- const instantiate = jest.fn(() => generate())
40
-
41
- exports.context = context
42
- exports.instantiate = instantiate
@@ -1,26 +0,0 @@
1
- 'use strict'
2
-
3
- const { newid, random, repeat } = require('@toa.io/gears')
4
- const { join } = require('node:path')
5
- const { generate } = require('randomstring')
6
-
7
- const mock = {
8
- execa: jest.fn(() => ({ stdout: { pipe: jest.fn() } }))
9
- }
10
-
11
- const composition = {
12
- name: generate(),
13
- components: repeat(() => ({ locator: { id: newid() } }), random(5) + 5)
14
- }
15
-
16
- const context = {
17
- registry: `registry-${newid()}:${random(999) + 5000}`,
18
- runtime: `${random(9)}.${random(9)}.${random(20)}`
19
- }
20
-
21
- const DOCKERFILE = join(__dirname, '../src/images/Dockerfile')
22
-
23
- exports.mock = mock
24
- exports.composition = composition
25
- exports.context = context
26
- exports.DOCKERFILE = DOCKERFILE