@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 +3 -2
- package/src/deployment.js +15 -4
- package/src/images/Dockerfile +10 -0
- package/src/images/image.js +38 -0
- package/src/images.js +26 -0
- package/src/index.js +2 -0
- package/test/deployment.fixtures.js +34 -0
- package/test/deployment.test.js +88 -0
- package/test/image.fixtures.js +24 -0
- package/test/image.test.js +45 -0
- package/test/images.fixtures.js +29 -0
- package/test/images.test.js +35 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/operations",
|
|
3
|
-
"version": "0.
|
|
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": "
|
|
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,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
|
@@ -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
|
+
})
|