@catladder/pipeline 1.34.0 → 1.36.0

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.
Files changed (33) hide show
  1. package/dist/constants.js +1 -1
  2. package/dist/context/index.js +15 -1
  3. package/dist/deploy/base.js +1 -1
  4. package/dist/deploy/custom/index.d.ts +2 -1
  5. package/dist/deploy/custom/index.js +12 -18
  6. package/dist/deploy/index.d.ts +6 -6
  7. package/dist/deploy/index.js +2 -12
  8. package/dist/deploy/kubernetes/additionalSecretKeys.d.ts +2 -0
  9. package/dist/deploy/kubernetes/additionalSecretKeys.js +27 -0
  10. package/dist/deploy/kubernetes/deployJob.js +5 -62
  11. package/dist/deploy/kubernetes/index.d.ts +2 -1
  12. package/dist/deploy/kubernetes/index.js +11 -17
  13. package/dist/deploy/kubernetes/kubeEnv.d.ts +10 -0
  14. package/dist/deploy/kubernetes/kubeEnv.js +90 -0
  15. package/dist/deploy/kubernetes/kubeValues.d.ts +136 -0
  16. package/dist/deploy/kubernetes/kubeValues.js +62 -0
  17. package/dist/deploy/kubernetes/mongodb.d.ts +17 -4
  18. package/dist/deploy/kubernetes/mongodb.js +119 -8
  19. package/dist/deploy/kubernetes/processSecretsAsFiles.d.ts +2 -3
  20. package/dist/deploy/types.d.ts +32 -4
  21. package/dist/tsconfig.tsbuildinfo +1 -1
  22. package/package.json +1 -1
  23. package/src/context/index.ts +14 -2
  24. package/src/deploy/base.ts +1 -1
  25. package/src/deploy/custom/index.ts +8 -1
  26. package/src/deploy/index.ts +12 -15
  27. package/src/deploy/kubernetes/additionalSecretKeys.ts +20 -0
  28. package/src/deploy/kubernetes/deployJob.ts +2 -70
  29. package/src/deploy/kubernetes/index.ts +9 -1
  30. package/src/deploy/kubernetes/kubeEnv.ts +48 -0
  31. package/src/deploy/kubernetes/kubeValues.ts +60 -0
  32. package/src/deploy/kubernetes/mongodb.ts +103 -7
  33. package/src/deploy/types.ts +43 -6
@@ -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 { createCloudsqlBaseConfig } from "./cloudsql";
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 allEnvVars = context.environment.envVars;
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
- export * from "./deployJob";
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 { Context } from "../..";
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 = true; // ["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
- backup: {
8
- enabled: ["prod", "stage"].includes(context.environment.envType),
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
- "mongodb-replicaset": {
12
- replicas: 1,
13
- persistentVolume: {
14
- storageClass: "standard",
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
  };
@@ -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
- * Consider using external services instead of this.
144
+ * See https://github.com/bitnami/charts/tree/master/bitnami/mongodb
126
145
  */
127
- mongodb?: AllowUnknownProps<{
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.
@@ -142,7 +159,21 @@ export type DeployConfigKubernetesValues = AllowUnknownProps<{
142
159
  * post-install/upgrade jobs. These use the app image and have all env vars available.
143
160
  * Typically used for migrations and seeds
144
161
  */
145
- jobs?: Record<string, AllowUnknownProps<{ command: string }>>;
162
+ jobs?: Record<
163
+ string,
164
+ AllowUnknownProps<{
165
+ /**
166
+ * the command to execute
167
+ */
168
+ command: string;
169
+ /**
170
+ * comma-separated list of helm hooks, see https://helm.sh/docs/topics/charts_hooks/
171
+ *
172
+ * defaults to pre-install,pre-upgrade
173
+ */
174
+ hook?: string;
175
+ }>
176
+ >;
146
177
  /**
147
178
  * cronjobs that run periodically. These use the app image have all env vars available.
148
179
  */
@@ -232,7 +263,13 @@ export type DeployConfigCustom = {
232
263
 
233
264
  export type DeployConfig = DeployConfigKubernetes | DeployConfigCustom;
234
265
 
235
- export const isOfDeployType = <T extends Array<DeployConfig["type"]>>(
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>>(
236
273
  t: DeployConfig | false,
237
274
  ...types: T
238
275
  ): t is Extract<DeployConfig, { type: T[number] }> => {