@leverege/build-tools 2.59.2 → 2.61.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leverege/build-tools",
3
- "version": "2.59.2",
3
+ "version": "2.61.0-alpha.1",
4
4
  "description": "A collection of build / support tools for Leverege developers",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -81,6 +81,7 @@
81
81
  "readline-sync": "^1.4.10",
82
82
  "semver": "^7.6.3",
83
83
  "simple-git": "^3.27.0",
84
+ "toml": "^3.0.0",
84
85
  "zx": "^8.3.0"
85
86
  },
86
87
  "devDependencies": {
package/src/Docker.mjs CHANGED
@@ -1,4 +1,6 @@
1
1
  import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import url from 'node:url'
2
4
 
3
5
  import chalk from 'chalk'
4
6
  import handlebars from 'handlebars'
@@ -13,6 +15,8 @@ import {
13
15
  warning
14
16
  } from './Utils.mjs'
15
17
 
18
+ const DIRNAME = path.dirname( url.fileURLToPath( import.meta.url ) )
19
+
16
20
  // TODO: change default to node 22 (jod) after 01/01/25
17
21
  const dockerfileNodeVersion = 'jod-alpine'
18
22
 
@@ -20,74 +24,6 @@ const dockerfileNodeVersion = 'jod-alpine'
20
24
  // the build-tools repository, but it just became simpler to pull the contents
21
25
  // directly into these variables and forego the file loading.
22
26
  //
23
- const dockerfileTemplate = `
24
- # Version {{regvers}} @ {{date}}
25
- #
26
- # The FROM directive sets the Base Image for subsequent instructions
27
- FROM node:{{nodeimage}} as intermediate
28
- ENV NODE_ENV production
29
- ENV NPM_CONFIG_USERCONFIG /usr/src/app/.npmrc
30
-
31
- RUN mkdir -p /usr/src/app
32
- WORKDIR /usr/src/app
33
-
34
- # Install app dependencies
35
- COPY ./workspace/ /usr/src/app/
36
- ENV GRPC_VERBOSITY ERROR
37
-
38
- # --------------------------------------------------------------
39
- # copy the ssh keys into place, npm install, and remove them
40
- # --------------------------------------------------------------
41
-
42
- # Install packages to install private repos with ssh keys
43
- COPY ./.npmrc \${NPM_CONFIG_USERCONFIG}
44
- RUN apk --no-cache add openssh-client && \
45
- apk --update add --no-cache --virtual build-dep g++ gcc libgcc \\
46
- libstdc++ linux-headers make {{apkadds}} && \
47
- npm install -g {{npmVersion}}
48
-
49
- {{preInstallPluginfile}}
50
-
51
- # Use SKIP_PREPARE to disable hook-and-release from running
52
- ENV SKIP_PREPARE=true
53
-
54
- # Do the clean install and remove the npm token and ssh keys from the image
55
- RUN npm ci --only=production --no-optional {{npmlogging}} && \
56
- rm -f \${NPM_CONFIG_USERCONFIG} /root/.ssh/*
57
-
58
- # --------------------------------------------------------------
59
- # Docker Final Stage
60
- # --------------------------------------------------------------
61
-
62
- FROM node:{{nodeimage}}
63
- ENV NPM_CONFIG_USERCONFIG /usr/src/app/.npmrc
64
-
65
- # Copy .npmrc for private registry access for access to any @leverege packages
66
- COPY ./.npmrc \${NPM_CONFIG_USERCONFIG}
67
-
68
- # Install tini for PID 1 and replace shell with bash so we can source files
69
- RUN apk update && \
70
- apk add --no-cache bash curl tini vim {{apkadds}} && \
71
- npm install -g {{npmVersion}} && \
72
- rm /bin/sh && ln -s /bin/bash /bin/sh && \
73
- mkdir -p /usr/src/app /tmp/levlog && \
74
- chown node:node /usr/src/app
75
-
76
- # Pull in the Dockerfile.plugin file here
77
- {{pluginfile}}
78
-
79
- # Eliminate the sensitive info from the final stage image
80
- RUN rm -f \${NPM_CONFIG_USERCONFIG}
81
-
82
- ENTRYPOINT [ "/sbin/tini", "--" ]
83
- WORKDIR /usr/src/app
84
-
85
- COPY --chown=node:node --from=intermediate /usr/src/app /usr/src/app
86
-
87
- USER {{runuser}}
88
- COPY ./bashrc /home/node/.bashrc
89
- CMD [ "/bin/bash", "-c", "source /home/node/.bashrc && node index.js" ]`
90
-
91
27
  const pluginTemplate = `
92
28
  # Dockerfile.plugin - optionally extend the service Docker image
93
29
  #
@@ -103,72 +39,15 @@ const pluginTemplate = `
103
39
  # footprints small, so adding packages "just because" is not considered
104
40
  # a best practice.
105
41
  `
106
-
107
- const bashrcTemplate = `#!/bin/bash
108
- #
109
- alias h=history
110
-
111
- alias ls='ls -CF --color=auto'
112
- alias ll='ls -lh'
113
- alias lla='ls -lha'
114
- alias glep='grep -l -s'
115
- alias m=less
116
- alias menv='env | sort | less'
117
-
118
- alias whatsmyip='wget -qO- ifconfig.co'
119
-
120
- alias err='wget -q -O- localhost:5111/logLevel/error'
121
- alias wrn='wget -q -O- localhost:5111/logLevel/warn'
122
- alias inf='wget -q -O- localhost:5111/logLevel/info'
123
- alias dbg='wget -q -O- localhost:5111/logLevel/debug'
124
- alias trc='wget -q -O- localhost:5111/logLevel/trace'
125
-
126
- socks()
127
- {
128
- netstat -ant | awk '{print }' | sort | uniq -c | sort -n
129
- }
130
-
131
- cmetrics()
132
- {
133
- wget -qO- localhost:5111/metrics
134
- }
135
-
136
- cmstat()
137
- {
138
- wget -qO- localhost:5111
139
- }
140
-
141
- cmclear()
142
- {
143
- # Future me - thinking this should call cmstat and pass in the endpoint as
144
- # a parameter? Well don't do it - without escaping the dollar 1 in cmstat
145
- # the port will end up being 51111, and escaping causes linting to complain
146
- # about unnecessary escaping.
147
- wget -qO- localhost:5111/status/clear
148
- }
149
-
150
- dunm() {
151
- du -sh /usr/src/app/node_modules/* | sort -sh
152
- }
153
- `
154
- const cloudBuildSteps = `
155
- steps:
156
- - name: 'gcr.io/cloud-builders/docker'
157
- args:
158
- - 'build'
159
- - '--network=cloudbuild'
160
- - '-t'
161
- - '{{imageName}}:{{imageVersion}}'
162
- - '.'
163
-
164
- images:
165
- - '{{imageName}}:{{imageVersion}}'
166
- `
42
+ const dockerfileTemplate = fs.readFileSync( path.join( DIRNAME, './templates/dockerfileTemplate.hbs' ), 'utf8' )
43
+ const bashrcTemplate = fs.readFileSync( path.join( DIRNAME, './templates/bashrcTemplate.hbs' ), 'utf8' )
44
+ const cloudBuildSteps = fs.readFileSync( path.join( DIRNAME, './templates/cloudBuildSteps.hbs' ), 'utf8' )
167
45
 
168
46
  const defaultSettings = {
169
47
  apkadds : '',
170
48
  date : getDateTimestamp(),
171
49
  nodeimage : 'jod-alpine',
50
+ imageBase : 'node:jod-alpine',
172
51
  npmlogging : process.env.VERBOSE_NPM_LOGGING ? '--ddd' : '--silent',
173
52
  npmVersion : 'npm@11',
174
53
  pluginfile : '# NO PLUGIN',
@@ -196,7 +75,7 @@ const generateDockerfile = ( options ) => {
196
75
  if ( !settings.nodeimage ) {
197
76
  settings.nodeimage = dockerfileNodeVersion
198
77
  }
199
-
78
+ settings.image = settings.imageBase || `node:${settings.nodeimage}`
200
79
  // Ensure the presence of the docker dir with plugin and bashrc - this code
201
80
  // could probably be tightened up a little.
202
81
  try {
@@ -207,7 +86,9 @@ const generateDockerfile = ( options ) => {
207
86
  }
208
87
 
209
88
  const bashrcFile = './docker/bashrc'
210
- if ( !fs.existsSync( bashrcFile ) ) {
89
+ // DISABLING exists check. With addition of Bashrc.plugin, the file needs to
90
+ // always be created or we encounter errors
91
+ if ( true ) { // !fs.existsSync( bashrcFile ) ) {
211
92
  let bf = bashrcTemplate
212
93
  const bashrcFilePlugin = './docker/Bashrc.plugin'
213
94
  if ( fs.existsSync( bashrcFilePlugin ) ) {
@@ -0,0 +1,257 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import url from 'node:url'
4
+
5
+ import chalk from 'chalk'
6
+ import handlebars from 'handlebars'
7
+
8
+ import {
9
+ errorExit,
10
+ getDateTimestamp,
11
+ getGcpAccount,
12
+ log,
13
+ proceed,
14
+ shellCmd,
15
+ warning
16
+ } from './Utils.mjs'
17
+
18
+ const DIRNAME = path.dirname( url.fileURLToPath( import.meta.url ) )
19
+
20
+ // The contents of these variables were initially located in files that live in
21
+ // the build-tools repository, but it just became simpler to pull the contents
22
+ // directly into these variables and forego the file loading.
23
+ //
24
+ const pluginTemplate = `
25
+ # Dockerfile.plugin - optionally extend the service Docker image
26
+ #
27
+ # This file may be used to run additional docker commands during the image
28
+ # build process without needing to maintain a local custom Dockerfile. For
29
+ # example, uncommenting the following docker RUN command will cause the
30
+ # curl and vim packages to be added to the deployed image thus making them
31
+ # available from the pod's command line on k8s:
32
+ #
33
+ # RUN apk update && apk add --no-cache curl vim
34
+ #
35
+ # Keep in mind that the Alpine Linux base image is used to keep image
36
+ # footprints small, so adding packages "just because" is not considered
37
+ # a best practice.
38
+ `
39
+ const dockerfileTemplate = fs.readFileSync( path.join( DIRNAME, './templates/dockerfileTemplatePy.hbs' ), 'utf8' )
40
+ const cloudBuildSteps = fs.readFileSync( path.join( DIRNAME, './templates/cloudBuildSteps.hbs' ), 'utf8' )
41
+ let bashrcTemplate = fs.readFileSync( path.join( DIRNAME, './templates/bashrcTemplate.hbs' ), 'utf8' )
42
+ const bashrcPyTemplate = fs.readFileSync( path.join( DIRNAME, './templates/bashrcTemplatePy.hbs' ), 'utf8' )
43
+ bashrcTemplate = `${bashrcTemplate}\n${bashrcPyTemplate}`
44
+
45
+ const defaultSettings = {
46
+ apkadds : '',
47
+ date : getDateTimestamp(),
48
+ nodeimage : 'jod-alpine',
49
+ imageBase : 'node:jod-alpine',
50
+ npmlogging : process.env.VERBOSE_NPM_LOGGING ? '--ddd' : '--silent',
51
+ npmVersion : 'npm@11',
52
+ pluginfile : '# NO PLUGIN',
53
+ preInstallPluginfile : '# NO PRE-INSTALL PLUGIN',
54
+ regvers : 'package.version',
55
+ runuser : 'python',
56
+ }
57
+
58
+ const previousFile = './docker/.previous'
59
+
60
+ function getPreviousBuild() {
61
+ return fs.existsSync( previousFile ) ? fs.readFileSync( previousFile ) : 'FIRST BUILD'
62
+ }
63
+
64
+ const setPreviousBuild = ( version ) => {
65
+ fs.writeFileSync( previousFile, version )
66
+ }
67
+
68
+ function generateDockerfile( options ) {
69
+
70
+ const settings = { ...defaultSettings, ...options }
71
+
72
+ if ( !settings.imageBase ) {
73
+ warning( 'using the default image python:3.12.8-slim-bookworm' )
74
+ settings.image = 'python:3.12.8-slim-bookworm'
75
+ } else {
76
+ settings.image = settings.imageBase
77
+ }
78
+ // Ensure the presence of the docker dir with plugin and bashrc - this code
79
+ // could probably be tightened up a little.
80
+ try {
81
+ fs.readdirSync( './docker' )
82
+ } catch ( err ) {
83
+ warning( 'creating the missing ./docker directory' )
84
+ fs.mkdirSync( './docker' ) // TODO: blindly assumes the mkdir succeeds
85
+ }
86
+
87
+ const bashrcFile = './docker/bashrc'
88
+ let bf = bashrcTemplate
89
+ const bashrcFilePlugin = './docker/Bashrc.plugin'
90
+ if ( fs.existsSync( bashrcFilePlugin ) ) {
91
+ const bashrcAdditions = fs.readFileSync( bashrcFilePlugin )
92
+ bf = `${bf}\n${bashrcAdditions}`
93
+ }
94
+ fs.writeFileSync( bashrcFile, bf )
95
+
96
+ const dockerfilePlugin = './docker/Dockerfile.plugin'
97
+ if ( !fs.existsSync( dockerfilePlugin ) ) {
98
+ warning( `creating the missing ${dockerfilePlugin} file in the docker directory` )
99
+ fs.writeFileSync( dockerfilePlugin, pluginTemplate )
100
+ }
101
+
102
+ try {
103
+ const build = fs.readdirSync( './build' )
104
+ if ( build.length === 0 ) {
105
+ errorExit( `***ERROR: the build dir is empty - ${chalk.yellow( 'npm run build' )}` )
106
+ }
107
+ } catch ( err ) {
108
+ errorExit( `***ERROR: expected the ./build directory to exist - ${chalk.yellow( 'npm run build' )}` )
109
+ }
110
+
111
+ // At this point the plugin template better exist.
112
+ if ( fs.existsSync( dockerfilePlugin ) ) {
113
+ settings.pluginfile = fs.readFileSync( dockerfilePlugin )
114
+ } else {
115
+ errorExit( `***ERROR: something went wrong with ${dockerfilePlugin} plugin creation` )
116
+ }
117
+
118
+ const dockerfilePrePlugin = './docker/DockerfilePreInstall.plugin'
119
+ if ( fs.existsSync( dockerfilePrePlugin ) ) {
120
+ settings.preInstallPluginfile = fs.readFileSync( dockerfilePrePlugin )
121
+ }
122
+
123
+ const compiled = handlebars.compile( dockerfileTemplate, { noEscape : true } )
124
+ const replaced = compiled( settings )
125
+ // temporarily write the Dockerfile to the docker subdir - the container
126
+ // image builder will then move it over to the build subdir
127
+ const dockerfile = './docker/Dockerfile'
128
+ try {
129
+ fs.writeFileSync( dockerfile, replaced )
130
+ } catch ( err ) {
131
+ errorExit( `***ERROR: failed to write ${dockerfile}\n${err}` )
132
+ }
133
+
134
+ const previousBuild = getPreviousBuild()
135
+ const dockerInfo = { dockerfile, previousBuild, ...settings }
136
+ delete dockerInfo.pluginfile
137
+ return dockerInfo
138
+ }
139
+
140
+ function formCloudBuildBucketName( artifactProject ) {
141
+ return `gs://${artifactProject}_cloudbuild`
142
+ }
143
+
144
+ async function validateCloudBuildBucket( artifactProject ) {
145
+ const bucketName = formCloudBuildBucketName( artifactProject )
146
+ log( chalk.green.bold( `Verifying the build bucket exists => ${bucketName}\n` ) )
147
+ try {
148
+ await shellCmd( `gsutil ls -p ${artifactProject} -b ${bucketName}` )
149
+ } catch ( error ) {
150
+ errorExit( `***Error: from Docker.validateCloudBuildBucket\n\n${chalk.bold.yellow( error )}` )
151
+ }
152
+ }
153
+
154
+ async function buildContainerImage( {
155
+ artifactProject, artifactRegistry, containerName,
156
+ imageVersion, isNpmWorkspace, options
157
+ } ) {
158
+
159
+ const useCloudBuildFile = true
160
+ const buildWorkspace = './build/workspace' // passed to Cloud Build
161
+ const imageName = `${artifactRegistry}/images/${containerName}`
162
+
163
+ if ( fs.existsSync( buildWorkspace ) ) {
164
+ fs.renameSync( buildWorkspace, `${buildWorkspace}-junk` )
165
+ try {
166
+ fs.rmSync( `${buildWorkspace}-junk`, { recursive : true } )
167
+ } catch ( err ) {
168
+ log( `***Error: from Docker.buildContainerImage\n\n${chalk.bold.yellow( err )}` )
169
+ }
170
+ }
171
+
172
+ // TODO: This is janky - I'll clean it up later
173
+ // NOTE: this is dealing with raw filesystem interactions, meaning there
174
+ // isn't a shell performing file globbing and what not
175
+ fs.renameSync( './build', './workspace' )
176
+ fs.mkdirSync( './build' )
177
+ fs.renameSync( './workspace', buildWorkspace )
178
+ fs.renameSync( './docker/Dockerfile', './build/Dockerfile' )
179
+ // fs.cpSync( `${process.env.HOME}/.npmrc`, './build/.npmrc' )
180
+ fs.cpSync( './docker/bashrc', './build/bashrc' )
181
+ // fs.cpSync( './package.json', `${buildWorkspace}/package.json` )
182
+ // fs.cpSync( './package-lock.json', `${buildWorkspace}/package-lock.json` )
183
+
184
+ setPreviousBuild( imageVersion ) // update docker/.previous file
185
+
186
+ const { fullName : builderName } = await getGcpAccount()
187
+ const gcloudBuild = `time gcloud builds submit --project ${artifactProject}`
188
+ const gcloudLogs = `--gcs-log-dir ${formCloudBuildBucketName( artifactProject )}/log`
189
+ let gcloudTags = `--tag ${imageName}:${imageVersion}`
190
+
191
+ if ( useCloudBuildFile ) {
192
+ const cloudBuildFile = './docker/cloudbuild.yaml'
193
+ gcloudTags = `--config=${cloudBuildFile}`
194
+ const cbst = handlebars.compile( cloudBuildSteps, { noEscape : true } )
195
+ const cbs = cbst( { artifactProject, artifactRegistry, imageName, imageVersion } )
196
+
197
+ fs.writeFileSync( cloudBuildFile, cbs )
198
+ }
199
+
200
+ log( chalk.green.bold( `
201
+ *** Submitting Build as ${builderName} ***
202
+ ${gcloudBuild} \\
203
+ ${gcloudLogs} \\
204
+ ${gcloudTags}
205
+ ` ) )
206
+
207
+ if ( process.env.DRY_RUN === '1' ) {
208
+ log( '\n***Exiting from DRY_RUN\n' )
209
+ process.exit( 0 )
210
+ }
211
+
212
+ await shellCmd( `${gcloudBuild} ${gcloudLogs} ${gcloudTags} ./build`, { stdio : 'inherit' } )
213
+
214
+ const imageTag = `${defaultSettings.date}-${defaultSettings.nodeimage}-${builderName}`
215
+ await shellCmd(
216
+ `gcloud container images add-tag --quiet ${imageName}:${imageVersion} ${imageName}:${imageTag} ${imageName}:latest`
217
+ )
218
+ }
219
+
220
+ async function repoCleanup() {
221
+ // remove garbage from the repo
222
+ const garbage = [
223
+ 'build',
224
+ 'dist',
225
+ 'docker/.keep',
226
+ 'docker/.npmrc',
227
+ 'docker/.gitignore',
228
+ 'docker/.previous',
229
+ 'docker/Dockerfile',
230
+ 'docker/workspace',
231
+ ]
232
+ garbage.forEach( trash => fs.rmSync( trash, { recursive : true, force : true } ) )
233
+
234
+ fs.writeFileSync( 'docker/.gitignore', '.previous\nDockerfile' )
235
+
236
+ await shellCmd( 'git add -f docker' )
237
+
238
+ log( `
239
+ ${chalk.yellow.bold( '***IMPORTANT***' )}
240
+
241
+ Please make the following .gitignore modifications:
242
+ change /dist to ${chalk.green.bold( '/build' )}
243
+ remove ${chalk.red.bold( '/docker' )}
244
+ ` )
245
+
246
+ await proceed( 'Acknowledge the .gitignore mods were made?' )
247
+ }
248
+
249
+ export default {
250
+ getPreviousBuild,
251
+ setPreviousBuild,
252
+ generateDockerfile,
253
+ formCloudBuildBucketName,
254
+ validateCloudBuildBucket,
255
+ buildContainerImage,
256
+ repoCleanup,
257
+ }
@@ -0,0 +1,291 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * docker-to-registry-py will...
4
+ */
5
+ import fs from 'node:fs'
6
+ import toml from 'toml'
7
+ import chalk from 'chalk'
8
+ import semver from 'semver'
9
+
10
+ import {
11
+ debug, log,
12
+ errorExit,
13
+ getGitBranchAndUpstream,
14
+ gitRepoIsDirty,
15
+ parseHelmChart,
16
+ proceed,
17
+ shellCmd } from './Utils.mjs'
18
+
19
+ import Docker from './DockerPy.mjs'
20
+ import { version } from 'node:os'
21
+
22
+ // refresh-npm-token does not throw so no need to try, but it emits in debug
23
+ const refreshErr = await shellCmd( 'refresh-npm-token' )
24
+ if ( refreshErr && !process.env.BUILD_TOOLS_DEBUG ) {
25
+ log( refreshErr ) // most likely the token refreshed message
26
+ }
27
+
28
+ const options = {}
29
+ let imageVersion = null
30
+ // Expected to be invoked like docker-to-registry-py v1.2.3
31
+ for ( let n = 2; n < process.argv.length; n++ ) {
32
+ if ( process.argv[n].startsWith( '-' ) ) {
33
+ const str = process.argv[n].slice( 1 )
34
+ if ( str.indexOf( '=' ) > 0 ) {
35
+ const [ key, value ] = str.split( '=' )
36
+ options[key] = value
37
+ } else {
38
+ options[str] = true
39
+ }
40
+ } else {
41
+ imageVersion = process.argv[n]
42
+ }
43
+ }
44
+ if ( !imageVersion ) {
45
+ errorExit( '***Error: docker-to-registry-py requires an image version' )
46
+ }
47
+
48
+ // may use guide-me or guideme to ask for update guidance
49
+ const giveGuidance = imageVersion.match( /^guide-*me$/ ) !== null
50
+
51
+ const willGitTag = imageVersion.match( /^v\d+\.\d+\.\d+$/ )
52
+ const tagInfo = willGitTag ?
53
+ chalk.yellow.bold( 'will be git tagged' ) :
54
+ chalk.red( 'BETA RELEASE WILL NOT BE GIT TAGGED' )
55
+
56
+ const repoIsDirty = await gitRepoIsDirty()
57
+
58
+ if ( willGitTag && repoIsDirty ) {
59
+ log( `
60
+ ${chalk.red.bold( '***ERROR: attempting to tag a non-beta dirty git repository' )}
61
+
62
+ You are attempting to create a tagged release image for pushing to the
63
+ artifact registry, but there are locally modified files. This is not
64
+ allowed since the applied tag will not be relevant to the image version
65
+ due to the pending commits.
66
+
67
+ Available options for proceeding:
68
+
69
+ ${chalk.green.bold( '1) cleanly commit all local mods assuming relevance' )}
70
+ ${chalk.yellow( '2) eliminate unwanted local modifications' )}
71
+ ${chalk.red( '3) stash anything that is irrelevant to this release' )}
72
+
73
+ ` )
74
+ process.exit( 1 )
75
+ }
76
+
77
+ if ( !fs.existsSync( './helm' ) ) {
78
+ log( `
79
+ ${chalk.red.bold( '***Error: docker-to-registry-py requires a helm chart directory' )}
80
+
81
+ The docker-to-registry-py script expects to run in the root directory of a k8s
82
+ service, which requires a helm chart directory. It also expects a docker
83
+ directory but will build that if it does not exist.
84
+
85
+ ` )
86
+ process.exit( 1 )
87
+ }
88
+
89
+ const tomlStr = fs.readFileSync( './pyproject.toml' )
90
+ const pyproject = toml.parse( tomlStr )
91
+ const repoDescr = {
92
+ artifactProject : pyproject.leverege.project || 'leverege-registry',
93
+ containerName : pyproject.project.name,
94
+ packageVersion : pyproject.project.version,
95
+ artifactRegistry : pyproject.leverege.registry || 'us-docker.pkg.dev/leverege-registry/stack',
96
+ registryFolder : `${pyproject.leverege.registryFolder}/images/${pyproject.project.name}`,
97
+ imageBase : pyproject.leverege.imageBase,
98
+ helmChart : await parseHelmChart(),
99
+ }
100
+
101
+ debug( { repoDescr }, '<==The Repo Description' )
102
+
103
+ const dockerInfo = await Docker.generateDockerfile( {
104
+ regvers : repoDescr.packageVersion,
105
+ imageBase : repoDescr.imageBase,
106
+ } )
107
+
108
+ debug( { dockerInfo }, '<==The Docker Info' )
109
+
110
+ const {
111
+ artifactProject,
112
+ artifactRegistry,
113
+ packageVersion,
114
+ containerName,
115
+ helmChart,
116
+ } = repoDescr
117
+
118
+ if ( giveGuidance ) {
119
+ await Docker.validateCloudBuildBucket( artifactProject )
120
+ }
121
+
122
+ const registryFolder = `${artifactRegistry}/images/${containerName}`
123
+
124
+ // Update the dependencies...
125
+ log( chalk.green.bold( 'Updating dependencies and workspace...' ) )
126
+ await shellCmd( 'npm install', { stdio : 'inherit' } )
127
+
128
+ if ( giveGuidance ) {
129
+ await Docker.repoCleanup() // adjusts gitignore files and removes cruft
130
+ log( `
131
+
132
+
133
+
134
+ ${chalk.green.bold( '-------------------- Potential Helm Chart Mods --------------------' )}
135
+
136
+ Moving to the new artifact-registry for docker image storage may also require
137
+ helm chart changes depending on how old the current charts are. Charts that
138
+ contain helm/values.yaml files resembling this:
139
+
140
+ ${chalk.yellow.bold( `serviceConfig:
141
+ VERSION: v1.2.3
142
+ PREEMPTIBLE: true` )}
143
+
144
+ are pre-ignition era charts and should be replaced with the latest helm chart
145
+ templates available from ignition. Either dive in or ask devops for a hand
146
+ when converting these legacy / deprecated charts.
147
+
148
+
149
+ More recently updated helm charts that have already started the migration to
150
+ the new approach may already have registry entries like this:
151
+
152
+ ${chalk.yellow.bold( `image:
153
+ registry: gcr.io/leverege-docker-images
154
+ tag: ""` )}
155
+
156
+ The above registry statement references the deprecated container registry and
157
+ must be adjusted accordingly to resemble:
158
+
159
+ ${chalk.green.bold( 'registry: us-docker.pkg.dev/leverege-registry/<registry folder>/images' )}
160
+
161
+ There may also be a corresponding modification needed in the helm template
162
+ deployment.yaml file in the container image spec:
163
+
164
+ ${chalk.green.bold( 'image: {{ .Values.image.registry }}/{{ .Chart.Name }}:{{ default .Chart.AppVersion .Values.image.tag }}' )}
165
+
166
+
167
+ ${chalk.magenta.bold( `Upgrading all service charts to the latest ignition template chart structure
168
+ is the preferred approach to making the transition to the artifact registry.` )}
169
+
170
+ ` )
171
+ log( chalk.green.bold( '*** docker-to-registry-py guidance complete - ready for dockerization ***' ) )
172
+ process.exit( 0 )
173
+ }
174
+
175
+ // do chart checks too
176
+ let chart
177
+ if ( options?.helmChecks !== 'false' ) {
178
+ chart = helmChart?.yaml['Chart.yaml']
179
+ const values = helmChart?.yaml['values.yaml']
180
+ const expectedChartRegistry = `${artifactRegistry}/images`
181
+
182
+ if ( expectedChartRegistry !== values.image?.registry ) {
183
+ log( `
184
+ ${chalk.red.bold( '***Error: mismatched package.json registry and helm/values.yaml' )}
185
+
186
+ The registry specified in the package.json leverege stanza does not line up
187
+ with the image registry in helm/values.yaml. The naming convention expects
188
+ the '/images' suffix to be added to the package.json registry string and then
189
+ stored as the image.registry in the helm/values.yaml file.
190
+
191
+ Current settings:
192
+ package.json registry => ${chalk.yellow.bold( artifactRegistry )}
193
+ values.yaml registry => ${chalk.red.bold( values.image?.registry )}
194
+
195
+ Expected settings:
196
+ values.yaml registry => ${chalk.green.bold( expectedChartRegistry )}
197
+ ` )
198
+
199
+ if ( !values.image ) {
200
+ log( `
201
+ ${chalk.yellow.bold( '***DEPRECATED: legacy helm charts detected' )}
202
+
203
+ The helm chart structure appears to be based on the pre-ignition helm chart
204
+ layouts. The charts must be upgraded before docker-to-registry-py can complete
205
+ its job.
206
+
207
+ ` )
208
+ }
209
+
210
+ process.exit( 1 )
211
+ }
212
+ }
213
+
214
+ // Give the summary and the user a chance to proceed or not
215
+ log( `
216
+ ${chalk.green.bold( 'Build Information:' )}
217
+ Container: ${chalk.green.bold( containerName )}
218
+ Version: ${chalk.green.bold( imageVersion )} ${tagInfo}
219
+ Registry: ${chalk.green.bold( artifactRegistry )}
220
+ Image Folder: ${chalk.yellow.bold( registryFolder )}
221
+
222
+ ${chalk.green.bold( 'Docker Information:' )}
223
+ NodeImage: ${chalk.green.bold( dockerInfo.imageBase || `node:${dockerInfo.nodeimage}` )}
224
+ Docker Env: ${chalk.green.bold( dockerInfo.buildEnv || 'alpine' )}
225
+ AddedPkgs: ${chalk.green.bold( dockerInfo.apkadds )}
226
+ Run User: ${chalk.green.bold( dockerInfo.runuser )}
227
+ Previous: ${chalk.yellow.bold( dockerInfo.previousBuild )}
228
+ DateStamp: ${chalk.green.bold( dockerInfo.date )}
229
+ NPM Logs: ${chalk.green.bold( dockerInfo.npmlogging )}
230
+ NPM Version: ${chalk.green.bold( dockerInfo.npmVersion )}
231
+ ` )
232
+
233
+ if ( imageVersion !== packageVersion ) {
234
+ log( `
235
+ ${chalk.yellow.bold( '***WARNING: specified version does not match project version' )}
236
+ pyproject.toml => ${chalk.green.bold( packageVersion )}
237
+ specified => ${chalk.red.bold( imageVersion )}
238
+ ` )
239
+
240
+ if ( willGitTag ) {
241
+ log( chalk.red.bold( '... and you are attempting to release so NOPE!\n' ) )
242
+ process.exit( 1 )
243
+ }
244
+ }
245
+
246
+ const repoInfo = await getGitBranchAndUpstream()
247
+
248
+ if ( willGitTag && !repoInfo.upstream ) {
249
+ log( `
250
+ ${chalk.red.bold( '***ERROR: the current branch must have an upstream' )}
251
+
252
+ You are attempting to create a tagged release image for pushing to the
253
+ container registry, but the current branch does not have an upstream to
254
+ push to. This is not allowed since the applied tag will be stranded here
255
+ in your local repository, which is what we are trying to avoid.
256
+
257
+ Available options for proceeding:` )
258
+ log( chalk.green.bold( `
259
+ 1) set an upstream for this branch via:
260
+ git push --set-upstream origin ${repoInfo.branch}` ) )
261
+ log( chalk.yellow.bold( `
262
+ 2) rebase, squash and merge onto a branch with an upstream` ) )
263
+
264
+ process.exit( 1 )
265
+ }
266
+
267
+ if ( willGitTag ) {
268
+ if ( chart.appVersion !== imageVersion ) {
269
+ log( `
270
+ ${chalk.yellow.bold( '***WARNING: the helm/Chart.yaml appVersion does not align with the build version' )}
271
+ chart appVersion => ${chalk.yellow.bold( chart.appVersion )}
272
+ build version => ${chalk.green.bold( imageVersion )}
273
+ ` )
274
+ }
275
+ }
276
+
277
+ await proceed()
278
+
279
+ log( chalk.green.bold( 'Building the Docker Image...' ) )
280
+ await Docker.buildContainerImage( { ...repoDescr, options, imageVersion } )
281
+ log( chalk.blue.bold( 'Done Building the Docker Image...' ) )
282
+
283
+ if ( willGitTag ) {
284
+ const tagDescription = `docker_${imageVersion}`
285
+ const fullImageTag = repoDescr.isMonoRepo ? `${repoDescr.containerName}/${imageVersion}` : imageVersion
286
+ log( chalk.green.bold( `Tagging and Pushing ${fullImageTag}\n` ) )
287
+ await shellCmd( `git tag -f ${fullImageTag} -m ${tagDescription}` )
288
+ await shellCmd( 'git push --follow-tags' )
289
+ } else {
290
+ log( chalk.yellow.bold( '\n*** Skipped git tagging - beta release' ) )
291
+ }
@@ -96,6 +96,8 @@ if ( semver.lt( semver.coerce( repoDescr.buildToolsVersion ), minimumBuildToolsV
96
96
 
97
97
  const dockerInfo = await docker.generateDockerfile( {
98
98
  regvers : repoDescr.packageVersion,
99
+ imageBase : repoDescr.closestPackageJson.leverege?.imageBase,
100
+ buildEnv : repoDescr.closestPackageJson.leverege?.buildEnv || 'alpine',
99
101
  nodeimage : repoDescr.closestPackageJson.leverege?.nodeimg, // left as nodeimg in package.json for backwards compat
100
102
  } )
101
103
  debug( { dockerInfo }, '<==The Docker Info' )
@@ -216,7 +218,8 @@ log( `
216
218
  NPM Workspace: ${chalk.green.bold( isNpmWorkspace )}
217
219
 
218
220
  ${chalk.green.bold( 'Docker Information:' )}
219
- NodeImage: ${chalk.green.bold( dockerInfo.nodeimage )}
221
+ NodeImage: ${chalk.green.bold( dockerInfo.imageBase || `node:${dockerInfo.nodeimage}` )}
222
+ Docker Env: ${chalk.green.bold( dockerInfo.buildEnv || 'alpine' )}
220
223
  AddedPkgs: ${chalk.green.bold( dockerInfo.apkadds )}
221
224
  Run User: ${chalk.green.bold( dockerInfo.runuser )}
222
225
  Previous: ${chalk.yellow.bold( dockerInfo.previousBuild )}
@@ -0,0 +1,46 @@
1
+ #!/bin/bash
2
+ #
3
+ alias h=history
4
+
5
+ alias ls='ls -CF --color=auto'
6
+ alias ll='ls -lh'
7
+ alias lla='ls -lha'
8
+ alias glep='grep -l -s'
9
+ alias m=less
10
+ alias menv='env | sort | less'
11
+
12
+ alias whatsmyip='wget -qO- ifconfig.co'
13
+
14
+ alias err='wget -q -O- localhost:5111/logLevel/error'
15
+ alias wrn='wget -q -O- localhost:5111/logLevel/warn'
16
+ alias inf='wget -q -O- localhost:5111/logLevel/info'
17
+ alias dbg='wget -q -O- localhost:5111/logLevel/debug'
18
+ alias trc='wget -q -O- localhost:5111/logLevel/trace'
19
+
20
+ socks()
21
+ {
22
+ netstat -ant | awk '{print }' | sort | uniq -c | sort -n
23
+ }
24
+
25
+ cmetrics()
26
+ {
27
+ wget -qO- localhost:5111/metrics
28
+ }
29
+
30
+ cmstat()
31
+ {
32
+ wget -qO- localhost:5111
33
+ }
34
+
35
+ cmclear()
36
+ {
37
+ # Future me - thinking this should call cmstat and pass in the endpoint as
38
+ # a parameter? Well don't do it - without escaping the dollar 1 in cmstat
39
+ # the port will end up being 51111, and escaping causes linting to complain
40
+ # about unnecessary escaping.
41
+ wget -qO- localhost:5111/status/clear
42
+ }
43
+
44
+ dunm() {
45
+ du -sh /usr/src/app/node_modules/* | sort -sh
46
+ }
@@ -0,0 +1 @@
1
+ export PYTHONPYCACHEPREFIX=/tmp/pycache
@@ -0,0 +1,13 @@
1
+ steps:
2
+ - name: 'gcr.io/cloud-builders/docker'
3
+ args:
4
+ - 'build'
5
+ - '--network=cloudbuild'
6
+ - '-t'
7
+ - '{{imageName}}:{{imageVersion}}'
8
+ - '--cache-from'
9
+ - '{{imageName}}:{{imageVersion}}'
10
+ - '.'
11
+
12
+ images:
13
+ - '{{imageName}}:{{imageVersion}}'
@@ -0,0 +1,86 @@
1
+ # Version {{regvers}} @ {{date}}
2
+
3
+ # The FROM directive sets the Base Image for subsequent instructions
4
+ FROM {{image}} as intermediate
5
+
6
+ ARG BUILD_ENV="{{buildEnv}}"
7
+
8
+ ENV NODE_ENV production
9
+ ENV NPM_CONFIG_USERCONFIG /usr/src/app/.npmrc
10
+
11
+ RUN mkdir -p /usr/src/app
12
+ WORKDIR /usr/src/app
13
+
14
+ # Install app dependencies
15
+ COPY ./workspace/ /usr/src/app/
16
+ ENV GRPC_VERBOSITY ERROR
17
+
18
+ # --------------------------------------------------------------
19
+ # copy the ssh keys into place, npm install, and remove them
20
+ # --------------------------------------------------------------
21
+
22
+ # Install packages to install private repos with ssh keys
23
+ COPY ./.npmrc ${NPM_CONFIG_USERCONFIG}
24
+
25
+ RUN if [ "${BUILD_ENV}" = "debian" ]; then \
26
+ apt-get update && \
27
+ apt-get install -y wget openssh-client build-essential && \
28
+ npm install -g npm@10; \
29
+ else \
30
+ apk --no-cache add openssh-client && \
31
+ apk --update add --no-cache --virtual build-dep g++ gcc libgcc libstdc++ linux-headers make {{apkadds}} && \
32
+ npm install -g npm@10; \
33
+ fi
34
+
35
+ # Use SKIP_PREPARE to disable hook-and-release from running
36
+ ENV SKIP_PREPARE=true
37
+
38
+ {{preInstallPluginfile}}
39
+
40
+ # Do the clean install and remove the npm token and ssh keys from the image
41
+ RUN npm ci --only=production --no-optional {{npmlogging}} && \
42
+ rm -f ${NPM_CONFIG_USERCONFIG} /root/.ssh/*
43
+
44
+ # --------------------------------------------------------------
45
+ # Docker Final Stage
46
+ # --------------------------------------------------------------
47
+
48
+ FROM {{image}}
49
+
50
+ ARG BUILD_ENV="{{buildEnv}}"
51
+
52
+ ENV NPM_CONFIG_USERCONFIG /usr/src/app/.npmrc
53
+
54
+ # Copy .npmrc for private registry access for access to any @leverege packages
55
+ COPY ./.npmrc ${NPM_CONFIG_USERCONFIG}
56
+
57
+ # Install tini for PID 1 and replace shell with bash so we can source files
58
+ RUN if [ "$BUILD_ENV" = "debian" ]; then \
59
+ apt-get update && \
60
+ apt-get install -y tini bash curl vim {{apkadds}} && \
61
+ npm install -g npm@10 && \
62
+ rm /bin/sh && ln -s /bin/bash /bin/sh && \
63
+ mkdir -p /usr/src/app /tmp/levlog && chown node:node /usr/src/app; \
64
+ else \
65
+ apk update && \
66
+ apk add --no-cache bash curl tini vim {{apkadds}} && \
67
+ npm install -g npm@10 && \
68
+ rm /bin/sh && ln -s /bin/bash /bin/sh && \
69
+ mkdir -p /usr/src/app /tmp/levlog && chown node:node /usr/src/app; \
70
+ fi
71
+
72
+
73
+ # Pull in the Dockerfile.plugin file here
74
+ {{pluginfile}}
75
+
76
+ # Eliminate the sensitive info from the final stage image
77
+ RUN rm -f ${NPM_CONFIG_USERCONFIG}
78
+
79
+ ENTRYPOINT [ "tini", "--" ]
80
+ WORKDIR /usr/src/app
81
+
82
+ COPY --chown=node:node --from=intermediate /usr/src/app /usr/src/app
83
+
84
+ USER {{runuser}}
85
+ COPY ./bashrc /home/node/.bashrc
86
+ CMD [ "/bin/bash", "-c", "source /home/node/.bashrc && node index.js" ]
@@ -0,0 +1,75 @@
1
+ # Version {{regvers}} @ {{date}}
2
+
3
+ # The FROM directive sets the Base Image for subsequent instructions
4
+ FROM {{image}} as intermediate
5
+
6
+ ARG BUILD_ENV="{{buildEnv}}"
7
+
8
+ RUN apt-get update && apt-get install -y \
9
+ wget
10
+ # gcc \
11
+ # git \
12
+ # libgl1 \
13
+ # libglib2.0-0 \
14
+ # && rm -rf /var/lib/apt/lists/*
15
+
16
+ # Copy uv binary from the official distroless Docker image.
17
+ COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
18
+
19
+ # Define the working directory.
20
+ WORKDIR /usr/src/app
21
+
22
+ #ENV NODE_ENV production
23
+ #ENV NPM_CONFIG_USERCONFIG /usr/src/app/.npmrc
24
+
25
+ RUN mkdir -p /usr/src/app
26
+ WORKDIR /usr/src/app
27
+
28
+ # Install app dependencies
29
+ COPY ./workspace/ /usr/src/app/
30
+ ENV GRPC_VERBOSITY ERROR
31
+
32
+
33
+ # Setup pip.conf for Google Artifact Registry.
34
+ RUN mkdir -p ~/.config/pip
35
+ RUN echo "[global]" >> ~/.config/pip/pip.conf && \
36
+ echo "break-system-packages = true" >> ~/.config/pip/pip.conf
37
+
38
+
39
+ # Set up the virtual environment.
40
+ RUN uv venv
41
+ ENV VIRTUAL_ENV=/usr/src/app/.venv
42
+ ENV PATH="$VIRTUAL_ENV/bin:$PATH"
43
+
44
+ # Setup keyring for Google Artifact Registry.
45
+ RUN uv pip install keyring
46
+ RUN uv pip install keyrings.google-artifactregistry-auth
47
+
48
+ RUN echo "extra-index-url = https://oauth2accesstoken@us-python.pkg.dev/leverege-registry/leverege-python-packages/simple" >> ~/.config/pip/pip.conf
49
+
50
+ ENV UV_EXTRA_INDEX_URL "https://oauth2accesstoken@us-python.pkg.dev/leverege-registry/leverege-python-packages/simple"
51
+
52
+
53
+ {{preInstallPluginfile}}
54
+
55
+ RUN cat
56
+
57
+ RUN ls -al
58
+ # Install uv
59
+ RUN uv sync --keyring-provider subprocess --extra-index-url https://oauth2accesstoken@us-python.pkg.dev/leverege-registry/leverege-python-packages/simple
60
+
61
+
62
+ # Pull in the Dockerfile.plugin file here
63
+ {{pluginfile}}
64
+
65
+ # Eliminate the sensitive info from the final stage image
66
+ #RUN rm -f ${NPM_CONFIG_USERCONFIG}
67
+
68
+ # ENTRYPOINT [ "tini", "--" ]
69
+ #WORKDIR /usr/src/app
70
+
71
+ #COPY --chown=node:node --from=intermediate /usr/src/app /usr/src/app
72
+
73
+ USER {{runuser}}
74
+ COPY ./bashrc /home/{{runuser}}/.bashrc
75
+ CMD [ "/bin/bash", "-c", "source /home/{{runuser}}/.bashrc && python scripts/server.py" ]