@toa.io/operations 0.1.1-dev.2 → 0.2.0-dev.2

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/operations",
3
- "version": "0.1.1-dev.2",
3
+ "version": "0.2.0-dev.2",
4
4
  "description": "Toa Deployment",
5
5
  "homepage": "https://toa.io",
6
6
  "author": {
@@ -26,7 +26,8 @@
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
30
  "execa": "5.1.1"
30
31
  },
31
- "gitHead": "76c1dfccbe68b25e272be0a20a79a84a61e7b4c3"
32
+ "gitHead": "83480176c88640dd2bed2a775ce2d13b95341f19"
32
33
  }
package/src/deployment.js CHANGED
@@ -5,10 +5,10 @@ const execa = require('execa')
5
5
  const { join } = require('node:path')
6
6
  const { yaml } = require('@toa.io/gears')
7
7
 
8
+ const { dependencies } = require('./deployment/dependencies')
8
9
  const { directory } = require('./deployment/directory')
9
10
  const { chart } = require('./deployment/chart')
10
11
  const { values } = require('./deployment/values')
11
- const { dependencies } = require('./deployment/dependencies')
12
12
 
13
13
  class Deployment {
14
14
  #context
@@ -31,11 +31,22 @@ class Deployment {
31
31
  return path
32
32
  }
33
33
 
34
- async install () {
34
+ async install (wait) {
35
35
  const path = await this.export()
36
+ const args = []
37
+
38
+ if (wait === true) args.push('--wait')
39
+
40
+ const update = execa('helm', ['dependency', 'update', path])
41
+
42
+ update.stdout.pipe(process.stdout)
43
+ await update
44
+
45
+ const upgrade = execa('helm', ['upgrade', this.#context.name, '-i', ...args, path])
46
+
47
+ upgrade.stdout.pipe(process.stdout)
48
+ await upgrade
36
49
 
37
- await execa('helm', ['dependency', 'update', path])
38
- await execa('helm', ['upgrade', this.#context.name, '-i', path])
39
50
  await fs.rm(path, { recursive: true })
40
51
  }
41
52
 
@@ -0,0 +1,10 @@
1
+ #TODO: bridge specifics
2
+
3
+ FROM node:alpine
4
+
5
+ RUN npm i -g @toa.io/runtime@0.1.0-dev.13
6
+
7
+ WORKDIR /app
8
+ ADD . .
9
+
10
+ CMD toa compose .
@@ -0,0 +1,38 @@
1
+ 'use strict'
2
+
3
+ const { join } = require('node:path')
4
+ const execa = require('execa')
5
+
6
+ class Image {
7
+ #manifest
8
+ #registry
9
+ #tag
10
+
11
+ constructor (manifest, registry) {
12
+ this.#manifest = manifest
13
+ this.#registry = registry
14
+
15
+ const { domain, name, version } = this.#manifest
16
+ this.#tag = this.#registry + '/' + domain + '-' + name + ':' + version
17
+ }
18
+
19
+ async build () {
20
+ const build = execa('docker', ['build', this.#manifest.path, '-f', DOCKERFILE, '-t', this.#tag])
21
+
22
+ build.stdout.pipe(process.stdout)
23
+
24
+ await build
25
+ }
26
+
27
+ async push () {
28
+ const push = execa('docker', ['push', this.#tag])
29
+
30
+ push.stdout.pipe(process.stdout)
31
+
32
+ await push
33
+ }
34
+ }
35
+
36
+ const DOCKERFILE = join(__dirname, 'Dockerfile')
37
+
38
+ exports.Image = Image
package/src/images.js ADDED
@@ -0,0 +1,26 @@
1
+ 'use strict'
2
+
3
+ const { Image } = require('./images/image')
4
+
5
+ class Images {
6
+ #images
7
+
8
+ constructor (context) {
9
+ this.#images = context.manifests.map((manifest) => new Image(manifest, context.registry))
10
+ }
11
+
12
+ async push () {
13
+ await this.#build()
14
+ await this.#push()
15
+ }
16
+
17
+ async #build () {
18
+ await Promise.all(this.#images.map((image) => image.build()))
19
+ }
20
+
21
+ async #push () {
22
+ await Promise.all(this.#images.map((image) => image.push()))
23
+ }
24
+ }
25
+
26
+ exports.Images = Images
package/src/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  'use strict'
2
2
 
3
+ const { Images } = require('./images')
3
4
  const { Deployment } = require('./deployment')
4
5
 
6
+ exports.Images = Images
5
7
  exports.Deployment = Deployment
@@ -0,0 +1,34 @@
1
+ 'use strict'
2
+
3
+ const { generate } = require('randomstring')
4
+
5
+ const context = {
6
+ name: generate(),
7
+ description: generate(),
8
+ version: '0.0.1',
9
+ runtime: '0.1.0',
10
+ packages: './path/to/' + generate()
11
+ }
12
+
13
+ const mock = {
14
+ dependencies: {
15
+ dependencies: jest.fn(() => [generate(), generate()])
16
+ },
17
+ directory: {
18
+ directory: jest.fn(() => generate())
19
+ },
20
+ chart: {
21
+ chart: jest.fn(() => ({ [generate()]: generate() }))
22
+ },
23
+ values: {
24
+ values: jest.fn(() => ({ [generate()]: generate() }))
25
+ },
26
+ fs: {
27
+ writeFile: jest.fn(),
28
+ rm: jest.fn()
29
+ },
30
+ execa: jest.fn(() => ({ stdout: { pipe: jest.fn() } }))
31
+ }
32
+
33
+ exports.context = context
34
+ exports.mock = mock
@@ -0,0 +1,88 @@
1
+ 'use strict'
2
+
3
+ const fixtures = require('./deployment.fixtures')
4
+ const mock = fixtures.mock
5
+
6
+ jest.mock('node:fs/promises', () => mock.fs)
7
+ jest.mock('execa', () => mock.execa)
8
+ jest.mock('../src/deployment/dependencies', () => mock.dependencies)
9
+ jest.mock('../src/deployment/directory', () => mock.directory)
10
+ jest.mock('../src/deployment/chart', () => mock.chart)
11
+ jest.mock('../src/deployment/values', () => mock.values)
12
+
13
+ const { Deployment } = require('../src')
14
+ const { join } = require('node:path')
15
+ const { generate } = require('randomstring')
16
+ const { yaml } = require('@toa.io/gears')
17
+
18
+ let deployment
19
+
20
+ beforeEach(() => {
21
+ deployment = new Deployment(fixtures.context)
22
+ })
23
+
24
+ describe('export', () => {
25
+ const test = async (arg) => {
26
+ const result = await deployment.export(arg)
27
+
28
+ if (arg !== undefined) expect(arg).toBe(mock.directory.directory.mock.calls[0][0])
29
+
30
+ const path = mock.directory.directory.mock.results[0].value
31
+ const chart = yaml.dump(mock.chart.chart.mock.results[0].value)
32
+ const values = yaml.dump(mock.values.values.mock.results[0].value)
33
+
34
+ expect(mock.fs.writeFile).toHaveBeenNthCalledWith(1, join(path, 'Chart.yaml'), chart)
35
+ expect(mock.fs.writeFile).toHaveBeenNthCalledWith(2, join(path, 'values.yaml'), values)
36
+ expect(result).toBe(path)
37
+ }
38
+
39
+ it('should export chart', async () => {
40
+ await test()
41
+ })
42
+
43
+ it('should export chart to given path', async () => {
44
+ await test(generate())
45
+ })
46
+ })
47
+
48
+ describe('install', () => {
49
+ let path
50
+
51
+ beforeEach(async () => {
52
+ await deployment.install()
53
+
54
+ path = mock.directory.directory.mock.results[0].value
55
+ })
56
+
57
+ it('should update', () => {
58
+ expect(mock.execa).toHaveBeenNthCalledWith(1, 'helm', ['dependency', 'update', path])
59
+ })
60
+
61
+ it('should upgrade', () => {
62
+ expect(mock.execa).toHaveBeenNthCalledWith(2,
63
+ 'helm', ['upgrade', fixtures.context.name, '-i', path])
64
+ })
65
+
66
+ it('should wait on upgrade', async () => {
67
+ jest.clearAllMocks()
68
+
69
+ await deployment.install(true)
70
+
71
+ path = mock.directory.directory.mock.results[0].value
72
+
73
+ expect(mock.execa).toHaveBeenNthCalledWith(2,
74
+ 'helm', ['upgrade', fixtures.context.name, '-i', '--wait', path])
75
+ })
76
+
77
+ it('should pipe stdout', () => {
78
+ const update = mock.execa.mock.results[0].value
79
+ const upgrade = mock.execa.mock.results[1].value
80
+
81
+ expect(update.stdout.pipe).toHaveBeenCalledWith(process.stdout)
82
+ expect(upgrade.stdout.pipe).toHaveBeenCalledWith(process.stdout)
83
+ })
84
+
85
+ it('should clear', () => {
86
+ expect(mock.fs.rm).toHaveBeenCalledWith(path, { recursive: true })
87
+ })
88
+ })
@@ -0,0 +1,24 @@
1
+ 'use strict'
2
+
3
+ const { newid, random } = require('@toa.io/gears')
4
+ const { join } = require('node:path')
5
+
6
+ const mock = {
7
+ execa: jest.fn(() => ({ stdout: { pipe: jest.fn() } }))
8
+ }
9
+
10
+ const manifest = {
11
+ domain: 'domain' + newid(),
12
+ name: 'component' + newid(),
13
+ version: '0.0.' + random(9),
14
+ path: newid()
15
+ }
16
+
17
+ const registry = `registry-${newid()}:${random(999) + 5000}`
18
+
19
+ const DOCKERFILE = join(__dirname, '../src/images/Dockerfile')
20
+
21
+ exports.mock = mock
22
+ exports.manifest = manifest
23
+ exports.registry = registry
24
+ exports.DOCKERFILE = DOCKERFILE
@@ -0,0 +1,45 @@
1
+ 'use strict'
2
+
3
+ const fixtures = require('./image.fixtures')
4
+ const mock = fixtures.mock
5
+
6
+ jest.mock('execa', () => mock.execa)
7
+
8
+ const { Image } = require('../src/images/image')
9
+
10
+ let image
11
+ let tag
12
+
13
+ beforeAll(() => {
14
+ const { domain, name, version } = fixtures.manifest
15
+
16
+ tag = `${fixtures.registry}/${domain}-${name}:${version}`
17
+ })
18
+
19
+ beforeEach(() => {
20
+ image = new Image(fixtures.manifest, fixtures.registry)
21
+ })
22
+
23
+ it('should build', async () => {
24
+ await image.build()
25
+
26
+ expect(mock.execa).toHaveBeenCalledWith('docker',
27
+ ['build', fixtures.manifest.path, '-f', fixtures.DOCKERFILE, '-t', tag])
28
+ })
29
+
30
+ it('should push', async () => {
31
+ await image.push()
32
+
33
+ expect(mock.execa).toHaveBeenCalledWith('docker', ['push', tag])
34
+ })
35
+
36
+ it('should pipe stdout', async () => {
37
+ await image.build()
38
+ await image.push()
39
+
40
+ const build = mock.execa.mock.results[0].value
41
+ const push = mock.execa.mock.results[1].value
42
+
43
+ expect(build.stdout.pipe).toHaveBeenCalledWith(process.stdout)
44
+ expect(push.stdout.pipe).toHaveBeenCalledWith(process.stdout)
45
+ })
@@ -0,0 +1,29 @@
1
+ 'use strict'
2
+
3
+ const { random, repeat, newid } = require('@toa.io/gears')
4
+
5
+ const mock = {
6
+ image: {
7
+ Image: jest.fn(() => {
8
+ return {
9
+ build: jest.fn(),
10
+ push: jest.fn()
11
+ }
12
+ })
13
+ }
14
+ }
15
+
16
+ const context = {
17
+ name: 'context-' + newid(),
18
+ registry: `registry-${newid()}:${random(999) + 5000}`,
19
+ manifests: repeat(
20
+ () => ({
21
+ domain: 'domain-' + newid(),
22
+ name: 'component-' + newid(),
23
+ version: '0.0.0'
24
+ }),
25
+ random(9) + 1)
26
+ }
27
+
28
+ exports.mock = mock
29
+ exports.context = context
@@ -0,0 +1,35 @@
1
+ 'use strict'
2
+
3
+ const fixtures = require('./images.fixtures')
4
+ const mock = fixtures.mock
5
+
6
+ jest.mock('../src/images/image', () => mock.image)
7
+
8
+ const { Images } = require('../src')
9
+
10
+ let images
11
+
12
+ beforeEach(() => {
13
+ images = new Images(fixtures.context)
14
+ })
15
+
16
+ it('should create Image instances', () => {
17
+ expect(mock.image.Image).toHaveBeenCalledTimes(fixtures.context.manifests.length)
18
+
19
+ for (let i = 0; i < fixtures.context.manifests.length; i++) {
20
+ expect(mock.image.Image).toHaveBeenCalledWith(fixtures.context.manifests[i], fixtures.context.registry)
21
+ }
22
+ })
23
+
24
+ describe('push', () => {
25
+ beforeEach(async () => {
26
+ await images.push()
27
+ })
28
+
29
+ it('should push images', () => {
30
+ mock.image.Image.mock.results.forEach(({ value: instance }) => {
31
+ expect(instance.build).toHaveBeenCalled()
32
+ expect(instance.push).toHaveBeenCalled()
33
+ })
34
+ })
35
+ })