presidium 4.8.2 → 4.9.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.
- package/Docker.js +6 -53
- package/internal/Archive.js +118 -0
- package/internal/Archive.test.js +78 -0
- package/package.json +2 -1
package/Docker.js
CHANGED
|
@@ -16,6 +16,7 @@ const { spawn } = require('child_process')
|
|
|
16
16
|
const Readable = require('./Readable')
|
|
17
17
|
const HTTP = require('./HTTP')
|
|
18
18
|
const querystring = require('querystring')
|
|
19
|
+
const Archive = require('./internal/Archive')
|
|
19
20
|
const split = require('./internal/split')
|
|
20
21
|
const join = require('./internal/join')
|
|
21
22
|
const isArray = require('./internal/isArray')
|
|
@@ -47,7 +48,6 @@ const handleDockerHTTPResponse = require('./internal/handleDockerHTTPResponse')
|
|
|
47
48
|
*
|
|
48
49
|
* System dependencies:
|
|
49
50
|
* * `docker`
|
|
50
|
-
* * `tar`
|
|
51
51
|
*
|
|
52
52
|
* Note: the Presidium Docker client connects to the Docker socket `/var/run/docker.sock`. For security purposes, please avoid exposing any code using the Presidium Docker client to the internet, see [How would an attacker gain access to the host machine from within a Docker container?](https://www.google.com/search?hl=en&q=how+would+an+attacker+gain+access+to+the+host+machine+from+within+a+docker+container&ia=web).
|
|
53
53
|
*/
|
|
@@ -408,54 +408,12 @@ class Docker {
|
|
|
408
408
|
* * [Dockerfile](https://docs.docker.com/engine/reference/builder/)
|
|
409
409
|
*/
|
|
410
410
|
async buildImage(path, options = {}) {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
await fs.promises.mkdir(archiveDir, { recursive: true })
|
|
416
|
-
|
|
417
|
-
const archiveFilename = `${archiveDir}/archive.tar`
|
|
418
|
-
|
|
419
|
-
const cmdArgs = []
|
|
420
|
-
|
|
421
|
-
cmdArgs.push(`--exclude=${archiveDir}`)
|
|
422
|
-
if (options.ignore) {
|
|
423
|
-
for (const pattern of options.ignore) {
|
|
424
|
-
cmdArgs.push(`--exclude=${pattern}`)
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
cmdArgs.push('-cf', archiveFilename, '-C', path, '.')
|
|
429
|
-
|
|
430
|
-
const cmd = spawn('tar', cmdArgs, { stdio: 'inherit' })
|
|
431
|
-
|
|
432
|
-
await new Promise(resolve => {
|
|
433
|
-
cmd.on('close', resolve)
|
|
411
|
+
const pack = Archive.tar(path, {
|
|
412
|
+
base: options.archive,
|
|
413
|
+
ignore: options.ignore ?? ['node_modules', '.git', '.nyc_output'],
|
|
434
414
|
})
|
|
435
415
|
|
|
436
|
-
|
|
437
|
-
for (const filename in options.archive) {
|
|
438
|
-
await fs.promises.writeFile(
|
|
439
|
-
`${archiveDir}/${filename}`, options.archive[filename]
|
|
440
|
-
)
|
|
441
|
-
|
|
442
|
-
const cmdArgs = [
|
|
443
|
-
'-rf',
|
|
444
|
-
archiveFilename,
|
|
445
|
-
`--directory=${archiveDir}`,
|
|
446
|
-
]
|
|
447
|
-
|
|
448
|
-
cmdArgs.push(filename)
|
|
449
|
-
|
|
450
|
-
const cmd = spawn('tar', cmdArgs, { stdio: 'inherit' })
|
|
451
|
-
|
|
452
|
-
await new Promise(resolve => {
|
|
453
|
-
cmd.on('close', resolve)
|
|
454
|
-
})
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
const archive = fs.createReadStream(archiveFilename).pipe(zlib.createGzip())
|
|
416
|
+
const compressed = pack.pipe(zlib.createGzip())
|
|
459
417
|
|
|
460
418
|
const response = await this.http.post(`/build?${querystring.stringify({
|
|
461
419
|
dockerfile: options.archiveDockerfile ?? 'Dockerfile',
|
|
@@ -463,18 +421,13 @@ class Docker {
|
|
|
463
421
|
forcerm: true,
|
|
464
422
|
platform: options.platform ?? '',
|
|
465
423
|
})}`, {
|
|
466
|
-
body:
|
|
424
|
+
body: compressed,
|
|
467
425
|
headers: {
|
|
468
426
|
'Content-Type': 'application/x-tar',
|
|
469
427
|
},
|
|
470
428
|
})
|
|
471
429
|
|
|
472
430
|
const dataStream = await handleDockerHTTPResponse(response, { stream: true })
|
|
473
|
-
|
|
474
|
-
dataStream.on('end', async () => {
|
|
475
|
-
await fs.promises.rm(archiveDir, { recursive: true })
|
|
476
|
-
})
|
|
477
|
-
|
|
478
431
|
return dataStream
|
|
479
432
|
}
|
|
480
433
|
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
require('rubico/global')
|
|
2
|
+
const tarStream = require('tar-stream')
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const walk = require('./walk')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @name Archive
|
|
8
|
+
*/
|
|
9
|
+
const Archive = {
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @name tar
|
|
13
|
+
*
|
|
14
|
+
* @docs
|
|
15
|
+
* ```coffeescript [specscript]
|
|
16
|
+
* module tar 'https://github.com/mafintosh/tar-stream'
|
|
17
|
+
*
|
|
18
|
+
* Archive.tar(path string, options {
|
|
19
|
+
* ignore: Array<string>,
|
|
20
|
+
* base: Object<string>,
|
|
21
|
+
* }) -> pack tar.Pack
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* Bundle multiple files and directories from a parent directory into a tarball.
|
|
25
|
+
*
|
|
26
|
+
* Arguments:
|
|
27
|
+
* * `path` - the path to the parent directory which contains the files and directories to bundle.
|
|
28
|
+
* * `options`
|
|
29
|
+
* * `ignore` - filepaths, filenames, or patterns to ignore.
|
|
30
|
+
*/
|
|
31
|
+
tar(path, options) {
|
|
32
|
+
path = path.endsWith('/') ? path : `${path}/`
|
|
33
|
+
const ignore = get(options, 'ignore')
|
|
34
|
+
const pack = tarStream.pack()
|
|
35
|
+
|
|
36
|
+
pipe(
|
|
37
|
+
walk(path),
|
|
38
|
+
|
|
39
|
+
filter(path => {
|
|
40
|
+
for (const part of ignore) {
|
|
41
|
+
if (path.includes(part)) {
|
|
42
|
+
return false
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return true
|
|
46
|
+
}),
|
|
47
|
+
|
|
48
|
+
reduce(async (pack, filepath) => {
|
|
49
|
+
const stats = await pipe(filepath, [
|
|
50
|
+
fs.promises.stat,
|
|
51
|
+
pick(['size', 'mode', 'mtime', 'uid', 'gid']),
|
|
52
|
+
])
|
|
53
|
+
|
|
54
|
+
const entry = pack.entry({ ...stats, name: filepath.replace(path, '') })
|
|
55
|
+
|
|
56
|
+
const fileStream = fs.createReadStream(filepath)
|
|
57
|
+
|
|
58
|
+
fileStream.pipe(entry)
|
|
59
|
+
|
|
60
|
+
await new Promise(resolve => {
|
|
61
|
+
fileStream.on('end', resolve)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
return pack
|
|
65
|
+
}, pack),
|
|
66
|
+
|
|
67
|
+
tap(pack => {
|
|
68
|
+
const base = options.base ?? {}
|
|
69
|
+
for (const filename in base) {
|
|
70
|
+
pack.entry({ name: filename }, base[filename])
|
|
71
|
+
}
|
|
72
|
+
pack.finalize()
|
|
73
|
+
}),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return pack
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @name untar
|
|
81
|
+
*
|
|
82
|
+
* @docs
|
|
83
|
+
* ```coffeescript [specscript]
|
|
84
|
+
* module tar 'https://github.com/mafintosh/tar-stream'
|
|
85
|
+
* module Readable 'https://presidium.services/Readable'
|
|
86
|
+
*
|
|
87
|
+
* Archive.untar(pack tar.Pack) ->
|
|
88
|
+
* contentMap Promise<Map<(filepath string)=>(content Readable)> >
|
|
89
|
+
* ```
|
|
90
|
+
*
|
|
91
|
+
* Extract a tarball into a map of file paths to content streams.
|
|
92
|
+
*
|
|
93
|
+
* Arguments:
|
|
94
|
+
* * `pack` - a tarball represented by the `tar.Pack` type. Returned by the [tar](#tar) method.
|
|
95
|
+
*/
|
|
96
|
+
untar(pack) {
|
|
97
|
+
const extractStream = tarStream.extract()
|
|
98
|
+
const extracted = new Map()
|
|
99
|
+
return new Promise((resolve, reject) => {
|
|
100
|
+
extractStream.on('entry', (header, stream, next) => {
|
|
101
|
+
stream.header = header
|
|
102
|
+
extracted.set(header.name, stream)
|
|
103
|
+
next()
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
extractStream.on('finish', () => {
|
|
107
|
+
resolve(extracted)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
extractStream.on('error', reject)
|
|
111
|
+
|
|
112
|
+
pack.pipe(extractStream)
|
|
113
|
+
})
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = Archive
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const assert = require('assert')
|
|
2
|
+
const stream = require('stream')
|
|
3
|
+
const fs = require('fs/promises')
|
|
4
|
+
const Test = require('thunk-test')
|
|
5
|
+
const Archive = require('./Archive')
|
|
6
|
+
const resolvePath = require('./resolvePath')
|
|
7
|
+
const map = require('rubico/map')
|
|
8
|
+
const reduce = require('rubico/reduce')
|
|
9
|
+
const Readable = require('../Readable')
|
|
10
|
+
|
|
11
|
+
const test = new Test('Archive', async function integration() {
|
|
12
|
+
|
|
13
|
+
{
|
|
14
|
+
const pack = await Archive.tar(__dirname, {
|
|
15
|
+
ignore: ['Dockerfile', 'node_modules', '.git', '.nyc_output'],
|
|
16
|
+
})
|
|
17
|
+
const extracted = await Archive.untar(pack)
|
|
18
|
+
assert(extracted.size > 0)
|
|
19
|
+
for (const [path, entry] of extracted) {
|
|
20
|
+
assert('header' in entry)
|
|
21
|
+
assert(!path.startsWith('/'))
|
|
22
|
+
assert.equal(typeof path, 'string')
|
|
23
|
+
assert.equal(typeof entry[Symbol.asyncIterator], 'function')
|
|
24
|
+
assert(entry.readable)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
{
|
|
29
|
+
const base = {
|
|
30
|
+
Dockerfile: 'FROM node:15-alpine'
|
|
31
|
+
}
|
|
32
|
+
const ignore = ['fixtures', 'tmp', 'google-chrome-for-testing']
|
|
33
|
+
const pack = await Archive.tar(__dirname, { ignore, base })
|
|
34
|
+
const extracted = await Archive.untar(pack)
|
|
35
|
+
const dir = await fs.readdir(__dirname)
|
|
36
|
+
.then(filter(filepath => {
|
|
37
|
+
for (const part of ignore) {
|
|
38
|
+
if (filepath.includes(part)) {
|
|
39
|
+
return false
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return true
|
|
43
|
+
}))
|
|
44
|
+
const extractedKeys = [...extracted.keys()]
|
|
45
|
+
assert.equal(extracted.size, dir.length + 1) // extra Dockerfile
|
|
46
|
+
assert(extracted.has('Dockerfile'))
|
|
47
|
+
assert.equal(
|
|
48
|
+
await reduce(extracted.get('Dockerfile'), (a, b) => a + b, ''),
|
|
49
|
+
'FROM node:15-alpine'
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
{
|
|
54
|
+
const base = {
|
|
55
|
+
Dockerfile: 'FROM busybox:1.32',
|
|
56
|
+
'.aws/credentials': '[presidium]\naccessKeyId\nsecretAccessKey',
|
|
57
|
+
}
|
|
58
|
+
const pack = await Archive.tar(`${__dirname}/`, {
|
|
59
|
+
ignore: ['hashJSON.js'],
|
|
60
|
+
base,
|
|
61
|
+
})
|
|
62
|
+
const extracted = await Archive.untar(pack)
|
|
63
|
+
assert(extracted.size > 0)
|
|
64
|
+
assert(extracted.has('Dockerfile'))
|
|
65
|
+
assert(extracted.has('.aws/credentials'))
|
|
66
|
+
assert.equal(
|
|
67
|
+
await reduce(extracted.get('Dockerfile'), (a, b) => a + b, ''),
|
|
68
|
+
'FROM busybox:1.32'
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
}).case()
|
|
73
|
+
|
|
74
|
+
if (process.argv[1] == __filename) {
|
|
75
|
+
test()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = test
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "presidium",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.9.0",
|
|
4
4
|
"description": "The Presidium library.",
|
|
5
5
|
"author": "Richard Tong",
|
|
6
6
|
"license": "CFOSS",
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
"presidium-db-client-server": ">=0.6.6",
|
|
72
72
|
"presidium-websocket": ">=4.2.2",
|
|
73
73
|
"rubico": "^2.21.0",
|
|
74
|
+
"tar-stream": "^3.2.2",
|
|
74
75
|
"uniqid": "^5.4.0"
|
|
75
76
|
},
|
|
76
77
|
"scripts": {
|