@catladder/pipeline 4.6.4 → 4.7.1
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/getEnvironmentVariables.js +6 -5
- package/dist/deploy/cloudRun/index.js +14 -2
- package/dist/deploy/cloudRun/utils/database.d.ts +6 -5
- package/dist/deploy/cloudRun/utils/database.js +22 -15
- package/dist/deploy/index.d.ts +7 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/pipeline/createJobsForComponent.js +5 -4
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/config.d.ts +8 -0
- package/dist/variables/VariableValueContainingReferences.d.ts +6 -0
- package/dist/variables/VariableValueContainingReferences.js +21 -0
- package/dist/variables/resolveAllReferences.d.ts +1 -2
- package/dist/variables/resolveAllReferences.js +7 -8
- package/dist/variables/resolveAllReferencesOnce.d.ts +1 -4
- package/dist/variables/resolveAllReferencesOnce.js +3 -3
- package/dist/verify/createVerifyJobs.d.ts +4 -0
- package/dist/verify/createVerifyJobs.js +229 -0
- package/dist/verify/index.d.ts +2 -0
- package/dist/verify/index.js +26 -0
- package/dist/verify/types.d.ts +16 -0
- package/dist/verify/types.js +5 -0
- package/examples/__snapshots__/cloud-run-verify.test.ts.snap +3061 -0
- package/examples/__snapshots__/cloud-run-with-sql-multiple-dbs.test.ts.snap +35 -35
- package/examples/__snapshots__/cloud-run-with-sql-reuse-db.test.ts.snap +64 -64
- package/examples/cloud-run-verify.test.ts +12 -0
- package/examples/cloud-run-verify.ts +50 -0
- package/examples/cloud-run-with-sql-reuse-db.ts +4 -0
- package/package.json +1 -1
- package/src/context/getEnvironmentVariables.ts +19 -10
- package/src/deploy/cloudRun/index.ts +25 -1
- package/src/deploy/cloudRun/utils/database.ts +54 -22
- package/src/deploy/index.ts +7 -2
- package/src/index.ts +1 -0
- package/src/pipeline/createJobsForComponent.ts +4 -2
- package/src/types/config.ts +9 -0
- package/src/variables/VariableValueContainingReferences.ts +11 -0
- package/src/variables/__tests__/resolveAllReferences.test.ts +49 -0
- package/src/variables/resolveAllReferences.ts +18 -14
- package/src/variables/resolveAllReferencesOnce.ts +17 -12
- package/src/verify/createVerifyJobs.ts +81 -0
- package/src/verify/index.ts +2 -0
- package/src/verify/types.ts +18 -0
|
@@ -5,6 +5,11 @@ import {
|
|
|
5
5
|
} from "../../../bash";
|
|
6
6
|
import type { ComponentContext } from "../../../types";
|
|
7
7
|
import { allowFailureInScripts, repeatOnFailure } from "../../../utils/gitlab";
|
|
8
|
+
import type { VariableValue } from "../../../variables/VariableValue";
|
|
9
|
+
import {
|
|
10
|
+
VariableReference,
|
|
11
|
+
VariableValueContainingReferences,
|
|
12
|
+
} from "../../../variables/VariableValueContainingReferences";
|
|
8
13
|
import type {
|
|
9
14
|
DeployConfigCloudRun,
|
|
10
15
|
DeployConfigCloudRunCloudSql,
|
|
@@ -73,7 +78,7 @@ export type DBVariables = {
|
|
|
73
78
|
* controls how variables in the connection string are handled
|
|
74
79
|
*
|
|
75
80
|
* - legacy: variables like $DB_USER will be kept as environment variables to be replaced at runtime (default). It is not using BashExpressions as this was not the case in the past.
|
|
76
|
-
* - embedded: variables will be replaced with
|
|
81
|
+
* - embedded: variables will be replaced with the component's final values in the connection string. This makes the connection string usable from other components (e.g. via ${otherComponent:DATABASE_URL}) and respects overrides of DB_USER, DB_PASSWORD, etc. in vars.public (e.g. DB_PASSWORD: "${otherComponent:DB_PASSWORD}" when reusing another component's database)
|
|
77
82
|
*
|
|
78
83
|
* We will remove the legacy mode in the future, as it is confusing. But its unclear whether its a breaking change in some edge cases
|
|
79
84
|
*/
|
|
@@ -81,82 +86,109 @@ export type DBVariablesMode = "legacy" | "embedded";
|
|
|
81
86
|
|
|
82
87
|
export const DEFAULT_DB_VARIABLES_MODE: DBVariablesMode = "legacy";
|
|
83
88
|
|
|
89
|
+
type DbUrlPart = StringOrBashExpression | VariableReference;
|
|
90
|
+
|
|
84
91
|
const getVariableOrValue = (
|
|
85
92
|
key: keyof DBVariables,
|
|
86
|
-
variables: DBVariables,
|
|
87
93
|
mode: DBVariablesMode,
|
|
88
|
-
|
|
89
|
-
|
|
94
|
+
componentName: string,
|
|
95
|
+
): DbUrlPart => {
|
|
96
|
+
// in embedded mode we reference the component's own env var, which gets
|
|
97
|
+
// resolved to its final value (including vars.public overrides) after all
|
|
98
|
+
// env vars have been merged
|
|
99
|
+
return mode === "legacy"
|
|
100
|
+
? `$${key}`
|
|
101
|
+
: new VariableReference(componentName, key);
|
|
90
102
|
};
|
|
91
103
|
|
|
104
|
+
const joinDbUrlParts = (
|
|
105
|
+
parts: DbUrlPart[],
|
|
106
|
+
mode: DBVariablesMode,
|
|
107
|
+
): VariableValue =>
|
|
108
|
+
mode === "legacy"
|
|
109
|
+
? joinBashExpressions(parts as StringOrBashExpression[])
|
|
110
|
+
: new VariableValueContainingReferences(parts);
|
|
111
|
+
|
|
92
112
|
export const getDatabaseJdbcUrl = (
|
|
93
113
|
variables: DBVariables,
|
|
94
114
|
mode: DBVariablesMode,
|
|
115
|
+
componentName: string,
|
|
95
116
|
) => {
|
|
96
117
|
const parts = [
|
|
97
118
|
"jdbc:postgresql:///",
|
|
98
|
-
getVariableOrValue("DB_NAME",
|
|
119
|
+
getVariableOrValue("DB_NAME", mode, componentName),
|
|
99
120
|
"?cloudSqlInstance=",
|
|
100
|
-
getVariableOrValue(
|
|
121
|
+
getVariableOrValue(
|
|
122
|
+
"CLOUD_SQL_INSTANCE_CONNECTION_NAME",
|
|
123
|
+
mode,
|
|
124
|
+
componentName,
|
|
125
|
+
),
|
|
101
126
|
"&socketFactory=com.google.cloud.sql.postgres.SocketFactory&user=",
|
|
102
|
-
getVariableOrValue("DB_USER",
|
|
127
|
+
getVariableOrValue("DB_USER", mode, componentName),
|
|
103
128
|
"&password=",
|
|
104
|
-
getVariableOrValue("DB_PASSWORD",
|
|
129
|
+
getVariableOrValue("DB_PASSWORD", mode, componentName),
|
|
105
130
|
];
|
|
106
131
|
|
|
107
|
-
return
|
|
132
|
+
return joinDbUrlParts(parts, mode);
|
|
108
133
|
};
|
|
109
134
|
|
|
110
135
|
export const getRailsDatabaseConnectionString = (
|
|
111
136
|
variables: DBVariables,
|
|
112
137
|
mode: DBVariablesMode,
|
|
138
|
+
componentName: string,
|
|
113
139
|
) => {
|
|
114
140
|
const parts = [
|
|
115
141
|
"postgresql://",
|
|
116
|
-
getVariableOrValue("DB_USER",
|
|
142
|
+
getVariableOrValue("DB_USER", mode, componentName),
|
|
117
143
|
":",
|
|
118
|
-
getVariableOrValue("DB_PASSWORD",
|
|
144
|
+
getVariableOrValue("DB_PASSWORD", mode, componentName),
|
|
119
145
|
"@",
|
|
120
146
|
encodeURIComponent(
|
|
121
147
|
`/cloudsql/${variables.CLOUD_SQL_INSTANCE_CONNECTION_NAME}`,
|
|
122
148
|
),
|
|
123
149
|
"/",
|
|
124
|
-
getVariableOrValue("DB_NAME",
|
|
150
|
+
getVariableOrValue("DB_NAME", mode, componentName),
|
|
125
151
|
"?",
|
|
126
152
|
];
|
|
127
|
-
return
|
|
153
|
+
return joinDbUrlParts(parts, mode);
|
|
128
154
|
};
|
|
129
155
|
|
|
130
156
|
export const getPrismaDatabaseConnectionString = (
|
|
131
157
|
variables: DBVariables,
|
|
132
158
|
mode: DBVariablesMode,
|
|
159
|
+
componentName: string,
|
|
133
160
|
) => {
|
|
134
161
|
const parts = [
|
|
135
162
|
"postgresql://",
|
|
136
|
-
getVariableOrValue("DB_USER",
|
|
163
|
+
getVariableOrValue("DB_USER", mode, componentName),
|
|
137
164
|
":",
|
|
138
|
-
getVariableOrValue("DB_PASSWORD",
|
|
165
|
+
getVariableOrValue("DB_PASSWORD", mode, componentName),
|
|
139
166
|
"@localhost/",
|
|
140
|
-
getVariableOrValue("DB_NAME",
|
|
167
|
+
getVariableOrValue("DB_NAME", mode, componentName),
|
|
141
168
|
"?host=/cloudsql/",
|
|
142
|
-
getVariableOrValue(
|
|
169
|
+
getVariableOrValue(
|
|
170
|
+
"CLOUD_SQL_INSTANCE_CONNECTION_NAME",
|
|
171
|
+
mode,
|
|
172
|
+
componentName,
|
|
173
|
+
),
|
|
143
174
|
];
|
|
144
|
-
return
|
|
175
|
+
return joinDbUrlParts(parts, mode);
|
|
145
176
|
};
|
|
146
177
|
|
|
147
178
|
export const getDatabaseConnectionString = (
|
|
148
179
|
config: DeployConfigCloudRunCloudSql,
|
|
149
180
|
variables: DBVariables,
|
|
150
|
-
|
|
181
|
+
componentName: string,
|
|
182
|
+
): VariableValue => {
|
|
151
183
|
const mode =
|
|
152
184
|
config.dbConnectionStringVariablesMode ?? DEFAULT_DB_VARIABLES_MODE;
|
|
153
185
|
switch (config.dbConnectionStringFormat) {
|
|
154
186
|
case "jdbc":
|
|
155
|
-
return getDatabaseJdbcUrl(variables, mode);
|
|
187
|
+
return getDatabaseJdbcUrl(variables, mode, componentName);
|
|
156
188
|
case "rails":
|
|
157
|
-
return getRailsDatabaseConnectionString(variables, mode);
|
|
189
|
+
return getRailsDatabaseConnectionString(variables, mode, componentName);
|
|
158
190
|
default:
|
|
159
191
|
// prisma
|
|
160
|
-
return getPrismaDatabaseConnectionString(variables, mode);
|
|
192
|
+
return getPrismaDatabaseConnectionString(variables, mode, componentName);
|
|
161
193
|
}
|
|
162
194
|
};
|
package/src/deploy/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { BuildConfig, SecretEnvVar } from "..";
|
|
2
|
-
import type { BashExpression } from "../bash/BashExpression";
|
|
3
2
|
import type { ComponentContext } from "../types/context";
|
|
4
3
|
import type { EnvironmentContext } from "../types/environmentContext";
|
|
5
4
|
import type { CatladderJob } from "../types/jobs";
|
|
6
5
|
import type { PartialDeep } from "../types/utils";
|
|
6
|
+
import type { VariableValue } from "../variables/VariableValue";
|
|
7
7
|
import { GCLOUD_RUN_DEPLOY_TYPE } from "./cloudRun";
|
|
8
8
|
import { CUSTOM_DEPLOY_TYPE } from "./custom";
|
|
9
9
|
import { DOCKER_TAG_DEPLOY_TYPE } from "./dockerTag";
|
|
@@ -26,7 +26,12 @@ export type DeployTypeDefinition<D extends DeployConfig> = {
|
|
|
26
26
|
) => SecretEnvVar[];
|
|
27
27
|
getAdditionalEnvVars: (
|
|
28
28
|
envContext: EnvironmentContext<BuildConfig, D>,
|
|
29
|
-
) => Record<string,
|
|
29
|
+
) => Record<string, VariableValue | undefined | null>;
|
|
30
|
+
/**
|
|
31
|
+
* script lines the deploy type contributes to the start of the verify job,
|
|
32
|
+
* e.g. to authenticate against a non-public service
|
|
33
|
+
*/
|
|
34
|
+
verifyJobSetupScript?: (context: ComponentContext) => string[];
|
|
30
35
|
};
|
|
31
36
|
export type DeployTypes = {
|
|
32
37
|
[T in DeployConfigType]: DeployTypeDefinition<DeployConfigGeneric<T>>;
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
ComponentContextWithBuild,
|
|
6
6
|
} from "../types/context";
|
|
7
7
|
import type { CatladderJob } from "../types/jobs";
|
|
8
|
+
import { createVerifyJobs } from "../verify/createVerifyJobs";
|
|
8
9
|
|
|
9
10
|
const injectDefaultVarsInCustomJobs = (
|
|
10
11
|
context: ComponentContext,
|
|
@@ -27,7 +28,7 @@ const getCustomJobs = (context: ComponentContext) => {
|
|
|
27
28
|
export const createJobsForComponentContext = async (
|
|
28
29
|
context: ComponentContext,
|
|
29
30
|
): Promise<CatladderJob[]> => {
|
|
30
|
-
const [buildJobs, deployJobs] = await Promise.all([
|
|
31
|
+
const [buildJobs, deployJobs, verifyJobs] = await Promise.all([
|
|
31
32
|
context.build.type !== "disabled"
|
|
32
33
|
? BUILD_TYPES[context.build.buildType].jobs(
|
|
33
34
|
context as ComponentContextWithBuild,
|
|
@@ -36,9 +37,10 @@ export const createJobsForComponentContext = async (
|
|
|
36
37
|
context.componentConfig.deploy !== false
|
|
37
38
|
? DEPLOY_TYPES[context.componentConfig.deploy.type].jobs(context)
|
|
38
39
|
: [],
|
|
40
|
+
createVerifyJobs(context),
|
|
39
41
|
]);
|
|
40
42
|
|
|
41
43
|
const customJobs = getCustomJobs(context);
|
|
42
44
|
|
|
43
|
-
return [...buildJobs, ...deployJobs, ...customJobs];
|
|
45
|
+
return [...buildJobs, ...deployJobs, ...verifyJobs, ...customJobs];
|
|
44
46
|
};
|
package/src/types/config.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { PipelineType, WorkspaceBuildConfig } from "..";
|
|
|
8
8
|
import type { AgentConfig } from "./agent";
|
|
9
9
|
import type { Hooks } from "./hooks";
|
|
10
10
|
import type { ReleaseConfig } from "./release";
|
|
11
|
+
import type { VerifyConfig } from "../verify/types";
|
|
11
12
|
|
|
12
13
|
export const ALL_PIPELINE_TRIGGERS = [
|
|
13
14
|
"mainBranch",
|
|
@@ -91,6 +92,14 @@ export type DefaultEnvConfig = {
|
|
|
91
92
|
* environment variables
|
|
92
93
|
*/
|
|
93
94
|
vars?: EnvVars;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* post-deploy verification: runs a job in the `verify` stage after the deploy,
|
|
98
|
+
* e.g. an e2e test suite against the freshly deployed environment.
|
|
99
|
+
*
|
|
100
|
+
* Runs in all deployed envs by default, disable per env with `verify: false`
|
|
101
|
+
*/
|
|
102
|
+
verify?: VerifyConfig | false;
|
|
94
103
|
};
|
|
95
104
|
|
|
96
105
|
export type DevLocalEnvConfig = {
|
|
@@ -43,6 +43,17 @@ export class VariableValueContainingReferences {
|
|
|
43
43
|
);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* concats values to this one and returns a new VariableValueContainingReferences.
|
|
48
|
+
* mirrors String.prototype.concat / BashExpression.concat so a VariableValue
|
|
49
|
+
* can be extended regardless of its concrete type
|
|
50
|
+
*/
|
|
51
|
+
public concat(
|
|
52
|
+
...values: Array<VariableValuePart | VariableValueContainingReferences>
|
|
53
|
+
): VariableValueContainingReferences {
|
|
54
|
+
return new VariableValueContainingReferences([...this.parts, ...values]);
|
|
55
|
+
}
|
|
56
|
+
|
|
46
57
|
public toString(
|
|
47
58
|
options: EscapeOptions = {
|
|
48
59
|
quotes: false,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { getBashVariable } from "../../bash/BashExpression";
|
|
2
3
|
import {
|
|
4
|
+
VariableReference,
|
|
3
5
|
VariableValueContainingReferences,
|
|
4
6
|
createVariableValueContainingReferencesFromString,
|
|
5
7
|
} from "../VariableValueContainingReferences";
|
|
@@ -68,6 +70,53 @@ describe("replaceAllReferences", () => {
|
|
|
68
70
|
});
|
|
69
71
|
});
|
|
70
72
|
|
|
73
|
+
it("passes through plain strings and bash expressions and resolves self references against the map fetched for the own component", async () => {
|
|
74
|
+
const DB_PASSWORD_SECRET = getBashVariable("CL_dev_api_DB_PASSWORD");
|
|
75
|
+
|
|
76
|
+
// a merged env var map as produced by getEnvironmentVariables: mostly
|
|
77
|
+
// plain values, plus a framework-generated value containing self
|
|
78
|
+
// references (like the embedded database connection string)
|
|
79
|
+
const values = {
|
|
80
|
+
DB_USER: "my-user",
|
|
81
|
+
DB_PASSWORD: DB_PASSWORD_SECRET,
|
|
82
|
+
DATABASE_URL: new VariableValueContainingReferences([
|
|
83
|
+
"postgresql://",
|
|
84
|
+
new VariableReference("worker", "DB_USER"),
|
|
85
|
+
":",
|
|
86
|
+
new VariableReference("worker", "DB_PASSWORD"),
|
|
87
|
+
"@localhost/db",
|
|
88
|
+
]),
|
|
89
|
+
EMPTY: null,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const getEnvVars = async (componentName: string) => {
|
|
93
|
+
expect(componentName).toBe("worker");
|
|
94
|
+
return {
|
|
95
|
+
DB_USER: "my-user",
|
|
96
|
+
// the fetched map contains the override, like a vars.public override
|
|
97
|
+
// referencing another component's password
|
|
98
|
+
DB_PASSWORD: DB_PASSWORD_SECRET,
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const result = await resolveAllReferences(values, getEnvVars);
|
|
103
|
+
expect(result.DB_USER).toBe("my-user");
|
|
104
|
+
expect(result.DB_PASSWORD).toBe(DB_PASSWORD_SECRET);
|
|
105
|
+
expect(result.EMPTY).toBe(null);
|
|
106
|
+
expect(result.DATABASE_URL).toEqual(
|
|
107
|
+
new VariableValueContainingReferences([
|
|
108
|
+
"postgresql://",
|
|
109
|
+
"my-user",
|
|
110
|
+
":",
|
|
111
|
+
DB_PASSWORD_SECRET,
|
|
112
|
+
"@localhost/db",
|
|
113
|
+
]),
|
|
114
|
+
);
|
|
115
|
+
expect(result.DATABASE_URL.toString()).toBe(
|
|
116
|
+
"postgresql://my-user:$CL_dev_api_DB_PASSWORD@localhost/db",
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
|
|
71
120
|
it("detects infinte loop", async () => {
|
|
72
121
|
const values = {
|
|
73
122
|
myVar: createVariableValueContainingReferencesFromString(
|
|
@@ -1,31 +1,35 @@
|
|
|
1
1
|
import type { VariableValue } from "./VariableValue";
|
|
2
|
-
import
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
VariableReference,
|
|
4
|
+
VariableValueContainingReferences,
|
|
5
|
+
} from "./VariableValueContainingReferences";
|
|
4
6
|
import { resolveAllReferencesOnce as resolveAllReferencesOnce } from "./resolveAllReferencesOnce";
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
const hasUnresolvedReferences = (value: VariableValue | null | undefined) =>
|
|
9
|
+
value instanceof VariableValueContainingReferences &&
|
|
10
|
+
value.parts.some((part) => part instanceof VariableReference);
|
|
11
|
+
|
|
12
|
+
export const resolveAllReferences = async <
|
|
13
|
+
T extends Record<string, VariableValue | null | undefined>,
|
|
14
|
+
>(
|
|
15
|
+
values: T,
|
|
8
16
|
getEnvVars: (
|
|
9
17
|
componentName: string,
|
|
10
18
|
) => Promise<Record<string, VariableValue | null | undefined>>,
|
|
11
|
-
) => {
|
|
19
|
+
): Promise<T> => {
|
|
12
20
|
// replace until there aren't any references left
|
|
13
21
|
let result = values;
|
|
14
22
|
|
|
15
23
|
let i = 0;
|
|
16
24
|
|
|
17
|
-
while (
|
|
18
|
-
Object.values(result).some((value) =>
|
|
19
|
-
value.parts.some((part) => part instanceof VariableReference),
|
|
20
|
-
)
|
|
21
|
-
) {
|
|
25
|
+
while (Object.values(result).some(hasUnresolvedReferences)) {
|
|
22
26
|
const replaced = await resolveAllReferencesOnce(result, getEnvVars);
|
|
23
27
|
|
|
24
28
|
result = replaced;
|
|
25
29
|
i++;
|
|
26
30
|
if (i > 1000) {
|
|
27
31
|
const unresolved = Object.entries(result).filter(([key, value]) =>
|
|
28
|
-
value
|
|
32
|
+
hasUnresolvedReferences(value),
|
|
29
33
|
);
|
|
30
34
|
|
|
31
35
|
throw new Error(
|
|
@@ -33,9 +37,9 @@ export const resolveAllReferences = async (
|
|
|
33
37
|
unresolved
|
|
34
38
|
.map(
|
|
35
39
|
([key, value]) =>
|
|
36
|
-
`${key} (last reference: ${
|
|
37
|
-
|
|
38
|
-
)})`,
|
|
40
|
+
`${key} (last reference: ${(
|
|
41
|
+
value as VariableValueContainingReferences
|
|
42
|
+
).parts.find((part) => part instanceof VariableReference)})`,
|
|
39
43
|
)
|
|
40
44
|
.join(", "),
|
|
41
45
|
);
|
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
import type { VariableValue } from "./VariableValue";
|
|
2
|
-
import
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
VariableReference,
|
|
4
|
+
VariableValueContainingReferences,
|
|
5
|
+
} from "./VariableValueContainingReferences";
|
|
4
6
|
import { resolveReferencesOnce } from "./resolveReferencesOnce";
|
|
5
7
|
|
|
6
|
-
export const resolveAllReferencesOnce = async
|
|
7
|
-
|
|
8
|
+
export const resolveAllReferencesOnce = async <
|
|
9
|
+
T extends Record<string, VariableValue | null | undefined>,
|
|
10
|
+
>(
|
|
11
|
+
values: T,
|
|
8
12
|
getEnvVars: (
|
|
9
13
|
componentName: string,
|
|
10
14
|
) => Promise<Record<string, VariableValue | undefined | null>>,
|
|
11
|
-
) => {
|
|
12
|
-
const allReferences = Object.values(values).flatMap(
|
|
13
|
-
|
|
14
|
-
value
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
): Promise<T> => {
|
|
16
|
+
const allReferences = Object.values(values).flatMap((value) =>
|
|
17
|
+
value instanceof VariableValueContainingReferences
|
|
18
|
+
? (value.parts.filter(
|
|
19
|
+
(part) => part instanceof VariableReference,
|
|
20
|
+
) as VariableReference[])
|
|
21
|
+
: [],
|
|
17
22
|
);
|
|
18
23
|
|
|
19
24
|
const allComponentsUnique = Array.from(
|
|
@@ -34,11 +39,11 @@ export const resolveAllReferencesOnce = async (
|
|
|
34
39
|
return Object.fromEntries(
|
|
35
40
|
Object.entries(values).map(([key, value]) => [
|
|
36
41
|
key,
|
|
37
|
-
value
|
|
42
|
+
value instanceof VariableValueContainingReferences
|
|
38
43
|
? resolveReferencesOnce(value, ({ componentName, variableName }) => {
|
|
39
44
|
return allEnvVarsInComponents[componentName][variableName];
|
|
40
45
|
})
|
|
41
46
|
: value,
|
|
42
47
|
]),
|
|
43
|
-
);
|
|
48
|
+
) as T;
|
|
44
49
|
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { createArtifactsConfig } from "../build/base/createArtifactsConfig";
|
|
2
|
+
import { createJobCacheFromCacheConfigs } from "../build/cache/createJobCache";
|
|
3
|
+
import { getNodeCache } from "../build/node/cache";
|
|
4
|
+
import { NODE_RUNNER_BUILD_VARIABLES } from "../build/node/constants";
|
|
5
|
+
import { ensureNodeVersion, getYarnInstall } from "../build/node/yarn";
|
|
6
|
+
import { DEPLOY_TYPES } from "../deploy";
|
|
7
|
+
import { DEPLOY_JOB_NAME } from "../deploy/base/deploy";
|
|
8
|
+
import { getRunnerImage } from "../runner";
|
|
9
|
+
import type { ComponentContext } from "../types/context";
|
|
10
|
+
import type { CatladderJob } from "../types/jobs";
|
|
11
|
+
import { ensureArray } from "../utils";
|
|
12
|
+
|
|
13
|
+
export const VERIFY_JOB_NAME = "🔍 verify";
|
|
14
|
+
|
|
15
|
+
export const createVerifyJobs = async (
|
|
16
|
+
context: ComponentContext,
|
|
17
|
+
): Promise<CatladderJob[]> => {
|
|
18
|
+
const verifyConfig = context.componentConfig.verify;
|
|
19
|
+
if (!verifyConfig) {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const deployConfig = context.deploy?.config;
|
|
24
|
+
if (!deployConfig) {
|
|
25
|
+
// nothing is deployed, so there is nothing to verify
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const isNodeBuild =
|
|
30
|
+
context.build.type !== "disabled" && context.build.buildType === "node";
|
|
31
|
+
|
|
32
|
+
const [yarnInstall, nodeCache] = isNodeBuild
|
|
33
|
+
? await Promise.all([
|
|
34
|
+
getYarnInstall(context),
|
|
35
|
+
getNodeCache(context, "pull"),
|
|
36
|
+
])
|
|
37
|
+
: [null, null];
|
|
38
|
+
|
|
39
|
+
const setupScript =
|
|
40
|
+
DEPLOY_TYPES[deployConfig.type].verifyJobSetupScript?.(context) ?? [];
|
|
41
|
+
|
|
42
|
+
return [
|
|
43
|
+
{
|
|
44
|
+
name: VERIFY_JOB_NAME,
|
|
45
|
+
stage: "verify",
|
|
46
|
+
envMode: "stagePerEnv",
|
|
47
|
+
// wait for this component's own deploy
|
|
48
|
+
needsStages: [{ stage: "deploy", artifacts: false }],
|
|
49
|
+
// ...and for the deploys of the components listed in waitFor
|
|
50
|
+
needs: (verifyConfig.waitFor ?? []).map((componentName) => ({
|
|
51
|
+
job: DEPLOY_JOB_NAME,
|
|
52
|
+
componentName,
|
|
53
|
+
artifacts: false,
|
|
54
|
+
})),
|
|
55
|
+
image: verifyConfig.jobImage ?? getRunnerImage("jobs-testing-chrome"),
|
|
56
|
+
cache: nodeCache
|
|
57
|
+
? createJobCacheFromCacheConfigs(context, nodeCache)
|
|
58
|
+
: undefined,
|
|
59
|
+
variables: {
|
|
60
|
+
...context.environment.envVars,
|
|
61
|
+
},
|
|
62
|
+
runnerVariables: {
|
|
63
|
+
...NODE_RUNNER_BUILD_VARIABLES,
|
|
64
|
+
...(verifyConfig.runnerVariables ?? {}),
|
|
65
|
+
},
|
|
66
|
+
script: [
|
|
67
|
+
...setupScript,
|
|
68
|
+
...(yarnInstall ? ensureNodeVersion(context) : []),
|
|
69
|
+
`cd ${context.build.dir}`,
|
|
70
|
+
...(yarnInstall ?? []),
|
|
71
|
+
...ensureArray(verifyConfig.command),
|
|
72
|
+
],
|
|
73
|
+
allow_failure: verifyConfig.allowFailure,
|
|
74
|
+
...createArtifactsConfig(
|
|
75
|
+
context.build.dir,
|
|
76
|
+
verifyConfig.artifactsReports,
|
|
77
|
+
verifyConfig.artifacts,
|
|
78
|
+
),
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { TestJobCustom } from "../build/types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* post-deploy verification job configuration
|
|
5
|
+
*/
|
|
6
|
+
export type VerifyConfig = TestJobCustom & {
|
|
7
|
+
/**
|
|
8
|
+
* command(s) to run against the deployed environment (e.g. `yarn e2e`).
|
|
9
|
+
* Runs in the component's directory.
|
|
10
|
+
*/
|
|
11
|
+
command: string | string[];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* names of other components whose deploy must finish before this verify job runs
|
|
15
|
+
* (in addition to this component's own deploy, which is always awaited)
|
|
16
|
+
*/
|
|
17
|
+
waitFor?: string[];
|
|
18
|
+
};
|