@vaharoni/devops 1.1.6 → 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/discovery/process-common.js +1 -1
- 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/discovery/process-common.ts +1 -1
- 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/src/cli/prep-build.ts
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
|
|
9
11
|
const oneLiner =
|
10
12
|
"Copies all dependencies of an image to a temporary folder in preparation for a Docker build";
|
@@ -16,7 +18,10 @@ const usage = `
|
|
16
18
|
${oneLiner}
|
17
19
|
|
18
20
|
USAGE
|
19
|
-
devops prep-build <image>
|
21
|
+
devops prep-build <image> --env <env>
|
22
|
+
|
23
|
+
If <env> is a remote environment (e.g. staging, production), the environment variables are
|
24
|
+
fetched from the cluster and injected in case they are needed during the build process.
|
20
25
|
|
21
26
|
EXAMPLES
|
22
27
|
${keyExamples}
|
@@ -61,8 +66,10 @@ async function run(cmdObj: CLICommandParser) {
|
|
61
66
|
fs.copySync(dockerCommonPayloadPath, destFolder);
|
62
67
|
}
|
63
68
|
|
64
|
-
|
65
|
-
|
69
|
+
if (fs.existsSync(dockerImagePayloadPath)) {
|
70
|
+
console.warn(`COPYING Docker image payload`);
|
71
|
+
fs.copySync(dockerImagePayloadPath, destFolder);
|
72
|
+
}
|
66
73
|
|
67
74
|
console.warn(`COPYING .devops/config`);
|
68
75
|
fs.mkdirSync(path.join(destFolder, ".devops"));
|
@@ -71,8 +78,29 @@ async function run(cmdObj: CLICommandParser) {
|
|
71
78
|
// 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
|
72
79
|
console.warn(`CREATING config for the build process`);
|
73
80
|
fs.mkdirSync(path.join(destFolder, "config"));
|
74
|
-
const
|
75
|
-
|
81
|
+
const destGlobalEnvPath = path.join(destFolder, "config/.env.global");
|
82
|
+
if (isLocalOrRemoteEnv(cmdObj.env) === "remote") {
|
83
|
+
const envFileData = getMonorepoSecretStr(cmdObj.env);
|
84
|
+
fs.writeFileSync(destGlobalEnvPath, envFileData);
|
85
|
+
} else {
|
86
|
+
let anyCopied = false;
|
87
|
+
const localGlobalEnvPath = "config/.env.global";
|
88
|
+
const localEnvPath = `config/.env.${cmdObj.env}`;
|
89
|
+
const destEnvPath = path.join(destFolder, `config/.env.${cmdObj.env}`);
|
90
|
+
if (fs.existsSync(localGlobalEnvPath)) {
|
91
|
+
console.warn(`COPYING ${localGlobalEnvPath} to ${destGlobalEnvPath}`);
|
92
|
+
fs.copyFileSync(localGlobalEnvPath, destGlobalEnvPath);
|
93
|
+
anyCopied = true;
|
94
|
+
}
|
95
|
+
if (fs.existsSync(localEnvPath)) {
|
96
|
+
console.warn(`COPYING ${localEnvPath} to ${destEnvPath}`);
|
97
|
+
fs.copyFileSync(localEnvPath, destEnvPath);
|
98
|
+
anyCopied = true;
|
99
|
+
}
|
100
|
+
if (!anyCopied) {
|
101
|
+
console.warn(chalk.red(`\nWarning: local environment ${cmdObj.env} has no .env files. Environment variables will not be injected.\n`));
|
102
|
+
}
|
103
|
+
}
|
76
104
|
|
77
105
|
// Copy all dependencies
|
78
106
|
getImageDescendentData(image).forEach((project) => {
|
package/src/cli/redis.ts
CHANGED
@@ -64,7 +64,7 @@ function run(cmdObj: CLICommandParser) {
|
|
64
64
|
const parsed = cmdObj.parseOptions({ params: ["-p"] });
|
65
65
|
|
66
66
|
const [command, namespace] = parsed.args;
|
67
|
-
const port = parsed.options["-p"]
|
67
|
+
const port = parsed.options["-p"];
|
68
68
|
// @ts-expect-error left as an exercise for the reader
|
69
69
|
const handler = handlers[command];
|
70
70
|
if (!handler) {
|
package/src/cli/registry.ts
CHANGED
@@ -11,21 +11,27 @@ const oneLiner = "Manage container repositories";
|
|
11
11
|
const keyExamples = `
|
12
12
|
$ devops registry server-url
|
13
13
|
$ devops registry reg-url
|
14
|
-
$ devops registry repo-url
|
15
|
-
$ devops registry
|
14
|
+
$ devops registry repo-url my-image sha
|
15
|
+
$ devops registry image-name my-image
|
16
|
+
$ devops registry prune my-image
|
16
17
|
`.trim();
|
17
18
|
|
18
19
|
const usage = `
|
19
20
|
${oneLiner}
|
20
21
|
|
21
22
|
USAGE
|
22
|
-
Get base URLs
|
23
|
+
Get base URLs for the container registry of the cluster:
|
23
24
|
devops registry server-url
|
24
25
|
devops registry reg-url
|
25
26
|
|
27
|
+
Note: for cloudrun images these URLs are not relevant.
|
28
|
+
|
26
29
|
Gets the URL of an image in the container registry:
|
27
30
|
devops registry repo-url <image> <sha> --env <env>
|
28
31
|
|
32
|
+
Gets the image name in the container registry:
|
33
|
+
devops registry image-name <image> --env <env>
|
34
|
+
|
29
35
|
Prunes the repository of old images to enforce the "image-versions-to-keep" constant in config/constants.yaml:
|
30
36
|
devops registry prune <image> --env <env>
|
31
37
|
|
@@ -47,6 +53,9 @@ const handlers = {
|
|
47
53
|
)
|
48
54
|
);
|
49
55
|
},
|
56
|
+
"image-name": (opts: StrongParams) => {
|
57
|
+
console.log(containerRegistryImageName(opts.required("image"), opts.required("env")));
|
58
|
+
},
|
50
59
|
prune: (opts: StrongParams) => {
|
51
60
|
const regName = containerRegistryPath();
|
52
61
|
const repoName = containerRegistryImageName(
|
package/src/devops.ts
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
|
|
26
27
|
const [_node, _scriptPath, ...commandArgs] = process.argv;
|
27
28
|
|
@@ -45,11 +46,12 @@ const allImports = [
|
|
45
46
|
job,
|
46
47
|
//= Deployment
|
47
48
|
prepBuild,
|
49
|
+
cloudrun,
|
48
50
|
affected,
|
49
51
|
constant,
|
50
52
|
registry,
|
51
53
|
internalCurl,
|
52
|
-
jwt
|
54
|
+
jwt,
|
53
55
|
];
|
54
56
|
|
55
57
|
const commands: {
|
@@ -0,0 +1,118 @@
|
|
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
|
+
|
8
|
+
function verifyCloudrunImage(image: string) {
|
9
|
+
const imageData = getImageData(image);
|
10
|
+
if (!imageData["cloudrun"]) {
|
11
|
+
console.error(`Image ${image} is not a cloudrun image. Add "cloudrun: true" in images.yaml`);
|
12
|
+
process.exit(1);
|
13
|
+
}
|
14
|
+
}
|
15
|
+
|
16
|
+
function getEnvValuesToForward(env: string, forwardEnv: string[]) {
|
17
|
+
if (!forwardEnv.length) return {};
|
18
|
+
|
19
|
+
let envValues: Record<string, string> = {};
|
20
|
+
const missingValues = new Set<string>();
|
21
|
+
for (const key of forwardEnv) {
|
22
|
+
const value = process.env[key];
|
23
|
+
if (value) {
|
24
|
+
envValues[key] = value;
|
25
|
+
} else {
|
26
|
+
missingValues.add(key);
|
27
|
+
}
|
28
|
+
}
|
29
|
+
if (missingValues.size > 0 && isLocalOrRemoteEnv(env) === "remote") {
|
30
|
+
const secretsFromCluster = getMonorepoSecretObject(env, Array.from(missingValues));
|
31
|
+
for (const key of Object.keys(secretsFromCluster)) {
|
32
|
+
envValues[key] = secretsFromCluster[key];
|
33
|
+
missingValues.delete(key);
|
34
|
+
}
|
35
|
+
}
|
36
|
+
if (missingValues.size > 0) {
|
37
|
+
console.error(`Some forwardEnv variables are missing: ${Array.from(missingValues).join(", ")}`);
|
38
|
+
process.exit(1);
|
39
|
+
}
|
40
|
+
return envValues;
|
41
|
+
}
|
42
|
+
|
43
|
+
export async function buildDev(image: string) {
|
44
|
+
verifyCloudrunImage(image);
|
45
|
+
const env = "development";
|
46
|
+
const sha = randomBytes(12).toString("hex");
|
47
|
+
|
48
|
+
const buildDir = new CommandExecutor(`devops prep-build ${image}`, {
|
49
|
+
env,
|
50
|
+
}).exec().trim();
|
51
|
+
|
52
|
+
const tag = containerRegistryRepoPath(image, env, sha);
|
53
|
+
console.warn(`Building ${tag} from ${buildDir}`);
|
54
|
+
|
55
|
+
await new CommandExecutor(
|
56
|
+
`docker build --platform linux/amd64 -t ${tag} ${buildDir} --build-arg MONOREPO_ENV=${env}`,
|
57
|
+
{ env }
|
58
|
+
).spawn();
|
59
|
+
|
60
|
+
console.warn(`Pushing ${tag}`);
|
61
|
+
await new CommandExecutor(`docker push ${tag}`, { env }).spawn();
|
62
|
+
|
63
|
+
console.warn(`\n✅ Built and pushed ${tag}\n`);
|
64
|
+
console.warn('Run "devops cloudrun deploy" next. For example:')
|
65
|
+
console.warn(chalk.blue(`./devops cloudrun deploy ${image} ${sha} --env ${env} --allow-unauthenticated --region us-east1 --forward-env ENV1,ENV2`));
|
66
|
+
console.warn();
|
67
|
+
console.log(tag);
|
68
|
+
}
|
69
|
+
|
70
|
+
export async function deploy({
|
71
|
+
image,
|
72
|
+
env,
|
73
|
+
sha,
|
74
|
+
region,
|
75
|
+
forwardEnv = [],
|
76
|
+
allowUnauthenticated = false,
|
77
|
+
cpu = "0.25",
|
78
|
+
memory = "256Mi",
|
79
|
+
minInstances = 0,
|
80
|
+
maxInstances = 1,
|
81
|
+
timeout = "60s",
|
82
|
+
extraArgs = "",
|
83
|
+
}: {
|
84
|
+
image: string;
|
85
|
+
env: string;
|
86
|
+
sha: string;
|
87
|
+
region: string;
|
88
|
+
forwardEnv?: string[];
|
89
|
+
allowUnauthenticated?: boolean;
|
90
|
+
cpu?: string;
|
91
|
+
memory?: string;
|
92
|
+
minInstances?: number;
|
93
|
+
maxInstances?: number;
|
94
|
+
timeout?: string;
|
95
|
+
extraArgs?: string;
|
96
|
+
}) {
|
97
|
+
verifyCloudrunImage(image);
|
98
|
+
const repoPath = containerRegistryRepoPath(image, env, sha);
|
99
|
+
const envValues = getEnvValuesToForward(env, forwardEnv);
|
100
|
+
const envValuesCsv = Object.entries(envValues).map(([key, value]) => `${key}="${value}"`).join(",");
|
101
|
+
const serviceName = `${image}-${env}`;
|
102
|
+
|
103
|
+
const cmd = `
|
104
|
+
gcloud run deploy ${serviceName}
|
105
|
+
--image ${repoPath}
|
106
|
+
${Object.keys(envValues).length > 0 ? `--set-env-vars ${envValuesCsv}` : ""}
|
107
|
+
${allowUnauthenticated ? "--allow-unauthenticated" : ""}
|
108
|
+
--region ${region}
|
109
|
+
--cpu ${cpu}
|
110
|
+
--memory ${memory}
|
111
|
+
--min-instances ${minInstances}
|
112
|
+
--max-instances ${maxInstances}
|
113
|
+
--timeout ${timeout}
|
114
|
+
${extraArgs}
|
115
|
+
`.trim().replace(/\s+/g, " ");
|
116
|
+
|
117
|
+
await new CommandExecutor(cmd, { env }).spawn();
|
118
|
+
}
|
@@ -34,7 +34,7 @@ export class PackageDataProcessor<T> {
|
|
34
34
|
console.error(`Error parsing ${packageFilePath}: ${parsedRes.error}`);
|
35
35
|
process.exit(1);
|
36
36
|
}
|
37
|
-
const rootDir = path.dirname(packageFilePath);
|
37
|
+
const rootDir = path.relative(process.cwd(), path.dirname(packageFilePath));
|
38
38
|
const name = this.nameExtractor(parsedRes.data)
|
39
39
|
this.workspaceNames.add(name);
|
40
40
|
this.loadedFiles[rootDir] = { name, data: parsedRes.data };
|
@@ -24,13 +24,28 @@ export function allSupportedEnvs() {
|
|
24
24
|
return [...remoteSupportedEnvs(), ...localSupportedEnvs()];
|
25
25
|
}
|
26
26
|
|
27
|
-
function
|
27
|
+
export function isLocalOrRemoteEnv(monorepoEnv: string): "local" | "remote" {
|
28
|
+
if (remoteSupportedEnvs().includes(monorepoEnv)) return "remote";
|
29
|
+
if (localSupportedEnvs().includes(monorepoEnv)) return "local";
|
30
|
+
throw new Error(`Unsupported environment: ${monorepoEnv}`);
|
31
|
+
}
|
32
|
+
|
33
|
+
function validateEnv(monorepoEnv?: string, { allowLocal = false }: { allowLocal?: boolean } = {}) {
|
28
34
|
if (!monorepoEnv) throw new Error("MONOREPO_ENV cannot be empty");
|
29
|
-
if (
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
35
|
+
if (allowLocal) {
|
36
|
+
if (!allSupportedEnvs().includes(monorepoEnv)) {
|
37
|
+
console.error(
|
38
|
+
`MONOREPO_ENV must be one of: ${allSupportedEnvs().join(", ")}. Can be set using --env flag.`
|
39
|
+
);
|
40
|
+
process.exit(1);
|
41
|
+
}
|
42
|
+
} else {
|
43
|
+
if (!remoteSupportedEnvs().includes(monorepoEnv)) {
|
44
|
+
console.error(
|
45
|
+
`MONOREPO_ENV must be one of: ${remoteSupportedEnvs().join(", ")}. Can be set using --env flag.`
|
46
|
+
);
|
47
|
+
process.exit(1);
|
48
|
+
}
|
34
49
|
}
|
35
50
|
}
|
36
51
|
|
@@ -52,7 +67,7 @@ export function imageConfigMap(image: string) {
|
|
52
67
|
}
|
53
68
|
|
54
69
|
export function containerRegistryImageName(image: string, monorepoEnv: string) {
|
55
|
-
validateEnv(monorepoEnv);
|
70
|
+
validateEnv(monorepoEnv, { allowLocal: true });
|
56
71
|
return `${getConst("project-name")}-${monorepoEnv}-${image}`;
|
57
72
|
}
|
58
73
|
|
@@ -65,11 +80,20 @@ export function containerRegistryRepoPath(
|
|
65
80
|
monorepoEnv: string,
|
66
81
|
gitSha: string
|
67
82
|
) {
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
[
|
72
|
-
|
83
|
+
const imageNameAndTag = [containerRegistryImageName(image, monorepoEnv), gitSha].join(":");
|
84
|
+
const imageData = getImageData(image);
|
85
|
+
if (imageData["cloudrun"]) {
|
86
|
+
return [
|
87
|
+
getConst("cloudrun-artifact-registry-repo-path"),
|
88
|
+
imageNameAndTag,
|
89
|
+
].join("/");
|
90
|
+
} else {
|
91
|
+
return [
|
92
|
+
getConst("registry-base-url"),
|
93
|
+
getConst("registry-image-path-prefix", { ignoreIfInvalid: true }),
|
94
|
+
imageNameAndTag,
|
95
|
+
].filter(Boolean).join("/");
|
96
|
+
}
|
73
97
|
}
|
74
98
|
|
75
99
|
export function domainNameForEnv(image: string, monorepoEnv: string) {
|
package/src/libs/k8s-generate.ts
CHANGED
@@ -6,7 +6,7 @@ import path from "path";
|
|
6
6
|
import yaml from "yaml";
|
7
7
|
import fs from 'fs';
|
8
8
|
import { globSync } from "glob";
|
9
|
-
import _
|
9
|
+
import _ from 'lodash';
|
10
10
|
import Handlebars from "handlebars";
|
11
11
|
import { getImageData } from "./config";
|
12
12
|
import { getImageDescendentData } from "./discovery/images";
|
@@ -33,7 +33,7 @@ export function generateImageDeployments(
|
|
33
33
|
return generateManifestForDeployment(projectData.rootPath, projectData.deployment!.template, renderFn);
|
34
34
|
});
|
35
35
|
const debug = generateDebugDeployment(monorepoEnv, image, gitSha);
|
36
|
-
const manifest = [debug, ...apps].join("\n---\n");
|
36
|
+
const manifest = [debug, ...apps].filter(Boolean).join("\n---\n");
|
37
37
|
return ensureProperDomainsPresent(manifest, monorepoEnv, image);
|
38
38
|
}
|
39
39
|
|
@@ -54,6 +54,7 @@ export function generateDebugDeployment(
|
|
54
54
|
const context = generator.getDebug();
|
55
55
|
const renderFn = (template: string) => Handlebars.compile(template)(context);
|
56
56
|
const debugTemplate = getImageData(image)["debug-template"];
|
57
|
+
if (!debugTemplate) return;
|
57
58
|
return generateManifestsFromTemplateName(debugTemplate, renderFn).map(x => yaml.stringify(x)).join("\n---\n");
|
58
59
|
}
|
59
60
|
|
@@ -16,7 +16,7 @@ function execUpdateSecret(
|
|
16
16
|
new CommandExecutor(fullCommand, { quiet: true, redactedCommand }).exec();
|
17
17
|
}
|
18
18
|
|
19
|
-
function
|
19
|
+
export function getMonorepoSecretObject(monorepoEnv: string, keys: string[] = []) {
|
20
20
|
// Dots in jsonpath can only be accessed with a \ prefix
|
21
21
|
const escapedSecretFileName = SECRET_FILE_NAME.replaceAll(".", "\\.");
|
22
22
|
// prettier-ignore
|
@@ -39,7 +39,7 @@ function updateSecret(monorepoEnv: string, vars: Record<string, string>) {
|
|
39
39
|
);
|
40
40
|
process.exit(1);
|
41
41
|
}
|
42
|
-
const current =
|
42
|
+
const current = getMonorepoSecretObject(monorepoEnv);
|
43
43
|
const newVars = { ...current, ...vars };
|
44
44
|
execUpdateSecret(monorepoEnv, newVars);
|
45
45
|
}
|
@@ -49,15 +49,19 @@ function deleteSecretKeys(monorepoEnv: string, keys: string[] = []) {
|
|
49
49
|
console.error("Keys to delete must be provided");
|
50
50
|
process.exit(1);
|
51
51
|
}
|
52
|
-
const secretValue =
|
52
|
+
const secretValue = getMonorepoSecretObject(monorepoEnv);
|
53
53
|
keys.forEach((key) => delete secretValue[key]);
|
54
54
|
execUpdateSecret(monorepoEnv, secretValue);
|
55
55
|
}
|
56
56
|
|
57
57
|
//= Interface (L3)
|
58
58
|
|
59
|
-
export function
|
60
|
-
const value =
|
59
|
+
export function getMonorepoSecretStr(monorepoEnv: string, keys: string[] = []) {
|
60
|
+
const value = getMonorepoSecretObject(monorepoEnv, keys);
|
61
|
+
if (Object.keys(value).length === 1) {
|
62
|
+
return Object.values(value)[0];
|
63
|
+
}
|
64
|
+
|
61
65
|
return Object.entries(value)
|
62
66
|
.map((pair) => pair.join("="))
|
63
67
|
.join("\n");
|
@@ -37,11 +37,13 @@
|
|
37
37
|
# This is also the basis for the name of the image in the registry.
|
38
38
|
# image-template The build process copies .devops/docker-images/<image-template>.Dockerfile and .devops/docker-images/<image-template>/ to the image.
|
39
39
|
# language The language of the image. Currently only "node" and "python" are supported.
|
40
|
-
# debug-template Each image comes with a debug pod that can be acccessed as a console. This is the name of the template to use.
|
40
|
+
# debug-template Each image comes with a debug pod that can be acccessed as a console. This is the name of the template to use.
|
41
|
+
# Only "debug-console" is currently supported. Can be left out if the image should not have a debug pod.
|
41
42
|
# can-db-migrate Whether this image can be used to run DB migrations. If set to true, the image could be used to run DB migrations if any project that uses this image depends
|
42
43
|
# on the db project and if the db project changed.
|
43
44
|
# domains The domains for the image. This is used to generate the ingress rules.
|
44
45
|
# This can be left out if the image does not have ingress rules, such as a worker image.
|
46
|
+
# cloudrun If this image should be pushed to cloudrun instead of the cluster registry, set to "true".
|
45
47
|
# applications List of applications that use this image. There is no need to specify dependencies - they will be derived as long as they are declared in package.json.
|
46
48
|
#
|
47
49
|
|
@@ -0,0 +1,31 @@
|
|
1
|
+
FROM node:24-bookworm-slim AS builder
|
2
|
+
|
3
|
+
RUN apt-get update && apt-get install -y jq curl
|
4
|
+
|
5
|
+
WORKDIR /app
|
6
|
+
|
7
|
+
ENV NODE_ENV=production
|
8
|
+
|
9
|
+
ARG MONOREPO_ENV
|
10
|
+
ENV MONOREPO_ENV=${MONOREPO_ENV}
|
11
|
+
RUN echo "Building for environment: $MONOREPO_ENV"
|
12
|
+
|
13
|
+
RUN npm install -g bun
|
14
|
+
|
15
|
+
# This assumes devops prep-build was called by the host, which creates the config/ folder with necessary env variables
|
16
|
+
# that are needed to be statitcally resolved by devops run-many build (e.g. NEXT_PUBLIC_*)
|
17
|
+
COPY . .
|
18
|
+
|
19
|
+
# Install dependencies using bun
|
20
|
+
RUN --mount=type=cache,target=/root/.bun/install/cache bun install
|
21
|
+
|
22
|
+
# This assumes the image has only one application that has a build script which outputs to dist/
|
23
|
+
RUN ./devops run-many build
|
24
|
+
|
25
|
+
|
26
|
+
FROM gcr.io/distroless/nodejs24-debian12 AS runner
|
27
|
+
WORKDIR /app
|
28
|
+
ENV NODE_ENV=production
|
29
|
+
COPY --from=builder /app/dist ./dist
|
30
|
+
EXPOSE 8080
|
31
|
+
CMD ["dist/index.js"]
|
@@ -4,6 +4,10 @@ inputs:
|
|
4
4
|
image_name:
|
5
5
|
description: 'The image key in images.yaml'
|
6
6
|
required: true
|
7
|
+
outputs:
|
8
|
+
affected:
|
9
|
+
description: 'Whether the specified image is affected (computed before deploy)'
|
10
|
+
value: ${{ steps.check_affected.outputs.affected }}
|
7
11
|
runs:
|
8
12
|
using: "composite"
|
9
13
|
steps:
|
package/src/types/index.ts
CHANGED
@@ -11,6 +11,7 @@ export const constFileSchema = z.object({
|
|
11
11
|
"image-versions-to-keep": z.number().optional(),
|
12
12
|
"registry-base-url": z.string(),
|
13
13
|
"registry-image-path-prefix": z.string().optional(),
|
14
|
+
"cloudrun-artifact-registry-repo-path": z.string().optional(),
|
14
15
|
"extra-remote-environments": z.array(z.string()),
|
15
16
|
"extra-local-environments": z.array(z.string()),
|
16
17
|
})
|
@@ -26,7 +27,8 @@ const singleImageSchema = z.object({
|
|
26
27
|
"image-template": z.string(),
|
27
28
|
"language": z.enum(SUPPORTED_LANGUAGES),
|
28
29
|
"domains": z.record(z.string()).optional(),
|
29
|
-
"debug-template": z.string(),
|
30
|
+
"debug-template": z.string().optional(),
|
31
|
+
"cloudrun": z.boolean().optional(),
|
30
32
|
"can-db-migrate": z.boolean().optional(),
|
31
33
|
applications: z.array(z.string()),
|
32
34
|
});
|