@leverege/build-tools 2.66.8 â 2.67.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/settings.json +5 -0
- package/package.json +9 -9
- package/src/Docker.mjs +18 -17
- package/src/DockerPy.mjs +41 -40
- package/src/Utils.mjs +31 -28
- package/src/docker-to-registry-py.mjs +1 -5
- package/src/helm-charts/cnpg-operator/helmup.plugin +5 -15
- package/src/helm-charts/prom-operator/helmup.plugin +5 -1
- package/src/helm-charts/prom-operator/prometheus-stack.yaml.ovh +5 -0
- package/src/helm-charts/prom-operator/rules/cnpg-rules.yaml +1 -1
- package/src/helm-charts/prom-operator/rules/elasticsearch-rules.yaml +6 -6
- package/src/helm-charts/prom-operator/rules/kubestate-rules.yaml +4 -4
- package/src/helm-charts/prom-operator/stackdriver-exporter-gpu-rules.yaml +1 -1
- package/src/helm-charts/traefik/helmup.plugin +1 -1
- package/src/helm-charts/velero/helmup.plugin +1 -1
- package/src/helm-charts/velero/values-local.yaml +1 -1
- package/src/helmup.sh +17 -9
- package/src/modify-gcs-access.mjs +84 -0
- package/src/templates/cloudBuildSteps.hbs +6 -6
- package/src/templates/dockerfileTemplatePy.hbs +38 -34
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leverege/build-tools",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.67.1",
|
|
4
4
|
"description": "A collection of build / support tools for Leverege developers",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
@@ -66,14 +66,14 @@
|
|
|
66
66
|
"chalk": "^5.4.1",
|
|
67
67
|
"command-line-args": "^6.0.1",
|
|
68
68
|
"command-line-usage": "^7.0.3",
|
|
69
|
-
"commander": "^
|
|
69
|
+
"commander": "^14.0.0",
|
|
70
70
|
"deepmerge": "^4.3.1",
|
|
71
71
|
"enquirer": "^2.4.1",
|
|
72
|
-
"execa": "^9.
|
|
72
|
+
"execa": "^9.6.0",
|
|
73
73
|
"glob": "^11.0.2",
|
|
74
74
|
"handlebars": "^4.7.8",
|
|
75
|
-
"ignore": "^7.0.
|
|
76
|
-
"inquirer": "^12.6.
|
|
75
|
+
"ignore": "^7.0.5",
|
|
76
|
+
"inquirer": "^12.6.3",
|
|
77
77
|
"js-yaml": "^4.1.0",
|
|
78
78
|
"jsdoc": "^4.0.4",
|
|
79
79
|
"ms": "^2.1.3",
|
|
@@ -83,13 +83,13 @@
|
|
|
83
83
|
"package-up": "^5.0.0",
|
|
84
84
|
"readline-sync": "^1.4.10",
|
|
85
85
|
"semver": "^7.7.2",
|
|
86
|
-
"shell-quote": "^1.8.
|
|
87
|
-
"simple-git": "^3.
|
|
86
|
+
"shell-quote": "^1.8.3",
|
|
87
|
+
"simple-git": "^3.28.0",
|
|
88
88
|
"toml": "^3.0.0",
|
|
89
|
-
"zx": "^8.5.
|
|
89
|
+
"zx": "^8.5.5"
|
|
90
90
|
},
|
|
91
91
|
"devDependencies": {
|
|
92
92
|
"@leverege/eslint-config-leverege": "^5.0.1",
|
|
93
|
-
"npm": "^11.
|
|
93
|
+
"npm": "^11.4.1"
|
|
94
94
|
}
|
|
95
95
|
}
|
package/src/Docker.mjs
CHANGED
|
@@ -177,7 +177,6 @@ const buildContainerImage = async ( {
|
|
|
177
177
|
artifactProject, artifactRegistry, containerName,
|
|
178
178
|
imageVersion, isNpmWorkspace, options
|
|
179
179
|
} ) => {
|
|
180
|
-
const useCloudBuildFile = true // { useCloudBuildFile } = options || {}
|
|
181
180
|
const buildWorkspace = './build/workspace' // passed to Cloud Build
|
|
182
181
|
const imageName = `${artifactRegistry}/images/${containerName}`
|
|
183
182
|
|
|
@@ -213,35 +212,37 @@ const buildContainerImage = async ( {
|
|
|
213
212
|
|
|
214
213
|
setPreviousBuild( imageVersion ) // update docker/.previous file
|
|
215
214
|
|
|
216
|
-
const { fullName :
|
|
217
|
-
const
|
|
215
|
+
const { fullName : imageBuilder } = await getGcpAccount()
|
|
216
|
+
const imageInfoTag = `zzinfo_${defaultSettings.date}_${defaultSettings.nodeimage}_${imageBuilder}`
|
|
218
217
|
const gcloudProjectNumber = await shellCmd( `gcloud projects describe ${artifactProject} --format="value(projectNumber)"` )
|
|
219
|
-
const gcloudBuildSA = `--service-account=projects/${artifactProject}/serviceAccounts/${gcloudProjectNumber}-compute@developer.gserviceaccount.com`
|
|
220
|
-
const gcloudLogs = `--gcs-log-dir ${formCloudBuildBucketName( artifactProject )}/log`
|
|
221
|
-
const imageTag = `${defaultSettings.date}-${defaultSettings.nodeimage}-${builderName}`
|
|
222
218
|
|
|
223
|
-
//
|
|
224
|
-
const cloudBuildFile = './
|
|
219
|
+
// build the cloudbuild.yaml from the handlebars template
|
|
220
|
+
const cloudBuildFile = './build/cloudbuild.yaml'
|
|
225
221
|
const cbst = handlebars.compile( cloudBuildSteps, { noEscape : true } )
|
|
226
|
-
const cbs = cbst( { artifactProject, artifactRegistry, imageName, imageVersion,
|
|
222
|
+
const cbs = cbst( { artifactProject, artifactRegistry, imageName, imageVersion, imageInfoTag, } )
|
|
227
223
|
fs.writeFileSync( cloudBuildFile, cbs )
|
|
228
224
|
|
|
229
|
-
|
|
230
|
-
|
|
225
|
+
// build up the cloud build command and options
|
|
226
|
+
const cloudBuildCmd = `gcloud builds submit --project ${artifactProject}`
|
|
227
|
+
const cloudBuildLogs = `--gcs-log-dir ${formCloudBuildBucketName( artifactProject )}/log`
|
|
228
|
+
const cloudBuildCfg = `--config=${cloudBuildFile}`
|
|
229
|
+
const cloudBuildSA = `--service-account=${gcloudProjectNumber}-compute@cloudbuild.gserviceaccount.com`
|
|
230
|
+
|
|
231
231
|
log( chalk.green.bold( `
|
|
232
|
-
*** Submitting Build as ${
|
|
233
|
-
${
|
|
234
|
-
${
|
|
235
|
-
${
|
|
236
|
-
${
|
|
232
|
+
*** Submitting Build as ${imageBuilder} ***
|
|
233
|
+
${cloudBuildCmd} \\
|
|
234
|
+
${cloudBuildLogs} \\
|
|
235
|
+
${cloudBuildCfg} \\
|
|
236
|
+
${cloudBuildSA}
|
|
237
237
|
` ) )
|
|
238
238
|
|
|
239
239
|
if ( process.env.DRY_RUN === '1' ) {
|
|
240
|
+
log( `Would have run => ${cloudBuildCmd} ${cloudBuildLogs} ${cloudBuildCfg} ./build` )
|
|
240
241
|
log( '\n***Exiting from DRY_RUN\n' )
|
|
241
242
|
process.exit( 0 )
|
|
242
243
|
}
|
|
243
244
|
|
|
244
|
-
await shellCmd( `${
|
|
245
|
+
await shellCmd( `${cloudBuildCmd} ${cloudBuildLogs} ${cloudBuildCfg} ./build`, { stdio : 'inherit' } )
|
|
245
246
|
}
|
|
246
247
|
|
|
247
248
|
const repoCleanup = async () => {
|
package/src/DockerPy.mjs
CHANGED
|
@@ -43,10 +43,8 @@ const bashrcPyTemplate = fs.readFileSync( path.join( DIRNAME, './templates/bashr
|
|
|
43
43
|
bashrcTemplate = `${bashrcTemplate}\n${bashrcPyTemplate}`
|
|
44
44
|
|
|
45
45
|
const defaultSettings = {
|
|
46
|
-
apkadds : '',
|
|
47
46
|
date : getDateTimestamp(),
|
|
48
|
-
|
|
49
|
-
imageBase : 'node:jod-alpine',
|
|
47
|
+
imageBase : 'python:3.12.8-slim-bookworm',
|
|
50
48
|
npmlogging : process.env.VERBOSE_NPM_LOGGING ? '--ddd' : '--silent',
|
|
51
49
|
npmVersion : 'npm@11',
|
|
52
50
|
pluginfile : '# NO PLUGIN',
|
|
@@ -55,6 +53,7 @@ const defaultSettings = {
|
|
|
55
53
|
runuser : 'python',
|
|
56
54
|
}
|
|
57
55
|
|
|
56
|
+
// the previous build version is simply informational for the user
|
|
58
57
|
const previousFile = './docker/.previous'
|
|
59
58
|
|
|
60
59
|
function getPreviousBuild() {
|
|
@@ -65,16 +64,26 @@ const setPreviousBuild = ( version ) => {
|
|
|
65
64
|
fs.writeFileSync( previousFile, version )
|
|
66
65
|
}
|
|
67
66
|
|
|
67
|
+
// the base image string to be in the form of an image name and a version
|
|
68
|
+
// for example:
|
|
69
|
+
//
|
|
70
|
+
// python:3.12.8-slim-bookworm becomes python-3.12.8
|
|
71
|
+
// nvcr.io/nvidia/cuda:12.3.1-base-ubuntu22.04 becomes cuda-12.3.1
|
|
72
|
+
//
|
|
73
|
+
// which will then be used to tag the image for information purposes only
|
|
74
|
+
const extractBaseImageTag = ( imageStr ) => {
|
|
75
|
+
const match = imageStr.match( /(\w+:\d+(?:\.\d+)*)(?=-)/ ) // eslint-disable-line security/detect-unsafe-regex
|
|
76
|
+
return match ? match[1].replace( ':', '-' ) : null
|
|
77
|
+
}
|
|
78
|
+
|
|
68
79
|
function generateDockerfile( options ) {
|
|
69
80
|
|
|
70
81
|
const settings = { ...defaultSettings, ...options }
|
|
71
82
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
settings.image = settings.imageBase
|
|
77
|
-
}
|
|
83
|
+
settings.image = settings.imageBase
|
|
84
|
+
// TODO: this is a janky way to pass the image tag back out
|
|
85
|
+
defaultSettings.buildTag = extractBaseImageTag( settings.image )
|
|
86
|
+
|
|
78
87
|
// Ensure the presence of the docker dir with plugin and bashrc - this code
|
|
79
88
|
// could probably be tightened up a little.
|
|
80
89
|
try {
|
|
@@ -155,18 +164,13 @@ async function buildContainerImage( {
|
|
|
155
164
|
artifactProject, artifactRegistry, containerName,
|
|
156
165
|
imageVersion, isNpmWorkspace, options
|
|
157
166
|
} ) {
|
|
158
|
-
|
|
159
|
-
const useCloudBuildFile = true
|
|
160
167
|
const buildWorkspace = './build/workspace' // passed to Cloud Build
|
|
161
168
|
const imageName = `${artifactRegistry}/images/${containerName}`
|
|
162
169
|
|
|
170
|
+
// clean up any residual build cruft (should be clean though)
|
|
163
171
|
if ( fs.existsSync( buildWorkspace ) ) {
|
|
164
172
|
fs.renameSync( buildWorkspace, `${buildWorkspace}-junk` )
|
|
165
|
-
|
|
166
|
-
fs.rmSync( `${buildWorkspace}-junk`, { recursive : true } )
|
|
167
|
-
} catch ( err ) {
|
|
168
|
-
log( `***Error: from Docker.buildContainerImage\n\n${chalk.bold.yellow( err )}` )
|
|
169
|
-
}
|
|
173
|
+
fs.rm( `${buildWorkspace}-junk`, { recursive : true }, ( err ) => {} ) // fire and forget
|
|
170
174
|
}
|
|
171
175
|
|
|
172
176
|
// TODO: This is janky - I'll clean it up later
|
|
@@ -176,48 +180,45 @@ async function buildContainerImage( {
|
|
|
176
180
|
fs.mkdirSync( './build' )
|
|
177
181
|
fs.renameSync( './workspace', buildWorkspace )
|
|
178
182
|
fs.renameSync( './docker/Dockerfile', './build/Dockerfile' )
|
|
183
|
+
fs.renameSync( './docker/bashrc', './build/bashrc' )
|
|
179
184
|
// fs.cpSync( `${process.env.HOME}/.npmrc`, './build/.npmrc' )
|
|
180
|
-
fs.cpSync( './docker/bashrc', './build/bashrc' )
|
|
181
185
|
// fs.cpSync( './package.json', `${buildWorkspace}/package.json` )
|
|
182
186
|
// fs.cpSync( './package-lock.json', `${buildWorkspace}/package-lock.json` )
|
|
183
187
|
|
|
184
188
|
setPreviousBuild( imageVersion ) // update docker/.previous file
|
|
185
189
|
|
|
186
|
-
const { fullName :
|
|
187
|
-
const
|
|
190
|
+
const { fullName : imageBuilder } = await getGcpAccount()
|
|
191
|
+
const imageArch = defaultSettings.buildTag
|
|
192
|
+
const imageInfoTag = `zzinfo_${imageArch}_${imageBuilder}_${defaultSettings.date}`
|
|
188
193
|
const gcloudProjectNumber = await shellCmd( `gcloud projects describe ${artifactProject} --format="value(projectNumber)"` )
|
|
194
|
+
|
|
195
|
+
// build the cloudbuild.yaml from the handlebars template
|
|
196
|
+
const cloudBuildFile = './build/cloudbuild.yaml'
|
|
197
|
+
const cbst = handlebars.compile( cloudBuildSteps, { noEscape : true } )
|
|
198
|
+
const cbs = cbst( { artifactProject, artifactRegistry, imageName, imageVersion, imageInfoTag, } )
|
|
199
|
+
fs.writeFileSync( cloudBuildFile, cbs )
|
|
200
|
+
|
|
201
|
+
// build up the cloud build command and options
|
|
202
|
+
const cloudBuildCmd = `gcloud builds submit --project ${artifactProject}`
|
|
203
|
+
const cloudBuildLogs = `--gcs-log-dir ${formCloudBuildBucketName( artifactProject )}/log`
|
|
204
|
+
const cloudBuildCfg = `--config=${cloudBuildFile}`
|
|
189
205
|
const cloudBuildSA = `--service-account=${gcloudProjectNumber}-compute@cloudbuild.gserviceaccount.com`
|
|
190
|
-
const gcloudLogs = `--gcs-log-dir ${formCloudBuildBucketName( artifactProject )}/log`
|
|
191
|
-
let gcloudTags = `--tag ${imageName}:${imageVersion}`
|
|
192
|
-
|
|
193
|
-
if ( useCloudBuildFile ) {
|
|
194
|
-
const cloudBuildFile = './docker/cloudbuild.yaml'
|
|
195
|
-
gcloudTags = `--config=${cloudBuildFile}`
|
|
196
|
-
const cbst = handlebars.compile( cloudBuildSteps, { noEscape : true } )
|
|
197
|
-
const cbs = cbst( { artifactProject, artifactRegistry, imageName, imageVersion } )
|
|
198
|
-
|
|
199
|
-
fs.writeFileSync( cloudBuildFile, cbs )
|
|
200
|
-
}
|
|
201
206
|
|
|
202
207
|
log( chalk.green.bold( `
|
|
203
|
-
*** Submitting Build as ${
|
|
204
|
-
${
|
|
205
|
-
${
|
|
206
|
-
${
|
|
208
|
+
*** Submitting Build as ${imageBuilder} ***
|
|
209
|
+
${cloudBuildCmd} \\
|
|
210
|
+
${cloudBuildLogs} \\
|
|
211
|
+
${cloudBuildCfg} \\
|
|
207
212
|
${cloudBuildSA}
|
|
208
213
|
` ) )
|
|
209
214
|
|
|
210
215
|
if ( process.env.DRY_RUN === '1' ) {
|
|
216
|
+
log( `Would have run => ${cloudBuildCmd} ${cloudBuildLogs} ${cloudBuildCfg} ./build` )
|
|
211
217
|
log( '\n***Exiting from DRY_RUN\n' )
|
|
212
218
|
process.exit( 0 )
|
|
213
219
|
}
|
|
214
220
|
|
|
215
|
-
await shellCmd( `${
|
|
216
|
-
|
|
217
|
-
const imageTag = `${defaultSettings.date}-${defaultSettings.nodeimage}-${builderName}`
|
|
218
|
-
await shellCmd(
|
|
219
|
-
`gcloud container images add-tag --quiet ${imageName}:${imageVersion} ${imageName}:${imageTag} ${imageName}:latest`
|
|
220
|
-
)
|
|
221
|
+
await shellCmd( `${cloudBuildCmd} ${cloudBuildLogs} ${cloudBuildCfg} ./build`, { stdio : 'inherit' } )
|
|
221
222
|
}
|
|
222
223
|
|
|
223
224
|
async function repoCleanup() {
|
package/src/Utils.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import inquirer from 'inquirer'
|
|
|
9
9
|
import { glob } from 'glob'
|
|
10
10
|
import { packageUp } from 'package-up'
|
|
11
11
|
import { parse } from 'shell-quote'
|
|
12
|
+
import toml from 'toml'
|
|
12
13
|
import YAML from 'js-yaml'
|
|
13
14
|
|
|
14
15
|
// __dirname goes away with ESM so we take this approach instead and
|
|
@@ -127,12 +128,6 @@ export const getDirectories = ( { path : srcPath = process.cwd(), respectGitigno
|
|
|
127
128
|
.map( dirent => dirent.name )
|
|
128
129
|
}
|
|
129
130
|
|
|
130
|
-
export const xgetDirectories = ( srcPath = process.cwd() ) => {
|
|
131
|
-
return fs.readdirSync( srcPath, { withFileTypes : true } ) // eslint-disable-line security/detect-non-literal-fs-filename
|
|
132
|
-
.filter( dirent => dirent.isDirectory() )
|
|
133
|
-
.map( dirent => dirent.name )
|
|
134
|
-
}
|
|
135
|
-
|
|
136
131
|
// returns YYYMMDD-hhmm
|
|
137
132
|
export const getDateTimestamp = () => {
|
|
138
133
|
const fullDTS = new Date().toISOString().replace( /[-T:]/g, '' ).slice( 0, 14 )
|
|
@@ -170,7 +165,8 @@ export const getGcpSecret = async ( gcpProject, secretName, opts ) => {
|
|
|
170
165
|
}
|
|
171
166
|
}
|
|
172
167
|
|
|
173
|
-
|
|
168
|
+
// just a wrapper to run command with try/catch
|
|
169
|
+
const tryCommand = async ( cmd, debugLabel ) => {
|
|
174
170
|
try {
|
|
175
171
|
const result = await shellCmd( cmd )
|
|
176
172
|
debug( { result }, `<==${debugLabel}` )
|
|
@@ -181,16 +177,16 @@ const runGitCommand = async ( cmd, debugLabel ) => {
|
|
|
181
177
|
}
|
|
182
178
|
}
|
|
183
179
|
|
|
184
|
-
export const getGitBranch = async () =>
|
|
185
|
-
export const getGitDiff = async opts =>
|
|
186
|
-
export const getGitHooksPath = async () =>
|
|
187
|
-
export const setGitHooksPath = async hookPath =>
|
|
188
|
-
export const getGitRootDirectory = async () =>
|
|
180
|
+
export const getGitBranch = async () => tryCommand( 'git rev-parse --abbrev-ref HEAD', 'getGitCurrentBranch' )
|
|
181
|
+
export const getGitDiff = async opts => tryCommand( `git diff ${opts}`, 'getGitDiff' )
|
|
182
|
+
export const getGitHooksPath = async () => tryCommand( 'git config --get core.hooksPath', 'getGitHooksPath' )
|
|
183
|
+
export const setGitHooksPath = async hookPath => tryCommand( `git config core.hooksPath ${hookPath}`, 'setGitHooksPath' )
|
|
184
|
+
export const getGitRootDirectory = async () => tryCommand( 'git rev-parse --show-toplevel', 'getGitRootDirectory' )
|
|
189
185
|
export const getGitBranchAndUpstream = async () => {
|
|
190
186
|
const branch = await getGitBranch() ?? 'detached'
|
|
191
187
|
let upstream = 'no-upstream'
|
|
192
188
|
try {
|
|
193
|
-
upstream = await
|
|
189
|
+
upstream = await tryCommand( `git rev-parse --abbrev-ref ${branch}@{u}`, 'getGitUpstream' ) ?? 'no-upstream'
|
|
194
190
|
} catch ( error ) {}
|
|
195
191
|
return { branch, upstream }
|
|
196
192
|
}
|
|
@@ -201,6 +197,8 @@ export const gitRepoIsDirty = async () => {
|
|
|
201
197
|
}
|
|
202
198
|
|
|
203
199
|
export const parseJsonFile = async ( jsonFile ) => {
|
|
200
|
+
// TODO: rework this file exists logic / throw
|
|
201
|
+
if ( !fs.existsSync( jsonFile ) ) { return undefined } // eslint-disable-line security/detect-non-literal-fs-filename
|
|
204
202
|
/* eslint-disable security/detect-non-literal-fs-filename */
|
|
205
203
|
if ( fs.existsSync( jsonFile ) ) {
|
|
206
204
|
try {
|
|
@@ -222,7 +220,9 @@ const deprecated = ( opts ) => {
|
|
|
222
220
|
process.exit( 1 )
|
|
223
221
|
}
|
|
224
222
|
|
|
225
|
-
export const parsePackageJson = async ( packageFileName ) => {
|
|
223
|
+
export const parsePackageJson = async ( packageFileName = './package.json' ) => {
|
|
224
|
+
if ( !fs.existsSync( packageFileName ) ) { return undefined } // eslint-disable-line security/detect-non-literal-fs-filename
|
|
225
|
+
|
|
226
226
|
const packageJson = await parseJsonFile( packageFileName )
|
|
227
227
|
|
|
228
228
|
// deprecation checks are here to ease the pain of upgrading older repos
|
|
@@ -411,6 +411,13 @@ https://console.cloud.google.com/artifacts/browse/leverege-registry?project=leve
|
|
|
411
411
|
return { artifactRegistry, ...packageJson }
|
|
412
412
|
}
|
|
413
413
|
|
|
414
|
+
export const parsePyProjectToml = async ( packageFileName = './pyproject.toml' ) => {
|
|
415
|
+
if ( !fs.existsSync( packageFileName ) ) { return undefined } // eslint-disable-line security/detect-non-literal-fs-filename
|
|
416
|
+
const tomlStr = fs.readFileSync( packageFileName ) // eslint-disable-line security/detect-non-literal-fs-filename
|
|
417
|
+
const pyproject = toml.parse( tomlStr )
|
|
418
|
+
return pyproject
|
|
419
|
+
}
|
|
420
|
+
|
|
414
421
|
export const parseHelmChart = async ( helmroot = './helm' ) => {
|
|
415
422
|
/* eslint-disable security/detect-non-literal-fs-filename */
|
|
416
423
|
if ( !fs.existsSync( helmroot ) ) { return undefined }
|
|
@@ -453,12 +460,8 @@ export const getRepositoryDescr = async () => {
|
|
|
453
460
|
const subPkgs = await glob( `${gitRoot}/packages/**/package.json`, { ignore : '/**/node_modules/**' } )
|
|
454
461
|
const helmChart = await parseHelmChart()
|
|
455
462
|
const isMonoRepo = subPkgs?.length > 0
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
rootPackageJson = await parseJsonFile( `${gitRoot}/package.json` )
|
|
459
|
-
} catch ( error ) {
|
|
460
|
-
rootPackageJson = { isInvalid : true, error : 'invalid or non-existent root package.json file' }
|
|
461
|
-
}
|
|
463
|
+
const rootPackageJson = await parseJsonFile( `${gitRoot}/package.json` )
|
|
464
|
+
const rootPyProjectToml = await parsePyProjectToml( `${gitRoot}/pyproject.toml` )
|
|
462
465
|
const isNpmWorkspace = rootPackageJson?.workspaces?.length > 0
|
|
463
466
|
|
|
464
467
|
return {
|
|
@@ -473,6 +476,7 @@ export const getRepositoryDescr = async () => {
|
|
|
473
476
|
isMonoRepo,
|
|
474
477
|
isNpmWorkspace,
|
|
475
478
|
rootPackageJson,
|
|
479
|
+
rootPyProjectToml,
|
|
476
480
|
subPkgs,
|
|
477
481
|
}
|
|
478
482
|
}
|
|
@@ -484,6 +488,7 @@ export const analyzeRepository = async ( giveGuidance ) => {
|
|
|
484
488
|
helmChart,
|
|
485
489
|
isMonoRepo,
|
|
486
490
|
rootPackageJson,
|
|
491
|
+
rootPyProjectToml,
|
|
487
492
|
isNpmWorkspace,
|
|
488
493
|
} = await getRepositoryDescr()
|
|
489
494
|
|
|
@@ -527,15 +532,12 @@ export const analyzeRepository = async ( giveGuidance ) => {
|
|
|
527
532
|
await shellCmd( 'npm run build' )
|
|
528
533
|
}
|
|
529
534
|
|
|
530
|
-
const buildToolsVersion = closestPackageJson
|
|
531
|
-
if ( !buildToolsVersion ) {
|
|
532
|
-
errorExit( 'Error: missing devDependency for @leverege/build-tools in package.json' )
|
|
533
|
-
}
|
|
535
|
+
const buildToolsVersion = closestPackageJson?.devDependencies['@leverege/build-tools'] || closestPackageJson?.dependencies['@leverege/build-tools']
|
|
534
536
|
|
|
535
|
-
const containerName = closestPackageJson
|
|
536
|
-
const artifactProject = closestPackageJson
|
|
537
|
-
const artifactRegistry = closestPackageJson
|
|
538
|
-
const packageVersion = `v${closestPackageJson.version}`
|
|
537
|
+
const containerName = closestPackageJson?.name.replace( /^@[^/]+\//, '' ) || rootPyProjectToml?.project.name
|
|
538
|
+
const artifactProject = closestPackageJson?.artifactRegistry.project || 'unused'
|
|
539
|
+
const artifactRegistry = closestPackageJson?.leverege.registry || rootPyProjectToml?.registry
|
|
540
|
+
const packageVersion = `v${closestPackageJson?.version || rootPyProjectToml?.project.version}`
|
|
539
541
|
|
|
540
542
|
const currentDir = process.cwd()
|
|
541
543
|
const closestPkg = await packageUp()
|
|
@@ -567,6 +569,7 @@ export const analyzeRepository = async ( giveGuidance ) => {
|
|
|
567
569
|
isMonoRepo,
|
|
568
570
|
isNpmWorkspace,
|
|
569
571
|
rootPackageJson,
|
|
572
|
+
rootPyProjectToml,
|
|
570
573
|
closestPackageJson,
|
|
571
574
|
packageVersion,
|
|
572
575
|
subPkgs,
|
|
@@ -89,7 +89,7 @@ const pyproject = toml.parse( tomlStr )
|
|
|
89
89
|
const repoDescr = {
|
|
90
90
|
artifactProject : pyproject.leverege.project || 'leverege-registry',
|
|
91
91
|
containerName : pyproject.project.name,
|
|
92
|
-
packageVersion : pyproject.project.version
|
|
92
|
+
packageVersion : `v${pyproject.project.version}`,
|
|
93
93
|
artifactRegistry : pyproject.leverege.registry || 'us-docker.pkg.dev/leverege-registry/stack',
|
|
94
94
|
registryFolder : `${pyproject.leverege.registryFolder}/images/${pyproject.project.name}`,
|
|
95
95
|
imageBase : pyproject.leverege.imageBase,
|
|
@@ -219,13 +219,9 @@ log( `
|
|
|
219
219
|
|
|
220
220
|
${chalk.green.bold( 'Docker Information:' )}
|
|
221
221
|
NodeImage: ${chalk.green.bold( dockerInfo.imageBase || `node:${dockerInfo.nodeimage}` )}
|
|
222
|
-
Docker Env: ${chalk.green.bold( dockerInfo.buildEnv || 'alpine' )}
|
|
223
|
-
AddedPkgs: ${chalk.green.bold( dockerInfo.apkadds )}
|
|
224
222
|
Run User: ${chalk.green.bold( dockerInfo.runuser )}
|
|
225
223
|
Previous: ${chalk.yellow.bold( dockerInfo.previousBuild )}
|
|
226
224
|
DateStamp: ${chalk.green.bold( dockerInfo.date )}
|
|
227
|
-
NPM Logs: ${chalk.green.bold( dockerInfo.npmlogging )}
|
|
228
|
-
NPM Version: ${chalk.green.bold( dockerInfo.npmVersion )}
|
|
229
225
|
` )
|
|
230
226
|
|
|
231
227
|
if ( imageVersion !== packageVersion ) {
|
|
@@ -2,26 +2,16 @@
|
|
|
2
2
|
#
|
|
3
3
|
# See => https://cloudnative-pg.io/documentation/current/installation_upgrade/
|
|
4
4
|
#
|
|
5
|
-
OPVER="1.
|
|
5
|
+
OPVER="1.26.0" # 05/23/2025
|
|
6
6
|
|
|
7
7
|
createNamespaceIfNeeded cnpg-system
|
|
8
8
|
|
|
9
9
|
kubectl apply --server-side -f \
|
|
10
10
|
https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-${OPVER%.*}/releases/cnpg-${OPVER}.yaml $K8S_WHAT
|
|
11
11
|
|
|
12
|
-
#
|
|
13
|
-
# added to the network layer on k8s. It should look like this:
|
|
12
|
+
# NOTE: installing release candidates may reference something that looks like:
|
|
14
13
|
#
|
|
15
|
-
#
|
|
16
|
-
# Ports : 8000,9443 <= kubectl cnpg status and webhooks
|
|
17
|
-
# Filters: 172.16.0.0/28 <= k8s control plane
|
|
14
|
+
# OPVER="1.26.0-rc1" # 03/28/2025
|
|
18
15
|
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#addHelmRepo cnpg https://cloudnative-pg.github.io/charts
|
|
22
|
-
#
|
|
23
|
-
#helm upgrade --install cnpg cnpg/cloudnative-pg \
|
|
24
|
-
# --namespace cnpg-system --create-namespace \
|
|
25
|
-
# --set webhook.port="10250" $HELM_WHAT
|
|
26
|
-
#
|
|
27
|
-
#removeHelmRepo cnpg
|
|
16
|
+
# kubectl apply --server-side -f \
|
|
17
|
+
# https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/main/releases/cnpg-${OPVER}.yaml $K8S_WHAT
|
|
@@ -6,7 +6,7 @@ addHelmRepo prometheus-community https://prometheus-community.github.io/helm-cha
|
|
|
6
6
|
NS="--namespace prometheus"
|
|
7
7
|
|
|
8
8
|
showInstalling "The Prometheus Operator (kube-prometheus-stack)"
|
|
9
|
-
[ -z "$PROMETHEUS_STACK_CHART_VERSION" ] && PROMETHEUS_STACK_CHART_VERSION="
|
|
9
|
+
[ -z "$PROMETHEUS_STACK_CHART_VERSION" ] && PROMETHEUS_STACK_CHART_VERSION="73"
|
|
10
10
|
|
|
11
11
|
# https://artifacthub.io/packages/helm/prometheus-community/kube-prometheus-stack
|
|
12
12
|
helm upgrade $NS --install prometheus-stack prometheus-community/kube-prometheus-stack \
|
|
@@ -34,4 +34,8 @@ helm upgrade $NS --install prometheus-adapter prometheus-community/prometheus-ad
|
|
|
34
34
|
--values prom-operator/prometheus-adapter.yaml \
|
|
35
35
|
--version $PROMETHEUS_ADAPTER_CHART_VERSION $HELM_WHAT
|
|
36
36
|
|
|
37
|
+
# finally, apply the PrometheusRule to generate the GPU duty cycle metric
|
|
38
|
+
showInstalling "The Prometheus Rule (custom GPU metrics rule)"
|
|
39
|
+
kubectl apply -n prometheus -f prom-operator/stackdriver-exporter-gpu-rules.yaml
|
|
40
|
+
|
|
37
41
|
removeHelmRepo prometheus-community
|
|
@@ -39,6 +39,11 @@ prometheus:
|
|
|
39
39
|
ruleSelectorNilUsesHelmValues: false
|
|
40
40
|
serviceMonitorSelectorNilUsesHelmValues: false
|
|
41
41
|
probeSelectorNilUsesHelmValues: false
|
|
42
|
+
maximumStartupDurationSeconds: 60
|
|
43
|
+
resources:
|
|
44
|
+
requests:
|
|
45
|
+
cpu: 500m
|
|
46
|
+
memory: 2Gi
|
|
42
47
|
storageSpec:
|
|
43
48
|
volumeClaimTemplate:
|
|
44
49
|
spec:
|
|
@@ -10,7 +10,7 @@ spec:
|
|
|
10
10
|
rules:
|
|
11
11
|
- alert: ElasticsearchHeapUsageTooHigh
|
|
12
12
|
expr: (elasticsearch_jvm_memory_used_bytes{area="heap"} / elasticsearch_jvm_memory_max_bytes{area="heap"}) * 100 > 90
|
|
13
|
-
for: 2m
|
|
13
|
+
for: 5m # awesome was => 2m
|
|
14
14
|
labels:
|
|
15
15
|
severity: critical
|
|
16
16
|
annotations:
|
|
@@ -21,7 +21,7 @@ spec:
|
|
|
21
21
|
LABELS = {{ $labels }}
|
|
22
22
|
- alert: ElasticsearchHeapUsageWarning
|
|
23
23
|
expr: (elasticsearch_jvm_memory_used_bytes{area="heap"} / elasticsearch_jvm_memory_max_bytes{area="heap"}) * 100 > 80
|
|
24
|
-
for: 2m
|
|
24
|
+
for: 5m # awesome was => 2m
|
|
25
25
|
labels:
|
|
26
26
|
severity: warning
|
|
27
27
|
annotations:
|
|
@@ -43,7 +43,7 @@ spec:
|
|
|
43
43
|
LABELS = {{ $labels }}
|
|
44
44
|
- alert: ElasticsearchDiskSpaceLow
|
|
45
45
|
expr: elasticsearch_filesystem_data_available_bytes / elasticsearch_filesystem_data_size_bytes * 100 < 20
|
|
46
|
-
for: 2m
|
|
46
|
+
for: 5m # awesome was => 2m
|
|
47
47
|
labels:
|
|
48
48
|
severity: warning
|
|
49
49
|
annotations:
|
|
@@ -54,7 +54,7 @@ spec:
|
|
|
54
54
|
LABELS = {{ $labels }}
|
|
55
55
|
- alert: ElasticsearchClusterRed
|
|
56
56
|
expr: elasticsearch_cluster_health_status{color="red"} == 1
|
|
57
|
-
for: 0m
|
|
57
|
+
for: 5m # awesome was => 0m
|
|
58
58
|
labels:
|
|
59
59
|
severity: critical
|
|
60
60
|
annotations:
|
|
@@ -65,7 +65,7 @@ spec:
|
|
|
65
65
|
LABELS = {{ $labels }}
|
|
66
66
|
- alert: ElasticsearchClusterYellow
|
|
67
67
|
expr: elasticsearch_cluster_health_status{color="yellow"} == 1
|
|
68
|
-
for: 0m
|
|
68
|
+
for: 5m # awesome was => 0m
|
|
69
69
|
labels:
|
|
70
70
|
severity: warning
|
|
71
71
|
annotations:
|
|
@@ -174,7 +174,7 @@ spec:
|
|
|
174
174
|
VALUE = {{ $value }}
|
|
175
175
|
LABELS = {{ $labels }}
|
|
176
176
|
- alert: ElasticsearchHighIndexingLatency
|
|
177
|
-
expr: elasticsearch_indices_indexing_index_time_seconds_total / elasticsearch_indices_indexing_index_total > 0.
|
|
177
|
+
expr: elasticsearch_indices_indexing_index_time_seconds_total / elasticsearch_indices_indexing_index_total > 0.005 # awesome was 0.0005
|
|
178
178
|
for: 10m
|
|
179
179
|
labels:
|
|
180
180
|
severity: warning
|
|
@@ -21,7 +21,7 @@ spec:
|
|
|
21
21
|
LABELS = {{ $labels }}
|
|
22
22
|
- alert: KubernetesNodeMemoryPressure
|
|
23
23
|
expr: kube_node_status_condition{condition="MemoryPressure",status="true"} == 1
|
|
24
|
-
for: 2m
|
|
24
|
+
for: 5m # awesome was => 2m
|
|
25
25
|
labels:
|
|
26
26
|
severity: critical
|
|
27
27
|
annotations:
|
|
@@ -32,7 +32,7 @@ spec:
|
|
|
32
32
|
LABELS = {{ $labels }}
|
|
33
33
|
- alert: KubernetesNodeDiskPressure
|
|
34
34
|
expr: kube_node_status_condition{condition="DiskPressure",status="true",node!~".*preemptible.*"} == 1 # awesome mod => added preemptible check
|
|
35
|
-
for: 2m
|
|
35
|
+
for: 5m # awesome was => 2m
|
|
36
36
|
labels:
|
|
37
37
|
severity: critical
|
|
38
38
|
annotations:
|
|
@@ -43,7 +43,7 @@ spec:
|
|
|
43
43
|
LABELS = {{ $labels }}
|
|
44
44
|
- alert: KubernetesNodeNetworkUnavailable
|
|
45
45
|
expr: kube_node_status_condition{condition="NetworkUnavailable",status="true"} == 1
|
|
46
|
-
for: 2m
|
|
46
|
+
for: 5m # awesome was => 2m
|
|
47
47
|
labels:
|
|
48
48
|
severity: critical
|
|
49
49
|
annotations:
|
|
@@ -54,7 +54,7 @@ spec:
|
|
|
54
54
|
LABELS = {{ $labels }}
|
|
55
55
|
- alert: KubernetesNodeOutOfPodCapacity
|
|
56
56
|
expr: sum by (node) ((kube_pod_status_phase{phase="Running"} == 1) + on(uid, instance) group_left(node) (0 * kube_pod_info{pod_template_hash=""})) / sum by (node) (kube_node_status_allocatable{resource="pods"}) * 100 > 90
|
|
57
|
-
for: 2m
|
|
57
|
+
for: 5m # awesome was => 2m
|
|
58
58
|
labels:
|
|
59
59
|
severity: warning
|
|
60
60
|
annotations:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#
|
|
2
2
|
# these custom rules exist to essentially boil down the GPU information provided
|
|
3
|
-
# by the stackdriver exporter into a form that is a little more
|
|
3
|
+
# by the stackdriver exporter into a form that is a little more human readable
|
|
4
4
|
# in an attempt to keep the grafana / alert manager rules a little less "verbose"
|
|
5
5
|
#
|
|
6
6
|
# In a nutshell, we take metrics in the form of:
|
|
@@ -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="36"
|
|
21
21
|
helm upgrade --install traefik traefik/traefik \
|
|
22
22
|
--namespace $TRAEFIK_NAMESPACE --create-namespace \
|
|
23
23
|
--values traefik/values.yaml \
|
|
@@ -4,7 +4,7 @@ showInstalling "Velero Backup System"
|
|
|
4
4
|
|
|
5
5
|
addHelmRepo vmware-tanzu https://vmware-tanzu.github.io/helm-charts
|
|
6
6
|
|
|
7
|
-
[ -z "$VELERO_CHART_VERSION" ] && VELERO_CHART_VERSION="
|
|
7
|
+
[ -z "$VELERO_CHART_VERSION" ] && VELERO_CHART_VERSION="10"
|
|
8
8
|
#
|
|
9
9
|
# Build the bucket, region and SA email variables and use --set to mod the
|
|
10
10
|
# chart values as opposed to using an OVH:<label> approach. This eliminates
|
package/src/helmup.sh
CHANGED
|
@@ -12,8 +12,8 @@ REGISTRY_DOMAIN="us-docker.pkg.dev"
|
|
|
12
12
|
# open container initiative (oci) => https://opencontainers.org/
|
|
13
13
|
ARTIFACT_REGISTRY="oci://$REGISTRY_DOMAIN/leverege-registry"
|
|
14
14
|
# login to the registry in the background
|
|
15
|
-
gcloud auth print-access-token | helm registry login
|
|
16
|
-
--password-stdin
|
|
15
|
+
gcloud auth print-access-token | helm registry login \
|
|
16
|
+
-u oauth2accesstoken --password-stdin $REGISTRY_DOMAIN &> $DEVNULL
|
|
17
17
|
|
|
18
18
|
# make sure we have a current npmrc file
|
|
19
19
|
refresh-npm-token
|
|
@@ -175,21 +175,21 @@ function createNamespaceIfNeeded() {
|
|
|
175
175
|
function makeUniformBucketWithCors() {
|
|
176
176
|
local BUCKET_NAME="gs://${GCP_PROJECT_ID}-${1}"
|
|
177
177
|
printf "Creating/Updating bucket: `color g $BUCKET_NAME`\n"
|
|
178
|
-
gsutil ls -p $GCP_PROJECT_ID -b $BUCKET_NAME &>
|
|
178
|
+
gsutil ls -p $GCP_PROJECT_ID -b $BUCKET_NAME &> $DEVNULL
|
|
179
179
|
if [ "$?" -ne 0 ];
|
|
180
180
|
then
|
|
181
|
-
gsutil mb -l $GCE_REGION -p $GCP_PROJECT_ID $BUCKET_NAME &>
|
|
182
|
-
|
|
181
|
+
gsutil mb -l $GCE_REGION -p $GCP_PROJECT_ID $BUCKET_NAME &> $DEVNULL
|
|
182
|
+
exitOnError $? "failed to create the bucket: `color y $BUCKET_NAME`"
|
|
183
183
|
fi
|
|
184
184
|
gcloud storage buckets update $BUCKET_NAME --uniform-bucket-level-access
|
|
185
185
|
CORS_JSON_FILE="$(build-tools --reporoot)/src/helmup-cors-for-buckets.json"
|
|
186
186
|
printf " Applying CORS policy\n"
|
|
187
|
-
gsutil cors set $CORS_JSON_FILE $BUCKET_NAME &>
|
|
187
|
+
gsutil cors set $CORS_JSON_FILE $BUCKET_NAME &> $DEVNULL
|
|
188
188
|
|
|
189
189
|
if [ "$2" == "PUBLIC" ];
|
|
190
190
|
then
|
|
191
191
|
printf " Making the bucket `color r PUBLIC`\n"
|
|
192
|
-
gsutil iam ch allUsers:objectViewer $BUCKET_NAME &>
|
|
192
|
+
gsutil iam ch allUsers:objectViewer $BUCKET_NAME &> $DEVNULL
|
|
193
193
|
fi
|
|
194
194
|
}
|
|
195
195
|
|
|
@@ -479,7 +479,15 @@ spec:
|
|
|
479
479
|
- "/bin/sh"
|
|
480
480
|
args:
|
|
481
481
|
- "-c"
|
|
482
|
-
-
|
|
482
|
+
- |
|
|
483
|
+
echo 'Hunting Zombies' ;
|
|
484
|
+
namespaces="default cnpg-operands elastic prometheus traefik velero"
|
|
485
|
+
for ns in \$namespaces; do
|
|
486
|
+
pods=\$(kubectl get pods -n "\$ns" --field-selector=status.phase=Failed -o name 2>/dev/null)
|
|
487
|
+
if [ -n "\$pods" ]; then
|
|
488
|
+
echo "\$pods" | xargs -r kubectl delete -n "\$ns"
|
|
489
|
+
fi
|
|
490
|
+
done
|
|
483
491
|
status: {}
|
|
484
492
|
END_OF_ZOMBIE
|
|
485
493
|
}
|
|
@@ -793,7 +801,7 @@ function installVeleroEnvironment() {
|
|
|
793
801
|
gcloud config set project $GCP_PROJECT_ID
|
|
794
802
|
|
|
795
803
|
## The major chart version will determine the bucket suffix
|
|
796
|
-
[ -z "$VELERO_CHART_VERSION" ] && VELERO_CHART_VERSION="
|
|
804
|
+
[ -z "$VELERO_CHART_VERSION" ] && VELERO_CHART_VERSION="10"
|
|
797
805
|
|
|
798
806
|
## Create a bucket
|
|
799
807
|
BUCKET="$GCP_PROJECT_ID-velero-$VELERO_CHART_VERSION"
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander'
|
|
4
|
+
import { shellCmd, log, warning, errorExit } from './Utils.mjs'
|
|
5
|
+
|
|
6
|
+
const program = new Command()
|
|
7
|
+
|
|
8
|
+
program
|
|
9
|
+
.requiredOption( '--project <projectId>', 'GCP project ID of the GSA' )
|
|
10
|
+
.requiredOption( '--bucket <bucketName>', 'GCS bucket name (no gs:// prefix)' )
|
|
11
|
+
.requiredOption( '--gsa <gsaName>', 'Service account name, e.g. my-service' )
|
|
12
|
+
.requiredOption( '--role <roleId>', 'Custom role ID', 'leveregeStorageObjectReader' )
|
|
13
|
+
.option( '--org', 'Treat as GCP organizational level role' )
|
|
14
|
+
.option( '--add', 'Add IAM binding (default)' )
|
|
15
|
+
.option( '--remove', 'Remove IAM binding' )
|
|
16
|
+
.option( '--dry-run', 'Show command without executing' )
|
|
17
|
+
|
|
18
|
+
program.parse( process.argv )
|
|
19
|
+
|
|
20
|
+
const opts = program.opts()
|
|
21
|
+
|
|
22
|
+
// Validate mutually exclusive add/remove
|
|
23
|
+
if ( opts.add && opts.remove ) {
|
|
24
|
+
errorExit( 'You cannot specify both --add and --remove.' )
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const action = opts.remove ? 'remove' : 'add'
|
|
28
|
+
|
|
29
|
+
const {
|
|
30
|
+
project,
|
|
31
|
+
org,
|
|
32
|
+
bucket,
|
|
33
|
+
gsa,
|
|
34
|
+
role,
|
|
35
|
+
dryRun
|
|
36
|
+
} = opts
|
|
37
|
+
|
|
38
|
+
const orgId = await shellCmd( 'gcloud organizations list --format="value(ID)"' )
|
|
39
|
+
const gsaEmail = `${gsa}@${project}.iam.gserviceaccount.com`
|
|
40
|
+
const fullRole = org ? `organizations/${orgId}/roles/${role}` : `roles/${role}`
|
|
41
|
+
const bucketUri = `gs://${bucket}`
|
|
42
|
+
|
|
43
|
+
const iamMember = `serviceAccount:${gsaEmail}:${fullRole}`
|
|
44
|
+
|
|
45
|
+
const command = action === 'add' ?
|
|
46
|
+
[ 'gsutil', 'iam', 'ch', iamMember, bucketUri ] :
|
|
47
|
+
[ 'gsutil', 'iam', 'ch', '-d', iamMember, bucketUri ]
|
|
48
|
+
|
|
49
|
+
log( `đ§ ${action === 'add' ? 'Granting' : 'Removing'} ${fullRole} for ${gsaEmail} on ${bucketUri}...` )
|
|
50
|
+
|
|
51
|
+
if ( dryRun ) {
|
|
52
|
+
warning( 'đĄ DRY RUN mode â no commands executed.' )
|
|
53
|
+
log( `đ Command:\n$ ${command.join( ' ' )}` )
|
|
54
|
+
process.exit( 0 )
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const result = await shellCmd( command )
|
|
59
|
+
log( `â
IAM ${action} successful:\n${result}` )
|
|
60
|
+
} catch ( err ) {
|
|
61
|
+
errorExit( `â Failed to ${action} IAM binding: ${err.message}` )
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// === Post-change audit ===
|
|
65
|
+
log( 'đ Verifying IAM policy for this GSA on the bucket...\n' )
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const policyRaw = await shellCmd( [ 'gsutil', 'iam', 'get', bucketUri ] )
|
|
69
|
+
const policy = JSON.parse( policyRaw )
|
|
70
|
+
|
|
71
|
+
const matchingRoles = policy.bindings.filter(
|
|
72
|
+
b => b.members.includes( `serviceAccount:${gsaEmail}` )
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if ( matchingRoles.length === 0 ) {
|
|
76
|
+
log( `âšī¸ No IAM bindings found for ${gsaEmail} on ${bucketUri}.` )
|
|
77
|
+
} else {
|
|
78
|
+
for ( const binding of matchingRoles ) {
|
|
79
|
+
log( `đ ${gsaEmail} has role: ${binding.role}` )
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} catch ( err ) {
|
|
83
|
+
warning( `â ī¸ Failed to retrieve or parse IAM policy: ${err.message}` )
|
|
84
|
+
}
|
|
@@ -20,23 +20,23 @@ steps:
|
|
|
20
20
|
args:
|
|
21
21
|
- 'tag'
|
|
22
22
|
- '{{imageName}}:{{imageVersion}}'
|
|
23
|
-
- '{{imageName}}:
|
|
23
|
+
- '{{imageName}}:latest'
|
|
24
24
|
|
|
25
25
|
- name: 'gcr.io/cloud-builders/docker'
|
|
26
26
|
args:
|
|
27
|
-
- '
|
|
28
|
-
- '{{imageName}}:{{imageVersion}}'
|
|
27
|
+
- 'push'
|
|
29
28
|
- '{{imageName}}:latest'
|
|
30
29
|
|
|
31
30
|
- name: 'gcr.io/cloud-builders/docker'
|
|
32
31
|
args:
|
|
33
|
-
- '
|
|
34
|
-
- '{{imageName}}:{{
|
|
32
|
+
- 'tag'
|
|
33
|
+
- '{{imageName}}:{{imageVersion}}'
|
|
34
|
+
- '{{imageName}}:{{imageInfoTag}}'
|
|
35
35
|
|
|
36
36
|
- name: 'gcr.io/cloud-builders/docker'
|
|
37
37
|
args:
|
|
38
38
|
- 'push'
|
|
39
|
-
- '{{imageName}}:
|
|
39
|
+
- '{{imageName}}:{{imageInfoTag}}'
|
|
40
40
|
|
|
41
41
|
images:
|
|
42
42
|
- '{{imageName}}:{{imageVersion}}'
|
|
@@ -1,60 +1,64 @@
|
|
|
1
1
|
# Version {{regvers}} @ {{date}}
|
|
2
2
|
|
|
3
|
-
# The FROM directive sets the Base Image for subsequent instructions
|
|
4
3
|
FROM {{image}}
|
|
5
4
|
|
|
6
5
|
ARG BUILD_ENV="{{buildEnv}}"
|
|
7
6
|
|
|
8
7
|
RUN useradd -ms /bin/bash {{runuser}} && \
|
|
9
|
-
mkdir -p /usr/src/app && \
|
|
10
|
-
chown {{runuser}} /usr/src/app && \
|
|
8
|
+
mkdir -p /usr/src/app /home/{{runuser}} && \
|
|
9
|
+
chown -R {{runuser}} /usr/src/app /home/{{runuser}} && \
|
|
11
10
|
apt-get update && apt-get install -y \
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
wget git libgl1 libglib2.0-0 && \
|
|
12
|
+
rm -rf /var/lib/apt/lists/*
|
|
14
13
|
|
|
15
|
-
#
|
|
16
|
-
# git \
|
|
17
|
-
# && rm -rf /var/lib/apt/lists/*
|
|
18
|
-
|
|
19
|
-
# Copy uv binary from the official distroless Docker image.
|
|
14
|
+
# Copy uv binary from distroless image
|
|
20
15
|
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
|
|
21
16
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
# Install app dependencies
|
|
25
|
-
COPY --chown={{runuser}}:{{runuser}} ./workspace/ /usr/src/app/
|
|
26
|
-
ENV GRPC_VERBOSITY ERROR
|
|
27
|
-
|
|
28
|
-
# Setup pip.conf for Google Artifact Registry.
|
|
29
|
-
RUN mkdir -p ~/.config/pip && \
|
|
30
|
-
echo "[global]" >> ~/.config/pip/pip.conf && \
|
|
31
|
-
echo "break-system-packages = true" >> ~/.config/pip/pip.conf
|
|
32
|
-
|
|
17
|
+
# Copy bashrc early
|
|
18
|
+
COPY --chown={{runuser}}:{{runuser}} ./bashrc /home/{{runuser}}/.bashrc
|
|
33
19
|
|
|
34
|
-
|
|
20
|
+
WORKDIR /usr/src/app
|
|
35
21
|
USER {{runuser}}
|
|
36
|
-
COPY --chown={{runuser}}:{{runuser}} ./bashrc /home/{{runuser}}/.bashrc
|
|
37
22
|
|
|
38
|
-
|
|
23
|
+
ENV GRPC_VERBOSITY=ERROR
|
|
39
24
|
ENV VIRTUAL_ENV=/usr/src/app/.venv
|
|
40
25
|
ENV PATH="$VIRTUAL_ENV/bin:$PATH:/home/{{runuser}}/.local/bin"
|
|
41
26
|
|
|
42
|
-
# Setup
|
|
43
|
-
RUN
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
#ENV UV_EXTRA_INDEX_URL "https://oauth2accesstoken@us-python.pkg.dev/leverege-registry/leverege-python-packages/simple"
|
|
27
|
+
# Setup pip.conf for GCP Artifact Registry in case pip is needed for building
|
|
28
|
+
RUN mkdir -p /home/{{runuser}}/.config/pip && \
|
|
29
|
+
echo "[global]" >> /home/{{runuser}}/.config/pip/pip.conf && \
|
|
30
|
+
echo "break-system-packages = true" >> /home/{{runuser}}/.config/pip/pip.conf
|
|
47
31
|
|
|
32
|
+
# Set up venv and keyring tools before app copy
|
|
33
|
+
RUN uv venv && \
|
|
34
|
+
uv tool install keyring --with keyrings.google-artifactregistry-auth
|
|
48
35
|
|
|
36
|
+
# Pre-plugin block: models or pre-install commands
|
|
49
37
|
{{preInstallPluginfile}}
|
|
50
38
|
|
|
51
|
-
|
|
39
|
+
# Copy only dependency definitions first
|
|
40
|
+
COPY --chown=python:python ./workspace/pyproject.toml \
|
|
41
|
+
./workspace/uv.lock \
|
|
42
|
+
./workspace/.python-version \
|
|
43
|
+
./workspace/README.md \
|
|
44
|
+
./workspace/LICENSE.md \
|
|
45
|
+
/usr/src/app/
|
|
52
46
|
|
|
53
|
-
#
|
|
47
|
+
# Sync dependencies
|
|
48
|
+
RUN uv sync --keyring-provider subprocess \
|
|
49
|
+
--extra-index-url https://oauth2accesstoken@us-python.pkg.dev/leverege-registry/leverege-python-packages/simple \
|
|
50
|
+
&& rm -rf /home/{{runuser}}/.cache
|
|
51
|
+
|
|
52
|
+
# Copy application source AFTER venv + tool setup
|
|
53
|
+
COPY --chown={{runuser}}:{{runuser}} ./workspace/ /usr/src/app/
|
|
54
|
+
|
|
55
|
+
# Plugin block for additional pip packages
|
|
54
56
|
{{pluginfile}}
|
|
55
57
|
|
|
56
|
-
#
|
|
57
|
-
|
|
58
|
+
# Ensure src/ layout is importable from anywhere in the container
|
|
59
|
+
ENV PYTHONPATH="/usr/src/app/src"
|
|
60
|
+
|
|
61
|
+
# Show final venv state (optional)
|
|
58
62
|
RUN ls /usr/src/app/.venv/bin
|
|
63
|
+
|
|
59
64
|
CMD [ "/bin/bash", "-c", "source /home/{{runuser}}/.bashrc && python scripts/server.py" ]
|
|
60
|
-
#CMD [ "/bin/bash" ]
|