presidium 4.8.1 → 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 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
- path = path.endsWith('/') ? path : `${path}/`
412
-
413
- const archiveDir = `presidium-Docker-buildImage-tmp-${uniqid()}`
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
- if (options.archive) {
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: archive,
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.8.1",
3
+ "version": "4.9.0",
4
4
  "description": "The Presidium library.",
5
5
  "author": "Richard Tong",
6
6
  "license": "CFOSS",
@@ -68,9 +68,10 @@
68
68
  "adm-zip": "^0.6.1",
69
69
  "html-entities": "^2.6.0",
70
70
  "presidium-db": ">=4.6.2",
71
- "presidium-db-client-server": ">=0.6.3",
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": {