@leverege/build-tools 2.65.0 → 2.66.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leverege/build-tools",
3
- "version": "2.65.0",
3
+ "version": "2.66.0",
4
4
  "description": "A collection of build / support tools for Leverege developers",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -83,10 +83,10 @@
83
83
  "semver": "^7.7.1",
84
84
  "simple-git": "^3.27.0",
85
85
  "toml": "^3.0.0",
86
- "zx": "^8.5.0"
86
+ "zx": "^8.5.2"
87
87
  },
88
88
  "devDependencies": {
89
89
  "@leverege/eslint-config-leverege": "^5.0.1",
90
- "npm": "^11.2.0"
90
+ "npm": "^11.3.0"
91
91
  }
92
92
  }
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 = `#!/usr/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-volumesnapshots.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( '\n [✓] Parallel cleanup complete - final GCE snapshot retained for CNPG bootstrapping\n' ) )
356
438
  }
357
439
 
358
440
  if ( cleanupOnly ) {
@@ -365,3 +447,5 @@ await main().catch( err => errorExit( err.message || err ) )
365
447
  if ( cleanup ) {
366
448
  await cleanupResources()
367
449
  }
450
+
451
+ log( chalk.green( ` [✓] Cloning complete - recovery image name is => ${chalk.bold.yellow( k8sSnapshotName )}\n` ) )
@@ -0,0 +1,67 @@
1
+ apiVersion: v1
2
+ kind: ServiceAccount
3
+ metadata:
4
+ name: cnpg-cleanup-sa
5
+ namespace: cnpg-operands
6
+ annotations:
7
+ # wire up this KSA to the GSA to hook into IAM for workload identity
8
+ iam.gke.io/gcp-service-account: cnpg-cleanup-sa@${PROJECT_ID}.iam.gserviceaccount.com
9
+ ---
10
+ apiVersion: rbac.authorization.k8s.io/v1
11
+ kind: Role
12
+ metadata:
13
+ name: cnpg-cleanup-role
14
+ namespace: cnpg-operands
15
+ rules:
16
+ - apiGroups: ["postgresql.cnpg.io"]
17
+ resources: ["backups"]
18
+ verbs: ["get", "list", "delete"]
19
+ - apiGroups: ["snapshot.storage.k8s.io"]
20
+ resources: ["volumesnapshots"]
21
+ verbs: ["get", "list", "delete"]
22
+ ---
23
+ apiVersion: rbac.authorization.k8s.io/v1
24
+ kind: RoleBinding
25
+ metadata:
26
+ name: cnpg-cleanup-rolebinding
27
+ namespace: cnpg-operands
28
+ subjects:
29
+ - kind: ServiceAccount
30
+ name: cnpg-cleanup-sa
31
+ namespace: cnpg-operands
32
+ roleRef:
33
+ kind: Role
34
+ name: cnpg-cleanup-role
35
+ apiGroup: rbac.authorization.k8s.io
36
+ ---
37
+ apiVersion: batch/v1
38
+ kind: CronJob
39
+ metadata:
40
+ name: cnpg-cleanup-snapshot
41
+ namespace: cnpg-operands
42
+ spec:
43
+ schedule: "0 11 * * *" # 11 UTC is ~7AM EST
44
+ # schedule: "*/5 * * * *" # every 5 minutes for testing
45
+ successfulJobsHistoryLimit: 1
46
+ jobTemplate:
47
+ spec:
48
+ template:
49
+ spec:
50
+ restartPolicy: Never
51
+ serviceAccountName: cnpg-cleanup-sa
52
+ containers:
53
+ - name: cnpg-cleanup
54
+ image: gcr.io/google.com/cloudsdktool/cloud-sdk:latest
55
+ env:
56
+ - name: DRY_RUN
57
+ value: "false"
58
+ - name: RETENTION_DAYS
59
+ value: "21"
60
+ command:
61
+ - /bin/bash
62
+ - -c
63
+ - |
64
+ echo "🚀 Fetching cleanup script from GCS..."
65
+ apt-get update -qq
66
+ apt-get install -y jq libjq1 libonig5 --no-install-recommends -o=Dpkg::Use-Pty=0 -o=APT::Get::Assume-Yes=1 -o=Debug::pkgProblemResolver=1
67
+ gsutil cat gs://${PROJECT_ID}-k8s-cronjobs/cnpg-cleanup-script.sh | bash
@@ -0,0 +1,73 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ KUBECTL_DELETE_ARGS=""
5
+ [[ "$DRY_RUN" == "true" ]] && KUBECTL_DELETE_ARGS="--dry-run=server"
6
+
7
+ # volumesnapshots namespace can be set here but if we need more than just the
8
+ # cnpg-operands then we'll need to the Role with a ClusterRole and bind it
9
+ # differently as well.
10
+ VSNAPS_NS="-n cnpg-operands"
11
+
12
+ # Default retention period (can override with env)
13
+ RETENTION_DAYS="${RETENTION_DAYS:-21}"
14
+ CURRENT_DATE=$(date +%s)
15
+
16
+ log() {
17
+ echo "[$(date -u '+%Y-%m-%dT%H:%M:%SZ')] $*"
18
+ }
19
+
20
+ should_delete() {
21
+ local creation_ts="$1"
22
+ local age_days
23
+ local created_secs
24
+ created_secs=$(date -d "$creation_ts" +%s)
25
+ age_days=$(( (CURRENT_DATE - created_secs) / 86400 ))
26
+ if (( age_days > RETENTION_DAYS )); then
27
+ echo "$age_days"
28
+ return 0
29
+ fi
30
+ return 1
31
+ }
32
+
33
+ cleanup_cnpg_backups() {
34
+ log "🔍 Cleaning CNPG backups older than $RETENTION_DAYS days"
35
+ kubectl get backups.postgresql.cnpg.io -n cnpg-operands -o json | jq -c '.items[]' | while read -r item; do
36
+ name=$(echo "$item" | jq -r '.metadata.name')
37
+ created=$(echo "$item" | jq -r '.metadata.creationTimestamp')
38
+ [[ "$name" != *"-backup-"* ]] && log "Skipping manual backup: $name" && continue
39
+
40
+ if age=$(should_delete "$created"); then
41
+ log "🗑 Deleting CNPG backup: $name (Age: $age days)"
42
+ kubectl delete backups.postgresql.cnpg.io "$name" -n cnpg-operands --ignore-not-found --wait=false $KUBECTL_DELETE_ARGS
43
+ fi
44
+ done
45
+ log "✅ CNPG backup cleanup complete"
46
+ }
47
+
48
+ cleanup_volume_snapshots() {
49
+ log "🔍 Cleaning VolumeSnapshots older than $RETENTION_DAYS days"
50
+ kubectl get volumesnapshots $VSNAPS_NS -o json | jq -c '.items[]' | while read -r item; do
51
+ ns=$(echo "$item" | jq -r '.metadata.namespace')
52
+ name=$(echo "$item" | jq -r '.metadata.name')
53
+ created=$(echo "$item" | jq -r '.metadata.creationTimestamp')
54
+ ready=$(echo "$item" | jq -r '.status.readyToUse // false')
55
+
56
+ [[ "$ready" != "true" ]] && continue
57
+
58
+ if age=$(should_delete "$created"); then
59
+ log "🗑 Deleting VolumeSnapshot: $ns/$name (Age: $age days)"
60
+ kubectl delete volumesnapshot -n "$ns" "$name" --ignore-not-found --wait=false $KUBECTL_DELETE_ARGS
61
+ fi
62
+ done
63
+ log "✅ VolumeSnapshot cleanup complete"
64
+ }
65
+
66
+ main() {
67
+ log "🚀 Starting cleanup job (retention: $RETENTION_DAYS days)"
68
+ cleanup_cnpg_backups
69
+ cleanup_volume_snapshots
70
+ log "🏁 All cleanup tasks complete"
71
+ }
72
+
73
+ main
@@ -0,0 +1 @@
1
+ cnpg-cleanup-cronjob.yaml
@@ -0,0 +1,28 @@
1
+ #!/bin/bash
2
+ #
3
+ # See => https://cloudnative-pg.io/documentation/current/installation_upgrade/
4
+ #
5
+ # OPVER="1.25.1"
6
+ OPVER="1.26.0-rc1" # 03/28/2025
7
+
8
+ createNamespaceIfNeeded cnpg-system
9
+
10
+ kubectl apply --server-side -f \
11
+ https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/main/releases/cnpg-${OPVER}.yaml $K8S_WHAT
12
+
13
+ # Legacy terraformed clusters (like sandbox) may need a special firewall rule
14
+ # added to the network layer on k8s. It should look like this:
15
+ #
16
+ # Name : cnpg-operator
17
+ # Ports : 8000,9443 <= kubectl cnpg status and webhooks
18
+ # Filters: 172.16.0.0/28 <= k8s control plane
19
+ #
20
+ # Old approach used the helm chart.
21
+ #
22
+ #addHelmRepo cnpg https://cloudnative-pg.github.io/charts
23
+ #
24
+ #helm upgrade --install cnpg cnpg/cloudnative-pg \
25
+ # --namespace cnpg-system --create-namespace \
26
+ # --set webhook.port="10250" $HELM_WHAT
27
+ #
28
+ #removeHelmRepo cnpg
@@ -2,13 +2,12 @@
2
2
  #
3
3
  # See => https://cloudnative-pg.io/documentation/current/installation_upgrade/
4
4
  #
5
- # OPVER="1.25.1"
6
- OPVER="1.26.0-rc1" # 03/28/2025
5
+ OPVER="1.25.1"
7
6
 
8
7
  createNamespaceIfNeeded cnpg-system
9
8
 
10
9
  kubectl apply --server-side -f \
11
- https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/main/releases/cnpg-${OPVER}.yaml $K8S_WHAT
10
+ https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-${OPVER%.*}/releases/cnpg-${OPVER}.yaml $K8S_WHAT
12
11
 
13
12
  # Legacy terraformed clusters (like sandbox) may need a special firewall rule
14
13
  # added to the network layer on k8s. It should look like this:
@@ -2,7 +2,7 @@
2
2
  #
3
3
  showInstalling "Elastic Search 8"
4
4
 
5
- [ -z "$ELASTIC_CHART_VERSION" ] && ELASTIC_CHART_VERSION="21.4.8"
5
+ [ -z "$ELASTIC_CHART_VERSION" ] && ELASTIC_CHART_VERSION="21.5.0"
6
6
 
7
7
  OCI_CHART="oci://registry-1.docker.io/bitnamicharts/elasticsearch"
8
8
 
@@ -4,7 +4,7 @@ showInstalling "The Prometheus Operator and Components"
4
4
  addHelmRepo prometheus-community https://prometheus-community.github.io/helm-charts
5
5
 
6
6
  showInstalling "The Prometheus Operator (kube-prometheus-stack)"
7
- [ -z "$PROMETHEUS_STACK_CHART_VERSION" ] && PROMETHEUS_STACK_CHART_VERSION="69"
7
+ [ -z "$PROMETHEUS_STACK_CHART_VERSION" ] && PROMETHEUS_STACK_CHART_VERSION="70"
8
8
 
9
9
  NS="--namespace prometheus"
10
10
 
@@ -7,7 +7,25 @@ fullnameOverride: "prometheus-stack-stackdriver-metrics"
7
7
  stackdriver:
8
8
  projectId: "${PROJECT_NAME}"
9
9
  metrics:
10
- typePrefixes: "pubsub.googleapis.com/subscription/oldest_unacked_message_age,pubsub.googleapis.com/subscription/num_undelivered_messages,pubsub.googleapis.com/subscription/pull_ack_request_count,pubsub.googleapis.com/subscription/streaming_pull_ack_request_count,pubsub.googleapis.com/topic/message_sizes,pubsub.googleapis.com/topic/send_request_count,firebasedatabase.googleapis.com/io/database_load,firebasedatabase.googleapis.com/network/sent_bytes_count,firebasedatabase.googleapis.com/network/active_connections"
10
+ typePrefixes: >
11
+ pubsub.googleapis.com/subscription/oldest_unacked_message_age,
12
+ pubsub.googleapis.com/subscription/num_undelivered_messages,
13
+ pubsub.googleapis.com/subscription/pull_ack_request_count,
14
+ pubsub.googleapis.com/subscription/streaming_pull_ack_request_count,
15
+ pubsub.googleapis.com/topic/message_sizes,
16
+ pubsub.googleapis.com/topic/send_request_count,
17
+ firebasedatabase.googleapis.com/io/database_load,
18
+ firebasedatabase.googleapis.com/network/sent_bytes_count,
19
+ firebasedatabase.googleapis.com/network/active_connections,
20
+ container.googleapis.com/accelerator/duty_cycle,
21
+ container.googleapis.com/accelerator/memory_usage,
22
+ container.googleapis.com/accelerator/request_count,
23
+ agent.googleapis.com/memory/bytes_used,
24
+ agent.googleapis.com/cpu/utilization,
25
+ storage.googleapis.com/network/received_bytes_count,
26
+ storage.googleapis.com/network/sent_bytes_count,
27
+ monitoring.googleapis.com/uptime_check/check_passed,
28
+ quota.googleapis.com/allocation/usage
11
29
 
12
30
  # https://artifacthub.io/packages/helm/prometheus-community/prometheus-stackdriver-exporter?modal=values&path=serviceMonitor
13
31
  serviceMonitor:
@@ -15,6 +33,8 @@ serviceMonitor:
15
33
  namespace: prometheus
16
34
  interval: "30s"
17
35
  relabelings:
36
+ # Legacy labelmap used to preserve compatibility with pre-operator Grafana dashboards
37
+ # Can be removed once legacy Prometheus stack is deprecated
18
38
  - action: labelmap
19
39
  regex: __meta_kubernetes_pod_label_(.+)
20
40
  - action: labelmap
@@ -4,7 +4,7 @@ showInstalling "Redis"
4
4
 
5
5
  OCI_CHART="oci://registry-1.docker.io/bitnamicharts/redis"
6
6
 
7
- [ -z "$REDIS_CHART_VERSION" ] && REDIS_CHART_VERSION="20.11.3"
7
+ [ -z "$REDIS_CHART_VERSION" ] && REDIS_CHART_VERSION="20.11.5"
8
8
 
9
9
  helm upgrade --install redis $OCI_CHART \
10
10
  --values redis/values.yaml \
@@ -17,7 +17,7 @@ TRAEFIK_NAMESPACE="traefik"
17
17
 
18
18
  addHelmRepo traefik https://helm.traefik.io/traefik
19
19
 
20
- [ -z "$TRAEFIK_CHART_VERSION" ] && TRAEFIK_CHART_VERSION="34"
20
+ [ -z "$TRAEFIK_CHART_VERSION" ] && TRAEFIK_CHART_VERSION="35"
21
21
  helm upgrade --install traefik traefik/traefik \
22
22
  --namespace $TRAEFIK_NAMESPACE --create-namespace \
23
23
  --values traefik/values.yaml \
@@ -25,7 +25,7 @@ velero schedule delete --all --confirm &> /dev/null
25
25
  helm upgrade \
26
26
  --install velero vmware-tanzu/velero \
27
27
  --namespace velero --create-namespace \
28
- --values velero/velero-local.yaml \
28
+ --values velero/values-local.yaml \
29
29
  --version $VELERO_CHART_VERSION $HELM_WHAT \
30
30
  --set configuration.backupStorageLocation[0].bucket="$GCE_BUCKET" \
31
31
  --set configuration.backupStorageLocation[0].config.serviceAccount="$GCE_SA_EMAIL" \
@@ -71,7 +71,7 @@ resources:
71
71
  cpu: 250m
72
72
  memory: 128Mi
73
73
  limits:
74
- cpu: 300m
74
+ cpu: 500m
75
75
  memory: 256Mi
76
76
 
77
77
  # Annotations to add to the Velero deployment's. Optional.
package/src/helmup.sh CHANGED
@@ -702,7 +702,11 @@ EOSNAPSC
702
702
  sleep 2 # hold up processing for a moment to allow IAM mods to propagate
703
703
  }
704
704
 
705
- function installCronCnpgCleanupSnapshot() {
705
+ # this function is a little convoluted in the way it attempts to install the
706
+ # scripts and yaml necessary for the CNPG cleanup activities - the idea is to
707
+ # be able to update the scripts without touching anything else since the target
708
+ # directory is the cnpg-operator setup
709
+ function installCnpgCleanupCronjob() {
706
710
  CNPG_OPERATOR_DIR="cnpg-operator"
707
711
  if [ ! -d "$CNPG_OPERATOR_DIR" ];
708
712
  then
@@ -713,23 +717,52 @@ CNPG_OPERATOR_MISSING
713
717
  exit 1
714
718
  fi
715
719
 
716
- CNPG_CLEANUP_CRONJOB_YAML="cnpg-cleanup-snapshot-cronjob.yaml"
717
- CNPG_CLEANUP_CRONJOB="$CNPG_OPERATOR_DIR/$CNPG_CLEANUP_CRONJOB_YAML"
718
- if [ ! -f "$CNPG_CLEANUP_CRONJOB" ];
720
+ # create the destination bucket for k8s cronjob scripts
721
+ BUCKET_SUFFIX="k8s-cronjobs"
722
+ makeUniformBucketWithCors $BUCKET_SUFFIX
723
+
724
+ CNPG_CLEANUP_CRONJOB_OVH="cnpg-cleanup-cronjob.yaml.ovh"
725
+ CNPG_CLEANUP_CRONJOB="$CNPG_OPERATOR_DIR/$CNPG_CLEANUP_CRONJOB_OVH"
726
+ CNPG_CLEANUP_SCRIPT_BASH="cnpg-cleanup-script.sh"
727
+ CNPG_CLEANUP_SCRIPT="$CNPG_OPERATOR_DIR/$CNPG_CLEANUP_SCRIPT_BASH"
728
+ if [ ! -f "$CNPG_CLEANUP_SCRIPT" ];
719
729
  then
720
730
  cat<<CNPG_FETCH_CLEANUP_CRONJOB_YAML
721
- `color y "Missing $CNPG_CLEANUP_CRONJOB_YAML - grabbing a copy from build-tools"`
731
+ `color y "Missing $CNPG_CLEANUP_SCRIPT_BASH - updating local cron setup scripts"`
722
732
 
723
733
  CNPG_FETCH_CLEANUP_CRONJOB_YAML
724
734
  CNPG_OP_ROOT="$(build-tools --reporoot)/src/helm-charts/cnpg-operator"
725
- cp $CNPG_OP_ROOT/$CNPG_CLEANUP_CRONJOB_YAML $CNPG_CLEANUP_CRONJOB
735
+ cp $CNPG_OP_ROOT/$CNPG_CLEANUP_CRONJOB_OVH $CNPG_CLEANUP_CRONJOB
736
+ cp $CNPG_OP_ROOT/$CNPG_CLEANUP_SCRIPT_BASH $CNPG_CLEANUP_SCRIPT
726
737
  fi
727
738
 
739
+ # copy / update the script in the bucket referenced in the cronjob
740
+ printf "\ngsutil cp $CNPG_CLEANUP_SCRIPT gs://$GCP_PROJECT_ID-$BUCKET_SUFFIX\n"
741
+ gsutil cp $CNPG_CLEANUP_SCRIPT gs://$GCP_PROJECT_ID-$BUCKET_SUFFIX
742
+
743
+ local CNPG_NAMESPACE="cnpg-operands"
744
+ local CNPG_CLEANUP_SA="cnpg-cleanup-sa"
745
+
746
+ gcloud iam service-accounts create "$CNPG_CLEANUP_SA" \
747
+ --project="$GCP_PROJECT_ID" \
748
+ --description="Used for cronjob cleanup with Workload Identity" \
749
+ --display-name="CNPG Cleanup SA" &> $DEVNULL
750
+ warnOnError $? "the GCP SA $CNPG_CLEANUP_SA may already exist"
751
+
752
+ gcloud projects add-iam-policy-binding "$GCP_PROJECT_ID" \
753
+ --project="$GCP_PROJECT_ID" \
754
+ --member="serviceAccount:$CNPG_CLEANUP_SA@$GCP_PROJECT_ID.iam.gserviceaccount.com" \
755
+ --role="roles/storage.objectViewer"
756
+
757
+ bindWorkloadIdentity "$CNPG_CLEANUP_SA" "$CNPG_NAMESPACE"
758
+
759
+ CNPG_CLEANUP_CRONJOB_YAML="$CNPG_OPERATOR_DIR/$(basename $CNPG_CLEANUP_CRONJOB_OVH .ovh)"
760
+ overwhelm -g # allow ovh -> yaml to occur
728
761
  cat<<CNPG_CLEANUP_CRONJOB_APPLY
729
- `color g "Creating cronjob =>"``color y " kubectl apply -f $CNPG_CLEANUP_CRONJOB"`
762
+ `color g "Creating cronjob =>"``color y " kubectl apply -f $CNPG_CLEANUP_CRONJOB_YAML"`
730
763
 
731
764
  CNPG_CLEANUP_CRONJOB_APPLY
732
- kubectl apply -f $CNPG_CLEANUP_CRONJOB
765
+ kubectl apply -f $CNPG_CLEANUP_CRONJOB_YAML
733
766
  }
734
767
 
735
768
  function ensureCnpgSecret() {
@@ -1001,7 +1034,7 @@ CATBACKUP
1001
1034
  ;;
1002
1035
 
1003
1036
  "cnpg-cronjob")
1004
- installCronCnpgCleanupSnapshot
1037
+ installCnpgCleanupCronjob
1005
1038
  ;;
1006
1039
 
1007
1040
  "cronZombieKiller"|"zombie-killer")
@@ -1,4 +1,8 @@
1
1
  steps:
2
+ - name: 'gcr.io/cloud-builders/docker'
3
+ entrypoint: 'bash'
4
+ args: ['-c', 'docker pull {{imageName}}:latest || exit 0']
5
+
2
6
  - name: 'gcr.io/cloud-builders/docker'
3
7
  args:
4
8
  - 'build'
@@ -6,8 +10,8 @@ steps:
6
10
  - '-t'
7
11
  - '{{imageName}}:{{imageVersion}}'
8
12
  - '--cache-from'
9
- - '{{imageName}}:{{imageVersion}}'
13
+ - '{{imageName}}:latest'
10
14
  - '.'
11
15
 
12
16
  images:
13
- - '{{imageName}}:{{imageVersion}}'
17
+ - '{{imageName}}:{{imageVersion}}'
@@ -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.
@@ -1,73 +0,0 @@
1
- apiVersion: v1
2
- kind: ServiceAccount
3
- metadata:
4
- name: cnpg-cleanup-sa
5
- namespace: cnpg-operands
6
- ---
7
- apiVersion: rbac.authorization.k8s.io/v1
8
- kind: Role
9
- metadata:
10
- name: cnpg-cleanup-role
11
- namespace: cnpg-operands
12
- rules:
13
- - apiGroups: ["postgresql.cnpg.io"]
14
- resources: ["backups"]
15
- verbs: ["get", "list", "delete"]
16
- ---
17
- apiVersion: rbac.authorization.k8s.io/v1
18
- kind: RoleBinding
19
- metadata:
20
- name: cnpg-cleanup-rolebinding
21
- namespace: cnpg-operands
22
- subjects:
23
- - kind: ServiceAccount
24
- name: cnpg-cleanup-sa
25
- namespace: cnpg-operands
26
- roleRef:
27
- kind: Role
28
- name: cnpg-cleanup-role
29
- apiGroup: rbac.authorization.k8s.io
30
- ---
31
- apiVersion: batch/v1
32
- kind: CronJob
33
- metadata:
34
- name: cnpg-cleanup-snapshot
35
- namespace: cnpg-operands
36
- spec:
37
- schedule: "0 13 * * *" # 13 UTC is ~8AM EST
38
- successfulJobsHistoryLimit: 2
39
- jobTemplate:
40
- spec:
41
- template:
42
- spec:
43
- serviceAccountName: cnpg-cleanup-sa
44
- restartPolicy: Never
45
- containers:
46
- - name: cnpg-cleanup-snapshots
47
- image: bitnami/kubectl:latest
48
- command:
49
- - "/bin/bash"
50
- - "-c"
51
- - |
52
- echo "Starting CNPG Cleanup"
53
- CURRENT_DATE=$(date +%s)
54
- RETENTION_DAYS=21
55
- SNAPSHOTS_JSON=$(kubectl get backups.postgresql.cnpg.io -n cnpg-operands -o json)
56
- echo "$SNAPSHOTS_JSON" | jq -c '.items[]' | while read -r snapshot
57
- do
58
- NAME=$(echo "$snapshot" | jq -r '.metadata.name')
59
- CREATION_TIMESTAMP=$(echo "$snapshot" | jq -r '.metadata.creationTimestamp')
60
- if [[ "$NAME" != *"-backup-"* ]];
61
- then
62
- echo "Skipping manual snapshot: $NAME"
63
- continue
64
- fi
65
- SNAPSHOT_DATE=$(date -d "$CREATION_TIMESTAMP" +%s)
66
- AGE=$(( (CURRENT_DATE - SNAPSHOT_DATE) / 86400 ))
67
- if [ "$AGE" -gt "$RETENTION_DAYS" ];
68
- then
69
- echo "Deleting snapshot: $NAME (Age: $AGE days)"
70
- kubectl delete backups.postgresql.cnpg.io "$NAME" -n cnpg-operands 2> /dev/null
71
- fi
72
- done
73
- echo "Cleanup complete."