@catladder/pipeline 1.35.0 → 1.36.2
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/dist/constants.js +1 -1
- package/dist/context/index.js +15 -1
- package/dist/deploy/custom/index.d.ts +2 -1
- package/dist/deploy/custom/index.js +12 -18
- package/dist/deploy/index.d.ts +6 -6
- package/dist/deploy/index.js +2 -12
- package/dist/deploy/kubernetes/additionalSecretKeys.d.ts +2 -0
- package/dist/deploy/kubernetes/additionalSecretKeys.js +27 -0
- package/dist/deploy/kubernetes/deployJob.js +5 -62
- package/dist/deploy/kubernetes/index.d.ts +2 -1
- package/dist/deploy/kubernetes/index.js +11 -17
- package/dist/deploy/kubernetes/kubeEnv.d.ts +10 -0
- package/dist/deploy/kubernetes/kubeEnv.js +90 -0
- package/dist/deploy/kubernetes/kubeValues.d.ts +136 -0
- package/dist/deploy/kubernetes/kubeValues.js +62 -0
- package/dist/deploy/kubernetes/mongodb.d.ts +17 -4
- package/dist/deploy/kubernetes/mongodb.js +118 -8
- package/dist/deploy/kubernetes/processSecretsAsFiles.d.ts +1 -3
- package/dist/deploy/types.d.ts +22 -4
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/context/index.ts +14 -2
- package/src/deploy/custom/index.ts +8 -1
- package/src/deploy/index.ts +12 -15
- package/src/deploy/kubernetes/additionalSecretKeys.ts +20 -0
- package/src/deploy/kubernetes/deployJob.ts +2 -70
- package/src/deploy/kubernetes/index.ts +9 -1
- package/src/deploy/kubernetes/kubeEnv.ts +48 -0
- package/src/deploy/kubernetes/kubeValues.ts +60 -0
- package/src/deploy/kubernetes/mongodb.ts +103 -7
- package/src/deploy/types.ts +28 -5
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
import { dump } from "js-yaml";
|
|
2
2
|
import { merge } from "lodash";
|
|
3
|
-
import { DeployConfigKubernetesValues } from "..";
|
|
4
3
|
import { getSecretVarNameForContext } from "../..";
|
|
5
4
|
import { getRunnerImage } from "../../runner";
|
|
6
5
|
import { Context } from "../../types/context";
|
|
7
6
|
import { CatladderJob } from "../../types/jobs";
|
|
8
|
-
import { mergeWithMergingArrays } from "../../utils";
|
|
9
7
|
import {
|
|
10
8
|
getBaseDeploymentJob,
|
|
11
9
|
getBaseDeploymentStopJob,
|
|
12
10
|
getBaseRollbackJob,
|
|
13
11
|
} from "../base";
|
|
14
12
|
import { isOfDeployType } from "../types";
|
|
15
|
-
import {
|
|
16
|
-
import { createMongodbBaseConfig } from "./mongodb";
|
|
17
|
-
import { processSecretsAsFiles } from "./processSecretsAsFiles";
|
|
13
|
+
import { createKubeValues } from "./kubeValues";
|
|
18
14
|
|
|
19
15
|
export const createKubernetesDeployJobs = (
|
|
20
16
|
context: Context
|
|
@@ -28,71 +24,7 @@ export const createKubernetesDeployJobs = (
|
|
|
28
24
|
throw new Error("deploy config is not kubernetes");
|
|
29
25
|
}
|
|
30
26
|
|
|
31
|
-
const
|
|
32
|
-
/**
|
|
33
|
-
* separate by secrets and public.
|
|
34
|
-
* we evalulate the actual values later, but want to store the secrets in kubernetes secrets
|
|
35
|
-
*/
|
|
36
|
-
const env = Object.entries(allEnvVars).reduce<{
|
|
37
|
-
secret: Record<string, string>;
|
|
38
|
-
public: Record<string, string>;
|
|
39
|
-
}>(
|
|
40
|
-
(acc, [key, value]) => {
|
|
41
|
-
if (String(value)?.startsWith("$CL_")) {
|
|
42
|
-
acc.secret = {
|
|
43
|
-
...acc.secret,
|
|
44
|
-
[key]: value,
|
|
45
|
-
};
|
|
46
|
-
return acc;
|
|
47
|
-
}
|
|
48
|
-
acc.public = {
|
|
49
|
-
...acc.public,
|
|
50
|
-
[key]: value,
|
|
51
|
-
};
|
|
52
|
-
return acc;
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
secret: {},
|
|
56
|
-
public: {},
|
|
57
|
-
}
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
const defaultAppConfig: DeployConfigKubernetesValues["application"] = {
|
|
61
|
-
host: context.environment.host,
|
|
62
|
-
command: context.componentConfig.build.startCommand,
|
|
63
|
-
livenessProbe: {
|
|
64
|
-
httpGet: {
|
|
65
|
-
path: deployConfig.values?.application?.healthRoute ?? "__health",
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
readinessProbe: {
|
|
69
|
-
httpGet: {
|
|
70
|
-
path: deployConfig.values?.application?.healthRoute ?? "__health",
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
startupProbe: {
|
|
74
|
-
httpGet: {
|
|
75
|
-
path: deployConfig.values?.application?.healthRoute ?? "__health",
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
const defaultKubeValues = {
|
|
81
|
-
application: defaultAppConfig,
|
|
82
|
-
|
|
83
|
-
env: env,
|
|
84
|
-
...(deployConfig.values?.cloudsql?.enabled
|
|
85
|
-
? createCloudsqlBaseConfig(context)
|
|
86
|
-
: {}),
|
|
87
|
-
...(deployConfig.values?.mongodb?.enabled
|
|
88
|
-
? createMongodbBaseConfig(context)
|
|
89
|
-
: {}),
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
const kubeValues = processSecretsAsFiles(
|
|
93
|
-
mergeWithMergingArrays(defaultKubeValues, deployConfig.values)
|
|
94
|
-
);
|
|
95
|
-
|
|
27
|
+
const kubeValues = createKubeValues(context);
|
|
96
28
|
const kubernetesEnvironment = {
|
|
97
29
|
namespace: context.environment.envVars.KUBE_NAMESPACE,
|
|
98
30
|
};
|
|
@@ -1 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import { DeployTypeDefinition } from "..";
|
|
2
|
+
import { additionalKubernetesSecretKeys } from "./additionalSecretKeys";
|
|
3
|
+
import { createKubernetesDeployJobs } from "./deployJob";
|
|
4
|
+
|
|
5
|
+
export const KUBERNETES_DEPLOY_TYPE: DeployTypeDefinition<"kubernetes"> = {
|
|
6
|
+
jobs: createKubernetesDeployJobs,
|
|
7
|
+
defaults: () => ({}),
|
|
8
|
+
additionalSecretKeys: additionalKubernetesSecretKeys,
|
|
9
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Context } from "../../types";
|
|
2
|
+
import { isOfDeployType } from "../types";
|
|
3
|
+
|
|
4
|
+
const shouldGoIntoSecrets = (key: string, value: string) => {
|
|
5
|
+
if (String(value)?.includes("$CL_")) {
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
return false;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* separate by secrets and public.
|
|
13
|
+
* we evalulate the actual values later, but want to store the secrets in kubernetes secrets
|
|
14
|
+
*/
|
|
15
|
+
export const createKubeEnv = (context: Context) => {
|
|
16
|
+
if (!isOfDeployType(context.componentConfig.deploy, "kubernetes")) {
|
|
17
|
+
// should not happen
|
|
18
|
+
throw new Error("deploy config is not kubernetes");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const allEnvVars = context.environment.envVars;
|
|
22
|
+
|
|
23
|
+
const env = Object.entries(allEnvVars).reduce<{
|
|
24
|
+
secret: Record<string, string>;
|
|
25
|
+
public: Record<string, string>;
|
|
26
|
+
}>(
|
|
27
|
+
(acc, [key, value]) => {
|
|
28
|
+
if (shouldGoIntoSecrets(key, value)) {
|
|
29
|
+
acc.secret = {
|
|
30
|
+
...acc.secret,
|
|
31
|
+
[key]: value,
|
|
32
|
+
};
|
|
33
|
+
return acc;
|
|
34
|
+
}
|
|
35
|
+
acc.public = {
|
|
36
|
+
...acc.public,
|
|
37
|
+
[key]: value,
|
|
38
|
+
};
|
|
39
|
+
return acc;
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
secret: {},
|
|
43
|
+
public: {},
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
return env;
|
|
48
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { merge } from "lodash";
|
|
2
|
+
import { DeployConfigKubernetesValues } from "..";
|
|
3
|
+
import { Context } from "../../types/context";
|
|
4
|
+
import { mergeWithMergingArrays } from "../../utils";
|
|
5
|
+
import { isOfDeployType } from "../types";
|
|
6
|
+
import { createCloudsqlBaseConfig } from "./cloudsql";
|
|
7
|
+
import { createKubeEnv } from "./kubeEnv";
|
|
8
|
+
import { createMongodbBaseConfig } from "./mongodb";
|
|
9
|
+
import { processSecretsAsFiles } from "./processSecretsAsFiles";
|
|
10
|
+
|
|
11
|
+
export const createKubeValues = (context: Context) => {
|
|
12
|
+
const deployConfig = context.componentConfig.deploy;
|
|
13
|
+
if (deployConfig === false) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
if (!isOfDeployType(deployConfig, "kubernetes")) {
|
|
17
|
+
// should not happen
|
|
18
|
+
throw new Error("deploy config is not kubernetes");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const env = createKubeEnv(context);
|
|
22
|
+
const defaultAppConfig: DeployConfigKubernetesValues["application"] = {
|
|
23
|
+
host: context.environment.host,
|
|
24
|
+
command: context.componentConfig.build.startCommand,
|
|
25
|
+
livenessProbe: {
|
|
26
|
+
httpGet: {
|
|
27
|
+
path: deployConfig.values?.application?.healthRoute ?? "__health",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
readinessProbe: {
|
|
31
|
+
httpGet: {
|
|
32
|
+
path: deployConfig.values?.application?.healthRoute ?? "__health",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
startupProbe: {
|
|
36
|
+
httpGet: {
|
|
37
|
+
path: deployConfig.values?.application?.healthRoute ?? "__health",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const defaultKubeValues = merge(
|
|
43
|
+
{
|
|
44
|
+
application: defaultAppConfig,
|
|
45
|
+
env: env,
|
|
46
|
+
},
|
|
47
|
+
deployConfig.values?.cloudsql?.enabled
|
|
48
|
+
? createCloudsqlBaseConfig(context)
|
|
49
|
+
: {},
|
|
50
|
+
deployConfig.values?.mongodb?.enabled
|
|
51
|
+
? createMongodbBaseConfig(context)
|
|
52
|
+
: {}
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const kubeValues = processSecretsAsFiles(
|
|
56
|
+
mergeWithMergingArrays(defaultKubeValues, deployConfig.values)
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
return kubeValues;
|
|
60
|
+
};
|
|
@@ -1,17 +1,113 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { range } from "lodash";
|
|
2
|
+
import { Context, getSecretVarNameForContext, isOfDeployType } from "../..";
|
|
2
3
|
|
|
4
|
+
const getCredentialString = (context: Context) =>
|
|
5
|
+
`root:$${getSecretVarNameForContext(context, "MONGODB_ROOT_PASSWORD")}@`;
|
|
6
|
+
const getMongodbHost = (context: Context, name: string) => {
|
|
7
|
+
const namespace = context.environment.envVars.KUBE_NAMESPACE;
|
|
8
|
+
return `${name}.${namespace}.svc.cluster.local:27017`;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const getMongodbStandaloneHost = (context: Context) => {
|
|
12
|
+
const fullAppname = context.environment.envVars.KUBE_APP_NAME;
|
|
13
|
+
return getMongodbHost(context, `${fullAppname}-mongodb`);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const getMongodbReplicasetHost = (context: Context, index: number) => {
|
|
17
|
+
const fullAppname = context.environment.envVars.KUBE_APP_NAME;
|
|
18
|
+
return getMongodbHost(
|
|
19
|
+
context,
|
|
20
|
+
`${fullAppname}-mongodb-${index}.${fullAppname}-mongodb-headless`
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
const createMongodbUrl = (context: Context, dbName: string) => {
|
|
24
|
+
if (!isOfDeployType(context.componentConfig.deploy, "kubernetes")) {
|
|
25
|
+
throw new Error("can only createMongodbUrl on supported deploys");
|
|
26
|
+
}
|
|
27
|
+
const mongodbConfig = context.componentConfig.deploy.values?.mongodb;
|
|
28
|
+
|
|
29
|
+
let queryParams: string | undefined = undefined;
|
|
30
|
+
|
|
31
|
+
let hosts = "";
|
|
32
|
+
if (mongodbConfig?.architecture === "replicaset") {
|
|
33
|
+
hosts = range(0, mongodbConfig?.replicaCount ?? 2)
|
|
34
|
+
.map((i) => getMongodbReplicasetHost(context, i))
|
|
35
|
+
.join(",");
|
|
36
|
+
|
|
37
|
+
queryParams = "replicaSet=rs0&authSource=admin";
|
|
38
|
+
} else {
|
|
39
|
+
hosts = getMongodbStandaloneHost(context);
|
|
40
|
+
queryParams = "authSource=admin";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return `mongodb://${getCredentialString(context)}${hosts}/${dbName}${
|
|
44
|
+
queryParams ? `?${queryParams}` : ""
|
|
45
|
+
}`;
|
|
46
|
+
};
|
|
47
|
+
const createMongoBackupDefaultConfig = (context: Context) => {
|
|
48
|
+
if (!isOfDeployType(context.componentConfig.deploy, "kubernetes")) {
|
|
49
|
+
throw new Error("can only create mongodb base config on supported deploys");
|
|
50
|
+
}
|
|
51
|
+
const mongodbConfig = context.componentConfig.deploy.values?.mongodb;
|
|
52
|
+
const fullAppName = context.environment.envVars.KUBE_APP_NAME;
|
|
53
|
+
const backupEnabled = ["prod", "stage"].includes(context.environment.envType);
|
|
54
|
+
|
|
55
|
+
let hostToBackup: string;
|
|
56
|
+
let pvcToBackup: string;
|
|
57
|
+
|
|
58
|
+
if (mongodbConfig?.architecture === "replicaset") {
|
|
59
|
+
// bit quirky, we need to specify the host and its volume
|
|
60
|
+
// on replicaset its probably best to not use the first one, whcih usually starts as master, so we take the second (last one would also be ok)
|
|
61
|
+
const backupHostIndex =
|
|
62
|
+
mongodbConfig?.architecture === "replicaset" &&
|
|
63
|
+
mongodbConfig.replicaCount &&
|
|
64
|
+
mongodbConfig.replicaCount > 1
|
|
65
|
+
? 1
|
|
66
|
+
: 0;
|
|
67
|
+
|
|
68
|
+
hostToBackup = getMongodbReplicasetHost(context, backupHostIndex);
|
|
69
|
+
pvcToBackup = `datadir-${fullAppName}-mongodb-${backupHostIndex}`;
|
|
70
|
+
} else {
|
|
71
|
+
hostToBackup = getMongodbStandaloneHost(context);
|
|
72
|
+
pvcToBackup = `${fullAppName}-mongodb`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
enabled: backupEnabled,
|
|
77
|
+
hostToBackup,
|
|
78
|
+
pvcToBackup,
|
|
79
|
+
image: "mrelite/kubectlmongoshell:v1.0",
|
|
80
|
+
schedule: "0 0 1 1 1",
|
|
81
|
+
volumeSnapshotClass: "snapshotclass",
|
|
82
|
+
};
|
|
83
|
+
};
|
|
3
84
|
export const createMongodbBaseConfig = (context: Context) => {
|
|
85
|
+
if (!isOfDeployType(context.componentConfig.deploy, "kubernetes")) {
|
|
86
|
+
throw new Error("can only create mongodb base config on supported deploys");
|
|
87
|
+
}
|
|
88
|
+
const mongodbConfig = context.componentConfig.deploy.values?.mongodb;
|
|
89
|
+
|
|
4
90
|
return {
|
|
5
91
|
mongodb: {
|
|
6
92
|
enabled: true,
|
|
7
|
-
|
|
8
|
-
enabled:
|
|
93
|
+
auth: {
|
|
94
|
+
enabled: true,
|
|
95
|
+
rootPassword:
|
|
96
|
+
"$" + getSecretVarNameForContext(context, "MONGODB_ROOT_PASSWORD"),
|
|
97
|
+
replicaSetKey:
|
|
98
|
+
"$" + getSecretVarNameForContext(context, "MONGODB_REPLICASET_KEY"),
|
|
99
|
+
},
|
|
100
|
+
persistence: {
|
|
101
|
+
storageClass: "standard-rwo",
|
|
9
102
|
},
|
|
103
|
+
backup: createMongoBackupDefaultConfig(context),
|
|
10
104
|
},
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
105
|
+
env: {
|
|
106
|
+
secret: {
|
|
107
|
+
MONGO_URL: createMongodbUrl(context, mongodbConfig?.dbName ?? "app"),
|
|
108
|
+
...(mongodbConfig?.architecture === "replicaset"
|
|
109
|
+
? { MONGO_OPLOG_URL: createMongodbUrl(context, "local") } // oplog only works with replicasets
|
|
110
|
+
: {}),
|
|
15
111
|
},
|
|
16
112
|
},
|
|
17
113
|
};
|
package/src/deploy/types.ts
CHANGED
|
@@ -110,6 +110,25 @@ export type KubernetesWorkerDef = {
|
|
|
110
110
|
command?: string;
|
|
111
111
|
resources?: KubernetesResourcesDef;
|
|
112
112
|
};
|
|
113
|
+
|
|
114
|
+
export type DeployConfigMongodbBase = {
|
|
115
|
+
enabled?: boolean;
|
|
116
|
+
dbName?: string;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export type DeployConfigMongodbStandalone = {
|
|
120
|
+
architecture: "standalone";
|
|
121
|
+
};
|
|
122
|
+
export type DeployConfigMongodbReplicaset = {
|
|
123
|
+
architecture: "replicaset";
|
|
124
|
+
/**
|
|
125
|
+
* defaults to 2
|
|
126
|
+
*/
|
|
127
|
+
replicaCount?: number;
|
|
128
|
+
};
|
|
129
|
+
export type DeployConfigMongodb = DeployConfigMongodbBase &
|
|
130
|
+
(DeployConfigMongodbStandalone | DeployConfigMongodbReplicaset);
|
|
131
|
+
|
|
113
132
|
export type DeployConfigKubernetesValues = AllowUnknownProps<{
|
|
114
133
|
/**
|
|
115
134
|
* enable cloudsql db. Currently you have to manually set it up
|
|
@@ -122,11 +141,9 @@ export type DeployConfigKubernetesValues = AllowUnknownProps<{
|
|
|
122
141
|
};
|
|
123
142
|
/**
|
|
124
143
|
* enable mongodb. The mongodb is deployed using a helm chart.
|
|
125
|
-
*
|
|
144
|
+
* See https://github.com/bitnami/charts/tree/master/bitnami/mongodb
|
|
126
145
|
*/
|
|
127
|
-
mongodb?:
|
|
128
|
-
enabled: boolean;
|
|
129
|
-
}>;
|
|
146
|
+
mongodb?: DeployConfigMongodb;
|
|
130
147
|
/**
|
|
131
148
|
* enable mailhog. Mailhog is a virtual mail server that catches all outgoing mailsl and show them in a mailbox.
|
|
132
149
|
* This is great for development as it prevents to accidentially send out real emails and helps with debugging outgoing mails.
|
|
@@ -246,7 +263,13 @@ export type DeployConfigCustom = {
|
|
|
246
263
|
|
|
247
264
|
export type DeployConfig = DeployConfigKubernetes | DeployConfigCustom;
|
|
248
265
|
|
|
249
|
-
export
|
|
266
|
+
export type DeployConfigType = DeployConfig["type"];
|
|
267
|
+
export type DeployConfigGeneric<T extends DeployConfigType> = Extract<
|
|
268
|
+
DeployConfig,
|
|
269
|
+
{ type: T }
|
|
270
|
+
>;
|
|
271
|
+
|
|
272
|
+
export const isOfDeployType = <T extends Array<DeployConfigType>>(
|
|
250
273
|
t: DeployConfig | false,
|
|
251
274
|
...types: T
|
|
252
275
|
): t is Extract<DeployConfig, { type: T[number] }> => {
|