@toa.io/operations 0.8.0-dev.0 → 0.20.0-alpha.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.
Files changed (44) hide show
  1. package/package.json +6 -5
  2. package/readme.md +42 -0
  3. package/src/deployment/.deployment/.describe/components.js +1 -0
  4. package/src/deployment/.deployment/.describe/compositions.js +11 -0
  5. package/src/deployment/.deployment/.describe/index.js +4 -0
  6. package/src/deployment/.deployment/.describe/services.js +11 -0
  7. package/src/deployment/.deployment/.describe/variables.js +13 -13
  8. package/src/deployment/.deployment/describe.js +9 -12
  9. package/src/deployment/.deployment/merge.js +0 -8
  10. package/src/deployment/chart/templates/compositions.yaml +7 -12
  11. package/src/deployment/chart/templates/services.yaml +10 -10
  12. package/src/deployment/chart/values.yaml +13 -13
  13. package/src/deployment/composition.js +0 -12
  14. package/src/deployment/deployment.js +28 -25
  15. package/src/deployment/factory.js +4 -30
  16. package/src/deployment/images/composition.Dockerfile +10 -5
  17. package/src/deployment/images/composition.js +8 -6
  18. package/src/deployment/images/factory.js +19 -5
  19. package/src/deployment/images/image.fixtures.js +6 -0
  20. package/src/deployment/images/image.js +54 -18
  21. package/src/deployment/images/image.test.js +3 -5
  22. package/src/deployment/images/service.Dockerfile +7 -5
  23. package/src/deployment/images/service.js +7 -3
  24. package/src/deployment/operator.js +0 -3
  25. package/src/deployment/registry.js +4 -1
  26. package/src/deployment/service.js +2 -15
  27. package/types/_deployment/images/image.d.ts +14 -0
  28. package/types/_deployment/operator.d.ts +20 -0
  29. package/types/{deployment → _deployment}/registry.d.ts +1 -1
  30. package/types/dependency.d.ts +29 -0
  31. package/types/index.d.ts +1 -0
  32. package/src/deployment/chart/templates/proxies.yaml +0 -10
  33. package/src/deployment/deployment.fixtures.js +0 -42
  34. package/src/deployment/deployment.test.js +0 -78
  35. package/types/deployment/images/image.d.ts +0 -14
  36. package/types/deployment/operator.d.ts +0 -18
  37. package/types/process.d.ts +0 -13
  38. /package/types/{deployment → _deployment}/composition.d.ts +0 -0
  39. /package/types/{deployment → _deployment}/dependency.d.ts +0 -0
  40. /package/types/{deployment → _deployment}/deployment.d.ts +0 -0
  41. /package/types/{deployment → _deployment}/factory.d.ts +0 -0
  42. /package/types/{deployment → _deployment}/images/factory.d.ts +0 -0
  43. /package/types/{deployment → _deployment}/images/registry.d.ts +0 -0
  44. /package/types/{deployment → _deployment}/service.d.ts +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/operations",
3
- "version": "0.8.0-dev.0",
3
+ "version": "0.20.0-alpha.0",
4
4
  "description": "Toa Deployment",
5
5
  "homepage": "https://toa.io",
6
6
  "author": {
@@ -22,14 +22,15 @@
22
22
  "access": "public"
23
23
  },
24
24
  "main": "src/index.js",
25
+ "types": "types/index.d.ts",
25
26
  "scripts": {
26
27
  "test": "echo \"Error: run tests from root\" && exit 1"
27
28
  },
28
29
  "dependencies": {
29
- "@toa.io/filesystem": "1.1.0-dev.2",
30
- "@toa.io/generic": "0.11.0-dev.16",
31
- "@toa.io/yaml": "0.7.6-dev.16",
30
+ "@toa.io/filesystem": "0.20.0-alpha.0",
31
+ "@toa.io/generic": "0.20.0-alpha.0",
32
+ "@toa.io/yaml": "0.20.0-alpha.0",
32
33
  "execa": "5.1.1"
33
34
  },
34
- "gitHead": "669be360acebac4dbe49735ec506127d9d0a05e3"
35
+ "gitHead": "d047190899218b5249901a01a2a4caec5b34cf09"
35
36
  }
package/readme.md ADDED
@@ -0,0 +1,42 @@
1
+ # Toa Operations
2
+
3
+ ## Context
4
+
5
+ ### Container Registry
6
+
7
+ #### Build Options
8
+
9
+ ```yaml
10
+ # context.toa.yaml
11
+
12
+ registry:
13
+ build:
14
+ arguments: [GITHUB_TOKEN]
15
+ run: npm config set //npm.pkg.github.com/:_authToken ${GITHUB_TOKEN}
16
+ ```
17
+
18
+ `arguments` is a list of environemt varialbes to be passed to `docker build`.
19
+
20
+ `run` is a command(s) to be executed during build. Multiline is supported.
21
+
22
+ ```yaml
23
+ # context.toa.yaml
24
+
25
+ registry:
26
+ build:
27
+ run: |
28
+ echo test > .test
29
+ rm .test
30
+ ```
31
+
32
+ #### Registry Credentials
33
+
34
+ When using private container registry,
35
+ a secret containing required credentials can be specified using `registry.credentials` option.
36
+
37
+ ```yaml
38
+ # context.toa.yaml
39
+
40
+ registry:
41
+ credentials: docker-credentials-secret-name
42
+ ```
@@ -16,4 +16,5 @@ const components = (compositions) => {
16
16
 
17
17
  return Array.from(components)
18
18
  }
