@leverege/build-tools 2.46.0 → 2.47.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.46.0",
3
+ "version": "2.47.0",
4
4
  "description": "A collection of build / support tools for Leverege developers",
5
5
  "main": "index.js",
6
6
  "repository": {
package/src/Docker.mjs CHANGED
@@ -3,7 +3,14 @@ import fs from 'node:fs'
3
3
  import chalk from 'chalk'
4
4
  import handlebars from 'handlebars'
5
5
 
6
- import { errorExit, log, proceed, shellCmd, warning } from './Utils.mjs'
6
+ import {
7
+ errorExit,
8
+ getGcpAccount,
9
+ log,
10
+ proceed,
11
+ shellCmd,
12
+ warning
13
+ } from './Utils.mjs'
7
14
 
8
15
  // The contents of these variables were initially located in files that live in
9
16
  // the build-tools repository, but it just became simpler to pull the contents
@@ -217,8 +224,11 @@ const validateCloudBuildBucket = async ( artifactProject ) => {
217
224
  }
218
225
  }
219
226
 
220
- const buildContainerImage = async ( { artifactProject, artifactRegistry, containerName, imageVersion } ) => {
227
+ const buildContainerImage = async ( {
228
+ artifactProject, artifactRegistry, containerName, imageVersion, isNpmWorkspace,
229
+ } ) => {
221
230
  const buildWorkspace = './build/workspace' // passed to Cloud Build
231
+ const imageName = `${artifactRegistry}/images/${containerName}`
222
232
 
223
233
  if ( fs.existsSync( buildWorkspace ) ) {
224
234
  fs.renameSync( buildWorkspace, `${buildWorkspace}-junk` )
@@ -235,26 +245,40 @@ const buildContainerImage = async ( { artifactProject, artifactRegistry, contain
235
245
  fs.cpSync( `${process.env.HOME}/.npmrc`, './build/.npmrc' )
236
246
  fs.cpSync( './docker/bashrc', './build/bashrc' )
237
247
  fs.cpSync( './package.json', `${buildWorkspace}/package.json` )
238
- fs.renameSync( './package-lock.json', `${buildWorkspace}/package-lock.json` )
248
+ fs.cpSync( './package-lock.json', `${buildWorkspace}/package-lock.json` )
249
+ if ( isNpmWorkspace ) {
250
+ fs.rm( './package-lock.json', ( err ) => {} ) // no leaf node locks in workspaces
251
+ }
239
252
 
240
253
  setPreviousBuild( imageVersion ) // update docker/.previous file
241
254
 
255
+ const { fullName : builderName } = await getGcpAccount()
242
256
  const gcloudBuild = `time gcloud builds submit --project ${artifactProject}`
243
257
  const gcloudLogs = `--gcs-log-dir ${formCloudBuildBucketName( artifactProject )}/log`
244
- const gcloudTags = `--tag ${artifactRegistry}/images/${containerName}:${imageVersion}`
258
+ const gcloudTags = `--tag ${imageName}:${imageVersion}`
245
259
 
246
260
  log( chalk.green.bold( `
247
- *** Submitting Build ***
261
+ *** Submitting Build as ${builderName} ***
248
262
  ${gcloudBuild} \\
249
263
  ${gcloudLogs} \\
250
264
  ${gcloudTags}
251
265
  ` ) )
252
266
 
267
+ if ( process.env.DRY_RUN === '1' ) {
268
+ log( '\n***Exiting from DRY_RUN\n' )
269
+ process.exit( 0 )
270
+ }
271
+
253
272
  await shellCmd( `${gcloudBuild} ${gcloudLogs} ${gcloudTags} ./build`, { stdio : 'inherit' } )
273
+
274
+ const imageTag = `${defaultSettings.date}-${defaultSettings.nodeimage}-${builderName}`
275
+ await shellCmd(
276
+ `gcloud container images add-tag --quiet ${imageName}:${imageVersion} ${imageName}:${imageTag} ${imageName}:latest`
277
+ )
254
278
  }
255
279
 
256
280
  const repoCleanup = async () => {
257
- // remove garbase from the repo
281
+ // remove garbage from the repo
258
282
  const garbage = [
259
283
  'build',
260
284
  'dist',
@@ -266,9 +290,9 @@ const repoCleanup = async () => {
266
290
  ]
267
291
  garbage.forEach( trash => fs.rmSync( trash, { recursive : true, force : true } ) )
268
292
 
269
- fs.writeFileSync( 'docker/.gitignore', '.previous' )
293
+ fs.writeFileSync( 'docker/.gitignore', '.previous\nDockerfile' )
270
294
 
271
- await shellCmd( 'git add -f docker' )
295
+ await shellCmd( 'git add docker' )
272
296
 
273
297
  log( `
274
298
  ${chalk.yellow.bold( '***IMPORTANT***' )}
package/src/Utils.mjs CHANGED
@@ -68,6 +68,18 @@ export const shellCmd = async ( cmdstr, opts = {} ) => {
68
68
  }
69
69
  }
70
70
 
71
+ // returns YYYMMDD-hhmm
72
+ export const getDateTimestamp = () => {
73
+ const fullDTS = new Date().toISOString().replace( /[-T:]/g, '' ).slice( 0, 14 )
74
+ return `${fullDTS.slice( 0, 8 )}-${fullDTS.slice( 8, 12 )}`
75
+ }
76
+
77
+ export const getGcpAccount = async () => {
78
+ const fullAccount = await shellCmd( 'gcloud config get account' )
79
+ const [ fullName, domain ] = fullAccount.split( '@' )
80
+ return { fullAccount, fullName, domain }
81
+ }
82
+
71
83
  export const getGcpSecret = async ( gcpProject, secretName, opts ) => {
72
84
  const secretFetchCmd = `gcloud secrets versions access latest --project=${gcpProject} --secret=${secretName}`
73
85
  let secretString
@@ -100,6 +112,22 @@ export const getGitRootDirectory = async () => {
100
112
  }
101
113
  }
102
114
 
115
+ export const getGitUpstream = async () => {
116
+ let branch
117
+ try {
118
+ branch = await shellCmd( 'git rev-parse --abbrev-ref HEAD' )
119
+ const upstream = await shellCmd( `git rev-parse --abbrev-ref ${branch}@{u}` )
120
+ return { branch, upstream }
121
+ } catch ( err ) {
122
+ return { branch, upstream : undefined }
123
+ }
124
+ }
125
+
126
+ export const gitRepoIsDirty = async () => {
127
+ const cleanliness = await shellCmd( 'git status --porcelain' )
128
+ return cleanliness?.length
129
+ }
130
+
103
131
  export const parseJsonFile = async ( jsonFile ) => {
104
132
  /* eslint-disable security/detect-non-literal-fs-filename */
105
133
  if ( existsSync( jsonFile ) ) {
@@ -290,7 +318,7 @@ https://console.cloud.google.com/artifacts/browse/leverege-registry?project=leve
290
318
  return { artifactRegistry, ...packageJson }
291
319
  }
292
320
 
293
- export const parseHelmChart = async ( helmroot ) => {
321
+ export const parseHelmChart = async ( helmroot = './helm' ) => {
294
322
  /* eslint-disable security/detect-non-literal-fs-filename */
295
323
  const chartFiles = readdirSync( helmroot, { encoding : 'utf8', recursive : true } )
296
324
  const chartYaml = YAML.load( readFileSync( `${helmroot}/Chart.yaml`, 'utf8' ) )
@@ -308,6 +336,7 @@ export const parseHelmChart = async ( helmroot ) => {
308
336
  export const analyzeRepository = async () => {
309
337
  const gitRoot = await getGitRootDirectory()
310
338
  const subPkgs = await glob( `${gitRoot}/packages/**/package.json`, { ignore : '/**/node_modules/**' } )
339
+ const helmChart = await parseHelmChart()
311
340
  const isMonoRepo = subPkgs?.length > 0
312
341
 
313
342
  // Verify the presence of a root package.json file for all monorepos
@@ -355,6 +384,7 @@ export const analyzeRepository = async () => {
355
384
  const containerName = closestPackageJson.name
356
385
  const artifactProject = closestPackageJson.artifactRegistry.project
357
386
  const artifactRegistry = closestPackageJson.leverege.registry
387
+ const packageVersion = `v${closestPackageJson.version}`
358
388
 
359
389
  const currentDir = process.cwd()
360
390
  const closestPkg = await packageUp()
@@ -381,10 +411,12 @@ export const analyzeRepository = async () => {
381
411
  closestPkg,
382
412
  gitRoot,
383
413
  gitRemote,
414
+ helmChart,
384
415
  isMonoRepo,
385
416
  isNpmWorkspace,
386
417
  rootPackageJson,
387
418
  closestPackageJson,
419
+ packageVersion,
388
420
  subPkgs,
389
421
  wsName,
390
422
  }
@@ -8,6 +8,8 @@ import {
8
8
  debug, log,
9
9
  errorExit,
10
10
  analyzeRepository,
11
+ getGitUpstream,
12
+ gitRepoIsDirty,
11
13
  proceed,
12
14
  shellCmd } from './Utils.mjs'
13
15
 
@@ -16,23 +18,55 @@ import docker from './Docker.mjs'
16
18
  // refresh-npm-token does not throw so no need to try, but it emits in debug
17
19
  const refreshErr = await shellCmd( 'refresh-npm-token' )
18
20
  if ( refreshErr && !process.env.BUILD_TOOLS_DEBUG ) {
19
- errorExit( `***Error: npm token refresh failed\n\n${refreshErr}` )
21
+ log( refreshErr ) // most likely the token refreshed message
20
22
  }
21
23
 
22
24
  // Expected to be invoked like docker-to-registry v1.2.3
23
25
  const imageVersion = process.argv[2]
26
+ if ( !imageVersion ) {
27
+ errorExit( '***Error: docker-to-registry requires an image version' )
28
+ }
29
+
24
30
  const willGitTag = imageVersion.match( /^v\d+\.\d+\.\d+$/ )
25
31
  const tagInfo = willGitTag ?
26
32
  chalk.yellow.bold( 'will be git tagged' ) :
27
33
  chalk.red( 'BETA RELEASE WILL NOT BE GIT TAGGED' )
28
34
 
35
+ const repoIsDirty = await gitRepoIsDirty()
36
+
37
+ if ( willGitTag && repoIsDirty ) {
38
+ log( `
39
+ ${chalk.red.bold( '***ERROR: attempting to tag a non-beta dirty git repository' )}
40
+
41
+ You are attempting to create a tagged release image for pushing to the
42
+ artifact registry, but there are locally modified files. This is not
43
+ allowed since the applied tag will not be relevant to the image version
44
+ due to the pending commits.
45
+
46
+ Available options for proceeding:
47
+
48
+ ${chalk.green.bold( '1) cleanly commit all local mods assuming relevance' )}
49
+ ${chalk.yellow( '2) eliminate unwanted local modifications' )}
50
+ ${chalk.red( '3) stash anything that is irrelevant to this release' )}
51
+
52
+ ` )
53
+ process.exit( 1 )
54
+ }
55
+
29
56
  const repoDescr = await analyzeRepository()
30
57
  debug( { repoDescr }, '<==The Repo Description' )
31
58
 
32
59
  const dockerInfo = await docker.generateDockerfile()
33
60
  debug( { dockerInfo }, '<==The Docker Info' )
34
61
 
35
- const { artifactProject, artifactRegistry, containerName } = repoDescr
62
+ const {
63
+ artifactProject,
64
+ artifactRegistry,
65
+ packageVersion,
66
+ containerName,
67
+ helmChart,
68
+ isNpmWorkspace,
69
+ } = repoDescr
36
70
 
37
71
  if ( imageVersion === 'guide-me' ) {
38
72
  await docker.validateCloudBuildBucket( artifactProject )
@@ -50,6 +84,9 @@ if ( imageVersion === 'guide-me' ) {
50
84
  log( `
51
85
 
52
86
 
87
+
88
+ ${chalk.green.bold( '-------------------- Potential Helm Chart Mods --------------------' )}
89
+
53
90
  Moving to the new artifact-registry for docker image storage may also require
54
91
  helm chart changes depending on how old the current charts are. Charts that
55
92
  contain helm/values.yaml files that resemble this:
@@ -89,6 +126,31 @@ if ( imageVersion === 'guide-me' ) {
89
126
  process.exit( 0 )
90
127
  }
91
128
 
129
+ // do chart checks too
130
+ const chart = helmChart?.yaml['Chart.yaml']
131
+ const values = helmChart?.yaml['values.yaml']
132
+ const expectedChartRegistry = `${artifactRegistry}/images`
133
+
134
+ if ( expectedChartRegistry !== values.image.registry ) {
135
+ log( `
136
+ ${chalk.red.bold( '***Error: mismatched package.json registry and helm/values.yaml' )}
137
+
138
+ The registry specified in the package.json leverege stanza does not line up
139
+ with the image registry in helm/values.yaml. The naming convention expects
140
+ the '/images' suffix to be added to the package.json registry string and then
141
+ stored as the image.registry in the helm/values.yaml file.
142
+
143
+ Current settings:
144
+ package.json registry => ${chalk.yellow.bold( artifactRegistry )}
145
+ values.yaml registry => ${chalk.red.bold( values.image.registry )}
146
+
147
+ Expected settings:
148
+ values.yaml registry => ${chalk.green.bold( expectedChartRegistry )}
149
+ ` )
150
+
151
+ process.exit( 1 )
152
+ }
153
+
92
154
  // Give the summary and the user a chance to proceed or not
93
155
  log( `
94
156
  ${chalk.green.bold( 'Build Information:' )}
@@ -97,7 +159,7 @@ log( `
97
159
  Registry: ${chalk.green.bold( artifactRegistry )}
98
160
  Image Folder: ${chalk.yellow.bold( registryFolder )}
99
161
  Monorepo: ${chalk.green.bold( repoDescr.isMonoRepo )}
100
- NPM Workspace: ${chalk.green.bold( repoDescr.isNpmWorkspace )}
162
+ NPM Workspace: ${chalk.green.bold( isNpmWorkspace )}
101
163
 
102
164
  ${chalk.green.bold( 'Docker Information:' )}
103
165
  NodeImage: ${chalk.green.bold( dockerInfo.nodeimage )}
@@ -108,11 +170,64 @@ log( `
108
170
  NPM Logs: ${chalk.green.bold( dockerInfo.npmlogging )}
109
171
  ` )
110
172
 
173
+ if ( imageVersion !== packageVersion ) {
174
+ log( `
175
+ ${chalk.yellow.bold( '***WARNING: specified version does not match package.json version' )}
176
+ package.json => ${chalk.green.bold( packageVersion )}
177
+ specified => ${chalk.red.bold( imageVersion )}
178
+ ` )
179
+
180
+ if ( willGitTag ) {
181
+ log( chalk.red.bold( '... and you are attempting to release so NOPE!\n' ) )
182
+ process.exit( 1 )
183
+ }
184
+ }
185
+
186
+ const repoInfo = await getGitUpstream()
187
+
188
+ if ( willGitTag && !repoInfo.upstream ) {
189
+ log( `
190
+ ${chalk.red.bold( '***ERROR: the current branch must have an upstream' )}
191
+
192
+ You are attempting to create a tagged release image for pushing to the
193
+ container registry, but the current branch does not have an upstream to
194
+ push to. This is not allowed since the applied tag will be stranded here
195
+ in your local repository, which is what we are trying to avoid.
196
+
197
+ Available options for proceeding:` )
198
+ log( chalk.green.bold( `
199
+ 1) set an upstream for this branch via:
200
+ git push --set-upstream origin ${repoInfo.branch}` ) )
201
+ log( chalk.yellow.bold( `
202
+ 2) rebase, squash and merge onto a branch with an upstream` ) )
203
+
204
+ process.exit( 1 )
205
+ }
206
+
207
+ if ( willGitTag ) {
208
+ if ( chart.appVersion !== imageVersion ) {
209
+ log( `
210
+ ${chalk.yellow.bold( '***WARNING: the helm/Chart.yaml appVersion does not align with the build version' )}
211
+ chart appVersion => ${chalk.yellow.bold( chart.appVersion )}
212
+ build version => ${chalk.green.bold( imageVersion )}
213
+ ` )
214
+ }
215
+ }
216
+
111
217
  await proceed()
112
218
 
113
- if ( repoDescr.isNpmWorkspace ) {
219
+ if ( isNpmWorkspace ) {
114
220
  log( chalk.yellow.bold( 'Generating an NPM workspace package-lock.json file' ) )
115
221
  await shellCmd( 'npm install --package-lock-only --workspaces false', { stdio : 'inherit' } )
116
222
  }
117
223
 
118
- await docker.buildContainerImage( { artifactProject, artifactRegistry, containerName, imageVersion } )
224
+ await docker.buildContainerImage( { artifactProject, artifactRegistry, containerName, imageVersion, isNpmWorkspace } )
225
+
226
+ if ( willGitTag ) {
227
+ log( chalk.green.bold( `Tagging and Pushing ${imageVersion}\n` ) )
228
+ const tagDescription = `docker_${imageVersion}`
229
+ await shellCmd( `git tag -f ${imageVersion} -m ${tagDescription}` )
230
+ await shellCmd( 'git push --follow-tags' )
231
+ } else {
232
+ log( chalk.yellow.bold( '\n*** Skipped git tagging - beta release' ) )
233
+ }
@@ -14,6 +14,13 @@ BASE_NODE_IMAGE="${BASE_NODE_IMAGE:-iron-alpine}" # node 20, iron
14
14
 
15
15
  # Load common bash functions
16
16
  . `build-tools --bashfun`
17
+ cat<<DEPRECATED_TOOL
18
+
19
+ `color y "***DEPRECATED docker-to-registry.sh - upgrade to docker-to-registry.mjs***"`
20
+
21
+ DEPRECATED_TOOL
22
+ sleep 3
23
+
17
24
  PKGTOP="$npm_config_local_prefix"
18
25
  cd $PKGTOP/docker
19
26
 
package/src/dockreate.sh CHANGED
@@ -8,6 +8,13 @@ fi
8
8
 
9
9
  # Load common bash functions
10
10
  . `build-tools --bashfun`
11
+ cat<<DOCKREATE_DEPRECATED
12
+
13
+ `color y "***DOCKREATE is DEPRECATED - move to docker-to-registry***"`
14
+
15
+ DOCKREATE_DEPRECATED
16
+ sleep 3
17
+
11
18
  PKGTOP="$npm_config_local_prefix"
12
19
  cd $PKGTOP/docker
13
20
 
package/src/k8x.sh CHANGED
@@ -16,6 +16,21 @@ cmd=${@:-"bash"}
16
16
  ns="default"
17
17
 
18
18
  case "$cmd" in
19
+ "help")
20
+ cat<<K8X_HELP
21
+
22
+ Usage: `color g 'k8x <target>'` where target can be:
23
+
24
+ `color g cnpg` - run bash on a cloudnative-pg cluster node
25
+ `color g es8` - run bash on an elasticsearch8 node
26
+ `color g psql` - run psql on a legacy database node
27
+ `color g redis` - run redis-cli on a redis master/replica
28
+ `color g help` - this right here
29
+
30
+ K8X_HELP
31
+ exit 0
32
+ ;;
33
+
19
34
  "bash")
20
35
  # invoked simply as k8x
21
36
  ;;
@@ -36,29 +51,8 @@ case "$cmd" in
36
51
  FZFQ="--query=redis" && cmd='redis-cli'
37
52
  ;;
38
53
 
39
- "help"|*)
40
- if [ "$cmd" != "help" ];
41
- then
42
- cat<<BAD_K8X
43
-
44
- `color r ***Error:` unrecognized k8x target => `color y "$cmd"`
45
- BAD_K8X
46
- k8x.sh help
47
- exit 1
48
- fi
49
-
50
- cat<<K8X_HELP
51
-
52
- Usage: `color g 'k8x <target>'` where target can be:
53
-
54
- `color g cnpg` - run bash on a cloudnative-pg cluster node
55
- `color g es8` - run bash on an elasticsearch8 node
56
- `color g psql` - run psql on a legacy database node
57
- `color g redis` - run redis-cli on a redis master/replica
58
- `color g help` - this right here
59
-
60
- K8X_HELP
61
- exit 0
54
+ *)
55
+ FZFQ="--query=$cmd" && cmd='bash'
62
56
  ;;
63
57
  esac
64
58