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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) 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/chart/Chart.yaml +7 -0
  11. package/src/deployment/chart/templates/components.yaml +16 -0
  12. package/src/deployment/chart/templates/compositions.yaml +38 -0
  13. package/src/deployment/chart/templates/proxies.yaml +10 -0
  14. package/src/deployment/chart/templates/services.yaml +63 -0
  15. package/src/deployment/chart/templates/variables.yaml +12 -0
  16. package/src/deployment/chart/values.yaml +47 -0
  17. package/src/deployment/composition.js +30 -0
  18. package/src/deployment/deployment.js +82 -0
  19. package/src/deployment/factory.js +100 -0
  20. package/src/deployment/images/composition.Dockerfile +14 -0
  21. package/src/deployment/images/composition.js +50 -0
  22. package/src/deployment/images/factory.js +39 -0
  23. package/src/deployment/images/image.js +91 -0
  24. package/src/deployment/images/index.js +7 -0
  25. package/src/deployment/images/registry.js +102 -0
  26. package/src/deployment/images/service.Dockerfile +12 -0
  27. package/src/deployment/images/service.js +70 -0
  28. package/src/deployment/index.js +5 -0
  29. package/src/deployment/operator.js +85 -0
  30. package/src/deployment/service.js +25 -0
  31. package/src/index.js +1 -5
  32. package/src/process.js +25 -0
  33. package/test/deployment/deployment.fixtures.js +18 -0
  34. package/test/deployment/deployment.test.js +41 -0
  35. package/test/deployment/images/image.fixtures.js +33 -0
  36. package/test/deployment/images/image.test.js +25 -0
  37. package/types/deployment/composition.d.ts +13 -0
  38. package/types/deployment/dependency.d.ts +77 -0
  39. package/types/deployment/deployment.d.ts +67 -0
  40. package/types/deployment/factory.d.ts +12 -0
  41. package/types/deployment/images/factory.d.ts +17 -0
  42. package/types/deployment/images/image.d.ts +14 -0
  43. package/types/deployment/images/registry.d.ts +20 -0
  44. package/types/deployment/index.d.ts +1 -0
  45. package/types/deployment/operator.d.ts +21 -0
  46. package/types/deployment/service.d.ts +20 -0
  47. package/types/process.d.ts +13 -0
  48. package/LICENSE +0 -22
  49. package/src/deployment/chart.js +0 -15
  50. package/src/deployment/dependencies.js +0 -25
  51. package/src/deployment/directory.js +0 -21
  52. package/src/deployment/values.js +0 -7
  53. package/src/deployment.js +0 -64
  54. package/src/images/Dockerfile +0 -10
  55. package/src/images/image.js +0 -38
  56. package/src/images.js +0 -26
  57. package/test/deployment.fixtures.js +0 -34
  58. package/test/deployment.test.js +0 -88
  59. package/test/image.fixtures.js +0 -24
  60. package/test/image.test.js +0 -45
  61. package/test/images.fixtures.js +0 -29
  62. package/test/images.test.js +0 -35
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/operations",
3
- "version": "0.2.0-dev.2",
3
+ "version": "0.2.1-dev.3",
4
4
  "description": "Toa Deployment",
5
5
  "homepage": "https://toa.io",
6
6
  "author": {
@@ -26,8 +26,10 @@
26
26
  "test": "echo \"Error: run tests from root\" && exit 1"
27
27
  },
28
28
  "dependencies": {
29
- "@toa.io/gears": "0.2.0-dev.2",
29
+ "@toa.io/filesystem": "*",
30
+ "@toa.io/generic": "*",
31
+ "@toa.io/yaml": "*",
30
32
  "execa": "5.1.1"
31
33
  },
32
- "gitHead": "83480176c88640dd2bed2a775ce2d13b95341f19"
34
+ "gitHead": "2be07592325b2e4dc823e81d882a4e50bf50de24"
33
35
  }
