@catladder/pipeline 1.45.0 → 1.47.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.
@@ -1,214 +1,13 @@
1
- import { isObject, merge } from "lodash";
2
- import slugify from "slugify";
3
1
  import { BUILD_TYPES } from "../build";
4
2
  import type { BuildConfig } from "../build/types";
5
- import {
6
- GCLOUD_RUN_CANONICAL_HOST_SUFFIX,
7
- DEPLOY_TYPES,
8
- getKubernetesNamespace,
9
- } from "../deploy";
3
+ import { DEPLOY_TYPES } from "../deploy";
10
4
  import type { DeployConfig } from "../deploy/types";
11
- import { isOfDeployType } from "../deploy/types";
12
- import type { Config, DevLocalEnvConfig } from "../types/config";
13
- import { isKnowEnvType } from "../types/config";
14
- import type {
15
- CommitInfo,
16
- Context,
17
- Environment,
18
- PackageManagerInfo,
19
- } from "../types/context";
5
+ import type { Config } from "../types/config";
6
+ import type { CommitInfo, Context, PackageManagerInfo } from "../types/context";
20
7
  import { mergeWithMergingArrays } from "../utils";
21
- import {
22
- resolveReferences,
23
- translateLegacyFromComponents,
24
- } from "./resolveReferences";
8
+ import { getEnvironment } from "./getEnvironment";
25
9
 