19
+
19
20
  exports.components = components
@@ -0,0 +1,11 @@
1
+ 'use strict'
2
+
3
+ const { addVariables } = require('./variables')
4
+
5
+ function compositions (compositions, variables) {
6
+ for (const composition of compositions) {
7
+ addVariables(composition, variables)
8
+ }
9
+ }
10
+
11
+ exports.compositions = compositions
@@ -2,8 +2,12 @@
2
2
 
3
3
  const { dependencies } = require('./dependencies')
4
4
  const { components } = require('./components')
5
+ const { compositions } = require('./compositions')
5
6
  const { variables } = require('./variables')
7
+ const { services } = require('./services')
6
8
 
7
9
  exports.dependencies = dependencies
8
10
  exports.components = components
11
+ exports.compositions = compositions
9
12
  exports.variables = variables
13
+ exports.services = services
@@ -0,0 +1,11 @@
1
+ 'use strict'
2
+
3
+ const { addVariables } = require('./variables')
4
+
5
+ function services (services, variables) {
6
+ for (const service of services) {
7
+ addVariables(service, variables)
8
+ }
9
+ }
10
+
11
+ exports.services = services
@@ -1,20 +1,20 @@
1
1
  'use strict'
2
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 = []
3
+ function addVariables (deployment, variables) {
4
+ const used = new Set()
10
5
 
11
- if (context.environment !== undefined) {
12
- const variable = { name: 'TOA_ENV', value: context.environment }
6
+ deployment.variables ??= []
13
7
 
14
- variables.global.unshift(variable)
15
- }
8
+ for (const [key, set] of Object.entries(variables)) {
9
+ if (key !== 'global' && !deployment.components?.includes(key)) continue
10
+
11
+ for (const variable of set) {
12
+ if (used.has(variable.name)) continue
16
13
 
17
- return variables
14
+ deployment.variables.push(variable)
15
+ used.add(variable.name)
16
+ }
17
+ }
18
18
  }
19
19
 
20
- exports.variables = variables
20
+ exports.addVariables = addVariables
@@ -2,26 +2,23 @@
2
2
 
3
3
  const get = require('./.describe')
4
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
5
  const describe = (context, compositions, dependency) => {
12
- const { references, services, proxies } = dependency
6
+ const { services } = dependency
7
+
8
+ dependency.variables.global ??= []
9
+ dependency.variables.global.unshift({ name: 'TOA_ENV', value: context.environment })
13
10
 
14
11
  const components = get.components(compositions)
15
- const dependencies = get.dependencies(references)
16
- const variables = get.variables(context, dependency.variables)
12
+ const credentials = context.registry?.credentials
13
+
14
+ get.compositions(compositions, dependency.variables, context.environment)
15
+ get.services(services, dependency.variables)
17
16
 
18
17
  return {
19
18
  compositions,
20
19
  components,
21
20
  services,
22
- proxies,
23
- variables,
24
- ...dependencies
21
+ credentials,
25
22
  }
26
23
  }
27
24
 
@@ -1,9 +1,5 @@
1
1
  'use strict'
2
2
 
