@leverege/build-tools 2.65.0 → 2.65.1
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
package/src/Utils.mjs
CHANGED
|
@@ -90,11 +90,12 @@ export const shellCmd = async ( cmdstr, opts = {} ) => {
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
export const mkdirSafe =
|
|
93
|
+
export const mkdirSafe = ( dir ) => {
|
|
94
94
|
try {
|
|
95
|
-
|
|
96
|
-
} catch (
|
|
97
|
-
|
|
95
|
+
fs.mkdirSync( dir, { recursive : true } ) // eslint-disable-line security/detect-non-literal-fs-filename
|
|
96
|
+
} catch ( error ) {
|
|
97
|
+
condir( { error }, '<==fs.mkdir failed?' )
|
|
98
|
+
warning( { error }, `Could not create output dir: ${dir}` )
|
|
98
99
|
}
|
|
99
100
|
}
|
|
100
101
|
|
|
@@ -25,8 +25,7 @@ program
|
|
|
25
25
|
.requiredOption( '--source-project <project>', 'Source GCP project ID' )
|
|
26
26
|
.requiredOption( '--dest-project <project>', 'Destination GCP project ID' )
|
|
27
27
|
.requiredOption( '--dest-region <region>', 'Destination region, e.g. us-east4' )
|
|
28
|
-
.option( '--only
|
|
29
|
-
.option( '--emit-yaml-dir <dir>', 'Directory to emit VolumeSnapshot YAMLs', './out' )
|
|
28
|
+
.option( '--artifacts-only', 'Only generate the volume snapshot YAML, script and readme' )
|
|
30
29
|
.option( '--dry-run', 'Show commands but don\'t run them' )
|
|
31
30
|
.option( '--execute', 'Perform real actions (required to run gcloud commands)' )
|
|
32
31
|
.option( '--cleanup', 'Remove temporary GCP resources after snapshot creation' )
|
|
@@ -42,7 +41,6 @@ const {
|
|
|
42
41
|
destProject,
|
|
43
42
|
destRegion,
|
|
44
43
|
dryRun,
|
|
45
|
-
emitYamlDir,
|
|
46
44
|
snapshot,
|
|
47
45
|
sourceProject,
|
|
48
46
|
} = options
|
|
@@ -55,6 +53,10 @@ const destSnapshot = `snapshot-${sourceProject}-${snapshot}`
|
|
|
55
53
|
const tempDiskFromImage = `temp-from-img-${snapshot}`
|
|
56
54
|
const destZone = `${destRegion}-a` // infer from region - implies "-a" zone always available
|
|
57
55
|
|
|
56
|
+
// directory for receiving the YAML necessary for defining the snapshot class,
|
|
57
|
+
// content and volume
|
|
58
|
+
const emitYamlDir = `cnpg-clone-${snapshot}`
|
|
59
|
+
|
|
58
60
|
let sourceZone // set and used in main(), also needed by cleanupResouces
|
|
59
61
|
|
|
60
62
|
const getPrettyTimeNow = () => {
|
|
@@ -71,6 +73,61 @@ const getElapsedTime = ( startMs ) => {
|
|
|
71
73
|
return `${minutes}:${seconds}`
|
|
72
74
|
}
|
|
73
75
|
|
|
76
|
+
// equivalent to Unix touch command to create an empty file
|
|
77
|
+
const touchFile = async ( filename ) => {
|
|
78
|
+
if ( filename ) {
|
|
79
|
+
const handle = await fs.open( filename, 'w' ) // eslint-disable-line security/detect-non-literal-fs-filename
|
|
80
|
+
await handle.close()
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// generates an executable script to use for defining the volume on k8s
|
|
85
|
+
const writeApplyScript = async ( dir ) => {
|
|
86
|
+
const applyScript = `#!/ust/bin/env bash
|
|
87
|
+
#
|
|
88
|
+
# Apply VolumeSnapshot resources prior to deploying the CNPG cluster
|
|
89
|
+
#
|
|
90
|
+
kubectl apply -f ${dir}/volumesnapshotclass.yaml
|
|
91
|
+
kubectl apply -f ${dir}/volumesnapshotcontent.yaml
|
|
92
|
+
kubectl apply -f ${dir}/volumesnapshot.yaml
|
|
93
|
+
`
|
|
94
|
+
|
|
95
|
+
const scriptPath = path.join( dir, 'apply-volumesnapshots.sh' )
|
|
96
|
+
await fs.writeFile( scriptPath, applyScript, { mode : 0o755 } ) // eslint-disable-line security/detect-non-literal-fs-filename
|
|
97
|
+
log( chalk.green( ' [✓] Helper script written:' ), scriptPath )
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const writeReadme = async ( dir ) => {
|
|
101
|
+
const readmePath = path.join( dir, 'readme.txt' )
|
|
102
|
+
const content = `This directory contains the Kubernetes manifests needed to bootstrap a new CNPG cluster
|
|
103
|
+
using a cloned volume snapshot from another GCP project.
|
|
104
|
+
|
|
105
|
+
Files:
|
|
106
|
+
- volumesnapshotclass.yaml (optional, use if the class doesn't already exist)
|
|
107
|
+
- volumesnapshotcontent.yaml (refers to the GCE snapshot copied into this project)
|
|
108
|
+
- volumesnapshot.yaml (used by CNPG to bootstrap from the volume snapshot)
|
|
109
|
+
- apply-volumesnapshots.sh (helper script to apply these manifests in correct order)
|
|
110
|
+
- .nohelm (marker file for overwhelm to skip values.yaml generation)
|
|
111
|
+
|
|
112
|
+
To deploy the volume snapshots to your Kubernetes cluster:
|
|
113
|
+
|
|
114
|
+
$ ./apply-volumesnapshots.sh
|
|
115
|
+
|
|
116
|
+
Once applied, reference the following VolumeSnapshot in your CNPG cluster bootstrap config:
|
|
117
|
+
|
|
118
|
+
spec.bootstrap.recovery.volumeSnapshot.volumeSnapshotName: ${k8sSnapshotName}
|
|
119
|
+
|
|
120
|
+
Then deploy your CNPG cluster normally. Validate the cluster startup:
|
|
121
|
+
|
|
122
|
+
$ kubectl get pods -n cnpg-operands
|
|
123
|
+
|
|
124
|
+
Enjoy your cloned cluster ✨
|
|
125
|
+
`
|
|
126
|
+
|
|
127
|
+
await fs.writeFile( readmePath, content ) // eslint-disable-line security/detect-non-literal-fs-filename
|
|
128
|
+
log( chalk.green( ' [✓] README written:' ), readmePath )
|
|
129
|
+
}
|
|
130
|
+
|
|
74
131
|
const writeYaml = async ( filename, data ) => {
|
|
75
132
|
const fullPath = path.join( emitYamlDir, filename )
|
|
76
133
|
const yaml = YAML.dump( data )
|
|
@@ -98,7 +155,9 @@ const getVolumeHandle = async ( vscName ) => {
|
|
|
98
155
|
return handle
|
|
99
156
|
}
|
|
100
157
|
|
|
101
|
-
|
|
158
|
+
// this function is responsible for emitting all of the artifacts for the creation
|
|
159
|
+
// of the k8s volume that will then be used to bootstrap the cnpg cluster
|
|
160
|
+
const emitCloningArtifacts = async () => {
|
|
102
161
|
try {
|
|
103
162
|
const snapshotYamlRaw = await shellCmd(
|
|
104
163
|
`kubectl get volumesnapshot ${snapshot} -n cnpg-operands -o json`
|
|
@@ -120,6 +179,14 @@ const generateK8sYaml = async () => {
|
|
|
120
179
|
)
|
|
121
180
|
)
|
|
122
181
|
|
|
182
|
+
const snapshotClass = {
|
|
183
|
+
apiVersion : 'snapshot.storage.k8s.io/v1',
|
|
184
|
+
kind : 'VolumeSnapshotClass',
|
|
185
|
+
metadata : { name : 'cnpg-preprovisioned' },
|
|
186
|
+
driver : 'pd.csi.storage.gke.io',
|
|
187
|
+
deletionPolicy : 'Retain'
|
|
188
|
+
}
|
|
189
|
+
|
|
123
190
|
const snapshotContent = {
|
|
124
191
|
apiVersion : 'snapshot.storage.k8s.io/v1',
|
|
125
192
|
kind : 'VolumeSnapshotContent',
|
|
@@ -155,16 +222,18 @@ const generateK8sYaml = async () => {
|
|
|
155
222
|
},
|
|
156
223
|
}
|
|
157
224
|
|
|
158
|
-
|
|
225
|
+
mkdirSafe( emitYamlDir )
|
|
226
|
+
|
|
227
|
+
await writeYaml( 'volumesnapshotclass.yaml', snapshotClass )
|
|
159
228
|
await writeYaml( 'volumesnapshotcontent.yaml', snapshotContent )
|
|
160
229
|
await writeYaml( 'volumesnapshot.yaml', snapshotYaml )
|
|
230
|
+
await touchFile( path.join( emitYamlDir, '.nohelm' ) )
|
|
231
|
+
await writeApplyScript( emitYamlDir )
|
|
232
|
+
await writeReadme( emitYamlDir )
|
|
233
|
+
|
|
161
234
|
} catch ( err ) {
|
|
162
235
|
errorExit( `Unable to generate VolumeSnapshot YAML: ${err.message}` )
|
|
163
236
|
}
|
|
164
|
-
|
|
165
|
-
if ( options.onlyK8sYaml ) {
|
|
166
|
-
process.exit( 0 )
|
|
167
|
-
}
|
|
168
237
|
}
|
|
169
238
|
|
|
170
239
|
// runs a CLI command with a silly little spinner in an attempt to convince
|
|
@@ -246,7 +315,11 @@ const main = async () => {
|
|
|
246
315
|
log( ` from handle: ${chalk.cyan( volumeHandle )}` )
|
|
247
316
|
log( ` in zone: ${chalk.cyan( sourceZone )}\n` )
|
|
248
317
|
|
|
249
|
-
|
|
318
|
+
// if all we want are the artifacts
|
|
319
|
+
if ( options.artifactsOnly ) {
|
|
320
|
+
await emitCloningArtifacts()
|
|
321
|
+
process.exit( 0 )
|
|
322
|
+
}
|
|
250
323
|
|
|
251
324
|
log( `\n[STEP 2] 📸 Creating GCE snapshot from source disk [${diskName}]` )
|
|
252
325
|
await runCommand( [
|
|
@@ -309,50 +382,59 @@ const main = async () => {
|
|
|
309
382
|
`--project=${destProject}`,
|
|
310
383
|
] )
|
|
311
384
|
|
|
312
|
-
|
|
385
|
+
await emitCloningArtifacts()
|
|
386
|
+
|
|
387
|
+
log( `
|
|
388
|
+
✅ Done. Snapshot: ${chalk.cyan( destSnapshot )}
|
|
389
|
+
Recovery YAMLs written to: ${chalk.cyan( emitYamlDir )}
|
|
390
|
+
Run '${chalk.yellow( './apply-recovery-yaml.sh' )}' to prep the cluster for recovery.
|
|
391
|
+
` )
|
|
313
392
|
}
|
|
314
393
|
|
|
315
394
|
const cleanupResources = async () => {
|
|
316
395
|
log( '\n[STEP 9] 🧹 Cleaning up temporary resources (unwinding the stack)...' )
|
|
317
396
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
397
|
+
const commands = [
|
|
398
|
+
[ // STEP 7: Remove temp disk from image in destination project
|
|
399
|
+
`gcloud compute disks delete ${tempDiskFromImage}`,
|
|
400
|
+
`--zone=${destZone}`,
|
|
401
|
+
`--project=${destProject}`,
|
|
402
|
+
'--quiet'
|
|
403
|
+
],
|
|
404
|
+
|
|
405
|
+
[ // STEP 6: Remove copied image from destination project
|
|
406
|
+
`gcloud compute images delete ${tempImage}`,
|
|
407
|
+
`--project=${destProject}`,
|
|
408
|
+
'--quiet'
|
|
409
|
+
],
|
|
410
|
+
|
|
411
|
+
[ // STEP 4: Remove image from source project
|
|
412
|
+
`gcloud compute images delete ${tempImage}`,
|
|
413
|
+
`--project=${sourceProject}`,
|
|
414
|
+
'--quiet'
|
|
415
|
+
],
|
|
416
|
+
|
|
417
|
+
[ // STEP 3: Remove temp disk from source project
|
|
418
|
+
`gcloud compute disks delete ${tempDisk}`,
|
|
419
|
+
`--zone=${sourceZone}`,
|
|
420
|
+
`--project=${sourceProject}`,
|
|
421
|
+
'--quiet'
|
|
422
|
+
],
|
|
423
|
+
|
|
424
|
+
[ // STEP 2: Remove source snapshot
|
|
425
|
+
`gcloud compute snapshots delete ${intermediateSnapshot}`,
|
|
426
|
+
`--project=${sourceProject}`,
|
|
427
|
+
'--quiet'
|
|
428
|
+
],
|
|
429
|
+
]
|
|
430
|
+
|
|
431
|
+
await Promise.all(
|
|
432
|
+
commands.map( cmdParts => runCommand( cmdParts ).catch( ( err ) => {
|
|
433
|
+
warning( `Non-fatal error during cleanup: ${err.message}` )
|
|
434
|
+
} ) )
|
|
435
|
+
)
|
|
354
436
|
|
|
355
|
-
log( chalk.green( ' [✓]
|
|
437
|
+
log( chalk.green( ' [✓] Parallel complete. Final GCE snapshot retained for CNPG bootstrapping.' ) )
|
|
356
438
|
}
|
|
357
439
|
|
|
358
440
|
if ( cleanupOnly ) {
|
|
@@ -9,13 +9,11 @@ RUN useradd -ms /bin/bash {{runuser}} && \
|
|
|
9
9
|
mkdir -p /usr/src/app && \
|
|
10
10
|
chown {{runuser}} /usr/src/app && \
|
|
11
11
|
apt-get update && apt-get install -y \
|
|
12
|
-
wget git \
|
|
12
|
+
wget git libgl1 libglib2.0-0 \
|
|
13
13
|
&& rm -rf /var/lib/apt/lists/*
|
|
14
14
|
|
|
15
15
|
# gcc \
|
|
16
16
|
# git \
|
|
17
|
-
# libgl1 \
|
|
18
|
-
# libglib2.0-0 \
|
|
19
17
|
# && rm -rf /var/lib/apt/lists/*
|
|
20
18
|
|
|
21
19
|
# Copy uv binary from the official distroless Docker image.
|