26
- const sanitizeForEnVar = (s: string) => s.replace(/-/g, "_");
27
- export const getSecretVarName = (
28
- env: string,
29
- componentName: string,
30
- key: string
31
- ) => `CL_${sanitizeForEnVar(env)}_${sanitizeForEnVar(componentName)}_${key}`; // remove dash from component name
32
-
33
- const addIndexVar = (vars: Record<string, unknown>) => ({
34
- ...vars,
35
- _ALL_ENV_VAR_KEYS: JSON.stringify(Object.keys(vars)),
36
- });
37
-
38
- export const getSecretVarNameForContext = (context: Context, key: string) =>
39
- getSecretVarName(context.environment.shortName, context.componentName, key);
40
- export const getEnvironment = async (
41
- config: Config,
42
- componentName: string,
43
- env: string,
44
- commitInfo?: CommitInfo,
45
- alreadyVisited: Record<string, Record<string, boolean>> = {} // to prevent endless loop
46
- ): Promise<Environment> => {
47
- const defaultConfig = config.components[componentName];
48
- if (!defaultConfig) {
49
- throw new Error("unknown component " + componentName);
50
- }
51
-
52
- const envConfig = defaultConfig.env?.[env] ?? {};
53
- if (envConfig === false) {
54
- throw new Error("env is disabled: " + env);
55
- }
56
-
57
- const mergedConfig = mergeWithMergingArrays(defaultConfig, envConfig);
58
-
59
- // env type: if its set manually, use that, otherwise use the known env types
60
-
61
- const envType = envConfig?.type ?? (isKnowEnvType(env) ? env : null);
62
-
63
- if (!envType) {
64
- throw new Error(
65
- "Missing type in environment " + env + " in component " + componentName
66
- );
67
- }
68
-
69
- const basePredefinedVariables = {
70
- ENV_SHORT: env,
71
- APP_DIR: mergedConfig.dir,
72
- ENV_TYPE: envType,
73
- };
74
-
75
- const environmentName =
76
- envType === "review" && commitInfo
77
- ? `${env}/${commitInfo.refName}/${componentName}`
78
- : `${env}/${componentName}`;
79
-
80
- const environmentSlug =
81
- envType === "review" && commitInfo
82
- ? `${env}-${commitInfo.reviewSlug}-${componentName}`
83
- : `${env}-${componentName}`;
84
-
85
- let predefinedVariables: Record<string, string>;
86
- let host: string;
87
- let url: string;
88
- if (envType === "local") {
89
- const devLocalConfig: DevLocalEnvConfig = mergedConfig;
90
- const port = devLocalConfig.port ?? 3000;
91
- host = "localhost:" + port;
92
- url = "http://" + host;
93
- predefinedVariables = {
94
- ENV_SHORT: "local",
95
- ROOT_URL: url,
96
- PORT: port.toString(),
97
- };
98
- } else {
99
- const RELEASE_NAME = `${config.customerName}-${config.appName}-${environmentSlug}`;
100
-
101
- const componentSlug = slugify(componentName);
102
- const envInUrl =
103
- envType === "review" && commitInfo
104
- ? `${commitInfo.reviewSlug}.${env}`
105
- : env;
106
-
107
- const domainCanonical =
108
- (mergedConfig?.deploy &&
109
- mergedConfig?.deploy.type === "kubernetes" &&
110
- mergedConfig.deploy.cluster?.domainCanonical) || // for convenience, we allow clusters to define a canonical domain, because a cluster has a fixed ip and you will usually have a domain pointing to that cluster
111
- config.domainCanonical ||
112
- "panter.cloud";
113
-
114
- const HOST_CANONICAL = isOfDeployType(mergedConfig.deploy, "kubernetes")
115
- ? `${componentSlug}.${envInUrl}.${config.appName}.${config.customerName}.${domainCanonical}`
116
- : isOfDeployType(mergedConfig.deploy, "google-cloudrun")
117
- ? environmentSlug +
118
- "-" +
119
- process.env[
120
- getSecretVarName(env, componentName, GCLOUD_RUN_CANONICAL_HOST_SUFFIX)
121
- ]
122
- : "";
123
- host = mergedConfig?.host ?? HOST_CANONICAL;
124
- const url = `https://${host}`;
125
-
126
- // FIXME: move to kube specific jobs
127
- const KUBE_APP_NAME_PREFIX =
128
- envType === "review" && commitInfo ? `${commitInfo.reviewSlug}-` : "";
129
-
130
- const KUBE_APP_NAME = `${KUBE_APP_NAME_PREFIX}${componentName}`;
131
- const KUBE_NAMESPACE = getKubernetesNamespace(config, env);
132
-
133
- predefinedVariables = {
134
- ...basePredefinedVariables,
135
- HOST_CANONICAL,
136
- HOST: host,
137
- ROOT_URL: url,
138
- KUBE_NAMESPACE,
139
- KUBE_APP_NAME,
140
- KUBE_APP_NAME_PREFIX,
141
- RELEASE_NAME,
142
- };
143
- }
144
- const publicEnvVarsRaw = mergedConfig.vars?.public ?? {};
145
-
146
- const additionalSecretKeys = mergedConfig.deploy
147
- ? DEPLOY_TYPES[mergedConfig.deploy.type].additionalSecretKeys(
148
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
149
- mergedConfig.deploy as any
150
- )
151
- : [];
152
-
153
- const secretEnvVarKeys = [
154
- ...(mergedConfig.vars?.secret ?? []),
155
- ...additionalSecretKeys,
156
- ];
157
- const secretEnvVars = Object.fromEntries(
158
- secretEnvVarKeys.map((key) => [
159
- key,
160
- `$${getSecretVarName(env, componentName, key)}`,
161
- ])
162
- );
163
-
164
- // this is deprecated, we now support: $componentname:FOO
165
- const legacyFromComponents = mergedConfig.vars?.fromComponents ?? {};
166
- const publicEnvVarsRawWithLegasyFromComponents = merge(
167
- {},
168
- translateLegacyFromComponents(legacyFromComponents),
169
- publicEnvVarsRaw
170
- );
171
-
172
- const publicEnvVarsRawSanitized = Object.fromEntries(
173
- Object.entries(publicEnvVarsRawWithLegasyFromComponents).map(
174
- ([key, value]) => [
175
- key,
176
- isObject(value) ? JSON.stringify(value) : `${value}`,
177
- ]
178
- )
179
- );
180
-
181
- const envVarsRaw = addIndexVar({
182
- ...predefinedVariables,
183
- ...secretEnvVars,
184
- ...publicEnvVarsRawSanitized,
185
- });
186
-
187
- const envVars = await resolveReferences(
188
- envVarsRaw,
189
- async (componentName, variableName, alreadyVisited) => {
190
- const { envVars } = await getEnvironment(
191
- config,
192
- componentName,
193
- env,
194
- commitInfo,
195
- alreadyVisited
196
- );
197
- return envVars[variableName];
198
- },
199
- alreadyVisited
200
- );
201
- return {
202
- envType,
203
- host,
204
- fullName: environmentName,
205
- slug: environmentSlug,
206
- shortName: env,
207
- url: predefinedVariables.ROOT_URL,
208
- envVars,
209
- secretEnvVarKeys,
210
- };
211
- };
10
+ export * from "./getEnvironment";
212
11
 