@@ -0,0 +1,19 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * @param {toa.deployment.Composition[]} compositions
5
+ * @returns {string[]}
6
+ */
7
+ const components = (compositions) => {
8
+ /** @type {Set<string>} */
9
+ const components = new Set()
10
+
11
+ for (const composition of compositions) {
12
+ for (const component of composition.components) {
13
+ components.add(component)
14
+ }
15
+ }
16
+
17
+ return Array.from(components)
18
+ }
19
+ exports.components = components
@@ -0,0 +1,18 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * @param {toa.deployment.dependency.Reference[]} references
5
+ * @returns {*}
6
+ */
7
+ const dependencies = (references) => {
8
+ return references?.reduce((map, reference) => {
9
+ const { name, alias, values } = reference
10
+
11
+ map[alias || name] = values
12
+
13
+ return map
14
+ }, {})
15
+ }
16
+
17
+
18
+ exports.dependencies = dependencies
@@ -0,0 +1,9 @@
1
+ 'use strict'
2
+
3
+ const { dependencies } = require('./dependencies')
4
+ const { components } = require('./components')
5
+ const { variables } = require('./variables')
6
+
7
+ exports.dependencies = dependencies
8
+ exports.components = components
9
+ exports.variables = variables
@@ -0,0 +1,27 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * @param {toa.norm.Context} context
5
+ * @param {toa.deployment.dependency.Variables} variables
6
+ * @returns {toa.deployment.dependency.Variables}
7
+ */
8
+ const variables = (context, variables) => {
9
+ if (variables.global === undefined) variables.global = []
10
+
11
+ if (context.environment !== undefined) {
12
+ const variable = format('TOA_ENV', context.environment)
13
+
14
+ variables.global.push(variable)
15
+ }
16
+
17
+ return variables
18
+ }
19
+
20
+ /**
21
+ * @param {string} name
22
+ * @param {string} value
23
+ * @returns {toa.deployment.dependency.Variable}
24
+ */
25
+ const format = (name, value) => ({ name, value })
26
+
27
+ exports.variables = variables
@@ -0,0 +1,29 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * @param {toa.norm.Context} context
5
+ * @param {toa.deployment.Dependency} dependency
6
+ * @returns {toa.deployment.Declaration}
7
+ */
8
+ const declare = (context, dependency) => {
9
+ const { references } = dependency
10
+ const { name, description, version } = context
11
+
12
+ const dependencies = references.map(({ values, ...rest }) => rest)
13
+
14
+ return {
15
+ ...DECLARATION,
16
+ name,
17
+ description,
18
+ version,
19
+ appVersion: version,
20
+ dependencies
21
+ }
22
+ }
23
+
24
+ const DECLARATION = {
25
+ apiVersion: 'v2',
26
+ type: 'application'
27
+ }
28
+
29
+ exports.declare = declare
@@ -0,0 +1,28 @@
1
+ 'use strict'
2
+
3
+ const get = require('./.describe')
4
+
5
+ /**
6
+ * @param {toa.norm.Context} context
7
+ * @param {toa.deployment.Composition[]} compositions
8
+ * @param {toa.deployment.Dependency} dependency
9
+ * @returns {toa.deployment.Contents}
10
+ */
11
+ const describe = (context, compositions, dependency) => {
12
+ const { references, services, proxies } = dependency
13
+
14
+ const components = get.components(compositions)
15
+ const dependencies = get.dependencies(references)
16
+ const variables = get.variables(context, dependency.variables)
17
+
18
+ return {
19
+ compositions,
20
+ components,
21
+ services,
22
+ proxies,
23
+ variables,
24
+ ...dependencies
25
+ }
26
+ }
27
+
28
+ exports.describe = describe
@@ -0,0 +1,9 @@
1
+ 'use strict'
2
+
3
+ const { declare } = require('./declare')
4
+ const { describe } = require('./describe')
5
+ const { merge } = require('./merge')
6
+
7
+ exports.declare = declare
8
+ exports.describe = describe
9
+ exports.merge = merge
@@ -0,0 +1,42 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * @param {toa.deployment.Dependency[]} dependencies
5
+ * @returns {toa.deployment.Dependency}
6
+ */
7
+ const merge = (dependencies) => {
8
+ /** @type {toa.deployment.dependency.Reference[]} */
9
+ const references = []
10
+
11
+ /** @type {toa.deployment.Service[]} */
12
+ const services = []
13
+
14
+ /** @type {toa.deployment.dependency.Proxy[]} */
15
+ const proxies = []
16
+
17
+ /** @type {toa.deployment.dependency.Variables} */
18
+ const variables = {}
19
+
20
+ for (const dependency of dependencies) {
21
+ if (dependency.references !== undefined) references.push(...dependency.references)
22
+ if (dependency.services !== undefined) services.push(...dependency.services)
23
+ if (dependency.proxies !== undefined) proxies.push(...dependency.proxies)
24
+ if (dependency.variables !== undefined) append(variables, dependency.variables)
25
+ }
26
+
27
+ return { references, services, proxies, variables }
28
+ }
29
+
30
+ /**
31
+ * @param {toa.deployment.dependency.Variables} merged
32
+ * @param {toa.deployment.dependency.Variables} variables
33
+ */
34
+ const append = (merged, variables) => {
35
+ for (const [component, vars] of Object.entries(variables)) {
36
+ if (merged[component] === undefined) merged[component] = []
37
+
38
+ merged[component].push(...vars)
39
+ }
40
+ }
41
+
42
+ exports.merge = merge
@@ -0,0 +1,7 @@
1
+ # This file helps with IDE autocompletion
2
+ apiVersion: v2
3
+ name: toa-application
4
+ description: Toa Application Chart
5
+ type: application
6
+ version: 0.0.0
7
+ appVersion: "0.0.0"
@@ -0,0 +1,16 @@
1
+ {{- range .Values.components }}
2
+ apiVersion: v1
3
+ kind: Service
4
+ metadata:
5
+ name: {{ . }}
6
+ spec:
7
+ type: ClusterIP
8
+ selector:
9
+ {{ . }}: "1"
10
+ ports:
11
+ - name: http-binding
12
+ protocol: TCP
13
+ port: 3000
14
+ targetPort: 3000
15
+ ---
16
+ {{- end }}
@@ -0,0 +1,38 @@
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
+ {{- if $.Values.variables }}
23
+ env:
24
+ {{- range $component := .components }}
25
+ {{- range $key, $vars := $.Values.variables }}
26
+ {{- if eq $component $key }}
27
+ {{- range $vars }}
28
+ {{- include "env.var" . | indent 12 }}
29
+ {{- end }}
30
+ {{- end }}
31
+ {{- end }}
32
+ {{- end }}
33
+ {{- range $.Values.variables.global }}
34
+ {{- include "env.var" . | indent 12 }}
35
+ {{- end }}
36
+ {{- end }}
37
+ ---
38
+ {{- end }}
@@ -0,0 +1,10 @@
1
+ {{- range .Values.proxies }}
2
+ apiVersion: v1
3
+ kind: Service
4
+ metadata:
5
+ name: {{ .name }}
6
+ spec:
7
+ type: ExternalName
8
+ externalName: {{ .target }}
9
+ ---
10
+ {{- end }}
@@ -0,0 +1,63 @@
1
+ {{- range .Values.services }}
2
+ apiVersion: apps/v1
3
+ kind: Deployment
4
+ metadata:
5
+ name: service-{{ required "deployment name is required" .name }}
6
+ spec:
7
+ replicas: {{ .replicas | default 2 }}
8
+ selector:
9
+ matchLabels:
10
+ toa.io/service: {{ .name }}
11
+ template:
12
+ metadata:
13
+ labels:
14
+ toa.io/service: {{ .name }}
15
+ spec:
16
+ containers:
17
+ - name: {{ .name }}
18
+ image: {{ .image }}
19
+ {{- if $.Values.variables }}
20
+ env:
21
+ {{- range $.Values.variables.global }}
22
+ {{- include "env.var" . | indent 12 }}
23
+ {{- end }}
24
+ {{- end }}
25
+ ---
26
+ apiVersion: v1
27
+ kind: Service
28
+ metadata:
29
+ name: service-{{ .name }}
30
+ spec:
31
+ type: ClusterIP
32
+ selector:
33
+ toa.io/service: {{ .name }}
34
+ ports:
35
+ - name: port-{{ .port }}
36
+ protocol: TCP
37
+ port: {{ .port }}
38
+ targetPort: {{ .port }}
39
+ ---
40
+ {{- if .ingress }}
41
+ apiVersion: networking.k8s.io/v1
42
+ kind: Ingress
43
+ metadata:
44
+ name: {{ .name }}
45
+ {{- if .ingress.annotations }}
46
+ annotations:
47
+ {{ toYaml .ingress.annotations | indent 4 }}
48
+ {{- end }}
49
+ spec:
50
+ ingressClassName: {{ required "ingress.class is required" .ingress.class }}
51
+ rules:
52
+ - host: {{ required "ingress.host is required" .ingress.host }}
53
+ http:
54
+ paths:
55
+ - path: /
56
+ pathType: Prefix
57
+ backend:
58
+ service:
59
+ name: service-{{ .name }}
60
+ port:
61
+ number: 8000
62
+ {{- end }}
63
+ {{- end }}
@@ -0,0 +1,12 @@
1
+ {{- define "env.var" }}
2
+ - name: {{ .name }}
3
+ {{- if .value }}
4
+ value: {{ .value | quote }}
5
+ {{- end }}
6
+ {{- if .secret }}
7
+ valueFrom:
8
+ secretKeyRef:
9
+ name: {{ .secret.name }}
10
+ key: {{ .secret.key }}
11
+ {{- end }}
12
+ {{- end }}
@@ -0,0 +1,47 @@
1
+ # This file helps with IDE autocompletion
2
+ compositions:
3
+ - name: todos
4
+ image: localhost:5000/composition-todos:0.0.0
5
+ replicas: 2
6
+ components:
7
+ - todos-tasks
8
+ - todos-stats
9
+ - name: users
10
+ image: localhost:5000/composition-users:0.0.0
11
+ replicas: 3
12
+ components:
13
+ - users-users
14
+ # TODO: create component services only if sync binding is being used
15
+ components:
16
+ - todos-tasks
17
+ - todos-stats
18
+ - users-users
19
+ variables:
20
+ global:
21
+ - name: TOA_BINDINGS_AMQP_SYSTEM_USERNAME
22
+ value: foo
23
+ secret:
24
+ name: secret-name
25
+ key: secret-key
26
+ todos-tasks:
27
+ - name: TOA_CONFIGURATION_TODOS_TASKS
28
+ value: foo
29
+ secret:
30
+ name: secret-name
31
+ key: secret-key
32
+ services:
33
+ - name: resources-gateway
34
+ image: localhost:5000/resources-gateway:0.0.0
35
+ port: 8000
36
+ replicas: 2
37
+ ingress:
38
+ host: dummies.toa.io
39
+ class: alb
40
+ annotations:
41
+ alb.ingress.kubernetes.io/scheme: internet-facing
42
+ alb.ingress.kubernetes.io/target-type: ip
43
+ alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS": 443}]'
44
+ proxies:
45
+ - name: storage-proxy
46
+ target: host.docker.internal
47
+ environment: development
@@ -0,0 +1,30 @@
1
+ 'use strict'
2
+
3
+ // noinspection JSClosureCompilerSyntax
4
+ /**
5
+ * @implements {toa.deployment.Composition}
6
+ */
7
+ class Composition {
8
+ name
9
+ image
10
+ /** @type {string[]} */
11
+ components
12
+
13
+ /**
14
+ * @param composition {toa.norm.context.Composition}
15
+ * @param image {toa.deployment.images.Image}
16
+ */
17
+ constructor (composition, image) {
18
+ this.name = composition.name
19
+ this.image = image.reference
20
+ this.components = composition.components.map(component)
21
+ }
22
+ }
23
+
24
+ /**
25
+ * @param {toa.norm.Component} component
26
+ * @returns {string}
27
+ */
28
+ const component = (component) => component.locator.label
29
+
30
+ exports.Composition = Composition
@@ -0,0 +1,82 @@
1
+ 'use strict'
2
+
3
+ const { join } = require('node:path')
4
+ const { writeFile: write } = require('node:fs/promises')
5
+ const { directory: { copy } } = require('@toa.io/filesystem')
6
+ const { dump } = require('@toa.io/yaml')
7
+
8
+ const { merge, declare, describe } = require('./.deployment')
9
+
10
+ /**
11
+ * @implements {toa.deployment.Deployment}
12
+ */
13
+ class Deployment {
14
+ /** @type {toa.deployment.Declaration} */
15
+ #declaration
16
+ /** @type {toa.deployment.Contents} */
17
+ #contents
18
+ /** @type {toa.operations.Process} */
19
+ #process
20
+ /** @type {string} */
21
+ #target
22
+
23
+ /**
24
+ * @param context {toa.norm.Context}
25
+ * @param compositions {toa.deployment.Composition[]}
26
+ * @param dependencies {toa.deployment.Dependency[]}
27
+ * @param process {toa.operations.Process}
28
+ */
29
+ constructor (context, compositions, dependencies, process) {
30
+ const dependency = merge(dependencies)
31
+
32
+ if (context.environment === 'local') throw new Error('Deployment environment name \'local\' is not allowed.')
33
+
34
+ this.#declaration = declare(context, dependency)
35
+ this.#contents = describe(context, compositions, dependency)
36
+ this.#process = process
37
+ }
38
+
39
+ async export (target) {
40
+ const chart = dump(this.#declaration)
41
+ const values = dump(this.#contents)
42
+
43
+ await Promise.all([
44
+ write(join(target, 'Chart.yaml'), chart),
45
+ write(join(target, 'values.yaml'), values),
46
+ copy(TEMPLATES, join(target, 'templates'))
47
+ ])
48
+
49
+ this.#target = target
50
+ }
51
+
52
+ async install (options) {
53
+ if (options.target) this.#target = options.target
54
+ if (this.#target === undefined) throw new Error('Deployment hasn\'t been exported')
55
+
56
+ const args = []
57
+
58
+ if (options.namespace !== undefined) args.push('-n', options.namespace)
59
+ if (options.wait === true) args.push('--wait')
60
+
61
+ await this.#process.execute('helm', ['dependency', 'update', this.#target])
62
+ await this.#process.execute('helm', ['upgrade', this.#declaration.name, '-i', ...args, this.#target])
63
+ }
64
+
65
+ async template (options) {
66
+ if (this.#target === undefined) throw new Error('Deployment hasn\'t been exported')
67
+
68
+ await this.#process.execute('helm', ['dependency', 'update', this.#target], { silently: true })
69
+
70
+ const args = []
71
+
72
+ if (options.namespace !== undefined) args.push('-n', options.namespace)
73
+
74
+ return await this.#process.execute('helm',
75
+ ['template', this.#declaration.name, ...args, this.#target],
76
+ { silently: true })
77
+ }
78
+ }
79
+
80
+ const TEMPLATES = join(__dirname, 'chart/templates')
81
+
82
+ exports.Deployment = Deployment
@@ -0,0 +1,100 @@
1
+ 'use strict'
2
+
3
+ const { Process } = require('../process')
4
+ const { Operator } = require('./operator')
5
+ const { Factory: Images, Registry } = require('./images')
6
+ const { Deployment } = require('./deployment')
7
+ const { Composition } = require('./composition')
8
+ const { Service } = require('./service')
9
+
10
+ /**
11
+ * @implements {toa.deployment.Factory}
12
+ */
13
+ class Factory {
14
+ /** @type {toa.norm.Context} */
15
+ #context
16
+ /** @type {toa.deployment.images.Registry} */
17
+ #registry
18
+ /** @type {toa.operations.Process} */
19
+ #process
20
+
21
+ /**
22
+ * @param context {toa.norm.Context}
23
+ */
24
+ constructor (context) {
25
+ this.#context = context
26
+ this.#process = new Process()
27
+
28
+ const images = new Images(context.name, context.runtime)
29
+ this.#registry = new Registry(context.registry, images, this.#process)
30
+ }
31
+
32
+ operator () {
33
+ const compositions = this.#context.compositions.map((composition) => this.#composition(composition))
34
+ const dependencies = this.#dependencies()
35
+ const deployment = new Deployment(this.#context, compositions, dependencies, this.#process)
36
+
37
+ return new Operator(deployment, this.#registry)
38
+ }
39
+
40
+ /**
41
+ * @param composition {toa.norm.context.Composition}
42
+ * @returns {Composition}
43
+ */
44
+ #composition (composition) {
45
+ const image = this.#registry.composition(composition)
46
+
47
+ return new Composition(composition, image)
48
+ }
49
+
50
+ /**
51
+ * @returns {toa.deployment.Dependency[]}
52
+ */
53
+ #dependencies () {
54
+ /** @type {toa.deployment.Dependency[]} */
55
+ const dependencies = []
56
+
57
+ for (const [reference, instances] of Object.entries(this.#context.dependencies)) {
58
+ const dependency = this.#dependency(reference, instances)
59
+
60
+ if (dependency !== undefined) dependencies.push(dependency)
61
+ }
62
+
63
+ return dependencies
64
+ }
65
+
66
+ /**
67
+ * @param {string} path
68
+ * @param {toa.norm.context.dependencies.Instance[]} instances
69
+ * @returns {toa.deployment.Dependency | undefined}
70
+ */
71
+ #dependency (path, instances) {
72
+ const module = require(path)
73
+ const pkg = require(path + '/package.json')
74
+
75
+ if (module.deployment === undefined) return
76
+
77
+ const annotations = this.#context.annotations?.[pkg.name]
78
+
79
+ /** @type {toa.deployment.dependency.Declaration} */
80
+ const dependency = module.deployment(instances, annotations)
81
+
82
+ /** @type {toa.deployment.Service[]} */
83
+ const services = dependency.services?.map((service) => this.#service(path, service))
84
+
85
+ return { ...dependency, services }
86
+ }
87
+
88
+ /**
89
+ * @param path {string}
90
+ * @param service {toa.deployment.dependency.Service}
91
+ * @returns {Service}
92
+ */
93
+ #service (path, service) {
94
+ const image = this.#registry.service(path, service)
95
+
96
+ return new Service(service, image)
97
+ }
98
+ }
99
+
100
+ exports.Factory = Factory
@@ -0,0 +1,14 @@
1
+ FROM node:18.2.0-alpine3.15
2
+
3
+ ENV NODE_ENV=production
4
+ RUN npm set registry {{registry}}
5
+ RUN npm set proxy {{proxy}}
6
+ RUN npm i -g @toa.io/runtime@{{version}}
7
+
8
+ WORKDIR /composition
9
+ ADD . .
10
+
11
+ # run 'npm i' in each component
12
+ RUN find . -maxdepth 1 -type d \( ! -name . \) -exec /bin/sh -c "cd '{}' && if [ -f package.json ]; then npm i; fi" \;
13
+
14
+ CMD toa compose *
@@ -0,0 +1,50 @@
1
+ 'use strict'
2
+
3
+ const { join } = require('node:path')
4
+ const { hash } = require('@toa.io/generic')
5
+ const { directory: { copy } } = require('@toa.io/filesystem')
6
+
7
+ const { Image } = require('./image')
8
+
9
+ class Composition extends Image {
10
+ dockerfile = join(__dirname, 'composition.Dockerfile')
11
+
12
+ /** @type {string} */
13
+ #name
14
+ /** @type {Array<toa.norm.Component>} */
15
+ #components
16
+
17
+ /**
18
+ * @param scope {string}
19
+ * @param runtime {toa.norm.context.Runtime}
20
+ * @param composition {toa.norm.context.Composition}
21
+ */
22
+ constructor (scope, runtime, composition) {
23
+ super(scope, runtime)
24
+
25
+ this.#name = composition.name
26
+ this.#components = composition.components
27
+ }
28
+
29
+ get name () {
30
+ return this.#name
31
+ }
32
+
33
+ get version () {
34
+ const tags = this.#components.map((component) => component.locator.id + ':' + component.version)
35
+
36
+ return hash(tags.join(';'))
37
+ }
38
+
39
+ async prepare (root) {
40
+ const context = await super.prepare(root)
41
+
42
+ for (const component of this.#components) {
43
+ await copy(component.path, join(context, component.locator.label))
44
+ }
45
+
46
+ return context
47
+ }
48
+ }
49
+
50
+ exports.Composition = Composition