@catladder/pipeline 0.0.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.
Files changed (80) hide show
  1. package/Dockerfile +84 -0
  2. package/dist/scripts/magic/createChildPipeline/index.d.ts +5 -0
  3. package/dist/scripts/magic/createChildPipeline/index.js +141 -0
  4. package/dist/scripts/magic/createPipeline.d.ts +14 -0
  5. package/dist/scripts/magic/createPipeline.js +80 -0
  6. package/dist/scripts/magic/index.d.ts +2 -0
  7. package/dist/scripts/magic/index.js +16 -0
  8. package/dist/scripts/magic/sample-config.d.ts +3 -0
  9. package/dist/scripts/magic/sample-config.js +77 -0
  10. package/dist/scripts/magic/types/build/docker.d.ts +2 -0
  11. package/dist/scripts/magic/types/build/docker.js +63 -0
  12. package/dist/scripts/magic/types/build/index.d.ts +6 -0
  13. package/dist/scripts/magic/types/build/index.js +8 -0
  14. package/dist/scripts/magic/types/build/node.d.ts +3 -0
  15. package/dist/scripts/magic/types/build/node.js +118 -0
  16. package/dist/scripts/magic/types/build/types.d.ts +7 -0
  17. package/dist/scripts/magic/types/build/types.js +2 -0
  18. package/dist/scripts/magic/types/config.d.ts +49 -0
  19. package/dist/scripts/magic/types/config.js +17 -0
  20. package/dist/scripts/magic/types/context.d.ts +12 -0
  21. package/dist/scripts/magic/types/context.js +2 -0
  22. package/dist/scripts/magic/types/deploy/index.d.ts +6 -0
  23. package/dist/scripts/magic/types/deploy/index.js +8 -0
  24. package/dist/scripts/magic/types/deploy/kubernetes.d.ts +3 -0
  25. package/dist/scripts/magic/types/deploy/kubernetes.js +46 -0
  26. package/dist/scripts/magic/types/deploy/types.d.ts +41 -0
  27. package/dist/scripts/magic/types/deploy/types.js +7 -0
  28. package/dist/scripts/magic/types/gitlab-types.d.ts +63 -0
  29. package/dist/scripts/magic/types/gitlab-types.js +2 -0
  30. package/dist/scripts/magic/types/index.d.ts +3 -0
  31. package/dist/scripts/magic/types/index.js +15 -0
  32. package/dist/scripts/magic.d.ts +2 -0
  33. package/dist/scripts/magic.js +43 -0
  34. package/dist/scripts/monorepoGenerate.d.ts +2 -0
  35. package/dist/scripts/monorepoGenerate.js +164 -0
  36. package/dist/scripts/printAllValues.d.ts +2 -0
  37. package/dist/scripts/printAllValues.js +58 -0
  38. package/dist/scripts/printBuildEnv.d.ts +2 -0
  39. package/dist/scripts/printBuildEnv.js +77 -0
  40. package/dist/scripts/utils/extractAllValues.d.ts +1 -0
  41. package/dist/scripts/utils/extractAllValues.js +78 -0
  42. package/dist/scripts/utils/extractBuildEnv.d.ts +5 -0
  43. package/dist/scripts/utils/extractBuildEnv.js +152 -0
  44. package/dist/scripts/utils/extractValuesFromEnvVars.d.ts +2 -0
  45. package/dist/scripts/utils/extractValuesFromEnvVars.js +63 -0
  46. package/dist/scripts/utils/extractValuesFromMr.d.ts +2 -0
  47. package/dist/scripts/utils/extractValuesFromMr.js +86 -0
  48. package/dist/tests/extractValuesFromMr.test.d.ts +1 -0
  49. package/dist/tests/extractValuesFromMr.test.js +68 -0
  50. package/package.json +21 -0
  51. package/scripts/getCommitInfo +11 -0
  52. package/scripts/kubernetesCreateSecret +8 -0
  53. package/scripts/kubernetesDelete +4 -0
  54. package/scripts/kubernetesDeploy +57 -0
  55. package/scripts/kubernetesEnsureNamespace +3 -0
  56. package/scripts/magic/createChildPipeline/index.ts +163 -0
  57. package/scripts/magic/createPipeline.ts +48 -0
  58. package/scripts/magic/index.ts +2 -0
  59. package/scripts/magic/sample-config.ts +78 -0
  60. package/scripts/magic/types/build/docker.ts +36 -0
  61. package/scripts/magic/types/build/index.ts +11 -0
  62. package/scripts/magic/types/build/node.ts +107 -0
  63. package/scripts/magic/types/build/types.ts +9 -0
  64. package/scripts/magic/types/config.ts +54 -0
  65. package/scripts/magic/types/context.ts +13 -0
  66. package/scripts/magic/types/deploy/index.ts +11 -0
  67. package/scripts/magic/types/deploy/kubernetes.ts +38 -0
  68. package/scripts/magic/types/deploy/types.ts +29 -0
  69. package/scripts/magic/types/gitlab-types.ts +77 -0
  70. package/scripts/magic/types/index.ts +3 -0
  71. package/scripts/magic.ts +51 -0
  72. package/scripts/monorepoGenerate.ts +104 -0
  73. package/scripts/printAllValues.ts +17 -0
  74. package/scripts/printBuildEnv.ts +22 -0
  75. package/scripts/utils/extractAllValues.ts +32 -0
  76. package/scripts/utils/extractBuildEnv.ts +92 -0
  77. package/scripts/utils/extractValuesFromEnvVars.ts +21 -0
  78. package/scripts/utils/extractValuesFromMr.ts +39 -0
  79. package/tests/extractValuesFromMr.test.ts +156 -0
  80. package/tsconfig.json +10 -0
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ts-node
2
+
3
+ import { existsSync, writeFileSync } from "fs";
4
+ import { parse } from "yaml";
5
+ import { PipelineTrigger } from "./magic/types/config";
6
+ import { createPipeline } from "./magic/createPipeline";
7
+
8
+ const {
9
+ CI_MERGE_REQUEST_ID,
10
+ CI_COMMIT_TAG,
11
+ CI_COMMIT_BRANCH,
12
+ CI_DEFAULT_BRANCH,
13
+ } = process.env;
14
+
15
+ const isDefaultBranch = CI_COMMIT_BRANCH == CI_DEFAULT_BRANCH;
16
+ const isHotfixBranch = false; // TODO: $CI_COMMIT_BRANCH =~ /^[0-9]+\.([0-9]+|x)\.x$/
17
+ const isMergeRequest = Boolean(CI_MERGE_REQUEST_ID);
18
+ const isTaggedRelease = Boolean(CI_COMMIT_TAG);
19
+
20
+ const fullName = (ext: string) => "catladder." + ext;
21
+ const readConfig = () => {
22
+ const found = ["ts", "js", "yml", "yaml"].find((extension) =>
23
+ existsSync(fullName(extension))
24
+ );
25
+ if (found) {
26
+ if (found === "ts" || found === "js") {
27
+ return require(fullName(found));
28
+ } else {
29
+ return parse(fullName(found));
30
+ }
31
+ }
32
+ };
33
+
34
+ const trigger: PipelineTrigger | null =
35
+ isMergeRequest || isHotfixBranch
36
+ ? "mr"
37
+ : isDefaultBranch
38
+ ? "mainBranch"
39
+ : isTaggedRelease
40
+ ? "taggedRelease"
41
+ : null;
42
+ if (trigger) {
43
+ const config = readConfig();
44
+ createPipeline(trigger, config).then((mainPipeline) => {
45
+ writeFileSync(`__pipeline.yml`, JSON.stringify(mainPipeline, null, 2), {
46
+ encoding: "utf-8",
47
+ });
48
+ });
49
+ } else {
50
+ throw new Error("no matching trigger");
51
+ }
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env ts-node
2
+
3
+ import { readdirSync, readFileSync, lstatSync, existsSync } from "fs";
4
+ import { join } from "path";
5
+ import { parse } from "yaml";
6
+ import yargs from "yargs";
7
+
8
+ import { hideBin } from "yargs/helpers";
9
+ import { extractBuildEnv } from "./utils/extractBuildEnv";
10
+ type Type = "test" | "deploy";
11
+ const args = yargs(hideBin(process.argv)).array("env").argv as Record<
12
+ string,
13
+ unknown
14
+ >;
15
+
16
+ const thisGitlabCi = parse(
17
+ readFileSync(".gitlab-ci.yml", { encoding: "utf-8" })
18
+ );
19
+
20
+ const subcis = readdirSync(".")
21
+ .filter((file) => lstatSync(file).isDirectory())
22
+ .map((dir) => ({
23
+ dirname: dir,
24
+ ci: join(dir, ".gitlab-ci.yml"),
25
+ }))
26
+ .filter(({ ci }) => existsSync(ci));
27
+
28
+ const makeConfig = async ({
29
+ subapp,
30
+ type,
31
+ ci,
32
+ env,
33
+ }: {
34
+ subapp: string;
35
+ type: Type;
36
+ env: string;
37
+ ci: string;
38
+ }) => {
39
+ const changes = [subapp + "/**/*"];
40
+ const rules = [
41
+ // same as rules "always", but with `changes` to only trigger changed branches
42
+ { if: "$CI_COMMIT_TAG" },
43
+ {
44
+ if: "$CI_COMMIT_MESSAGE =~ /^chore(release).*/",
45
+ when: "never",
46
+ },
47
+ { if: "$CI_COMMIT_BRANCH =~ /^[0-9]+.([0-9]+|x).x$/" },
48
+ {
49
+ if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH",
50
+ changes,
51
+ },
52
+ // add it manually when no changes
53
+ {
54
+ if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH",
55
+ when: "manual",
56
+ },
57
+ { if: "$CI_MERGE_REQUEST_ID", changes },
58
+ // add it manually when no changes
59
+ { if: "$CI_MERGE_REQUEST_ID", when: "manual" },
60
+ ];
61
+
62
+ return {
63
+ variables: {
64
+ // merge
65
+ ...thisGitlabCi.variables,
66
+ APP_DIR: subapp,
67
+ VALUES_DIR: subapp,
68
+ COMPONENT_NAME: subapp,
69
+ PARENT_PIPELINE_ID: "$CI_PIPELINE_ID",
70
+ [type.toUpperCase() + "_ONLY"]: "1",
71
+ ...(await extractBuildEnv(subapp, env)),
72
+ },
73
+ stage: type,
74
+ rules: rules,
75
+ trigger: {
76
+ include: ci,
77
+ strategy: "depend",
78
+ },
79
+ };
80
+ };
81
+ const generateSubPipeline = async (type: Type, envs: string[]) => {
82
+ return await envs.reduce(async (acc, env) => {
83
+ return {
84
+ ...(await acc),
85
+ ...(await subcis.reduce(
86
+ async (inneracc, { dirname, ci }) => ({
87
+ ...(await inneracc),
88
+
89
+ [`${env}-${dirname} ${type}`]: await makeConfig({
90
+ subapp: dirname,
91
+ ci,
92
+ type,
93
+ env,
94
+ }),
95
+ }),
96
+ {} as Promise<Record<string, unknown>>
97
+ )),
98
+ };
99
+ }, {} as Promise<Record<string, unknown>>);
100
+ };
101
+
102
+ generateSubPipeline(args.t as Type, args.env as string[]).then((p) =>
103
+ console.log(JSON.stringify(p, null, 2))
104
+ );
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ts-node
2
+
3
+ import yargs from "yargs";
4
+ import { hideBin } from "yargs/helpers";
5
+ import { extractAllValues } from "./utils/extractAllValues";
6
+
7
+ const args = yargs(hideBin(process.argv)).argv as Record<string, unknown>;
8
+
9
+ extractAllValues(args.d as string, args.e as string)
10
+ .then(async (values) => {
11
+ console.log(JSON.stringify(values, null, 2) ?? "");
12
+ })
13
+ .catch((e) => {
14
+ console.error("could not extract values", {
15
+ error: e,
16
+ });
17
+ });
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ts-node
2
+
3
+ import { isObject } from "lodash";
4
+ import yargs from "yargs";
5
+ import { hideBin } from "yargs/helpers";
6
+ import { extractBuildEnv } from "./utils/extractBuildEnv";
7
+
8
+ const args = yargs(hideBin(process.argv)).argv as Record<string, unknown>;
9
+
10
+ extractBuildEnv(args.d as string, args.e as string)
11
+ .then(async (values) => {
12
+ Object.entries(values).forEach(([key, value]) =>
13
+ console.log(
14
+ `${key}: "${isObject(value) ? JSON.stringify(value) : value}"`
15
+ )
16
+ );
17
+ })
18
+ .catch((e) => {
19
+ console.error("could not extract values", {
20
+ error: e,
21
+ });
22
+ });
@@ -0,0 +1,32 @@
1
+ import { readFileSync } from "fs";
2
+ import { merge } from "lodash";
3
+ import { parse } from "yaml";
4
+ import { extractValuesFromEnvVars } from "./extractValuesFromEnvVars";
5
+ import { extractValuesFromMr } from "./extractValuesFromMr";
6
+
7
+ const { CI_PROJECT_ID, CI_MERGE_REQUEST_IID } = process.env;
8
+
9
+ const readYaml = (path: string) => {
10
+ try {
11
+ return parse(readFileSync(path, { encoding: "utf-8" }));
12
+ } catch (e) {
13
+ return {};
14
+ }
15
+ };
16
+
17
+ export const extractAllValues = async (
18
+ appDirectory: string,
19
+ env: string | null
20
+ ) => {
21
+ const valuesFromEnvVars = extractValuesFromEnvVars();
22
+ const valuesDefault = readYaml(appDirectory + "/values.yml");
23
+ const valuesEnv = env
24
+ ? readYaml(appDirectory + "/values-" + env + ".yml")
25
+ : {};
26
+ const valuesFromMR =
27
+ CI_PROJECT_ID && CI_MERGE_REQUEST_IID
28
+ ? await extractValuesFromMr(CI_PROJECT_ID, Number(CI_MERGE_REQUEST_IID))
29
+ : {};
30
+
31
+ return merge({}, valuesFromEnvVars, valuesDefault, valuesEnv, valuesFromMR);
32
+ };
@@ -0,0 +1,92 @@
1
+ import { extractAllValues } from "./extractAllValues";
2
+ import slugify from "slugify";
3
+
4
+ type EnvVars = Record<string, string>;
5
+ export const extractBuildEnv = async (
6
+ componentName: string,
7
+ env: string | null,
8
+ visitedReferencedApps: { [app: string]: EnvVars } = {} // this is just to avoid infinit recursion
9
+ ): Promise<EnvVars> => {
10
+ const values = await extractAllValues(componentName, env);
11
+ const {
12
+ APP_NAME,
13
+ CUSTOMER_NAME,
14
+ CI_COMMIT_REF_SLUG,
15
+ CI_MERGE_REQUEST_IID,
16
+ CI_ENVIRONMENT_SLUG,
17
+ } = process.env;
18
+
19
+ const isNotMonorepo = componentName === ".";
20
+
21
+ const KUBE_APP_NAME = CI_MERGE_REQUEST_IID
22
+ ? `${componentName}-${CI_COMMIT_REF_SLUG}`
23
+ : componentName;
24
+ const KUBE_APP_SLUG = isNotMonorepo
25
+ ? CI_ENVIRONMENT_SLUG
26
+ : slugify(KUBE_APP_NAME);
27
+ const CANONICAL_URL = `https://${APP_NAME}-${KUBE_APP_SLUG}.${CUSTOMER_NAME}.panter.cloud`;
28
+ const defaultBuildEnv = {
29
+ KUBE_APP_NAME,
30
+ CANONICAL_URL,
31
+ IMAGE_PULL_SECRET: `gitlab-registry-${componentName}`,
32
+ ROOT_URL: values?.application?.host || CANONICAL_URL,
33
+ };
34
+ const publicBuildEnv = values?.buildEnv?.public ?? {};
35
+ const merged = {
36
+ ...defaultBuildEnv,
37
+ ...publicBuildEnv,
38
+ };
39
+ if (process.env.DEBUG) {
40
+ console.log({ values, defaultBuildEnv, publicBuildEnv });
41
+ }
42
+ // extract referenced build env (only works in monorepo
43
+ // like this
44
+ /*
45
+ fromComponents:
46
+ vendure:
47
+ API_URL: ROOT_URL
48
+ */
49
+ const fromComponents: {
50
+ [otherApp: string]: {
51
+ [ourKey: string]: string;
52
+ };
53
+ } = values?.buildEnv?.fromComponents ?? {};
54
+ const referencedBuildEnv = await Object.entries(fromComponents).reduce(
55
+ async (acc, [otherApp, mapping]) => {
56
+ if (process.env.DEBUG) {
57
+ console.log("extract reference build env", otherApp, mapping);
58
+ }
59
+ if (!mapping) {
60
+ return await acc;
61
+ }
62
+
63
+ // prevent infinit loop
64
+ const otherBuildEnv =
65
+ visitedReferencedApps[otherApp] ??
66
+ (await extractBuildEnv(otherApp, env, {
67
+ ...visitedReferencedApps,
68
+ [componentName]: merged,
69
+ }));
70
+ if (process.env.DEBUG) {
71
+ console.log({ otherBuildEnv });
72
+ }
73
+ // resolve mapping
74
+ return {
75
+ ...(await acc),
76
+ ...Object.entries(mapping).reduce(
77
+ (inneracc, [ourKey, otherKey]) => ({
78
+ ...inneracc,
79
+ [ourKey]: otherBuildEnv[otherKey],
80
+ }),
81
+ {} as EnvVars
82
+ ),
83
+ };
84
+ },
85
+ {} as Promise<EnvVars>
86
+ );
87
+
88
+ return {
89
+ ...merged,
90
+ ...referencedBuildEnv,
91
+ };
92
+ };
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ts-node
2
+ import { set } from "lodash";
3
+
4
+ export const extractValuesFromEnvVars = () => {
5
+ const values = {};
6
+
7
+ const PREFIX = "DEFAULT_VALUE_";
8
+ for (const [key, value] of Object.entries(process.env).filter(([key]) =>
9
+ key.startsWith(PREFIX)
10
+ )) {
11
+ const path = key.replace(PREFIX, "").split("_");
12
+ let theValue;
13
+ try {
14
+ theValue = JSON.parse(value ?? "");
15
+ } catch (e) {
16
+ theValue = value;
17
+ }
18
+ set(values, path, theValue);
19
+ }
20
+ return values;
21
+ };
@@ -0,0 +1,39 @@
1
+ import { Gitlab } from "@gitbeaker/node"; // Just the Project Resource
2
+ import { parse } from "yaml";
3
+ import { merge } from "lodash";
4
+
5
+ const { GL_TOKEN, CI_SERVER_URL } = process.env;
6
+
7
+ const gitlabApi = new Gitlab({
8
+ token: GL_TOKEN,
9
+ host: CI_SERVER_URL,
10
+ });
11
+ const getMRInfo = async (projectId: string, mrId: number) => {
12
+ return await gitlabApi.MergeRequests.show(projectId, mrId);
13
+ };
14
+
15
+ export const extractValuesFromMRMessage = (mrMessage: string) => {
16
+ const codeBlocks = Array.from(mrMessage.matchAll(/```.*([^`]+)```/gm)).map(
17
+ (match) => match[1]
18
+ );
19
+
20
+ return codeBlocks.reduce((values = {}, block) => {
21
+ if (block.includes("$env") || block.includes("$values"))
22
+ try {
23
+ const { $values, $env } = parse(block);
24
+ if ($values) merge(values, $values);
25
+ if ($env) merge(values, { env: { public: $env } });
26
+ return values;
27
+ } catch (e) {
28
+ console.warn(e);
29
+ }
30
+ }, {});
31
+ };
32
+
33
+ export const extractValuesFromMr = async (
34
+ projectId: string,
35
+ mergeRequestId: number
36
+ ) => {
37
+ const mrInfo = await getMRInfo(projectId, mergeRequestId);
38
+ return extractValuesFromMRMessage(mrInfo.description);
39
+ };
@@ -0,0 +1,156 @@
1
+ import { extractValuesFromMRMessage } from "../scripts/utils/extractValuesFromMr";
2
+
3
+ describe("extractValuesFromMr", () => {
4
+ describe("extractValuesFromMRMessage", () => {
5
+ it("can extract $values yaml", () => {
6
+ const result = extractValuesFromMRMessage(`
7
+
8
+ blabla
9
+
10
+ test
11
+
12
+ \`\`\`
13
+ $values:
14
+ application:
15
+ host: www.foo.com
16
+ env:
17
+ public:
18
+ API_KEY: fooooxxx
19
+ BLA: "blubb"
20
+ \`\`\`
21
+ `);
22
+ expect(result).toEqual({
23
+ application: {
24
+ host: "www.foo.com",
25
+ },
26
+ env: {
27
+ public: {
28
+ API_KEY: "fooooxxx",
29
+ BLA: "blubb",
30
+ },
31
+ },
32
+ });
33
+ });
34
+ it("can handle $env as shortcut", () => {
35
+ const result = extractValuesFromMRMessage(`
36
+
37
+ blabla
38
+
39
+ test
40
+
41
+ \`\`\`
42
+ $env:
43
+ API_KEY: fooooxxx
44
+ BLA: "blubb"
45
+
46
+ \`\`\`
47
+ `);
48
+ expect(result).toEqual({
49
+ env: {
50
+ public: {
51
+ API_KEY: "fooooxxx",
52
+ BLA: "blubb",
53
+ },
54
+ },
55
+ });
56
+ });
57
+
58
+ it("ignores language in backticks", () => {
59
+ const result = extractValuesFromMRMessage(`
60
+
61
+ blabla
62
+
63
+ test
64
+
65
+ \`\`\`yaml
66
+ $env:
67
+ API_KEY: fooooxxx
68
+ BLA: "blubb"
69
+
70
+ \`\`\`
71
+ `);
72
+ expect(result).toEqual({
73
+ env: {
74
+ public: {
75
+ API_KEY: "fooooxxx",
76
+ BLA: "blubb",
77
+ },
78
+ },
79
+ });
80
+ });
81
+ it("can handle mixed blocks", () => {
82
+ const result = extractValuesFromMRMessage(`
83
+
84
+ blabla
85
+
86
+ test
87
+
88
+ \`\`\`
89
+ $values:
90
+ env:
91
+ public:
92
+ FOO: "bar"
93
+
94
+ $env:
95
+ API_KEY: fooooxxx
96
+ BLA: "blubb"
97
+
98
+ \`\`\`
99
+ `);
100
+ expect(result).toEqual({
101
+ env: {
102
+ public: {
103
+ FOO: "bar",
104
+ API_KEY: "fooooxxx",
105
+ BLA: "blubb",
106
+ },
107
+ },
108
+ });
109
+ });
110
+
111
+ it("can handle multiple blocks", () => {
112
+ const result = extractValuesFromMRMessage(`
113
+
114
+ blabla
115
+
116
+ test
117
+
118
+ \`\`\`
119
+ $values:
120
+ env:
121
+ public:
122
+ FOO: "bar"
123
+
124
+ $env:
125
+ API_KEY: fooooxxx
126
+ BLA: "blubb"
127
+
128
+ \`\`\`
129
+
130
+
131
+ fooo
132
+
133
+ \`\`\`
134
+ $values:
135
+ env:
136
+ public:
137
+ FOO: "blubb"
138
+
139
+ $env:
140
+ TEST: test
141
+
142
+ \`\`\`
143
+ `);
144
+ expect(result).toEqual({
145
+ env: {
146
+ public: {
147
+ FOO: "blubb",
148
+ API_KEY: "fooooxxx",
149
+ BLA: "blubb",
150
+ TEST: "test",
151
+ },
152
+ },
153
+ });
154
+ });
155
+ });
156
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["es2020", "DOM"],
4
+ "strict": true,
5
+ "downlevelIteration": true,
6
+ "declaration": true,
7
+ "outDir": "dist",
8
+ "esModuleInterop": true
9
+ }
10
+ }