213
12
  export const createContext = async (
214
13
  config: Config,
@@ -1,6 +1,7 @@
1
1
  import { getDockerImageVariables, requiresDockerBuild } from "../build/docker";
2
2
  import type { Context } from "../types";
3
3
  import type { CatladderJob } from "../types/jobs";
4
+ import { isOfDeployType } from "./types";
4
5
  import { contextIsStoppable } from "./utils";
5
6
 
6
7
  export const DEPLOY_JOB_NAME = "🚀 Deploy";
@@ -15,20 +16,16 @@ const DEPLOY_RUNNER_VARIABLES = {
15
16
  };
16
17
  type JobWithoutScript = Omit<CatladderJob, "script">;
17
18
  export const getBaseDeploymentJob = (context: Context): JobWithoutScript => {
18
- const environment = {
19
- name: context.environment.fullName,
20
- url: context.environment.url,
21
- };
22
-
23
19
  const hasDocker = requiresDockerBuild(context);
24
20
  const isStoppable = contextIsStoppable(context);
25
21
 
26
22
  const autoStop =
27
23
  context.environment.envType === "review"
28
- ? "3 days" // auto stop is not working properly when branch is merged, so we do
24
+ ? "2 week"
29
25
  : context.environment.envType === "dev"
30
- ? "3 weeks"
26
+ ? "4 weeks"
31
27
  : undefined;
28
+
32
29
  // if auto or manual is configured explicitly, use that
33
30
  const whenDeployDefined =
34
31
  context.componentConfig.deploy && context.componentConfig.deploy.when
@@ -82,7 +79,7 @@ export const getBaseDeploymentJob = (context: Context): JobWithoutScript => {
82
79
  ...(hasDocker ? getDockerImageVariables(context) : {}),
83
80
  },
84
81
  environment: {
85
- ...environment,
82
+ ...context.environment.gitlabEnvironment,
86
83
  ...(isStoppable
87
84
  ? {
88
85
  on_stop: STOP_JOB_NAME,
@@ -96,10 +93,6 @@ export const getBaseDeploymentJob = (context: Context): JobWithoutScript => {
96
93
  export const getBaseDeploymentStopJob = (
97
94
  context: Context
98
95
  ): JobWithoutScript => {
99
- const environment = {
100
- name: context.environment.fullName,
101
- url: context.environment.url,
102
- };
103
96
  return {
104
97
  name: STOP_JOB_NAME,
105
98
  envMode: "stagePerEnv", // makes it easier to run manual tasks er env
@@ -122,17 +115,13 @@ export const getBaseDeploymentStopJob = (
122
115
  },
123
116
  stage: "stop",
124
117
  environment: {
125
- ...environment,
118
+ ...context.environment.gitlabEnvironment,
126
119
  action: "stop",
127
120
  },
128
121
  };
129
122
  };
130
123
 
131
124
  export const getBaseRollbackJob = (context: Context): JobWithoutScript => {
132
- const environment = {
133
- name: context.environment.fullName,
134
- url: context.environment.url,
135
- };
136
125
  return {
137
126
  name: ROLLBACK_JOB_NAME,
138
127
  envMode: "stagePerEnv", // makes it easier to run manual tasks er env
@@ -149,7 +138,7 @@ export const getBaseRollbackJob = (context: Context): JobWithoutScript => {
149
138
  },
150
139
  stage: "rollback",
151
140
  environment: {
152
- ...environment,
141
+ ...context.environment.gitlabEnvironment,
153
142
  action: "access",
154
143
  },
155
144
  };
@@ -39,7 +39,7 @@ export const createDeployJob = (context: Context): CatladderJob[] => {
39
39
  GCLOUD_DEPLOY_CREDENTIALS_KEY
40
40
  );
41
41
 
42
- const serviceName = context.environment.slug;
42
+ const serviceName = context.environment.fullName;
43
43
 
44
44
  const labelsString = Object.entries(getLabels(context))
45
45
  .map(([key, value]) => `${key}=${value}`)
@@ -32,6 +32,7 @@ export const createKubernetesDeployJobs = (
32
32
  image: getRunnerImage("kubernetes"),
33
33
  variables: {
34
34
  ...context.environment.envVars,
35
+ RELEASE_NAME: context.environment.fullName,
35
36
  HELM_EXPERIMENTAL_OCI: "1",
36
37
  KUBE_DOCKER_IMAGE_PULL_SECRET: `gitlab-registry-${context.componentName}`,
37
38
  KUBE_VALUES: dump(kubeValues, {
@@ -1,11 +1,23 @@
1
- import type { PipelineTrigger, ComponentConfig, Config, EnvType } from "./config";
1
+ import type {
2
+ PipelineTrigger,
3
+ ComponentConfig,
4
+ Config,
5
+ EnvType,
6
+ } from "./config";
2
7
 
3
8
  export type Environment = {
4
9
  host: string;
10
+ url: string;
11
+ /**
12
+ * the full name of the app. We use this as RELEASE_NAME in kubernetes and the service name in google cloud run
13
+ */
5
14
  fullName: string;
15
+ gitlabEnvironment: {
16
+ name: string;
17
+ url: string;
18
+ };
6
19
  shortName: string;
7
20
  slug: string;
8
- url: string;
9
21
  /**
10
22
  * env vars contain all build-time env vars. secrets have to be resolved (they are stored in gitlab)
11
23
  */