3
- /**
4
- * @param {toa.deployment.Dependency[]} dependencies
5
- * @returns {toa.deployment.Dependency}
6
- */
7
3
  const merge = (dependencies) => {
8
4
  /** @type {toa.deployment.dependency.Reference[]} */
9
5
  const references = []
@@ -27,10 +23,6 @@ const merge = (dependencies) => {
27
23
  return { references, services, proxies, variables }
28
24
  }
29
25
 
30
- /**
31
- * @param {toa.deployment.dependency.Variables} merged
32
- * @param {toa.deployment.dependency.Variables} variables
33
- */
34
26
  const append = (merged, variables) => {
35
27
  for (const [component, vars] of Object.entries(variables)) {
36
28
  if (merged[component] === undefined) merged[component] = []
@@ -2,7 +2,7 @@
2
2
  apiVersion: apps/v1
3
3
  kind: Deployment
4
4
  metadata:
5
- name: composition-{{ required "deployment name is required" .name }}
5
+ name: composition-{{ required "composition name is required" .name }}
6
6
  spec:
7
7
  replicas: {{ .replicas | default 2 }}
8
8
  selector:
@@ -19,20 +19,15 @@ spec:
19
19
  containers:
20
20
  - name: {{ .name }}
21
21
  image: {{ .image }}
22
- {{- if $.Values.variables }}
22
+ {{- if .variables }}
23
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 }}
24
+ {{- range .variables }}
34
25
  {{- include "env.var" . | indent 12 }}
35
26
  {{- end }}
36
27
  {{- end }}
28
+ {{- if $.Values.credentials }}
29
+ imagePullSecrets:
30
+ - name: {{ $.Values.credentials }}
31
+ {{- end }}
37
32
  ---
38
33
  {{- end }}
@@ -2,23 +2,23 @@
2
2
  apiVersion: apps/v1
3
3
  kind: Deployment
4
4
  metadata:
5
- name: service-{{ required "deployment name is required" .name }}
5
+ name: extension-{{ required "deployment name is required" .name }}
6
6
  spec:
7
7
  replicas: {{ .replicas | default 2 }}
8
8
  selector:
9
9
  matchLabels:
10
- toa.io/service: {{ .name }}
10
+ toa.io/service: extension-{{ .name }}
11
11
  template:
12
12
  metadata:
13
13
  labels:
14
- toa.io/service: {{ .name }}
14
+ toa.io/service: extension-{{ .name }}
15
15
  spec:
16
16
  containers:
17
- - name: {{ .name }}
17
+ - name: extension-{{ .name }}
18
18
  image: {{ .image }}
19
- {{- if $.Values.variables }}
19
+ {{- if .variables }}
20
20
  env:
21
- {{- range $.Values.variables.global }}
21
+ {{- range .variables }}
22
22
  {{- include "env.var" . | indent 12 }}
23
23
  {{- end }}
24
24
  {{- end }}
@@ -26,11 +26,11 @@ spec:
26
26
  apiVersion: v1
27
27
  kind: Service
28
28
  metadata:
29
- name: service-{{ .name }}
29
+ name: extension-{{ .name }}
30
30
  spec:
31
31
  type: ClusterIP
32
32
  selector:
33
- toa.io/service: {{ .name }}
33
+ toa.io/service: extension-{{ .name }}
34
34
  ports:
35
35
  - name: port-{{ .port }}
36
36
  protocol: TCP
@@ -41,7 +41,7 @@ spec:
41
41
  apiVersion: networking.k8s.io/v1
42
42
  kind: Ingress
43
43
  metadata:
44
- name: {{ .name }}
44
+ name: extension-{{ .name }}
45
45
  {{- if .ingress.annotations }}
46
46
  annotations:
47
47
  {{ toYaml .ingress.annotations | indent 4 }}
@@ -58,7 +58,7 @@ spec:
58
58
  pathType: Prefix
59
59
  backend:
60
60
  service:
61
- name: service-{{ .name }}
61
+ name: extension-{{ .name }}
62
62
  port:
63
63
  number: 8000
64
64
  {{- end }}
@@ -6,6 +6,12 @@ compositions:
6
6
  components:
7
7
  - todos-tasks
8
8
  - todos-stats
9
+ variables:
10
+ - name: TOA_CONFIGURATION_TODOS_TASKS
11
+ value: foo
12
+ secret:
13
+ name: secret-name
14
+ key: secret-key
9
15
  - name: users
10
16
  image: localhost:5000/composition-users:0.0.0
11
17
  replicas: 3
@@ -16,19 +22,6 @@ components:
16
22
  - todos-tasks
17
23
  - todos-stats
18
24
  - 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
25
  services:
33
26
  - name: resources-gateway
34
27
  image: localhost:5000/resources-gateway:0.0.0
@@ -41,7 +34,14 @@ services:
41
34
  alb.ingress.kubernetes.io/scheme: internet-facing
42
35
  alb.ingress.kubernetes.io/target-type: ip
43
36
  alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS": 443}]'
37
+ variables:
38
+ - name: TOA_CONFIGURATION_TODOS_TASKS
39
+ value: foo
40
+ secret:
41
+ name: secret-name
42
+ key: secret-key
44
43
  proxies:
45
44
  - name: storage-proxy
46
45
  target: host.docker.internal
47
46
  environment: development
47
+ credentials: docker-credentials
@@ -1,19 +1,11 @@
1
1
  'use strict'
2
2
 
3
- // noinspection JSClosureCompilerSyntax
4
- /**
5
- * @implements {toa.deployment.Composition}
6
- */
7
3
  class Composition {
8
4
  name
9
5
  image
10
6
  /** @type {string[]} */
11
7
  components
12
8
 
13
- /**
14
- * @param composition {toa.norm.context.Composition}
15
- * @param image {toa.deployment.images.Image}
16
- */
17
9
  constructor (composition, image) {
18
10
  this.name = composition.name
19
11
  this.image = image.reference
@@ -21,10 +13,6 @@ class Composition {
21
13
  }
22
14
  }
23
15
 
24
- /**
25
- * @param {toa.norm.Component} component
26
- * @returns {string}
27
- */
28
16
  const component = (component) => component.locator.label
29
17
 
30
18
  exports.Composition = Composition
@@ -7,39 +7,23 @@ const { dump } = require('@toa.io/yaml')
7
7
 
8
8
  const { merge, declare, describe } = require('./.deployment')
9
9
 
10
- /**
11
- * @implements {toa.deployment.Deployment}
12
- */
13
10
  class Deployment {
14
- /** @type {toa.deployment.Declaration} */
15
- #declaration
16
-
17
- /** @type {toa.deployment.Contents} */
18
- #contents
19
-
20
- /** @type {toa.operations.Process} */
11
+ #chart
12
+ #values
21
13
  #process
22
-
23
- /** @type {string} */
24
14
  #target
25
15
 
26
- /**
27
- * @param context {toa.norm.Context}
28
- * @param compositions {toa.deployment.Composition[]}
29
- * @param dependencies {toa.deployment.Dependency[]}
30
- * @param process {toa.operations.Process}
31
- */
32
16
  constructor (context, compositions, dependencies, process) {
33
17
  const dependency = merge(dependencies)
34
18
 
35
- this.#declaration = declare(context, dependency)
36
- this.#contents = describe(context, compositions, dependency)
19
+ this.#chart = declare(context, dependency)
20
+ this.#values = describe(context, compositions, dependency)
37
21
  this.#process = process
38
22
  }
39
23
 
40
24
  async export (target) {
41
- const chart = dump(this.#declaration)
42
- const values = dump(this.#contents)
25
+ const chart = dump(this.#chart)
26
+ const values = dump(this.#values)
43
27
 
44
28
  await Promise.all([
45
29
  write(join(target, 'Chart.yaml'), chart),
@@ -60,7 +44,7 @@ class Deployment {
60
44
  if (options.wait === true) args.push('--wait')
61
45
 
62
46
  await this.#process.execute('helm', ['dependency', 'update', this.#target])
63
- await this.#process.execute('helm', ['upgrade', this.#declaration.name, '-i', ...args, this.#target])
47
+ await this.#process.execute('helm', ['upgrade', this.#chart.name, '-i', ...args, this.#target])
64
48
  }
65
49
 
66
50
  async template (options) {
@@ -73,12 +57,31 @@ class Deployment {
73
57
  if (options.namespace !== undefined) args.push('-n', options.namespace)
74
58
 
75
59
  return await this.#process.execute('helm',
76
- ['template', this.#declaration.name, ...args, this.#target],
60
+ ['template', this.#chart.name, ...args, this.#target],
77
61
  { silently: true })
78
62
  }
79
63
 
80
64
  variables () {
81
- return this.#contents.variables
65
+ const variables = []
66
+ const used = new Set()
67
+
68
+ addVariables(this.#values.compositions, variables, used)
69
+ addVariables(this.#values.services, variables, used)
70
+
71
+ return variables
72
+ }
73
+ }
74
+
75
+ function addVariables (list, variables, used = new Set()) {
76
+ for (const item of list) {
77
+ if (item.variables === undefined) continue
78
+
79
+ for (const variable of item.variables) {
80
+ if (used.has(variable.name)) continue
81
+
82
+ variables.push(variable)
83
+ used.add(variable.name)
84
+ }
82
85
  }
83
86
  }
84
87
 
@@ -8,33 +8,19 @@ const { Registry } = require('./registry')
8
8
  const { Composition } = require('./composition')
9
9
  const { Service } = require('./service')
10
10
 
11
- /**
12
- * @implements {toa.deployment.Factory}
13
- */
14
11
  class Factory {
15
- /** @type {toa.norm.Context} */
16
12
  #context
17
-
18
- /** @type {Composition[]} */
19
13
  #compositions
20
-
21
- /** @type {toa.deployment.Dependency[]} */
22
14
  #dependencies
23
-
24
- /** @type {toa.deployment.Registry} */
25
15
  #registry
26
-
27
- /** @type {toa.operations.Process} */
28
16
  #process
17
+ #extensionComponents = []
29
18
 
30
- /**
31
- * @param context {toa.norm.Context}
32
- */
33
19
  constructor (context) {
34
20
  this.#context = context
35
21
  this.#process = new Process()
36
22
 
37
- const imagesFactory = new ImagesFactory(context.name, context.runtime)
23
+ const imagesFactory = new ImagesFactory(context.name, context.runtime, context.registry)
38
24
 
39
25
  this.#registry = new Registry(context.registry, imagesFactory, this.#process)
40
26
  this.#compositions = context.compositions.map((composition) => this.#composition(composition))
@@ -51,19 +37,12 @@ class Factory {
51
37
  return this.#registry
52
38
  }
53
39
 
54
- /**
55
- * @param composition {toa.norm.context.Composition}
56
- * @returns {Composition}
57
- */
58
40
  #composition (composition) {
59
41
  const image = this.#registry.composition(composition)
60
42
 
61
43
  return new Composition(composition, image)
62
44
  }
63
45
 
64
- /**
65
- * @returns {toa.deployment.Dependency[]}
66
- */
67
46
  #getDependencies () {
68
47
  /** @type {toa.deployment.Dependency[]} */
69
48
  const dependencies = []
@@ -79,21 +58,16 @@ class Factory {
79
58
  return dependencies
80
59
  }
81
60
 
82
- /**
83
- * @param {string} path
84
- * @param {toa.norm.context.dependencies.Instance[]} instances
85
- * @returns {toa.deployment.Dependency | undefined}
86
- */
87
61
  #getDependency (path, instances) {
88
62
  const module = require(path)
89
63
  const pkg = require(path + '/package.json')
90
64
 
91
65
  if (module.deployment === undefined) return
92
66
 
93
- const annotations = this.#context.annotations?.[pkg.name]
67
+ const annotation = this.#context.annotations?.[pkg.name]
94
68
 
95
69
  /** @type {toa.deployment.dependency.Declaration} */
96
- const dependency = module.deployment(instances, annotations)
70
+ const dependency = module.deployment(instances, annotation)
97
71
 
98
72
  /** @type {toa.deployment.Service[]} */
99
73
  const services = dependency.services?.map((service) => this.#service(path, service))
@@ -1,14 +1,19 @@
1
1
  FROM node:18.16.0-alpine3.17
2
2
 
3
+ {{build.arguments}}
4
+
3
5
  ENV NODE_ENV=production
4
- RUN if [ {{registry}} != undefined ]; then npm set registry {{registry}}; fi
5
- RUN if [ {{proxy}} != undefined ]; then npm set proxy {{proxy}}; fi
6
- RUN npm i -g @toa.io/runtime@{{version}}
6
+ RUN if [ "{{runtime.registry}}" != "" ]; then npm set registry {{runtime.registry}}; fi
7
+ RUN if [ "{{runtime.proxy}}" != "" ]; then npm set proxy {{runtime.proxy}}; fi
8
+ RUN npm i -g @toa.io/runtime@{{runtime.version}} --omit=dev
7
9
 
8
10
  WORKDIR /composition
9
- ADD . .
11
+ COPY --chown=node:node . /composition
12
+
13
+ {{build.run}}
10
14
 
11
15
  # 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" \;
16
+ RUN for entry in *; do if [ -f "$entry/package.json" ]; then (cd $entry && npm i --omit=dev); fi; done
13
17
 
18
+ USER node
14
19
  CMD toa compose *
@@ -11,23 +11,25 @@ class Composition extends Image {
11
11
 
12
12
  /** @type {string} */
13
13
  #name
14
+
14
15
  /** @type {toa.norm.Component[]} */
15
16
  #components
16
17
 
17
18
  /**
18
- * @param scope {string}
19
- * @param runtime {toa.norm.context.Runtime}
20
- * @param composition {toa.norm.context.Composition}
19
+ * @param {string} scope
20
+ * @param {toa.norm.context.Runtime} runtime
21
+ * @param {toa.norm.context.Registry} registry
22
+ * @param {toa.norm.context.Composition} composition
21
23
  */
22
- constructor (scope, runtime, composition) {
23
- super(scope, runtime)
24
+ constructor (scope, runtime, registry, composition) {
25
+ super(scope, runtime, registry)
24
26
 
25
27
  this.#name = composition.name
26
28
  this.#components = composition.components
27
29
  }
28
30
 
29
31
  get name () {
30
- return this.#name
32
+ return 'composition-' + this.#name
31
33
  }
32
34
 
33
35
  get version () {
@@ -9,30 +9,44 @@ const { Service } = require('./service')
9
9
  class Factory {
10
10
  /** @type {string} */
11
11
  #scope
12
+
12
13
  /** @type {toa.norm.context.Runtime} */
13
14
  #runtime
14
15
 
16
+ /** @type {toa.norm.context.Registry} */
17
+ #registry
18
+
15
19
  /**
16
- * @param scope {string}
17
- * @param runtime {toa.norm.context.Runtime}
20
+ * @param {string} scope
21
+ * @param {toa.norm.context.Runtime} runtime
22
+ * @param {toa.norm.context.Registry} registry
18
23
  */
19
- constructor (scope, runtime) {
24
+ constructor (scope, runtime, registry) {
20
25
  this.#scope = scope
21
26
  this.#runtime = runtime
27
+ this.#registry = registry
22
28
  }
23
29
 
24
30
  /**
25
31
  * @returns {Composition}
26
32
  */
27
33
  composition (composition) {
28
- return new Composition(this.#scope, this.#runtime, composition)
34
+ const instance = new Composition(this.#scope, this.#runtime, this.#registry, composition)
35
+
36
+ instance.tag()
37
+
38
+ return instance
29
39
  }
30
40
 
31
41
  /**
32
42
  * @returns {Service}
33
43
  */
34
44
  service (path, service) {
35
- return new Service(this.#scope, this.#runtime, path, service)
45
+ const instance = new Service(this.#scope, this.#runtime, this.#registry, path, service)
46
+
47
+ instance.tag()
48
+
49
+ return instance
36
50
  }
37
51
  }
38
52
 
@@ -25,9 +25,15 @@ const runtime = {
25
25
  version: generate()
26
26
  }
27
27
 
28
+ /** @type {toa.norm.context.Registry} */
29
+ const registry = {
30
+ base: generate()
31
+ }
32
+
28
33
  exports.scope = generate()
29
34
  exports.name = name
30
35
  exports.version = hash(runtime.version + ';' + version)
31
36
  exports.Class = Class
32
37
  exports.runtime = runtime
38
+ exports.registry = registry
33
39
  exports.process = process
@@ -3,7 +3,7 @@
3
3
  const { join, posix } = require('node:path')
4
4
  const { readFile: read, writeFile: write } = require('node:fs/promises')
5
5
 
6
- const { hash } = require('@toa.io/generic')
6
+ const { hash, overwrite } = require('@toa.io/generic')
7
7
  const { directory } = require('@toa.io/filesystem')
8
8
 
9
9
  /**
@@ -22,19 +22,33 @@ class Image {
22
22
 
23
23
  /** @type {string} */
24
24
  #scope
25
+
26
+ /** @type {toa.norm.context.Registry} */
27
+ #registry
28
+
25
29
  /** @type {toa.norm.context.Runtime} */
26
30
  #runtime
27
- /** @type {string} */
28
- #type
31
+
32
+ /** @type {{ runtime?: Partial<toa.norm.context.Runtime>, build: object }} */
33
+ #values = { build: { command: 'echo hello' } }
29
34
 
30
35
  /**
31
- * @param scope {string}
32
- * @param runtime {toa.norm.context.Runtime}
36
+ * @param {string} scope
37
+ * @param {toa.norm.context.Runtime} runtime
38
+ * @param {toa.norm.context.Registry} registry
33
39
  */
34
- constructor (scope, runtime) {
40
+ constructor (scope, runtime, registry) {
35
41
  this.#scope = scope
42
+ this.#registry = registry
36
43
  this.#runtime = runtime
37
- this.#type = this.constructor.name.toLowerCase()
44
+
45
+ this.#setValues()
46
+ }
47
+
48
+ tag () {
49
+ const tag = hash(this.#runtime?.version + ';' + this.version)
50
+
51
+ this.reference = posix.join(this.#registry.base ?? '', this.#scope, `${this.name}:${tag}`)
38
52
  }
39
53
 
40
54
  /**
@@ -51,21 +65,15 @@ class Image {
51
65
  */
52
66
  get version () {}
53
67
 
54
- tag (base) {
55
- const tag = hash(this.#runtime?.version + ';' + this.version)
56
-
57
- this.reference = posix.join(base ?? '', this.#scope, `${this.#type}-${this.name}:${tag}`)
58
- }
59
-
60
68
  async prepare (root) {
61
69
  if (this.dockerfile === undefined) throw new Error('Dockerfile isn\'t specified')
62
70
 
63
- const path = join(root, `${this.#type}-${this.name}.${this.version}`)
71
+ const path = join(root, `${this.name}.${this.version}`)
64
72
 
65
73
  await directory.ensure(path)
66
74
 
67
75
  const template = await read(this.dockerfile, 'utf-8')
68
- const contents = template.replace(/{{(\.?\w+)}}/g, (_, key) => this.#value(key))
76
+ const contents = template.replace(/{{(\S{1,32})}}/g, (_, key) => this.#value(key))
69
77
  const ignore = 'Dockerfile'
70
78
 
71
79
  await write(join(path, 'Dockerfile'), contents)
@@ -76,16 +84,44 @@ class Image {
76
84
  return path
77
85
  }
78
86
 
87
+ #setValues () {
88
+ this.#values.runtime = this.#runtime
89
+ this.#values.build = overwrite(this.#values.build, this.#registry.build)
90
+
91
+ if (this.#values.build.arguments !== undefined) this.#values.build.arguments = createArguments(this.#values.build.arguments)
92
+ if (this.#values.build.run !== undefined) this.#values.build.run = createRunCommands(this.#values.build.run)
93
+ }
94
+
79
95
  /**
80
96
  * @param key {string}
81
97
  * @returns {string}
82
98
  */
83
99
  #value (key) {
84
- const [, property] = key.split('.')
100
+ const [source, property] = key.split('.')
101
+
102
+ return this.#values[source]?.[property] ?? ''
103
+ }
104
+ }
85
105
 
86
- if (property !== undefined) return this[property]
87
- else return this.#runtime[key]
106
+ function createRunCommands (input) {
107
+ const lines = input.split('\n')
108
+
109
+ return lines.reduce((commands, command) => {
110
+ commands += '\nRUN ' + command
111
+
112
+ return commands
113
+ }, '')
114
+ }
115
+
116
+ function createArguments (variables) {
117
+ const args = []
118
+
119
+ for (const variable of variables) {
120
+ args.push('ARG ' + variable)
121
+ args.push(`ENV ${variable}=$${variable}`)
88
122
  }
123
+
124
+ return args.join('\n')
89
125
  }
90
126
 
91
127
  exports.Image = Image
@@ -7,15 +7,13 @@ const { generate } = require('randomstring')
7
7
  let instance
8
8
 
9
9
  beforeEach(() => {
10
- instance = new fixtures.Class(fixtures.scope, fixtures.runtime)
10
+ instance = new fixtures.Class(fixtures.scope, fixtures.runtime, fixtures.registry)
11
11
  })
12
12
 
13
13
  it('should assign url', () => {
14
- const registry = generate()
14
+ instance.tag()
15
15
 
16
- instance.tag(registry)
17
-
18
- expect(instance.reference).toEqual(`${registry}/${fixtures.scope}/class-${fixtures.name}:${fixtures.version}`)
16
+ expect(instance.reference).toEqual(`${fixtures.registry.base}/${fixtures.scope}/${fixtures.name}:${fixtures.version}`)
19
17
  })
20
18
 
21
19
  describe('prepare', () => {
@@ -1,12 +1,14 @@
1
1
  FROM node:18.16.0-alpine3.17
2
2
 
3
3
  ENV NODE_ENV=production
4
- RUN if [ {{registry}} != undefined ]; then npm set registry {{registry}}; fi
5
- RUN if [ {{proxy}} != undefined ]; then npm set proxy {{proxy}}; fi
6
- RUN npm i -g @toa.io/runtime@{{version}}
4
+ RUN if [ "{{runtime.registry}}" != "" ]; then npm set registry {{runtime.registry}}; fi
5
+ RUN if [ "{{runtime.proxy}}" != "" ]; then npm set proxy {{runtime.proxy}}; fi
6
+ RUN npm i -g @toa.io/runtime@{{runtime.version}} --omit=dev
7
7
 
8
8
  WORKDIR /service
9
- ADD . .
10
- RUN npm i
9
+ COPY --chown=node:node . /service
11
10
 
11
+ RUN npm i --omit=dev
12
+
13
+ USER node
12
14
  CMD toa serve .
@@ -18,21 +18,25 @@ class Service extends Image {
18
18
 
19
19
  /** @type {string} */
20
20
  #group
21
+
21
22
  /** @type {string} */
22
23
  #name
24
+
23
25
  /** @type {string} */
24
26
  #path
27
+
25
28
  /** @type {string} */
26
29
  #version
27
30
 
28
31
  /**
29
32
  * @param {string} scope
30
33
  * @param {toa.norm.context.Runtime} runtime
34
+ * @param {toa.norm.context.Registry} registry
31
35
  * @param {string} reference
32
36
  * @param {toa.deployment.dependency.Service} service
33
37
  */
34
- constructor (scope, runtime, reference, service) {
35
- super(scope, runtime)
38
+ constructor (scope, runtime, registry, reference, service) {
39
+ super(scope, runtime, registry)
36
40
 
37
41
  this.service = service.name
38
42
 
@@ -43,7 +47,7 @@ class Service extends Image {
43
47
  }
44
48
 
45
49
  get name () {
46
- return this.#group + '-' + this.#name
50
+ return 'extension-' + this.#group + '-' + this.#name
47
51
  }
48
52
 
49
53
  get version () {
@@ -2,9 +2,6 @@
2
2
 
3
3
  const workspace = require('./workspace')
4
4
 
5
- /**
6
- * @implements {toa.deployment.Operator}
7
- */
8
5
  class Operator {
9
6
  /** @type {toa.deployment.Deployment} */
10
7
  #deployment
@@ -65,7 +65,6 @@ class Registry {
65
65
  #create (type, ...args) {
66
66
  const image = this.#factory[type](...args)
67
67
 
68
- image.tag(this.#registry?.base)
69
68
  this.#images.push(image)
70
69
 
71
70
  return image
@@ -85,6 +84,10 @@ class Registry {
85
84
 
86
85
  const multiarch = this.#registry.platforms !== null
87
86
 
87
+ if (this.#registry.build?.arguments !== undefined) {
88
+ for (const arg of this.#registry.build.arguments) args.push('--build-arg', `${arg}=${process.env[arg]}`)
89
+ }
90
+
88
91
  if (multiarch) {
89
92
  const platform = this.#registry.platforms.join(',')
90
93
 
@@ -1,24 +1,11 @@
1
1
  'use strict'
2
2
 
3
- // noinspection JSClosureCompilerSyntax
4
- /**
5
- * @implements {toa.deployment.Service}
6
- */
7
3
  class Service {
8
- name
9
- image
10
- port
11
- ingress
12
-
13
- /**
14
- * @param service {toa.deployment.dependency.Service}
15
- * @param image {toa.deployment.images.Image}
16
- */
17
4
  constructor (service, image) {
5
+ Object.assign(this, service)
6
+
18
7
  this.name = service.group + '-' + service.name
19
- this.port = service.port
20
8
  this.image = image.reference
21
- this.ingress = service.ingress
22
9
  }
23
10
  }
24
11
 
@@ -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(): 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
+ import * as _deployment from './deployment'
2
+ import * as _dependency from './dependency'
3
+
4
+ declare namespace toa.deployment {
5
+
6
+ interface Operator {
7
+ export (path?: string): Promise<string>
8
+
9
+ install (options?: _deployment.installation.Options): Promise<void>
10
+
11
+ template (options?: _deployment.template.Options): Promise<string>
12
+
13
+ variables (): _dependency.Variables
14
+
15
+ listVariables (): _dependency.Variable[]
16
+ }
17
+
18
+ }
19
+
20
+ export type Operator = toa.deployment.Operator
@@ -3,7 +3,7 @@ import type * as _dependency from './dependency'
3
3
  import type * as _image from "./images/image"
4
4
 
5
5
  declare namespace toa.deployment {
6
-
6
+
7
7
  interface Registry {
8
8
  composition(composition: _norm.Composition): _image.Image
9
9
 
@@ -0,0 +1,29 @@
1
+ export type Service = {
2
+ group: string
3
+ name: string
4
+ version: string
5
+ port: number
6
+ ingress?: {
7
+ host: string
8
+ class?: string
9
+ annotations?: object
10
+ }
11
+ variables: Variable[]
12
+ components?: string[]
13
+ }
14
+
15
+ export type Variable = {
16
+ name: string
17
+ value?: string
18
+ secret?: {
19
+ name: string,
20
+ key: string
21
+ }
22
+ }
23
+
24
+ export type Variables = Record<string, Variable[]>
25
+
26
+ export type Dependency = {
27
+ services?: Service[]
28
+ variables?: Variables
29
+ }
@@ -0,0 +1 @@
1
+ export * from './dependency'
@@ -1,10 +0,0 @@
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 }}
@@ -1,42 +0,0 @@
1
- 'use strict'
2
-
3
- const { generate } = require('randomstring')
4
-
5
- const context = /** @type {toa.norm.Context} */ { name: generate() }
6
- const compositions = []
7
- /** @type {toa.deployment.Dependency[]} */
8
-
9
- /** @type {toa.deployment.dependency.Variable} */
10
- const bindingVariable = {
11
- name: 'TOA_BINDINGS_AMQP_POINTER',
12
- value: 'eyJkZWZhdWx0IjoiYW1xcDovL3doYXRldmVyIiwic3lzdGVtIjoiYW1xcDovL2hvc3QwIn0='
13
- }
14
-
15
- /** @type {toa.deployment.dependency.Variable} */
16
- const secretVariable = {
17
- name: 'TOA_BINDINGS_AMQP_DEFAULT_USERNAME',
18
- secret: {
19
- name: 'toa-bindings-amqp-default',
20
- key: 'username'
21
- }
22
- }
23
-
24
- /** @type {toa.deployment.Dependency[]} */
25
- const dependencies = [{
26
- variables: { global: [bindingVariable, secretVariable] }
27
- }]
28
-
29
- const process = /** @type {toa.operations.Process} */ { execute: jest.fn() }
30
-
31
- const options = /** @type {toa.deployment.installation.Options} */ {
32
- namespace: generate(),
33
- target: generate()
34
- }
35
-
36
- exports.context = context
37
- exports.compositions = compositions
38
- exports.dependencies = dependencies
39
- exports.process = process
40
- exports.options = options
41
- exports.bindingVariable = bindingVariable
42
- exports.secretVariable = secretVariable
@@ -1,78 +0,0 @@
1
- 'use strict'
2
-
3
- const clone = require('clone-deep')
4
-
5
- const fixtures = require('./deployment.fixtures')
6
- const { Deployment } = require('../../src/deployment/deployment')
7
-
8
- /** @type {toa.deployment.Deployment} */
9
- let deployment
10
- /** @type {toa.deployment.installation.Options} */
11
- let options
12
-
13
- beforeEach(() => {
14
- deployment = new Deployment(fixtures.context, fixtures.compositions, fixtures.dependencies, fixtures.process)
15
- options = clone(fixtures.options)
16
- })
17
-
18
- it('should pass -n argument if options.namespace is set', async () => {
19
- await deployment.install(options)
20
-
21
- const call = fixtures.process.execute.mock.calls.find((call) => call[0] === 'helm' && call[1][0] === 'upgrade')
22
-
23
- expect(call).toBeDefined()
24
-
25
- const args = call[1]
26
- const index = args.findIndex((value) => value === '-n')
27
- const namespace = args[index + 1]
28
-
29
- expect(index).not.toStrictEqual(-1)
30
- expect(namespace).toStrictEqual(fixtures.options.namespace)
31
- })
32
-
33
- describe('variables', () => {
34
- let deployment /** @type {toa.deployment.Deployment} */
35
-
36
- it('should be define', () => {
37
- const context = clone(fixtures.context)
38
-
39
- deployment = new Deployment(context, fixtures.compositions, fixtures.dependencies, fixtures.process)
40
-
41
- expect(typeof deployment.variables).toBe('function')
42
- })
43
-
44
- it('should return variables', () => {
45
- const context = clone(fixtures.context)
46
- const [{ variables }] = fixtures.dependencies
47
-
48
- deployment = new Deployment(context, fixtures.compositions, fixtures.dependencies, fixtures.process)
49
- expect(deployment.variables()).toEqual(variables)
50
- })
51
-
52
- it('should return variables', () => {
53
- const context = clone(fixtures.context)
54
- const [{ variables }] = fixtures.dependencies
55
-
56
- deployment = new Deployment(context, fixtures.compositions, fixtures.dependencies, fixtures.process)
57
-
58
- expect(deployment.variables()).toEqual(variables)
59
- })
60
-
61
- it('should merge all variables', () => {
62
- const context = clone(fixtures.context)
63
-
64
- /** @type {toa.deployment.Dependency} */
65
- const dep1 = { variables: { global: [fixtures.secretVariable] } }
66
-
67
- /** @type {toa.deployment.Dependency} */
68
- const dep2 = { variables: { global: [fixtures.bindingVariable] } }
69
-
70
- deployment = new Deployment(context, fixtures.compositions, [dep1, dep2], fixtures.process)
71
-
72
- const result = deployment.variables()
73
- const expectedVariables = [fixtures.bindingVariable, fixtures.secretVariable]
74
-
75
- expect(result.global.length).toBe(expectedVariables.length)
76
- expect(result.global).toStrictEqual(expect.arrayContaining(expectedVariables))
77
- })
78
- })
@@ -1,14 +0,0 @@
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
@@ -1,18 +0,0 @@
1
- import * as _deployment from './deployment'
2
- import * as _dependency from './dependency'
3
-
4
- declare namespace toa.deployment {
5
-
6
- interface Operator {
7
- export(path?: string): Promise<string>
8
-
9
- install(options?: _deployment.installation.Options): Promise<void>
10
-
11
- template(options?: _deployment.template.Options): Promise<string>
12
-
13
- variables(): _dependency.Variables
14
- }
15
-
16
- }
17
-
18
- export type Operator = toa.deployment.Operator
@@ -1,13 +0,0 @@
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
- }
File without changes
File without changes