Repository navigation
Expand file tree
/
Copy pathimport.ts
More file actions
84 lines (69 loc) · 3.26 KB
/
Copy pathimport.ts
File metadata and controls
84 lines (69 loc) · 3.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import fs from 'node:fs'
import path from 'node:path'
import {Readable} from 'node:stream'
import {Command, Flags} from '@oclif/core'
import {request} from '../../auth-request.js'
import {getDisco} from '../../config.js'
export default class VolumesImport extends Command {
static override description = 'import a volume'
static override examples = [
'<%= config.bin %> <%= command.id %> --project=myproject --volume=myvolume --input volume.tar.gz',
'<%= config.bin %> <%= command.id %> --project=myproject --volume=myvolume < volume.tar.gz',
]
static override flags = {
disco: Flags.string({required: false, description: 'disco configuration to use'}),
input: Flags.string({required: false, description: 'input file path (instead of stdin)'}),
project: Flags.string({required: true, description: 'project ID'}),
volume: Flags.string({required: true, description: 'volume ID to export'}),
}
private getFileStream(filepath: string): Readable {
try {
const filePath = path.resolve(filepath)
const fileStats = fs.statSync(filePath)
if (!fileStats.isFile()) {
this.error(`"${filepath}" is not a file`)
}
return fs.createReadStream(filePath)
} catch (error: unknown) {
if (error instanceof Error) {
if ('code' in error && error.code === 'ENOENT') {
this.error(`File not found: "${filepath}"`)
} else {
this.error(`Error accessing "${filepath}": ${error.message}`)
}
} else {
this.error(`Unkown error occurred while accessing file`)
}
}
}
public async run(): Promise<void> {
const {flags} = await this.parse(VolumesImport)
const discoConfig = getDisco(flags.disco || null)
const url = `https://${discoConfig.host}/api/projects/${flags.project}/volumes/${flags.volume}`
let stream: Readable = process.stdin
stream.on('error', (err) => {
this.error(`Error reading from stdin: ${err.message}`)
})
const extraHeaders: Record<string, string> = {}
if (flags.input) {
// if we are given the file, get its actual file size, and then
// we'll pass it as a content-length header.
// incredibly... this is the volumes to run volumes:import for raspberry pis
// that use the cloudflare tunnel... we've seen consistently that the tunnel would...
// cut off? or somehow not let the stream'ed file through... and when we forced a content-length
// header, that problem went away.... so ... if you use a raspberry pi and a cloudflare tunnel,
// (and are somehow reading this comment), the solution is to NOT pipe .tar.gz volumes into
// disco volumes:import, but rather run volumes:import with the --input option...
// obviously, this is not ideal. but it is! what it is!
extraHeaders['Content-Length'] = fs.statSync(flags.input).size.toString()
stream = this.getFileStream(flags.input)
stream.on('error', (err) => {
this.error(`Error reading from "${flags.input}": ${err.message}`)
})
} else if (process.stdin.isTTY) {
this.error('No input provided. Please provide an input file with --input or pipe data to stdin')
}
const res = await request({method: 'PUT', url, discoConfig, bodyStream: stream, extraHeaders})
await res.json()
}
}