@leverege/build-tools 2.94.0 → 2.96.0-quinn.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/.vscode/launch.json +14 -0
- package/package.json +5 -2
- package/src/Docker.mjs +3 -2
- package/src/Utils.mjs +80 -6
- package/src/artifacts/Utils.mjs +1 -1
- package/src/bash-funcs +1 -1
- package/src/firebaseDeploy.mjs +8 -2
- package/src/firebaseServe.mjs +8 -2
- package/src/git-tar.sh +1 -1
- package/src/helm-charts/emailer/values-local.yaml +1 -8
- package/src/helm-charts/prom-operator/helmup.plugin +1 -1
- package/src/helm-charts/traefik/helmup.plugin +1 -1
- package/src/helm-charts/velero/README.md +135 -0
- package/src/helm-charts/velero/helmup.bootstrap +1 -0
- package/src/helm-charts/velero/helmup.plugin +29 -2
- package/src/helm-charts/velero/values-local.yaml +1 -1
- package/src/helmup.sh +49 -41
- package/src/service-man/ServiceMan.mjs +75 -0
- package/src/service-man/config/CommandLine.mjs +199 -0
- package/src/service-man/config/Config.mjs +203 -0
- package/src/service-man/config/VersionCheck.mjs +79 -0
- package/src/service-man/config/iam-roles/leveregeBigQueryReader.json +10 -0
- package/src/service-man/config/iam-roles/leveregeBigQueryWriter.json +13 -0
- package/src/service-man/config/iam-roles/leveregeCloudFunctionsManager.json +12 -0
- package/src/service-man/config/iam-roles/leveregeFirebaseHosting.json +9 -0
- package/src/service-man/config/iam-roles/leveregeFirebaseMessaging.json +13 -0
- package/src/service-man/config/iam-roles/leveregeFirebaseUpdates.json +10 -0
- package/src/service-man/config/iam-roles/leveregeLogsViewer.json +8 -0
- package/src/service-man/config/iam-roles/leveregePubSub.json +13 -0
- package/src/service-man/config/iam-roles/leveregeSecretsViewer.json +9 -0
- package/src/service-man/config/iam-roles/leveregeStorageObjectAdmin.json +21 -0
- package/src/service-man/config/iam-roles/leveregeStorageObjectReader.json +13 -0
- package/src/service-man/config/iam-roles/leveregeSvcAcctActor.json +8 -0
- package/src/service-man/config/iam-roles/leveregeSvcAcctTokenCreator.json +8 -0
- package/src/service-man/config/index.mjs +7 -0
- package/src/service-man/config/secrets/authz-jwt.json +6 -0
- package/src/service-man/config/secrets/authz-postgres.json +7 -0
- package/src/service-man/config/secrets/db-timescale-dense.json +8 -0
- package/src/service-man/config/secrets/jwt-options.json +7 -0
- package/src/service-man/config/secrets/mail-gun-api-key.json +6 -0
- package/src/service-man/config/secrets/npmrc.json +6 -0
- package/src/service-man/config/secrets/oidc-client-secret.json +6 -0
- package/src/service-man/config/secrets/postgresql-password.json +8 -0
- package/src/service-man/config/secrets/session-secret.json +6 -0
- package/src/service-man/config/secrets/twilio.json +8 -0
- package/src/service-man/config/service-accounts/api-server.json +12 -0
- package/src/service-man/config/service-accounts/authz-server.json +5 -0
- package/src/service-man/config/service-accounts/db-curator.json +8 -0
- package/src/service-man/config/service-accounts/emailer.json +7 -0
- package/src/service-man/config/service-accounts/fota-server.json +6 -0
- package/src/service-man/config/service-accounts/geotile-server.json +8 -0
- package/src/service-man/config/service-accounts/imagine.json +6 -0
- package/src/service-man/config/service-accounts/message-processor.json +6 -0
- package/src/service-man/config/service-accounts/messenger.json +7 -0
- package/src/service-man/config/service-accounts/pubsub-pulse.json +6 -0
- package/src/service-man/config/service-accounts/push-notifier.json +8 -0
- package/src/service-man/config/service-accounts/reason.json +5 -0
- package/src/service-man/config/service-accounts/resource-server.json +9 -0
- package/src/service-man/config/service-accounts/rule-engine.json +7 -0
- package/src/service-man/config/service-accounts/scheduler.json +6 -0
- package/src/service-man/config/service-accounts/transponder.json +15 -0
- package/src/service-man/gcloud-k8s/RolesManager.mjs +41 -0
- package/src/service-man/gcloud-k8s/SecretsManager.mjs +182 -0
- package/src/service-man/gcloud-k8s/ServiceAccountManager.mjs +246 -0
- package/src/service-man/gcloud-k8s/VersionManager.mjs +114 -0
- package/src/service-man/gcloud-k8s/index.mjs +11 -0
- package/src/service-man/service-man.mjs +18 -0
- package/src/service-man/utils/index.mjs +83 -0
package/.vscode/launch.json
CHANGED
|
@@ -17,6 +17,20 @@
|
|
|
17
17
|
"env": {
|
|
18
18
|
},
|
|
19
19
|
"outputCapture": "std"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "service-man (launch)",
|
|
23
|
+
"type": "node",
|
|
24
|
+
"request": "launch",
|
|
25
|
+
"program": "${workspaceFolder}/src/service-man/service-man.mjs",
|
|
26
|
+
"args": ["--dump-configs","dbg-cfgs"],
|
|
27
|
+
"cwd": "/Users/john/Work/leverege/k8s/tst-builder",
|
|
28
|
+
"runtimeExecutable": "node",
|
|
29
|
+
"console": "integratedTerminal",
|
|
30
|
+
"skipFiles": ["<node_internals>/**"],
|
|
31
|
+
"env": {
|
|
32
|
+
"NODE_ENV": "development"
|
|
33
|
+
}
|
|
20
34
|
}
|
|
21
35
|
]
|
|
22
36
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leverege/build-tools",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.96.0-quinn.1",
|
|
4
4
|
"description": "A collection of build / support tools for Leverege developers",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"getjson": "src/getjson.mjs",
|
|
41
41
|
"git-tar": "src/git-tar.sh",
|
|
42
42
|
"helm-audit": "src/helm-audit.mjs",
|
|
43
|
+
"helmboot": "src/helmup.sh",
|
|
43
44
|
"helmcycle": "src/helmup.sh",
|
|
44
45
|
"helmdn": "src/helmdn.sh",
|
|
45
46
|
"helminit": "src/helmup.sh",
|
|
@@ -64,6 +65,7 @@
|
|
|
64
65
|
"refresh-npm-token": "src/refresh-npm-token.mjs",
|
|
65
66
|
"refresh-py-idx": "src/refresh-py-idx.sh",
|
|
66
67
|
"send-to-slack": "src/send-to-slack.mjs",
|
|
68
|
+
"service-man": "src/service-man/service-man.mjs",
|
|
67
69
|
"snapshot-cnpg": "src/snapshot-cnpg.mjs",
|
|
68
70
|
"tag-release": "src/tag-release.mjs",
|
|
69
71
|
"unleash": "src/unleash.mjs",
|
|
@@ -99,12 +101,13 @@
|
|
|
99
101
|
"simple-git": "^3.30.0",
|
|
100
102
|
"sloc": "^0.3.2",
|
|
101
103
|
"superstruct": "^2.0.2",
|
|
104
|
+
"tmp": "^0.2.5",
|
|
102
105
|
"toml": "^3.0.0",
|
|
103
106
|
"zx": "^8.8.5"
|
|
104
107
|
},
|
|
105
108
|
"devDependencies": {
|
|
106
109
|
"@leverege/eslint-config-leverege": "^5.1.1",
|
|
107
|
-
"chai": "^6.2.
|
|
110
|
+
"chai": "^6.2.2",
|
|
108
111
|
"mocha": "^11.7.5",
|
|
109
112
|
"npm": "^11.7.0"
|
|
110
113
|
}
|
package/src/Docker.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable security/detect-non-literal-fs-filename */
|
|
1
2
|
import fs from 'node:fs'
|
|
2
3
|
import path from 'node:path'
|
|
3
4
|
import url from 'node:url'
|
|
@@ -23,7 +24,7 @@ const DIRNAME = path.dirname( url.fileURLToPath( import.meta.url ) )
|
|
|
23
24
|
//
|
|
24
25
|
// See the latest docker images here => https://hub.docker.com/_/node
|
|
25
26
|
//
|
|
26
|
-
const dockerfileNodeVersion = process.env.DOCKER_BUILD_NODE_VERSION || '
|
|
27
|
+
const dockerfileNodeVersion = process.env.DOCKER_BUILD_NODE_VERSION || '24.12.0-alpine3.23'
|
|
27
28
|
const dockerfileNpmVersion = process.env.DOCKER_BUILD_NPM_VERSION || '11.6.0'
|
|
28
29
|
|
|
29
30
|
// The contents of these variables were initially located in files that live in
|
|
@@ -83,7 +84,7 @@ const generateDockerfile = async ( options ) => {
|
|
|
83
84
|
const gitReference = await shellCmd( 'git rev-parse --short HEAD' )
|
|
84
85
|
const settings = { gitReference, ...defaultSettings, ...options }
|
|
85
86
|
const dockerTemplate = options.isNodeJsProject ? 'dockerfileTemplateNodeJs' : 'dockerfileTemplatePython'
|
|
86
|
-
const dockerfileTemplate = fs.readFileSync( path.join( DIRNAME, `./templates/${dockerTemplate}.hbs` ), 'utf8' )
|
|
87
|
+
const dockerfileTemplate = fs.readFileSync( path.join( DIRNAME, `./templates/${dockerTemplate}.hbs` ), 'utf8' )
|
|
87
88
|
|
|
88
89
|
// defaults to the current "official" version of node unless it gets
|
|
89
90
|
// overridden in the leverege.nodeimg package.json stanza
|
package/src/Utils.mjs
CHANGED
|
@@ -39,11 +39,42 @@ export const warning = ( warningText ) => {
|
|
|
39
39
|
console.error( `\n${chalk.yellow.bold( fullWarning )}` )
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
export const errorExit = (
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
export const errorExit = ( arg, opts = {} ) => {
|
|
43
|
+
const exitCode =
|
|
44
|
+
typeof opts.exitCode === 'number' ? opts.exitCode :
|
|
45
|
+
( arg && typeof arg.exitCode === 'number' ) ? arg.exitCode : 1
|
|
46
|
+
|
|
47
|
+
// lets figure out what to print
|
|
48
|
+
let message
|
|
49
|
+
let details
|
|
50
|
+
|
|
51
|
+
if ( arg instanceof Error ) {
|
|
52
|
+
message = arg.message || 'Error'
|
|
53
|
+
details = arg.stack
|
|
54
|
+
} else if ( typeof arg === 'string' ) {
|
|
55
|
+
message = arg
|
|
56
|
+
} else if ( arg && typeof arg === 'object' ) {
|
|
57
|
+
message = 'Error'
|
|
58
|
+
details = JSON.stringify( arg, null, 2 )
|
|
59
|
+
} else if ( arg != null ) {
|
|
60
|
+
message = String( arg )
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.error( `\n${chalk.red.bold( message )}\n` )
|
|
64
|
+
if ( details && details !== message ) {
|
|
65
|
+
console.error( `${details}\n` )
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
process.exit( exitCode )
|
|
46
69
|
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Clears the screen and optionally homes the cursor.
|
|
73
|
+
*/
|
|
74
|
+
export const clear = ( home = true ) => {
|
|
75
|
+
log( `${home ? '\x1B[0f' : ''}\x1B[2J` )
|
|
76
|
+
}
|
|
77
|
+
|
|
47
78
|
/* eslint-enable no-console */
|
|
48
79
|
|
|
49
80
|
export const isHex = ( hex ) => {
|
|
@@ -54,6 +85,22 @@ export const isString = ( s ) => {
|
|
|
54
85
|
return typeof s === 'string' || s instanceof String
|
|
55
86
|
}
|
|
56
87
|
|
|
88
|
+
/**
|
|
89
|
+
* * Sets first char to Upper
|
|
90
|
+
* */
|
|
91
|
+
export const capitalize = ( norm ) => {
|
|
92
|
+
return norm.charAt( 0 ).toUpperCase() + norm.slice( 1 )
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* A simple mS sleep delay.
|
|
97
|
+
*/
|
|
98
|
+
export const sleep = ( ms = 0 ) => {
|
|
99
|
+
return new Promise( ( resolve ) => {
|
|
100
|
+
setTimeout( resolve, ms )
|
|
101
|
+
} )
|
|
102
|
+
}
|
|
103
|
+
|
|
57
104
|
/**
|
|
58
105
|
* General "yes" to proceed function.
|
|
59
106
|
*/
|
|
@@ -226,7 +273,7 @@ export const gitRepoIsDirty = async () => {
|
|
|
226
273
|
|
|
227
274
|
export const parseYamlFile = async ( filePath ) => {
|
|
228
275
|
try {
|
|
229
|
-
const fileContent = await fs.promises.readFile( filePath, 'utf8' )
|
|
276
|
+
const fileContent = await fs.promises.readFile( filePath, 'utf8' ) /* eslint-disable-line security/detect-non-literal-fs-filename */
|
|
230
277
|
return YAML.load( fileContent )
|
|
231
278
|
} catch ( error ) {
|
|
232
279
|
throw new Error( `Failed to parse YAML file ${filePath}: ${error.message}` )
|
|
@@ -538,7 +585,7 @@ export const getRepositoryDescr = async () => {
|
|
|
538
585
|
const helmChart = await parseHelmChart()
|
|
539
586
|
const rootPackageJson = await parseJsonFile( `${gitRoot}/package.json` )
|
|
540
587
|
const rootPyProjectToml = await parsePyProjectToml( `${gitRoot}/pyproject.toml` )
|
|
541
|
-
const isYarnProject =
|
|
588
|
+
const isYarnProject = getIsYarnProject( gitRoot )
|
|
542
589
|
|
|
543
590
|
return {
|
|
544
591
|
currentDir : process.cwd(),
|
|
@@ -683,3 +730,30 @@ export const analyzeRepository = async ( giveGuidance ) => {
|
|
|
683
730
|
isYarnProject
|
|
684
731
|
}
|
|
685
732
|
}
|
|
733
|
+
|
|
734
|
+
export function isDir( path ) {
|
|
735
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
736
|
+
const exists = fs.existsSync( path )
|
|
737
|
+
|
|
738
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
739
|
+
return exists && fs.statSync( path ).isDirectory()
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
export function getSecretsDir() {
|
|
743
|
+
let secretsDir
|
|
744
|
+
|
|
745
|
+
if ( process.env.SECRETS_DIR ) {
|
|
746
|
+
secretsDir = process.env.SECRETS_DIR
|
|
747
|
+
} else if ( isDir( path.join( process.cwd(), 'env' ) ) ) {
|
|
748
|
+
secretsDir = 'env'
|
|
749
|
+
} else {
|
|
750
|
+
secretsDir = 'secrets'
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
return secretsDir
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
export function getIsYarnProject( gitRoot ) {
|
|
757
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
758
|
+
return fs.existsSync( path.join( gitRoot, 'yarn.lock' ) ) || isDir( path.join( gitRoot, '.yarn' ) )
|
|
759
|
+
}
|
package/src/artifacts/Utils.mjs
CHANGED
package/src/bash-funcs
CHANGED
package/src/firebaseDeploy.mjs
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
import pkg from 'enquirer'
|
|
5
5
|
import { $, fs, argv, path, chalk } from 'zx'
|
|
6
6
|
|
|
7
|
+
import { getGitRootDirectory, getIsYarnProject, getSecretsDir } from './Utils.mjs'
|
|
8
|
+
|
|
7
9
|
const { prompt } = pkg
|
|
8
10
|
|
|
9
11
|
// This will preserve coloration from spawned child processes
|
|
@@ -34,8 +36,9 @@ const INTERACTIVE_MODE = !TARGET
|
|
|
34
36
|
|
|
35
37
|
let exited = false
|
|
36
38
|
|
|
37
|
-
const SECRETS_DIR =
|
|
39
|
+
const SECRETS_DIR = await getSecretsDir()
|
|
38
40
|
const EXIT_EVENTS = [ 'SIGINT', 'exit', 'uncaughException', 'unhandledRejection' ]
|
|
41
|
+
const isYarnProject = getIsYarnProject( await getGitRootDirectory() )
|
|
39
42
|
|
|
40
43
|
const [
|
|
41
44
|
firebaserc,
|
|
@@ -211,7 +214,10 @@ try {
|
|
|
211
214
|
const deployType = CHANNEL ? `hosting:channel:deploy ${CHANNEL.name} ${CHANNEL.expiration ? `--expires ${CHANNEL.expiration}` : ''}`.split( ' ' ) : 'deploy'
|
|
212
215
|
const hostingPrefix = CHANNEL ? '' : 'hosting:'
|
|
213
216
|
|
|
214
|
-
|
|
217
|
+
const cleanCommand = isYarnProject ? 'yarn clean' : 'npm run clean'
|
|
218
|
+
const buildCommand = isYarnProject ? 'yarn build' : 'npm run build'
|
|
219
|
+
|
|
220
|
+
await $( { ac } )`${cleanCommand} && DOTENV_CONFIG_PATH=${path.join( process.cwd(), '.env.temp' )} DEPLOYMENT_TARGET=${TARGET.siteId} ${buildCommand} && firebase use ${TARGET.projectId} && firebase ${deployType} --only ${hostingPrefix}${TARGET.siteId}`
|
|
215
221
|
} catch ( err ) {
|
|
216
222
|
console.error( err )
|
|
217
223
|
}
|
package/src/firebaseServe.mjs
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
import pkg from 'enquirer'
|
|
5
5
|
import { $, fs, argv, path, chalk } from 'zx'
|
|
6
6
|
|
|
7
|
+
import { getGitRootDirectory, getIsYarnProject, getSecretsDir } from './Utils.mjs'
|
|
8
|
+
|
|
7
9
|
const { prompt } = pkg
|
|
8
10
|
|
|
9
11
|
// This will preserve coloration from spawned child processes
|
|
@@ -13,9 +15,12 @@ process.env.FORCE_COLOR = '1'
|
|
|
13
15
|
let TARGET = argv._[0]
|
|
14
16
|
let exited = false
|
|
15
17
|
|
|
16
|
-
const SECRETS_DIR = 'secrets'
|
|
17
18
|
const EXIT_EVENTS = [ 'SIGINT', 'exit', 'uncaughtException', 'unhandledRejection' ]
|
|
18
19
|
|
|
20
|
+
const SECRETS_DIR = await getSecretsDir()
|
|
21
|
+
|
|
22
|
+
const isYarnProject = getIsYarnProject( await getGitRootDirectory() )
|
|
23
|
+
|
|
19
24
|
const [
|
|
20
25
|
firebaserc,
|
|
21
26
|
firebaseJson,
|
|
@@ -142,7 +147,8 @@ EXIT_EVENTS
|
|
|
142
147
|
await fs.writeFile( path.join( process.cwd(), '.env.temp' ), envFileContent )
|
|
143
148
|
|
|
144
149
|
try {
|
|
145
|
-
|
|
150
|
+
const serveCommand = isYarnProject ? 'yarn serve' : 'npm run serve'
|
|
151
|
+
await $( { ac } )`DOTENV_CONFIG_PATH=${path.join( process.cwd(), '.env.temp' )} DEPLOYMENT_TARGET=${TARGET.siteId} ${serveCommand} --colors`
|
|
146
152
|
} catch ( err ) {
|
|
147
153
|
console.error( err )
|
|
148
154
|
}
|
package/src/git-tar.sh
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
image:
|
|
2
|
-
tag: #
|
|
2
|
+
tag: # v5.1.0
|
|
3
3
|
|
|
4
4
|
config:
|
|
5
5
|
LOG_CONFIG: '{"type":"pino","level":"warn"}'
|
|
6
|
-
|
|
7
|
-
API_UI_URL: "${PROJECT_ID}.web.app"
|
|
8
|
-
MAIL_GUN_DOMAIN: "${MAIL_GUN_DOMAIN}"
|
|
9
|
-
MAIL_GUN_DEFAULT_SENDER: "no-reply@${MAIL_GUN_DOMAIN}"
|
|
10
|
-
|
|
11
|
-
serviceMonitor:
|
|
12
|
-
enabled: true
|
|
@@ -8,7 +8,7 @@ showInstalling "The Prometheus Operator (kube-prometheus-stack)"
|
|
|
8
8
|
|
|
9
9
|
# https://artifacthub.io/packages/helm/prometheus-community/kube-prometheus-stack
|
|
10
10
|
OCI_CHART="oci://ghcr.io/prometheus-community/charts/kube-prometheus-stack"
|
|
11
|
-
[ -z "$PROMETHEUS_STACK_CHART_VERSION" ] && PROMETHEUS_STACK_CHART_VERSION="80.
|
|
11
|
+
[ -z "$PROMETHEUS_STACK_CHART_VERSION" ] && PROMETHEUS_STACK_CHART_VERSION="80.11.0"
|
|
12
12
|
helm upgrade $NS --install prometheus-stack $OCI_CHART \
|
|
13
13
|
--values prom-operator/prometheus-stack.yaml \
|
|
14
14
|
--version $PROMETHEUS_STACK_CHART_VERSION $HELM_WHAT
|
|
@@ -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="
|
|
20
|
+
[ -z "$TRAEFIK_CHART_VERSION" ] && TRAEFIK_CHART_VERSION="38"
|
|
21
21
|
helm upgrade --install traefik traefik/traefik \
|
|
22
22
|
--namespace $TRAEFIK_NAMESPACE --create-namespace \
|
|
23
23
|
--values traefik/values.yaml \
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Velero Deployment Notes
|
|
2
|
+
|
|
3
|
+
## Why this document exists
|
|
4
|
+
|
|
5
|
+
This document explains **why we had to change the Velero deployment process** and how we now safely manage **CRDs and Helm upgrades** without relying on Bitnami images or brittle Helm hook jobs.
|
|
6
|
+
|
|
7
|
+
These changes were driven by:
|
|
8
|
+
- Removal / breakage of Bitnami `kubectl` images referenced by the `vmware-tanzu/velero` Helm chart
|
|
9
|
+
- Broadcom’s changes to Bitnami image access and licensing
|
|
10
|
+
- The need for **deterministic, version-safe CRD management** on new and existing GKE clusters
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Background: What broke
|
|
15
|
+
|
|
16
|
+
The Velero Helm chart (`vmware-tanzu/velero` >= 11.x) includes a Helm **hook job** that attempts to install or upgrade CRDs using an initContainer based on:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
docker.io/bitnamilegacy/kubectl:<k8s-version>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
On Kubernetes 1.34+ clusters, this image **no longer exists**, causing installs to fail with errors such as:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
ErrImagePull: bitnamilegacy/kubectl:1.34 not found
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Attempts to substitute other kubectl images failed due to hard assumptions in the hook job (e.g. `/tmp/sh`, shell paths, filesystem layout).
|
|
29
|
+
|
|
30
|
+
Because this hook runs **before Helm considers the release successful**, it blocks both fresh installs and upgrades.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Why we disabled Helm-based CRD upgrades
|
|
35
|
+
|
|
36
|
+
Helm does not natively manage CRD upgrades well:
|
|
37
|
+
- CRDs in `crds/` directories are only installed once
|
|
38
|
+
- Schema upgrades require hooks or out-of-band logic
|
|
39
|
+
- Hook jobs are fragile and opaque when they fail
|
|
40
|
+
|
|
41
|
+
The Velero chart’s CRD hook job is effectively doing:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
velero install --crds-only | kubectl apply
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
…but wrapped in a container that is now broken due to image dependencies.
|
|
48
|
+
|
|
49
|
+
**Conclusion:** We disable the Helm CRD hook entirely and manage CRDs explicitly.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Our current approach (high-level)
|
|
54
|
+
|
|
55
|
+
1. **CRDs are managed explicitly**, outside Helm
|
|
56
|
+
2. **CRDs are generated from the exact Velero version being deployed**
|
|
57
|
+
3. Helm is used only for deploying/upgrading the Velero controller and configuration
|
|
58
|
+
4. Helm CRD hooks are disabled (`upgradeCRDs=false`)
|
|
59
|
+
|
|
60
|
+
This makes installs:
|
|
61
|
+
- Reproducible
|
|
62
|
+
- Debuggable
|
|
63
|
+
- Independent of third-party image publishing decisions
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Version alignment is critical
|
|
68
|
+
|
|
69
|
+
The command:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
velero install --crds-only --dry-run -o yaml
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**uses the local `velero` CLI version** to generate CRDs.
|
|
76
|
+
|
|
77
|
+
If the CLI version does not match the Velero server version deployed by Helm, you can:
|
|
78
|
+
- Apply incompatible CRD schemas
|
|
79
|
+
- Introduce validation failures on existing custom resources
|
|
80
|
+
|
|
81
|
+
### Our safeguard
|
|
82
|
+
|
|
83
|
+
`helmup.plugin` now:
|
|
84
|
+
- Reads the chart `appVersion` directly from Helm
|
|
85
|
+
- Verifies the local `velero` CLI version matches exactly
|
|
86
|
+
- Fails fast if versions are mismatched
|
|
87
|
+
|
|
88
|
+
This guarantees CRDs and controllers stay in lockstep.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## When CRDs are applied
|
|
93
|
+
|
|
94
|
+
CRDs are **not applied on every deploy**.
|
|
95
|
+
|
|
96
|
+
They are applied only when:
|
|
97
|
+
- Bootstrapping a new cluster
|
|
98
|
+
- Upgrading the Velero chart to a version with a new `appVersion`
|
|
99
|
+
|
|
100
|
+
This is controlled explicitly via:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
VELERO_APPLY_CRDS=1 helmup velero
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Normal configuration-only updates do **not** touch CRDs.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Summary of changes
|
|
111
|
+
|
|
112
|
+
| Area | Old behavior | New behavior |
|
|
113
|
+
|-----|-------------|-------------|
|
|
114
|
+
| CRD management | Helm hook job | Explicit, pinned CRD apply |
|
|
115
|
+
| kubectl dependency | Bitnami legacy image | None |
|
|
116
|
+
| Version safety | Implicit | Enforced |
|
|
117
|
+
| Failure mode | Late, opaque | Early, explicit |
|
|
118
|
+
| New cluster installs | Unreliable | Deterministic |
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Key takeaways
|
|
123
|
+
|
|
124
|
+
- **CRDs are part of the API, not application config** — they must be versioned and applied intentionally
|
|
125
|
+
- Helm hooks are not a reliable mechanism for critical cluster-scoped resources
|
|
126
|
+
- Pinning tool versions eliminates an entire class of upgrade failures
|
|
127
|
+
- This approach aligns with GitOps and production-safe Kubernetes practices
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## References
|
|
132
|
+
|
|
133
|
+
- Velero documentation: https://velero.io/docs/
|
|
134
|
+
- Velero Helm charts: https://github.com/vmware-tanzu/helm-charts
|
|
135
|
+
- Kubernetes CRD management: https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/
|
|
@@ -2,10 +2,36 @@
|
|
|
2
2
|
#
|
|
3
3
|
showInstalling "Velero Backup System"
|
|
4
4
|
|
|
5
|
+
# We use a specific version for the velero chart because we need to control
|
|
6
|
+
# the version of the locally installed velero client application.
|
|
7
|
+
#
|
|
8
|
+
VELERO_CHART_VERSION="11.3.1"
|
|
9
|
+
EXPECTED_CLIENT_VERSION="v1.17.1"
|
|
10
|
+
|
|
5
11
|
addHelmRepo vmware-tanzu https://vmware-tanzu.github.io/helm-charts
|
|
6
12
|
|
|
7
|
-
|
|
13
|
+
commandChecker velero # verify the velero client is installed
|
|
14
|
+
|
|
15
|
+
local client_version="$(velero version --client-only 2>/dev/null | awk -F': ' '/Version:/ {print $2; exit}')"
|
|
16
|
+
|
|
17
|
+
if [[ "$client_version" != "$EXPECTED_CLIENT_VERSION" ]]; then
|
|
18
|
+
cat<<WRONG_VELERO_APP_VERSION
|
|
19
|
+
|
|
20
|
+
ERROR: velero CLI version mismatch.
|
|
21
|
+
Expected: `color g "${EXPECTED_CLIENT_VERSION}"`
|
|
22
|
+
Found: `color r "${client_version}"`
|
|
23
|
+
|
|
24
|
+
Fix => `color y "install velero ${EXPECTED_CLIENT_VERSION}"`
|
|
25
|
+
WRONG_VELERO_APP_VERSION
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
# If the CRDs need to be updated set VELERO_APPLY_CRDS!
|
|
8
30
|
#
|
|
31
|
+
if [[ "${VELERO_APPLY_CRDS:-0}" == "1" ]]; then
|
|
32
|
+
velero install --crds-only --dry-run -o yaml | kubectl apply -f - --validate=false
|
|
33
|
+
fi
|
|
34
|
+
|
|
9
35
|
# Build the bucket, region and SA email variables and use --set to mod the
|
|
10
36
|
# chart values as opposed to using an ${label} approach. This eliminates
|
|
11
37
|
# the need for an overwhelm generated values.yaml file which also contains
|
|
@@ -13,7 +39,7 @@ addHelmRepo vmware-tanzu https://vmware-tanzu.github.io/helm-charts
|
|
|
13
39
|
#
|
|
14
40
|
# Note: GCP_PROJECT_ID is passed down from helmup's environment.
|
|
15
41
|
#
|
|
16
|
-
GCE_BUCKET="$GCP_PROJECT_ID-velero-$VELERO_CHART_VERSION"
|
|
42
|
+
GCE_BUCKET="$GCP_PROJECT_ID-velero-${VELERO_CHART_VERSION%%.*}"
|
|
17
43
|
GCE_REGION=`overwhelm -k GCE_REGION`
|
|
18
44
|
GCE_SA_EMAIL="velero@${GCP_PROJECT_ID}.iam.gserviceaccount.com"
|
|
19
45
|
|
|
@@ -27,6 +53,7 @@ helm upgrade \
|
|
|
27
53
|
--namespace velero --create-namespace \
|
|
28
54
|
--values velero/values-local.yaml \
|
|
29
55
|
--version $VELERO_CHART_VERSION $HELM_WHAT \
|
|
56
|
+
--set upgradeCRDs=false \
|
|
30
57
|
--set configuration.backupStorageLocation[0].bucket="$GCE_BUCKET" \
|
|
31
58
|
--set configuration.backupStorageLocation[0].config.serviceAccount="$GCE_SA_EMAIL" \
|
|
32
59
|
--set configuration.volumeSnapshotLocation[0].config.snapshotLocation="$GCE_REGION"
|
|
@@ -137,7 +137,7 @@ nodeSelector: {}
|
|
|
137
137
|
# See => https://github.com/vmware-tanzu/velero-plugin-for-gcp
|
|
138
138
|
initContainers:
|
|
139
139
|
- name: velero-plugin-for-gcp
|
|
140
|
-
image: velero/velero-plugin-for-gcp:v1.
|
|
140
|
+
image: velero/velero-plugin-for-gcp:v1.13.1
|
|
141
141
|
volumeMounts:
|
|
142
142
|
- mountPath: /target
|
|
143
143
|
name: plugins
|