@leverege/build-tools 2.50.2 → 2.50.3

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.50.2",
3
+ "version": "2.50.3",
4
4
  "description": "A collection of build / support tools for Leverege developers",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -64,7 +64,7 @@
64
64
  "execa": "^8.0.1",
65
65
  "glob": "^10.3.12",
66
66
  "handlebars": "^4.7.8",
67
- "inquirer": "^9.2.18",
67
+ "inquirer": "^9.2.19",
68
68
  "js-yaml": "^4.1.0",
69
69
  "ms": "^2.1.3",
70
70
  "npm-registry-fetch": "^16.2.1",
@@ -74,7 +74,7 @@
74
74
  "readline-sync": "^1.4.10",
75
75
  "semver": "^7.6.0",
76
76
  "simple-git": "^3.24.0",
77
- "zx": "^8.0.1"
77
+ "zx": "^8.0.2"
78
78
  },
79
79
  "devDependencies": {
80
80
  "@leverege/eslint-config-leverege": "^4.2.0",
@@ -0,0 +1,20 @@
1
+ registry:
2
+ - root: us-docker.pkg.dev
3
+ - repositories:
4
+ - name: stack
5
+ charts:
6
+ - api-server
7
+ - authz-server
8
+ - emailer
9
+ - message-processor
10
+ - name: leverege
11
+ charts:
12
+ - pubsub-pulse
13
+ - pusher
14
+ - overdose
15
+ - name: cox-health
16
+ charts:
17
+ - actions-server
18
+ - analytics-server
19
+ - centrak-healthz
20
+ - centrak-ingestor
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * chart-to-registry will...
4
+ */
5
+ import fs from 'node:fs'
6
+
7
+ import chalk from 'chalk'
8
+ import commandLineArgs from 'command-line-args'
9
+ import commandLineUsage from 'command-line-usage'
10
+ import { lt as semverLt } from 'semver'
11
+ import YAML from 'js-yaml'
12
+
13
+ import {
14
+ condir,
15
+ debug,
16
+ errorExit,
17
+ log,
18
+ warning,
19
+ getGitRootDirectory,
20
+ parsePackageJson,
21
+ parseHelmChart,
22
+ shellCmd } from './Utils.mjs'
23
+
24
+ const commandLineOptions = [ // Use commandLineOptions to tie into the Usage statements
25
+ /* eslint-disable max-len */
26
+ {
27
+ name : 'location',
28
+ type : String,
29
+ description : '{green the location of the artifact registry the chart will be pushed to (default us-docker.pkg.dev)}',
30
+ },
31
+ {
32
+ name : 'project',
33
+ type : String,
34
+ description : '{green the name of the google project containing the npmrc and slack config secrets (default leverege-registry)}',
35
+ },
36
+ {
37
+ name : 'repository',
38
+ type : String,
39
+ description : '{green the target repository to receive the pushed chart}',
40
+ },
41
+ {
42
+ name : 'dry-run',
43
+ type : Boolean,
44
+ description : '{yellow perform everything except the actual chart push}',
45
+ },
46
+ {
47
+ name : 'help',
48
+ type : Boolean,
49
+ description : '{green display this help screen}',
50
+ },
51
+ /* eslint-enable max-len */
52
+ ]
53
+
54
+ const sections = [
55
+ {
56
+ header : 'Leverege Helm Chart Compass (for helmup)',
57
+ content : `{green This tool helps helmup navigate the helm charts stored in the
58
+ artifact-registries.}`
59
+ },
60
+ { header : 'Options',
61
+ optionList : commandLineOptions,
62
+ },
63
+ ]
64
+
65
+ const args = commandLineArgs( commandLineOptions, { camelCase : true, partial : true } )
66
+ const usage = commandLineUsage( sections )
67
+
68
+ if ( args.help ) {
69
+ log( usage )
70
+ process.exit( 0 )
71
+ }
72
+
73
+ /* eslint-disable no-underscore-dangle */
74
+ if ( args._unknown ) {
75
+ log( usage )
76
+ log( `\nUnrecognized argument [${chalk.bold.red( args._unknown )}]\n` )
77
+ process.exit( 1 )
78
+ }
79
+ /* eslint-enable no-underscore-dangle */
80
+
81
+ const minNodejsVersion = '18.0.0'
82
+ if ( semverLt( process.version, minNodejsVersion ) ) {
83
+ errorExit( `\n***ERROR: must be running at least node ${minNodejsVersion}\n` )
84
+ }
85
+
86
+ // First of all, fail if we are not in a git repository
87
+ let gitRoot
88
+ try {
89
+ gitRoot = await getGitRootDirectory()
90
+ } catch ( error ) {
91
+ errorExit( chalk.red.bold( error ), { errorCode : 5 } )
92
+ }
93
+
94
+ const chartCompass = YAML.load( fs.readFileSync( './registry-compass.yaml', 'utf8' ) )
95
+
96
+ condir( { chartCompass }, '<==Navigation' )
@@ -28,5 +28,10 @@ service:
28
28
  prometheus.io/scrape: "true"
29
29
  es:
30
30
  uri: http://elasticsearch8.elastic.svc.cluster.local:9200
31
+
32
+ resources:
33
+ requests:
34
+ cpu: 50m
35
+ memory: 32Mi
31
36
  ELASTIC8_EXPORTER_CHART_MOD
32
37
  removeHelmRepo prometheus-community
@@ -4,7 +4,7 @@ showInstalling "Redis"
4
4
 
5
5
  OCI_CHART="oci://registry-1.docker.io/bitnamicharts/redis"
6
6
 
7
- [ -z "$REDIS_CHART_VERSION" ] && REDIS_CHART_VERSION="18.16.1"
7
+ [ -z "$REDIS_CHART_VERSION" ] && REDIS_CHART_VERSION="19.1.2"
8
8
 
9
9
  helm upgrade --install redis $OCI_CHART \
10
10
  --values redis/redis-local.yaml \
@@ -13,6 +13,27 @@ annotations:
13
13
  prometheus.io/port: "9255"
14
14
  prometheus.io/scrape: "true" # this is a string annotation - so quoted!
15
15
 
16
+ tolerations:
17
+ - key: "preemptible"
18
+ operator: "Equal"
19
+ value: "true"
20
+ effect: "NoSchedule"
21
+
22
+ affinity:
23
+ nodeAffinity:
24
+ requiredDuringSchedulingIgnoredDuringExecution:
25
+ nodeSelectorTerms:
26
+ - matchExpressions:
27
+ - key: target-env
28
+ operator: In
29
+ values:
30
+ - preemptible
31
+
32
+ resources:
33
+ requests:
34
+ cpu: 50m
35
+ memory: 32Mi
36
+
16
37
  # NOTE to future selves - the exporter's helm chart does not have a clue
17
38
  # regarding workload identity, which means the gcloud and k8s service
18
39
  # accounts have to be managed outside of the chart by helmup. In order to get
@@ -1,12 +1,19 @@
1
1
  #!/bin/bash
2
-
2
+ #
3
+ # Release Notes => https://github.com/traefik/traefik-helm-chart/releases
4
+ #
5
+ # ***REMEMBER*** the instructions provided on the internet typically assume
6
+ # the default namespace not traefik
7
+ #
8
+ # ***DO NOT FORGET*** the -n traefik for kubectl commands
9
+ #
3
10
  showInstalling "Traefik Load Balancer / Router"
4
11
 
5
12
  TRAEFIK_NAMESPACE="traefik"
6
13
 
7
14
  addHelmRepo traefik https://helm.traefik.io/traefik
8
15
 
9
- [ -z "$TRAEFIK_CHART_VERSION" ] && TRAEFIK_CHART_VERSION="26"
16
+ [ -z "$TRAEFIK_CHART_VERSION" ] && TRAEFIK_CHART_VERSION="27"
10
17
  helm upgrade --install traefik traefik/traefik \
11
18
  --namespace $TRAEFIK_NAMESPACE --create-namespace \
12
19
  --values traefik/traefik-local.yaml \
@@ -18,6 +18,12 @@ logs:
18
18
  enabled: true
19
19
  format: json
20
20
 
21
+ # minimal resources to prevent CPU starvation
22
+ resources:
23
+ requests:
24
+ cpu: "100m"
25
+ memory: "50Mi"
26
+
21
27
  # Custom entry points may be added here - the first two are port definitions
22
28
  # for both unsecured and secured MQTT ports used by connect. Note that the
23
29
  # mqtts port has TLS termination performed by traefik and both ports are then
@@ -13,6 +13,16 @@ schedules:
13
13
  release: "postgres"
14
14
  app: "postgresql"
15
15
 
16
+ elasticsearch:
17
+ disabled: false
18
+ schedule: "0 6 * * *"
19
+ template:
20
+ ttl: "720h"
21
+ snapshotVolumes: true
22
+ labelSelector:
23
+ matchLabels:
24
+ app.kubernetes.io/name: elasticsearch
25
+
16
26
  tsdb:
17
27
  disabled: false
18
28
  schedule: "0 8 * * *"
package/src/zx-testing ADDED
@@ -0,0 +1,216 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-console */
3
+ // https://github.com/google/zx
4
+ /* eslint-disable max-len */
5
+ import pkg from 'enquirer'
6
+ import colors from 'ansi-colors'
7
+ import { $, fs, argv, path, chalk } from 'zx'
8
+
9
+ const { prompt } = pkg
10
+
11
+ // This will preserve coloration from spawned child processes
12
+ process.env.FORCE_COLORS = 3
13
+ process.env.FORCE_COLOR = '1'
14
+
15
+ let TARGET
16
+ let CHANNEL
17
+
18
+ // A single unnamed parameter is assumed to be a deploy target.
19
+ // If named parameters exist, they may not be stored in argv,
20
+ // so don't look for a larger length of argv to decide whether to look for named paramters
21
+ if ( argv._.length === 1 ) {
22
+ TARGET = argv._[0]
23
+ } else {
24
+ TARGET = argv.target
25
+
26
+ if ( argv.channelName ) {
27
+ CHANNEL = {}
28
+ CHANNEL.name = argv.channelName
29
+ CHANNEL.expiration = argv.channelExpiration ?? '7d'
30
+ }
31
+ }
32
+
33
+ // If target has not been defined on the command line,
34
+ // then enter interactive mode
35
+ const INTERACTIVE_MODE = !TARGET
36
+
37
+ let exited = false
38
+
39
+ const SECRETS_DIR = 'secrets'
40
+ const EXIT_EVENTS = [ 'SIGINT', 'exit', 'uncaughException', 'unhandledRejection' ]
41
+
42
+ const [
43
+ firebaserc,
44
+ firebaseJson,
45
+ gitBranch
46
+ ] = await Promise.all( [
47
+ fs.exists( path.join( process.cwd(), '.firebaserc' ) ),
48
+ fs.exists( path.join( process.cwd(), 'firebase.json' ) ),
49
+ $`git rev-parse --abbrev-ref HEAD -C ${process.cwd()}`
50
+ .then( ( { stdout } ) => {
51
+ return stdout.split( '\n' )[0]
52
+ } )
53
+ ] )
54
+
55
+ if ( !firebaserc ) {
56
+ console.log( chalk.red( 'No .firebaserc file found. Make sure that you are executing firebaseDeploy from within a firebase project directory.' ) )
57
+ }
58
+
59
+ if ( !firebaseJson ) {
60
+ console.log( chalk.red( 'No firebase.json file found. Make sure that you are executing firebaseDeploy from within a firebase project directory.' ) )
61
+ }
62
+
63
+ if ( !firebaserc || !firebaseJson ) {
64
+ process.exit()
65
+ }
66
+
67
+ const firebasercContent = JSON.parse( await fs.readFile( path.join( process.cwd(), '.firebaserc' ), { encoding : 'utf-8' } ) )
68
+
69
+ let maxSiteIdLength = 0
70
+
71
+ const targets = Object
72
+ .entries( firebasercContent.targets )
73
+ .reduce( ( prev, [ projectId, config ] ) => {
74
+ Object
75
+ .entries( config.hosting )
76
+ .forEach( ( [ siteId, aliases ] ) => {
77
+ aliases.forEach( ( alias ) => {
78
+ // Store the longest length of site id for formatting later on
79
+ maxSiteIdLength = Math.max( siteId.length, maxSiteIdLength )
80
+
81
+ // eslint-disable-next-line no-param-reassign
82
+ prev[alias] = {
83
+ projectId,
84
+ siteId,
85
+ alias,
86
+ production : config.production
87
+ }
88
+ } )
89
+ } )
90
+
91
+ return prev
92
+ }, {} )
93
+
94
+ if ( INTERACTIVE_MODE ) {
95
+ const { targetEnv } = await prompt( [ {
96
+ type : 'autocomplete',
97
+ name : 'targetEnv',
98
+ message : 'To which environment would you like to deploy?',
99
+ choices : Object.values( targets ).map( ( t ) => {
100
+ const productionLabel = t.production ? colors.bold.red( '--PRODUCTION--' ) : ''
101
+ return { message : `${colors.bold.cyan( 'Site: ' )}${t.siteId.padEnd( maxSiteIdLength )} ${colors.bold.green( 'Project: ' )}${t.projectId} ${productionLabel}`, value : t.alias }
102
+ } )
103
+ } ] )
104
+
105
+ TARGET = targets[targetEnv]
106
+
107
+ if ( TARGET.production ) {
108
+ const { confirmProductionDeploy } = await prompt( [ {
109
+ type : 'confirm',
110
+ name : 'confirmProductionDeploy',
111
+ message : `${colors.bold.red( 'WARNING: ' )} This is a ${colors.bold.yellow( 'PRODUCTION' )} environment. Are you sure you want to deploy here?`
112
+ } ] )
113
+ if ( !confirmProductionDeploy ) {
114
+ console.log( 'Cancelling deployment...' )
115
+ process.exit()
116
+ }
117
+ }
118
+
119
+ const { useChannel } = await prompt( [ {
120
+ type : 'confirm',
121
+ name : 'useChannel',
122
+ message : 'Do you want to deploy to a temporary preview channel?'
123
+ } ] )
124
+
125
+ if ( useChannel ) {
126
+ const { channelName, channelExpiration } = await prompt( [ {
127
+ type : 'input',
128
+ name : 'channelName',
129
+ message : 'To which channel would you like to deploy?',
130
+ initial : gitBranch ?? 'temp-deploy'
131
+ }, {
132
+ type : 'input',
133
+ name : 'channelExpiration',
134
+ message : 'When should the channel expire?',
135
+ initial : '7d'
136
+ } ] )
137
+
138
+ CHANNEL = { name : channelName, expiration : channelExpiration }
139
+ }
140
+ } else {
141
+ TARGET = targets[TARGET]
142
+ }
143
+
144
+ if ( !TARGET ) {
145
+ console.log( chalk.red( 'No such target exists in .firebaserc config.' ) )
146
+ process.exit()
147
+ }
148
+
149
+ const [ aliasFileExists, siteIdFileExists, sharedFileExists ] = await Promise.all( [
150
+ fs.exists( path.join( process.cwd(), SECRETS_DIR, `${TARGET.alias}.env` ) ),
151
+ fs.exists( path.join( process.cwd(), SECRETS_DIR, `${TARGET.siteId}.env` ) ),
152
+ fs.exists( path.join( process.cwd(), SECRETS_DIR, 'shared.env' ) )
153
+ ] )
154
+
155
+ if ( !aliasFileExists && !siteIdFileExists ) {
156
+ console.log( chalk.red( `No env file for ${TARGET.alias} exists. Check your secrets directory and try again` ) )
157
+ process.exit()
158
+ }
159
+
160
+ const exec = []
161
+
162
+ if ( sharedFileExists ) {
163
+ exec.push( fs.readFile( path.join( process.cwd(), SECRETS_DIR, 'shared.env' ), { encoding : 'utf-8' } ) )
164
+ }
165
+
166
+ if ( aliasFileExists ) {
167
+ exec.push( fs.readFile( path.join( process.cwd(), SECRETS_DIR, `${TARGET.alias}.env` ), { encoding : 'utf-8' } ) )
168
+ } else {
169
+ exec.push( fs.readFile( path.join( process.cwd(), SECRETS_DIR, `${TARGET.siteId}.env` ), { encoding : 'utf-8' } ) )
170
+ }
171
+
172
+ console.log( `\n\n\n${colors.bold.green( '====== Deployment Summary ======' )}` )
173
+ console.log( `${colors.blue( 'Firebase project:' )} ${TARGET.projectId} ${TARGET.production ? colors.yellow( '--PRODUCTION--' ) : ''}` )
174
+ console.log( `${colors.blue( 'Site ID:' )} ${TARGET.siteId}` )
175
+ console.log( `${colors.blue( 'Channel Name:' )} ${CHANNEL?.name ?? colors.italic.gray( '(none)' )}` )
176
+ console.log( `${colors.blue( 'Channel Expiration date:' )} ${CHANNEL?.name ? CHANNEL.expiration ?? colors.italic.gray( '(none)' ) : colors.italic.gray( '(n/a)' )}` )
177
+ console.log( `${colors.blue( 'Shared .env file:' )} ${sharedFileExists ? `${SECRETS_DIR}/shared.env` : colors.italic.gray( '(none)' )}` )
178
+ console.log( `${colors.blue( 'Additional .env overrides:' )} ${aliasFileExists ? `${SECRETS_DIR}/${TARGET.alias}.env` : `${SECRETS_DIR}/${TARGET.siteId}.env`}` )
179
+ console.log( `${colors.bold.green( '===============================' )}` )
180
+
181
+ if ( INTERACTIVE_MODE ) {
182
+ const { confirmSelections } = await prompt( [ {
183
+ type : 'confirm',
184
+ name : 'confirmSelections',
185
+ message : 'Proceed with this deployment?'
186
+ } ] )
187
+
188
+ if ( !confirmSelections ) {
189
+ console.log( 'Cancelling deployment...' )
190
+ process.exit()
191
+ }
192
+ }
193
+ const envFileContent = ( await Promise.all( exec ) ).join( '\n' )
194
+
195
+ EXIT_EVENTS
196
+ .forEach( ( event ) => {
197
+ process.on( event, () => {
198
+ if ( !exited ) {
199
+ exited = true
200
+ fs.removeSync( path.join( process.cwd(), '.env.temp' ) )
201
+ }
202
+ } )
203
+ } )
204
+
205
+ await fs.writeFile( path.join( process.cwd(), '.env.temp' ), envFileContent )
206
+
207
+ try {
208
+
209
+ // deploying to a channel requires slightly different parameter structure
210
+ const deployType = CHANNEL ? `hosting:channel:deploy ${CHANNEL.name} ${CHANNEL.expiration ? `--expires ${CHANNEL.expiration}` : ''}`.split( ' ' ) : 'deploy'
211
+ const hostingPrefix = CHANNEL ? '' : 'hosting:'
212
+
213
+ await $`npm run clean && DOTENV_CONFIG_PATH=${path.join( process.cwd(), '.env.temp' )} DEPLOYMENT_TARGET=${TARGET.siteId} npm run build && firebase use ${TARGET.projectId} && firebase ${deployType} --only ${hostingPrefix}${TARGET.siteId}`
214
+ } catch ( err ) {
215
+ console.error( err )
216
+ }