@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.
@@ -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 = ["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.
@@ -246,7 +263,13 @@ export type DeployConfigCustom = {
246
263
 
247
264
  export type DeployConfig = DeployConfigKubernetes | DeployConfigCustom;
248
265
 
249
- 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>>(
250
273
  t: DeployConfig | false,
251
274
  ...types: T
252
275
  ): t is Extract<DeployConfig, { type: T[number] }> => {