@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leverege/build-tools",
3
- "version": "2.65.0",
3
+ "version": "2.65.1",
4
4
  "description": "A collection of build / support tools for Leverege developers",
5
5
  "main": "index.js",
6
6
  "repository": {
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 = async ( dir ) => {
93
+ export const mkdirSafe = ( dir ) => {
94
94
  try {
95
- await fs.mkdir( dir, { recursive : true } ) // eslint-disable-line security/detect-non-literal-fs-filename
96
- } catch ( e ) {
97
- warning( `Could not create output dir: ${dir}` )
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-k8s-yaml', 'Only generate the volume snapshot YAML files' )
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
- const generateK8sYaml = async () => {
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
- await mkdirSafe( emitYamlDir )
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
- await generateK8sYaml()
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
- log( `\n✅ Done. Snapshot: ${destSnapshot} and recovery YAMLs ready.` )
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
- // STEP 7: Remove temp disk from image in destination project
319
- await runCommand( [
320
- `gcloud compute disks delete ${tempDiskFromImage}`,
321
- `--zone=${destZone}`,
322
- `--project=${destProject}`,
323
- '--quiet'
324
- ] )
325
-
326
- // STEP 6: Remove copied image from destination project
327
- await runCommand( [
328
- `gcloud compute images delete ${tempImage}`,
329
- `--project=${destProject}`,
330
- '--quiet'
331
- ] )
332
-
333
- // STEP 4: Remove image from source project
334
- await runCommand( [
335
- `gcloud compute images delete ${tempImage}`,
336
- `--project=${sourceProject}`,
337
- '--quiet'
338
- ] )
339
-
340
- // STEP 3: Remove temp disk from source project
341
- await runCommand( [
342
- `gcloud compute disks delete ${tempDisk}`,
343
- `--zone=${sourceZone}`,
344
- `--project=${sourceProject}`,
345
- '--quiet'
346
- ] )
347
-
348
- // STEP 2: Remove source snapshot
349
- await runCommand( [
350
- `gcloud compute snapshots delete ${intermediateSnapshot}`,
351
- `--project=${sourceProject}`,
352
- '--quiet'
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( ' [✓] Cleanup complete. Final GCE snapshot retained for CNPG bootstrapping.' ) )
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.