@vaharoni/devops 1.1.7 → 1.1.8
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/README.md +5 -1
- package/dist/cli/cloudrun.d.ts +11 -0
- package/dist/cli/cloudrun.d.ts.map +1 -0
- package/dist/cli/cloudrun.js +121 -0
- package/dist/cli/common.d.ts +10 -12
- package/dist/cli/common.d.ts.map +1 -1
- package/dist/cli/common.js +28 -22
- package/dist/cli/env.js +2 -2
- package/dist/cli/prep-build.d.ts.map +1 -1
- package/dist/cli/prep-build.js +35 -6
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cli/registry.js +12 -3
- package/dist/devops.js +3 -1
- package/dist/libs/cloudrun-helpers.d.ts +16 -0
- package/dist/libs/cloudrun-helpers.d.ts.map +1 -0
- package/dist/libs/cloudrun-helpers.js +79 -0
- package/dist/libs/k8s-constants.d.ts +1 -0
- package/dist/libs/k8s-constants.d.ts.map +1 -1
- package/dist/libs/k8s-constants.js +35 -10
- package/dist/libs/k8s-generate.d.ts +1 -1
- package/dist/libs/k8s-generate.d.ts.map +1 -1
- package/dist/libs/k8s-generate.js +4 -2
- package/dist/libs/k8s-secrets-manager.d.ts +2 -1
- package/dist/libs/k8s-secrets-manager.d.ts.map +1 -1
- package/dist/libs/k8s-secrets-manager.js +8 -5
- package/dist/types/index.d.ts +19 -8
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +3 -1
- package/package.json +1 -1
- package/src/cli/cloudrun.ts +133 -0
- package/src/cli/common.ts +46 -38
- package/src/cli/db.ts +1 -1
- package/src/cli/dml.ts +1 -1
- package/src/cli/env.ts +2 -2
- package/src/cli/exec.ts +1 -1
- package/src/cli/job.ts +1 -1
- package/src/cli/prep-build.ts +34 -6
- package/src/cli/redis.ts +1 -1
- package/src/cli/registry.ts +12 -3
- package/src/devops.ts +3 -1
- package/src/libs/cloudrun-helpers.ts +118 -0
- package/src/libs/k8s-constants.ts +36 -12
- package/src/libs/k8s-generate.ts +3 -2
- package/src/libs/k8s-secrets-manager.ts +9 -5
- package/src/target-templates/lang-variants-common/python/.devops/config/images.yaml +3 -1
- package/src/target-templates/lang-variants-common/typescript/.devops/docker-images/cloudrun.Dockerfile +31 -0
- package/src/target-templates/lang-variants-common/typescript/.github/actions/deploy-image@v1/action.yaml +4 -0
- package/src/types/index.ts +3 -1
package/README.md
CHANGED
@@ -29,7 +29,6 @@ Currently, the repo works with `bun` as the package manager for node and `uv` fo
|
|
29
29
|
Run this in your local copy of the devops folder:
|
30
30
|
```shell
|
31
31
|
bun link
|
32
|
-
bun run build
|
33
32
|
```
|
34
33
|
|
35
34
|
Run this in a local package using the project for testing:
|
@@ -37,6 +36,11 @@ Run this in a local package using the project for testing:
|
|
37
36
|
bun link @vaharoni/devops
|
38
37
|
```
|
39
38
|
|
39
|
+
After changes you make in this local repo, run:
|
40
|
+
```shell
|
41
|
+
bun run build
|
42
|
+
```
|
43
|
+
|
40
44
|
When done:
|
41
45
|
```shell
|
42
46
|
# In the local copy of this repo
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { CLICommandParser } from "./common";
|
2
|
+
declare function run(cmdObj: CLICommandParser): Promise<void>;
|
3
|
+
declare const _default: {
|
4
|
+
cloudrun: {
|
5
|
+
oneLiner: string;
|
6
|
+
keyExamples: string;
|
7
|
+
run: typeof run;
|
8
|
+
};
|
9
|
+
};
|
10
|
+
export default _default;
|
11
|
+
//# sourceMappingURL=cloudrun.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"cloudrun.d.ts","sourceRoot":"","sources":["../../src/cli/cloudrun.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAmC,MAAM,UAAU,CAAC;AA6E7E,iBAAe,GAAG,CAAC,MAAM,EAAE,gBAAgB,iBAmD1C;;;;;;;;AAED,wBAEE"}
|
@@ -0,0 +1,121 @@
|
|
1
|
+
import { CLICommandParser, printUsageAndExit, StrongParams } from "./common";
|
2
|
+
import { buildDev, deploy } from "../libs/cloudrun-helpers";
|
3
|
+
const oneLiner = "Supports cloudrun images";
|
4
|
+
const keyExamples = `
|
5
|
+
$ devops cloudrun deploy cloudrun-image SHA --env staging --region us-central1 [--forward-env ENV1,ENV2 --allow-unauthenticated]
|
6
|
+
$ devops cloudrun build-dev cloudrun-image
|
7
|
+
`.trim();
|
8
|
+
const usage = `
|
9
|
+
${oneLiner}
|
10
|
+
|
11
|
+
USAGE
|
12
|
+
Configuration prerequisites:
|
13
|
+
- The image should be defined in images.yaml with:
|
14
|
+
cloudrun: true
|
15
|
+
- The artifact registry URL should be set in config/constants.yaml:
|
16
|
+
cloudrun-artifact-registry-repo-path: REGION-docker.pkg.dev/PROJECT_ID/REPO
|
17
|
+
|
18
|
+
Deploy a cloudrun image to Cloud Run:
|
19
|
+
devops cloudrun deploy <image> <sha> --env <env> --region <region> [options]
|
20
|
+
|
21
|
+
Options:
|
22
|
+
--forward-env ENV1,ENV2 Comma-separated env var names to forward into the service
|
23
|
+
--allow-unauthenticated Allow unauthenticated access
|
24
|
+
--cpu <cpu> CPU, e.g. 0.25, 0.5, 1
|
25
|
+
--memory <mem> Memory, e.g. 256Mi, 512Mi, 1Gi
|
26
|
+
--min-instances <n> Minimum instances
|
27
|
+
--max-instances <n> Maximum instances
|
28
|
+
--timeout <time> Request timeout, e.g. 60s
|
29
|
+
-- Pass through additional args to gcloud (e.g. -- --ingress internal)
|
30
|
+
|
31
|
+
Notes:
|
32
|
+
- The image must already be pushed to the artifact registry.
|
33
|
+
- <env> also supports local environments (e.g. development).
|
34
|
+
- For remote monorepo environments, variables specified in --forward-env that
|
35
|
+
are not present in the current process's env are fetched from the cluster.
|
36
|
+
|
37
|
+
Build a cloudrun image locally in development environment:
|
38
|
+
devops cloudrun build-dev cloudrun-image
|
39
|
+
|
40
|
+
This command builds the image locally with a random SHA and pushes it to the artifact registry.
|
41
|
+
|
42
|
+
EXAMPLES
|
43
|
+
${keyExamples}
|
44
|
+
`;
|
45
|
+
const handlers = {
|
46
|
+
"build-dev": (opts) => {
|
47
|
+
buildDev(opts.required("image"));
|
48
|
+
},
|
49
|
+
_deploy: (opts) => {
|
50
|
+
const rawForwardEnv = opts.optional("forwardEnv");
|
51
|
+
const forwardEnv = rawForwardEnv
|
52
|
+
? rawForwardEnv.split(",").map(v => v.trim()).filter(Boolean)
|
53
|
+
: [];
|
54
|
+
const minInstancesStr = opts.optional("minInstances");
|
55
|
+
const maxInstancesStr = opts.optional("maxInstances");
|
56
|
+
deploy({
|
57
|
+
image: opts.required("image"),
|
58
|
+
env: opts.required("env"),
|
59
|
+
sha: opts.required("sha"),
|
60
|
+
region: opts.required("region"),
|
61
|
+
forwardEnv,
|
62
|
+
allowUnauthenticated: opts.optional("allowUnauthenticated") === "true",
|
63
|
+
cpu: opts.optional("cpu"),
|
64
|
+
memory: opts.optional("memory"),
|
65
|
+
minInstances: minInstancesStr ? Number(minInstancesStr) : undefined,
|
66
|
+
maxInstances: maxInstancesStr ? Number(maxInstancesStr) : undefined,
|
67
|
+
timeout: opts.optional("timeout"),
|
68
|
+
extraArgs: opts.optional("extraArgs"),
|
69
|
+
});
|
70
|
+
},
|
71
|
+
};
|
72
|
+
async function run(cmdObj) {
|
73
|
+
if (cmdObj.help || cmdObj.args.length === 0)
|
74
|
+
printUsageAndExit(usage);
|
75
|
+
const parsed = cmdObj.parseOptions({
|
76
|
+
params: [
|
77
|
+
"--keep-last",
|
78
|
+
"--forward-env",
|
79
|
+
"--region",
|
80
|
+
"--cpu",
|
81
|
+
"--memory",
|
82
|
+
"--min-instances",
|
83
|
+
"--max-instances",
|
84
|
+
"--timeout",
|
85
|
+
"--sha",
|
86
|
+
],
|
87
|
+
booleans: ["--allow-unauthenticated"],
|
88
|
+
passthroughArgs: true,
|
89
|
+
});
|
90
|
+
const [subcommand, image, sha] = parsed.args;
|
91
|
+
// Inject env variables as forwarding is needed
|
92
|
+
if (subcommand === "deploy") {
|
93
|
+
cmdObj.executorFromEnv(`devops cloudrun _deploy ${cmdObj.args.slice(1).join(" ")}`, { checkEnvYaml: false }).spawn();
|
94
|
+
return;
|
95
|
+
}
|
96
|
+
const handler = handlers[subcommand];
|
97
|
+
if (!handler) {
|
98
|
+
console.error(`Unknown subcommand: ${subcommand}`);
|
99
|
+
printUsageAndExit(usage);
|
100
|
+
}
|
101
|
+
const params = new StrongParams(usage, {
|
102
|
+
env: cmdObj.env,
|
103
|
+
subcommand,
|
104
|
+
image,
|
105
|
+
sha,
|
106
|
+
keepLast: parsed.options["--keep-last"],
|
107
|
+
forwardEnv: parsed.options["--forward-env"],
|
108
|
+
region: parsed.options["--region"],
|
109
|
+
allowUnauthenticated: parsed.options["--allow-unauthenticated"] ? "true" : undefined,
|
110
|
+
cpu: parsed.options["--cpu"],
|
111
|
+
memory: parsed.options["--memory"],
|
112
|
+
minInstances: parsed.options["--min-instances"],
|
113
|
+
maxInstances: parsed.options["--max-instances"],
|
114
|
+
timeout: parsed.options["--timeout"],
|
115
|
+
extraArgs: parsed.passthrough ? parsed.passthrough.join(" ") : undefined,
|
116
|
+
});
|
117
|
+
handler(params);
|
118
|
+
}
|
119
|
+
export default {
|
120
|
+
cloudrun: { oneLiner, keyExamples, run },
|
121
|
+
};
|
package/dist/cli/common.d.ts
CHANGED
@@ -1,9 +1,7 @@
|
|
1
|
-
type ParsedArgs = {
|
1
|
+
type ParsedArgs<TBoolKeys extends readonly string[], TParamKeys extends readonly string[]> = {
|
2
2
|
args: string[];
|
3
3
|
argsStr: string;
|
4
|
-
options:
|
5
|
-
[key: string]: string | boolean;
|
6
|
-
};
|
4
|
+
options: Partial<Record<TBoolKeys[number], true>> & Partial<Record<TParamKeys[number], string>>;
|
7
5
|
passthrough?: string[];
|
8
6
|
};
|
9
7
|
export declare class CLICommandParser {
|
@@ -15,20 +13,20 @@ export declare class CLICommandParser {
|
|
15
13
|
skipEnvCheck: boolean;
|
16
14
|
constructor(cmdArray: string[]);
|
17
15
|
executorFromEnv(commandStr: string, options?: Omit<CommandExecutorOptions, "env">): CommandExecutor;
|
18
|
-
parseOptions({ params, booleans, passthroughArgs, }?: {
|
16
|
+
parseOptions<const TBoolKeys extends readonly string[], const TParamKeys extends readonly string[]>({ params, booleans, passthroughArgs, }?: {
|
19
17
|
/** Param is used like so: --param value */
|
20
|
-
params?:
|
18
|
+
params?: TParamKeys;
|
21
19
|
/** Boolean flag is used like so: --flag */
|
22
|
-
booleans?:
|
20
|
+
booleans?: TBoolKeys;
|
23
21
|
/** Pass through args are used like so: -- arg1 arg2 */
|
24
22
|
passthroughArgs?: boolean;
|
25
|
-
}): ParsedArgs
|
23
|
+
}): ParsedArgs<TBoolKeys, TParamKeys>;
|
26
24
|
_validateEnv(env: string): boolean;
|
27
|
-
_separateOptions(args: string[], { params, booleans, passthroughArgs, }?: {
|
28
|
-
params?:
|
29
|
-
booleans?:
|
25
|
+
_separateOptions<const TBoolKeys extends readonly string[], const TParamKeys extends readonly string[]>(args: string[], { params, booleans, passthroughArgs, }?: {
|
26
|
+
params?: TParamKeys;
|
27
|
+
booleans?: TBoolKeys;
|
30
28
|
passthroughArgs?: boolean;
|
31
|
-
}): ParsedArgs
|
29
|
+
}): ParsedArgs<TBoolKeys, TParamKeys>;
|
32
30
|
}
|
33
31
|
type CommandExecutorOptions = {
|
34
32
|
env?: string;
|
package/dist/cli/common.d.ts.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/cli/common.ts"],"names":[],"mappings":"AAMA,KAAK,UAAU,
|
1
|
+
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/cli/common.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/common.js
CHANGED
@@ -47,7 +47,7 @@ export class CLICommandParser {
|
|
47
47
|
// # => { args: ['arg1'], options: { '--some-flag': true, '--in': 'workspace' } }
|
48
48
|
//
|
49
49
|
// Note that the global param --env is already extracted and can be accessed with cmd.env
|
50
|
-
parseOptions({ params
|
50
|
+
parseOptions({ params, booleans, passthroughArgs = false, } = {}) {
|
51
51
|
return this._separateOptions(this.args, {
|
52
52
|
params,
|
53
53
|
booleans,
|
@@ -63,36 +63,42 @@ export class CLICommandParser {
|
|
63
63
|
}
|
64
64
|
return true;
|
65
65
|
}
|
66
|
-
_separateOptions(args, { params
|
67
|
-
const
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
...(passthroughArgs ? { passthrough: [] } : {}),
|
72
|
-
};
|
73
|
-
const paramsLookup = Object.fromEntries(params.map((x) => [x, true]));
|
74
|
-
const booleansLookup = Object.fromEntries(booleans.map((x) => [x, true]));
|
66
|
+
_separateOptions(args, { params, booleans, passthroughArgs = false, } = {}) {
|
67
|
+
const paramsLookup = new Set(params ?? []);
|
68
|
+
const booleansLookup = new Set(booleans ?? []);
|
69
|
+
const isParam = (arg) => paramsLookup.has(arg);
|
70
|
+
const isBoolean = (arg) => booleansLookup.has(arg);
|
75
71
|
const passthroughArgsStart = passthroughArgs ? args.indexOf("--") : -1;
|
72
|
+
// prettier-ignore
|
76
73
|
const numArgsToProcess = passthroughArgsStart === -1 ? args.length : passthroughArgsStart;
|
74
|
+
const getResPassthrough = () => {
|
75
|
+
if (!passthroughArgs || passthroughArgsStart < 0)
|
76
|
+
return { passthrough: [] };
|
77
|
+
return { passthrough: args.slice(passthroughArgsStart + 1) };
|
78
|
+
};
|
79
|
+
const resArgs = [];
|
80
|
+
const resParams = {};
|
81
|
+
const resOptions = {};
|
77
82
|
for (let i = 0; i < numArgsToProcess; ++i) {
|
78
83
|
const curr = args[i];
|
79
|
-
if (
|
84
|
+
if (isParam(curr)) {
|
80
85
|
const next = args[i + 1];
|
81
|
-
|
86
|
+
resParams[curr] = next;
|
82
87
|
++i;
|
83
88
|
}
|
84
|
-
else if (
|
85
|
-
|
89
|
+
else if (isBoolean(curr)) {
|
90
|
+
resOptions[curr] = true;
|
86
91
|
}
|
87
92
|
else {
|
88
|
-
|
93
|
+
resArgs.push(curr);
|
89
94
|
}
|
90
95
|
}
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
+
return {
|
97
|
+
args: resArgs,
|
98
|
+
argsStr: resArgs.join(" "),
|
99
|
+
options: { ...resOptions, ...resParams },
|
100
|
+
...getResPassthrough(),
|
101
|
+
};
|
96
102
|
}
|
97
103
|
}
|
98
104
|
export class CommandExecutor {
|
@@ -144,10 +150,10 @@ export class CommandExecutor {
|
|
144
150
|
const envToUse = this._getProcessEnv(env);
|
145
151
|
return new Promise((resolve) => {
|
146
152
|
try {
|
147
|
-
const
|
148
|
-
const childProcess = spawn(cmd, args, {
|
153
|
+
const childProcess = spawn(fullCommand, {
|
149
154
|
stdio: "inherit",
|
150
155
|
env: envToUse,
|
156
|
+
shell: true,
|
151
157
|
});
|
152
158
|
childProcess.on("close", (code) => {
|
153
159
|
if (code !== 0) {
|
package/dist/cli/env.js
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import { globSync } from "glob";
|
2
|
-
import { deleteMonorepoSecret,
|
2
|
+
import { deleteMonorepoSecret, getMonorepoSecretStr, setMonorepoSecret, } from "../libs/k8s-secrets-manager";
|
3
3
|
import { CombinedEnvValidator } from "../libs/validate-env";
|
4
4
|
import { CLICommandParser, dotEnvFilesForEnv, printUsageAndExit, } from "./common";
|
5
5
|
const oneLiner = "Commands to manipulate env variables";
|
@@ -46,7 +46,7 @@ function run(cmdObj) {
|
|
46
46
|
break;
|
47
47
|
}
|
48
48
|
case "get": {
|
49
|
-
console.log(
|
49
|
+
console.log(getMonorepoSecretStr(cmdObj.env, rest));
|
50
50
|
break;
|
51
51
|
}
|
52
52
|
case "set": {
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"prep-build.d.ts","sourceRoot":"","sources":["../../src/cli/prep-build.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAqB,MAAM,UAAU,CAAC;
|
1
|
+
{"version":3,"file":"prep-build.d.ts","sourceRoot":"","sources":["../../src/cli/prep-build.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAqB,MAAM,UAAU,CAAC;AA0B/D,iBAAe,GAAG,CAAC,MAAM,EAAE,gBAAgB,iBA0F1C;;;;;;;;AAED,wBAEE"}
|
package/dist/cli/prep-build.js
CHANGED
@@ -3,8 +3,10 @@ import os from "os";
|
|
3
3
|
import path from "path";
|
4
4
|
import { CLICommandParser, printUsageAndExit } from "./common";
|
5
5
|
import { getImageData, getTemplateData } from "../libs/config";
|
6
|
-
import {
|
6
|
+
import { getMonorepoSecretStr } from "../libs/k8s-secrets-manager";
|
7
7
|
import { getImageDescendentData } from "../libs/discovery/images";
|
8
|
+
import { isLocalOrRemoteEnv } from "../libs/k8s-constants";
|
9
|
+
import chalk from "chalk";
|
8
10
|
const oneLiner = "Copies all dependencies of an image to a temporary folder in preparation for a Docker build";
|
9
11
|
const keyExamples = `
|
10
12
|
$ devops prep-build main-node
|
@@ -13,7 +15,10 @@ const usage = `
|
|
13
15
|
${oneLiner}
|
14
16
|
|
15
17
|
USAGE
|
16
|
-
devops prep-build <image>
|
18
|
+
devops prep-build <image> --env <env>
|
19
|
+
|
20
|
+
If <env> is a remote environment (e.g. staging, production), the environment variables are
|
21
|
+
fetched from the cluster and injected in case they are needed during the build process.
|
17
22
|
|
18
23
|
EXAMPLES
|
19
24
|
${keyExamples}
|
@@ -52,16 +57,40 @@ async function run(cmdObj) {
|
|
52
57
|
console.warn(`COPYING Docker common`);
|
53
58
|
fs.copySync(dockerCommonPayloadPath, destFolder);
|
54
59
|
}
|
55
|
-
|
56
|
-
|
60
|
+
if (fs.existsSync(dockerImagePayloadPath)) {
|
61
|
+
console.warn(`COPYING Docker image payload`);
|
62
|
+
fs.copySync(dockerImagePayloadPath, destFolder);
|
63
|
+
}
|
57
64
|
console.warn(`COPYING .devops/config`);
|
58
65
|
fs.mkdirSync(path.join(destFolder, ".devops"));
|
59
66
|
fs.copySync(".devops/config", path.join(destFolder, ".devops/config"));
|
60
67
|
// Create config directory. It should be deleted by the docker image so that it can be mounted as a volume when the pod is run
|
61
68
|
console.warn(`CREATING config for the build process`);
|
62
69
|
fs.mkdirSync(path.join(destFolder, "config"));
|
63
|
-
const
|
64
|
-
|
70
|
+
const destGlobalEnvPath = path.join(destFolder, "config/.env.global");
|
71
|
+
if (isLocalOrRemoteEnv(cmdObj.env) === "remote") {
|
72
|
+
const envFileData = getMonorepoSecretStr(cmdObj.env);
|
73
|
+
fs.writeFileSync(destGlobalEnvPath, envFileData);
|
74
|
+
}
|
75
|
+
else {
|
76
|
+
let anyCopied = false;
|
77
|
+
const localGlobalEnvPath = "config/.env.global";
|
78
|
+
const localEnvPath = `config/.env.${cmdObj.env}`;
|
79
|
+
const destEnvPath = path.join(destFolder, `config/.env.${cmdObj.env}`);
|
80
|
+
if (fs.existsSync(localGlobalEnvPath)) {
|
81
|
+
console.warn(`COPYING ${localGlobalEnvPath} to ${destGlobalEnvPath}`);
|
82
|
+
fs.copyFileSync(localGlobalEnvPath, destGlobalEnvPath);
|
83
|
+
anyCopied = true;
|
84
|
+
}
|
85
|
+
if (fs.existsSync(localEnvPath)) {
|
86
|
+
console.warn(`COPYING ${localEnvPath} to ${destEnvPath}`);
|
87
|
+
fs.copyFileSync(localEnvPath, destEnvPath);
|
88
|
+
anyCopied = true;
|
89
|
+
}
|
90
|
+
if (!anyCopied) {
|
91
|
+
console.warn(chalk.red(`\nWarning: local environment ${cmdObj.env} has no .env files. Environment variables will not be injected.\n`));
|
92
|
+
}
|
93
|
+
}
|
65
94
|
// Copy all dependencies
|
66
95
|
getImageDescendentData(image).forEach((project) => {
|
67
96
|
console.warn(`COPYING ${project.rootPath}`);
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/cli/registry.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,gBAAgB,EAAmC,MAAM,UAAU,CAAC;
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/cli/registry.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,gBAAgB,EAAmC,MAAM,UAAU,CAAC;AA6D7E,iBAAS,GAAG,CAAC,MAAM,EAAE,gBAAgB,QAYpC;;;;;;;;AAED,wBAEE"}
|
package/dist/cli/registry.js
CHANGED
@@ -6,20 +6,26 @@ const oneLiner = "Manage container repositories";
|
|
6
6
|
const keyExamples = `
|
7
7
|
$ devops registry server-url
|
8
8
|
$ devops registry reg-url
|
9
|
-
$ devops registry repo-url
|
10
|
-
$ devops registry
|
9
|
+
$ devops registry repo-url my-image sha
|
10
|
+
$ devops registry image-name my-image
|
11
|
+
$ devops registry prune my-image
|
11
12
|
`.trim();
|
12
13
|
const usage = `
|
13
14
|
${oneLiner}
|
14
15
|
|
15
16
|
USAGE
|
16
|
-
Get base URLs
|
17
|
+
Get base URLs for the container registry of the cluster:
|
17
18
|
devops registry server-url
|
18
19
|
devops registry reg-url
|
19
20
|
|
21
|
+
Note: for cloudrun images these URLs are not relevant.
|
22
|
+
|
20
23
|
Gets the URL of an image in the container registry:
|
21
24
|
devops registry repo-url <image> <sha> --env <env>
|
22
25
|
|
26
|
+
Gets the image name in the container registry:
|
27
|
+
devops registry image-name <image> --env <env>
|
28
|
+
|
23
29
|
Prunes the repository of old images to enforce the "image-versions-to-keep" constant in config/constants.yaml:
|
24
30
|
devops registry prune <image> --env <env>
|
25
31
|
|
@@ -34,6 +40,9 @@ const handlers = {
|
|
34
40
|
"repo-url": (opts) => {
|
35
41
|
console.log(containerRegistryRepoPath(opts.required("image"), opts.required("env"), opts.required("sha")));
|
36
42
|
},
|
43
|
+
"image-name": (opts) => {
|
44
|
+
console.log(containerRegistryImageName(opts.required("image"), opts.required("env")));
|
45
|
+
},
|
37
46
|
prune: (opts) => {
|
38
47
|
const regName = containerRegistryPath();
|
39
48
|
const repoName = containerRegistryImageName(opts.required("image"), opts.required("env"));
|
package/dist/devops.js
CHANGED
@@ -22,6 +22,7 @@ import namespace from "./cli/namespace";
|
|
22
22
|
import image from "./cli/image";
|
23
23
|
import template from "./cli/template";
|
24
24
|
import job from "./cli/job";
|
25
|
+
import cloudrun from "./cli/cloudrun";
|
25
26
|
const [_node, _scriptPath, ...commandArgs] = process.argv;
|
26
27
|
const allImports = [
|
27
28
|
// day-to-day
|
@@ -43,11 +44,12 @@ const allImports = [
|
|
43
44
|
job,
|
44
45
|
//= Deployment
|
45
46
|
prepBuild,
|
47
|
+
cloudrun,
|
46
48
|
affected,
|
47
49
|
constant,
|
48
50
|
registry,
|
49
51
|
internalCurl,
|
50
|
-
jwt
|
52
|
+
jwt,
|
51
53
|
];
|
52
54
|
const commands = {};
|
53
55
|
allImports.forEach((imported) => {
|
@@ -0,0 +1,16 @@
|
|
1
|
+
export declare function buildDev(image: string): Promise<void>;
|
2
|
+
export declare function deploy({ image, env, sha, region, forwardEnv, allowUnauthenticated, cpu, memory, minInstances, maxInstances, timeout, extraArgs, }: {
|
3
|
+
image: string;
|
4
|
+
env: string;
|
5
|
+
sha: string;
|
6
|
+
region: string;
|
7
|
+
forwardEnv?: string[];
|
8
|
+
allowUnauthenticated?: boolean;
|
9
|
+
cpu?: string;
|
10
|
+
memory?: string;
|
11
|
+
minInstances?: number;
|
12
|
+
maxInstances?: number;
|
13
|
+
timeout?: string;
|
14
|
+
extraArgs?: string;
|
15
|
+
}): Promise<void>;
|
16
|
+
//# sourceMappingURL=cloudrun-helpers.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"cloudrun-helpers.d.ts","sourceRoot":"","sources":["../../src/libs/cloudrun-helpers.ts"],"names":[],"mappings":"AA0CA,wBAAsB,QAAQ,CAAC,KAAK,EAAE,MAAM,iBAyB3C;AAED,wBAAsB,MAAM,CAAC,EAC3B,KAAK,EACL,GAAG,EACH,GAAG,EACH,MAAM,EACN,UAAe,EACf,oBAA4B,EAC5B,GAAY,EACZ,MAAgB,EAChB,YAAgB,EAChB,YAAgB,EAChB,OAAe,EACf,SAAc,GACf,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,iBAsBA"}
|
@@ -0,0 +1,79 @@
|
|
1
|
+
import { randomBytes } from "crypto";
|
2
|
+
import { CommandExecutor } from "../cli/common";
|
3
|
+
import { containerRegistryRepoPath, isLocalOrRemoteEnv } from "./k8s-constants";
|
4
|
+
import { getImageData } from "./config";
|
5
|
+
import { getMonorepoSecretObject } from "./k8s-secrets-manager";
|
6
|
+
import chalk from "chalk";
|
7
|
+
function verifyCloudrunImage(image) {
|
8
|
+
const imageData = getImageData(image);
|
9
|
+
if (!imageData["cloudrun"]) {
|
10
|
+
console.error(`Image ${image} is not a cloudrun image. Add "cloudrun: true" in images.yaml`);
|
11
|
+
process.exit(1);
|
12
|
+
}
|
13
|
+
}
|
14
|
+
function getEnvValuesToForward(env, forwardEnv) {
|
15
|
+
if (!forwardEnv.length)
|
16
|
+
return {};
|
17
|
+
let envValues = {};
|
18
|
+
const missingValues = new Set();
|
19
|
+
for (const key of forwardEnv) {
|
20
|
+
const value = process.env[key];
|
21
|
+
if (value) {
|
22
|
+
envValues[key] = value;
|
23
|
+
}
|
24
|
+
else {
|
25
|
+
missingValues.add(key);
|
26
|
+
}
|
27
|
+
}
|
28
|
+
if (missingValues.size > 0 && isLocalOrRemoteEnv(env) === "remote") {
|
29
|
+
const secretsFromCluster = getMonorepoSecretObject(env, Array.from(missingValues));
|
30
|
+
for (const key of Object.keys(secretsFromCluster)) {
|
31
|
+
envValues[key] = secretsFromCluster[key];
|
32
|
+
missingValues.delete(key);
|
33
|
+
}
|
34
|
+
}
|
35
|
+
if (missingValues.size > 0) {
|
36
|
+
console.error(`Some forwardEnv variables are missing: ${Array.from(missingValues).join(", ")}`);
|
37
|
+
process.exit(1);
|
38
|
+
}
|
39
|
+
return envValues;
|
40
|
+
}
|
41
|
+
export async function buildDev(image) {
|
42
|
+
verifyCloudrunImage(image);
|
43
|
+
const env = "development";
|
44
|
+
const sha = randomBytes(12).toString("hex");
|
45
|
+
const buildDir = new CommandExecutor(`devops prep-build ${image}`, {
|
46
|
+
env,
|
47
|
+
}).exec().trim();
|
48
|
+
const tag = containerRegistryRepoPath(image, env, sha);
|
49
|
+
console.warn(`Building ${tag} from ${buildDir}`);
|
50
|
+
await new CommandExecutor(`docker build --platform linux/amd64 -t ${tag} ${buildDir} --build-arg MONOREPO_ENV=${env}`, { env }).spawn();
|
51
|
+
console.warn(`Pushing ${tag}`);
|
52
|
+
await new CommandExecutor(`docker push ${tag}`, { env }).spawn();
|
53
|
+
console.warn(`\n✅ Built and pushed ${tag}\n`);
|
54
|
+
console.warn('Run "devops cloudrun deploy" next. For example:');
|
55
|
+
console.warn(chalk.blue(`./devops cloudrun deploy ${image} ${sha} --env ${env} --allow-unauthenticated --region us-east1 --forward-env ENV1,ENV2`));
|
56
|
+
console.warn();
|
57
|
+
console.log(tag);
|
58
|
+
}
|
59
|
+
export async function deploy({ image, env, sha, region, forwardEnv = [], allowUnauthenticated = false, cpu = "0.25", memory = "256Mi", minInstances = 0, maxInstances = 1, timeout = "60s", extraArgs = "", }) {
|
60
|
+
verifyCloudrunImage(image);
|
61
|
+
const repoPath = containerRegistryRepoPath(image, env, sha);
|
62
|
+
const envValues = getEnvValuesToForward(env, forwardEnv);
|
63
|
+
const envValuesCsv = Object.entries(envValues).map(([key, value]) => `${key}="${value}"`).join(",");
|
64
|
+
const serviceName = `${image}-${env}`;
|
65
|
+
const cmd = `
|
66
|
+
gcloud run deploy ${serviceName}
|
67
|
+
--image ${repoPath}
|
68
|
+
${Object.keys(envValues).length > 0 ? `--set-env-vars ${envValuesCsv}` : ""}
|
69
|
+
${allowUnauthenticated ? "--allow-unauthenticated" : ""}
|
70
|
+
--region ${region}
|
71
|
+
--cpu ${cpu}
|
72
|
+
--memory ${memory}
|
73
|
+
--min-instances ${minInstances}
|
74
|
+
--max-instances ${maxInstances}
|
75
|
+
--timeout ${timeout}
|
76
|
+
${extraArgs}
|
77
|
+
`.trim().replace(/\s+/g, " ");
|
78
|
+
await new CommandExecutor(cmd, { env }).spawn();
|
79
|
+
}
|
@@ -1,5 +1,6 @@
|
|
1
1
|
export declare const MISSING_DOMAIN_KEY_ERROR = "$$$MISSING_DOMAIN_KEY$$$";
|
2
2
|
export declare function allSupportedEnvs(): string[];
|
3
|
+
export declare function isLocalOrRemoteEnv(monorepoEnv: string): "local" | "remote";
|
3
4
|
export declare function envToNamespace(monorepoEnv?: string): string;
|
4
5
|
export declare function secretName(): string;
|
5
6
|
export declare function imageDebugName(image: string): string;
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"k8s-constants.d.ts","sourceRoot":"","sources":["../../src/libs/k8s-constants.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,wBAAwB,6BAA6B,CAAA;AAoBlE,wBAAgB,gBAAgB,aAE/B;
|
1
|
+
{"version":3,"file":"k8s-constants.d.ts","sourceRoot":"","sources":["../../src/libs/k8s-constants.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,wBAAwB,6BAA6B,CAAA;AAoBlE,wBAAgB,gBAAgB,aAE/B;AAED,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAI1E;AAqBD,wBAAgB,cAAc,CAAC,WAAW,CAAC,EAAE,MAAM,UAGlD;AAED,wBAAgB,UAAU,WAEzB;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,UAE3C;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,UAE3C;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,UAG5E;AAED,wBAAgB,qBAAqB,WAEpC;AAED,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,UAgBf;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,UAIlE;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,UAE9C"}
|
@@ -21,12 +21,27 @@ function localSupportedEnvs() {
|
|
21
21
|
export function allSupportedEnvs() {
|
22
22
|
return [...remoteSupportedEnvs(), ...localSupportedEnvs()];
|
23
23
|
}
|
24
|
-
function
|
24
|
+
export function isLocalOrRemoteEnv(monorepoEnv) {
|
25
|
+
if (remoteSupportedEnvs().includes(monorepoEnv))
|
26
|
+
return "remote";
|
27
|
+
if (localSupportedEnvs().includes(monorepoEnv))
|
28
|
+
return "local";
|
29
|
+
throw new Error(`Unsupported environment: ${monorepoEnv}`);
|
30
|
+
}
|
31
|
+
function validateEnv(monorepoEnv, { allowLocal = false } = {}) {
|
25
32
|
if (!monorepoEnv)
|
26
33
|
throw new Error("MONOREPO_ENV cannot be empty");
|
27
|
-
if (
|
28
|
-
|
29
|
-
|
34
|
+
if (allowLocal) {
|
35
|
+
if (!allSupportedEnvs().includes(monorepoEnv)) {
|
36
|
+
console.error(`MONOREPO_ENV must be one of: ${allSupportedEnvs().join(", ")}. Can be set using --env flag.`);
|
37
|
+
process.exit(1);
|
38
|
+
}
|
39
|
+
}
|
40
|
+
else {
|
41
|
+
if (!remoteSupportedEnvs().includes(monorepoEnv)) {
|
42
|
+
console.error(`MONOREPO_ENV must be one of: ${remoteSupportedEnvs().join(", ")}. Can be set using --env flag.`);
|
43
|
+
process.exit(1);
|
44
|
+
}
|
30
45
|
}
|
31
46
|
}
|
32
47
|
export function envToNamespace(monorepoEnv) {
|
@@ -43,18 +58,28 @@ export function imageConfigMap(image) {
|
|
43
58
|
return `image-config-${image}`;
|
44
59
|
}
|
45
60
|
export function containerRegistryImageName(image, monorepoEnv) {
|
46
|
-
validateEnv(monorepoEnv);
|
61
|
+
validateEnv(monorepoEnv, { allowLocal: true });
|
47
62
|
return `${getConst("project-name")}-${monorepoEnv}-${image}`;
|
48
63
|
}
|
49
64
|
export function containerRegistryPath() {
|
50
65
|
return [getConst("registry-base-url"), getConst("registry-image-path-prefix", { ignoreIfInvalid: true })].filter(Boolean).join("/");
|
51
66
|
}
|
52
67
|
export function containerRegistryRepoPath(image, monorepoEnv, gitSha) {
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
[
|
57
|
-
|
68
|
+
const imageNameAndTag = [containerRegistryImageName(image, monorepoEnv), gitSha].join(":");
|
69
|
+
const imageData = getImageData(image);
|
70
|
+
if (imageData["cloudrun"]) {
|
71
|
+
return [
|
72
|
+
getConst("cloudrun-artifact-registry-repo-path"),
|
73
|
+
imageNameAndTag,
|
74
|
+
].join("/");
|
75
|
+
}
|
76
|
+
else {
|
77
|
+
return [
|
78
|
+
getConst("registry-base-url"),
|
79
|
+
getConst("registry-image-path-prefix", { ignoreIfInvalid: true }),
|
80
|
+
imageNameAndTag,
|
81
|
+
].filter(Boolean).join("/");
|
82
|
+
}
|
58
83
|
}
|
59
84
|
export function domainNameForEnv(image, monorepoEnv) {
|
60
85
|
const imageData = getImageData(image);
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import type { PackageData, TemplateDbMigrateObject, TemplateDebugObject, TemplateDeploymentObject, TemplateSharedContext } from "../types";
|
2
2
|
export declare function generateImageDeployments(monorepoEnv: string, image: string, gitSha: string): string;
|
3
3
|
export declare function generateWorkspaceDeployment(packageData: PackageData, monorepoEnv: string, image: string, gitSha: string): string;
|
4
|
-
export declare function generateDebugDeployment(monorepoEnv: string, image: string, gitSha: string): string;
|
4
|
+
export declare function generateDebugDeployment(monorepoEnv: string, image: string, gitSha: string): string | undefined;
|
5
5
|
export declare function generateDbMigrateJob(monorepoEnv: string, image: string, gitSha: string): string;
|
6
6
|
export declare class ImageContextGenerator {
|
7
7
|
monorepoEnv: string;
|