@catladder/pipeline 0.0.2 → 0.0.3
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/package.json +1 -1
- package/scripts/createSamplePipeline.ts +10 -0
- package/scripts/kubernetesCreateSecret +3 -2
- package/scripts/kubernetesDelete +1 -1
- package/scripts/kubernetesDeploy +6 -8
- package/scripts/kubernetesEnsureNamespace +2 -2
- package/scripts/magic/{types/build → build}/docker.ts +13 -6
- package/scripts/magic/build/index.ts +16 -0
- package/scripts/magic/{types/build → build}/node.ts +56 -33
- package/scripts/magic/build/types.ts +17 -0
- package/scripts/magic/createChildPipeline/index.ts +48 -35
- package/scripts/magic/createPipeline.ts +10 -6
- package/scripts/magic/deploy/index.ts +19 -0
- package/scripts/magic/deploy/kubernetes.ts +65 -0
- package/scripts/magic/{types/deploy → deploy}/types.ts +0 -0
- package/scripts/magic/sample-config.ts +1 -1
- package/scripts/magic/tests/createPipeline.test.ts +41 -0
- package/scripts/magic/types/config.ts +3 -3
- package/scripts/magic/types/context.ts +9 -1
- package/scripts/magic/types/gitlab-types.ts +4 -12
- package/scripts/magic.ts +4 -4
- package/scripts/utils/extractBuildEnv.ts +2 -0
- package/scripts/magic/types/build/index.ts +0 -11
- package/scripts/magic/types/build/types.ts +0 -9
- package/scripts/magic/types/deploy/index.ts +0 -11
- package/scripts/magic/types/deploy/kubernetes.ts +0 -38
package/package.json
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env ts-node
|
|
2
|
+
import { writeFileSync } from "fs";
|
|
3
|
+
import { createPipeline } from "./magic/createPipeline";
|
|
4
|
+
import sampleConfig from "./magic/sample-config";
|
|
5
|
+
|
|
6
|
+
createPipeline("taggedRelease", sampleConfig).then((mainPipeline) => {
|
|
7
|
+
writeFileSync(`__pipeline.yml`, JSON.stringify(mainPipeline, null, 2), {
|
|
8
|
+
encoding: "utf-8",
|
|
9
|
+
});
|
|
10
|
+
});
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
|
|
2
|
+
echo "KUBE_NAMESPACE: '$KUBE_NAMESPACE', IMAGE_PULL_SECRET: '$IMAGE_PULL_SECRET'"
|
|
3
|
+
kubectl create secret -n "$KUBE_NAMESPACE" \
|
|
3
4
|
docker-registry $IMAGE_PULL_SECRET \
|
|
4
5
|
--docker-server="$CI_REGISTRY" \
|
|
5
6
|
--docker-username="${CI_DEPLOY_USER:-$CI_REGISTRY_USER}" \
|
|
6
7
|
--docker-password="${CI_DEPLOY_PASSWORD:-$CI_REGISTRY_PASSWORD}" \
|
|
7
8
|
--docker-email="$GITLAB_USER_EMAIL" \
|
|
8
|
-
-o yaml --dry-run | kubectl replace -n "$
|
|
9
|
+
-o yaml --dry-run | kubectl replace -n "$KUBE_NAMESPACE" --force -f -
|
package/scripts/kubernetesDelete
CHANGED
package/scripts/kubernetesDeploy
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
|
|
3
3
|
echo "Deploy to kubernetes"
|
|
4
|
-
RELEASE_NAME=${CUSTOMER_NAME}-${APP_NAME}-${CI_ENVIRONMENT_SLUG}
|
|
5
4
|
echo "Release: $RELEASE_NAME"
|
|
6
5
|
echo "URL (canonical): $HOST_CANONICAL"
|
|
7
6
|
#helm3 init --client-only --stable-repo-url=https://charts.helm.sh/stable
|
|
@@ -16,10 +15,9 @@ readarray -t helmArgsArray <<<"$helmArgsEvaluated"
|
|
|
16
15
|
#Log the time until selfdestruct in case of review environment
|
|
17
16
|
if [[ ${ENV_SHORT} == "review" ]]; then echo "NOTE - This deployment will stop itself after 2 weeks"; fi
|
|
18
17
|
|
|
19
|
-
echo "
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
printAllValues.ts -d $VALUES_PATH -e $ENV_SHORT >__all_values.yml
|
|
18
|
+
echo "all values"
|
|
19
|
+
echo "$KUBE_VALUES"
|
|
20
|
+
echo $KUBE_VALUES >__all_values.yml
|
|
23
21
|
|
|
24
22
|
echo "doing helm upgrade"
|
|
25
23
|
helm3 upgrade --install "$RELEASE_NAME" /tmp/$HELM_GITLAB_CHART_NAME \
|
|
@@ -50,8 +48,8 @@ helm3 upgrade --install "$RELEASE_NAME" /tmp/$HELM_GITLAB_CHART_NAME \
|
|
|
50
48
|
--set gitlab.mergeRequestId="$CI_MERGE_REQUEST_IID" \
|
|
51
49
|
--set gitlab.projectId="$CI_PROJECT_ID" \
|
|
52
50
|
--set gitlab.env="$CI_ENVIRONMENT_SLUG" \
|
|
53
|
-
--set namespace="$
|
|
54
|
-
--namespace="$
|
|
51
|
+
--set namespace="$KUBE_NAMESPACE" \
|
|
52
|
+
--namespace="$KUBE_NAMESPACE" \
|
|
55
53
|
${helmArgsArray[@]} \
|
|
56
|
-
--
|
|
54
|
+
--values __all_values.yml
|
|
57
55
|
echo "Deployment successful 😻"
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
echo "Ensure Namespace $
|
|
3
|
-
kubectl describe namespace "$
|
|
2
|
+
echo "Ensure Namespace $KUBE_NAMESPACE"
|
|
3
|
+
kubectl describe namespace "$KUBE_NAMESPACE" || kubectl create namespace "$KUBE_NAMESPACE"
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { GitlabJobDef } from "../
|
|
1
|
+
import { GitlabJobDef, Context } from "../types";
|
|
2
2
|
|
|
3
|
-
export const createDockerBuildJob = (
|
|
4
|
-
|
|
5
|
-
}: Pick<GitlabJobDef, "script">
|
|
3
|
+
export const createDockerBuildJob = (
|
|
4
|
+
context: Context,
|
|
5
|
+
{ script }: Pick<GitlabJobDef, "script">
|
|
6
|
+
): GitlabJobDef => {
|
|
6
7
|
const base: Omit<GitlabJobDef, "script"> = {
|
|
7
8
|
stage: "build",
|
|
8
9
|
image:
|
|
@@ -15,9 +16,15 @@ export const createDockerBuildJob = ({
|
|
|
15
16
|
},
|
|
16
17
|
],
|
|
17
18
|
variables: {
|
|
19
|
+
APP_DIR: context.componentConfig.dir,
|
|
20
|
+
DOCKER_HOST: "tcp://0.0.0.0:2375",
|
|
21
|
+
DOCKER_TLS_CERTDIR: "",
|
|
22
|
+
DOCKER_DIR: ".",
|
|
23
|
+
IMAGE_TAG: "$CI_COMMIT_SHA",
|
|
18
24
|
DOCKER_DRIVER: "overlay2",
|
|
19
|
-
IMAGE_NAME: "$CI_REGISTRY_IMAGE
|
|
20
|
-
CACHE_IMAGE:
|
|
25
|
+
IMAGE_NAME: "$CI_REGISTRY_IMAGE/" + context.componentName,
|
|
26
|
+
CACHE_IMAGE:
|
|
27
|
+
"$CI_REGISTRY_IMAGE/" + context.componentName + "/cache:cache",
|
|
21
28
|
},
|
|
22
29
|
};
|
|
23
30
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { GitlabJobs } from "../types/gitlab-types";
|
|
2
|
+
import { Context } from "../types/context";
|
|
3
|
+
import { createNodeJobs, getNodeDefaults } from "./node";
|
|
4
|
+
import { BuildConfig } from "./types";
|
|
5
|
+
|
|
6
|
+
export const buildJobCreators: {
|
|
7
|
+
[type in BuildConfig["type"]]: (context: Context) => GitlabJobs;
|
|
8
|
+
} = {
|
|
9
|
+
node: createNodeJobs,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const getBuildDefaults: {
|
|
13
|
+
[type in BuildConfig["type"]]: () => Partial<BuildConfig>;
|
|
14
|
+
} = {
|
|
15
|
+
node: getNodeDefaults,
|
|
16
|
+
};
|
|
@@ -3,21 +3,21 @@ import {
|
|
|
3
3
|
GitlabJobCache,
|
|
4
4
|
GitlabJobs,
|
|
5
5
|
Retry,
|
|
6
|
-
} from "../gitlab-types";
|
|
7
|
-
import { Context } from "../context";
|
|
6
|
+
} from "../types/gitlab-types";
|
|
7
|
+
import { Context } from "../types/context";
|
|
8
8
|
import { createDockerBuildJob } from "./docker";
|
|
9
|
-
|
|
10
|
-
const yarnInstall = [
|
|
11
|
-
"cd $APP_PATH",
|
|
12
|
-
"if [ -f ./.nvmrc ]; then source /root/.nvm/nvm.sh && nvm install <<< .nvmrc; fi",
|
|
13
|
-
"yarn install --frozen-lockfile",
|
|
14
|
-
];
|
|
9
|
+
import { BuildConfigNode, isOfType } from "./types";
|
|
15
10
|
|
|
16
11
|
const baseRetry: Retry = {
|
|
17
12
|
max: 2,
|
|
18
13
|
when: ["runner_system_failure", "stuck_or_timeout_failure"],
|
|
19
14
|
};
|
|
20
15
|
|
|
16
|
+
const getYarnInstall = (context: Context) => [
|
|
17
|
+
`cd ${context.componentConfig.dir}`,
|
|
18
|
+
"if [ -f ./.nvmrc ]; then source /root/.nvm/nvm.sh && nvm install <<< .nvmrc; fi",
|
|
19
|
+
"yarn install --frozen-lockfile",
|
|
20
|
+
];
|
|
21
21
|
const createNodeTestJobs = (context: Context): GitlabJobs => {
|
|
22
22
|
const cache: GitlabJobCache[] = [
|
|
23
23
|
{
|
|
@@ -26,6 +26,7 @@ const createNodeTestJobs = (context: Context): GitlabJobs => {
|
|
|
26
26
|
paths: ["node_modules", "**/node_modules/"],
|
|
27
27
|
},
|
|
28
28
|
];
|
|
29
|
+
|
|
29
30
|
const base: Omit<GitlabJobDef, "script"> = {
|
|
30
31
|
variables: {
|
|
31
32
|
APP_PATH: context.componentConfig.dir,
|
|
@@ -36,50 +37,60 @@ const createNodeTestJobs = (context: Context): GitlabJobs => {
|
|
|
36
37
|
needs: [],
|
|
37
38
|
retry: baseRetry,
|
|
38
39
|
};
|
|
40
|
+
const yarnInstall = getYarnInstall(context);
|
|
39
41
|
return [
|
|
40
42
|
{
|
|
41
|
-
name: "
|
|
42
|
-
|
|
43
|
-
job:
|
|
43
|
+
name: "audit",
|
|
44
|
+
perEnv: false,
|
|
45
|
+
job: {
|
|
44
46
|
...base,
|
|
45
|
-
script: [...yarnInstall, "yarn
|
|
46
|
-
|
|
47
|
+
script: [...yarnInstall, "yarn audit"],
|
|
48
|
+
allow_failure: true,
|
|
49
|
+
},
|
|
47
50
|
},
|
|
48
51
|
{
|
|
49
52
|
name: "lint",
|
|
50
|
-
|
|
51
|
-
job:
|
|
53
|
+
perEnv: false,
|
|
54
|
+
job: {
|
|
52
55
|
...base,
|
|
53
56
|
script: [...yarnInstall, "yarn lint"],
|
|
54
|
-
}
|
|
57
|
+
},
|
|
55
58
|
},
|
|
56
59
|
{
|
|
57
|
-
name: "
|
|
58
|
-
|
|
59
|
-
job:
|
|
60
|
+
name: "test",
|
|
61
|
+
perEnv: false,
|
|
62
|
+
job: {
|
|
60
63
|
...base,
|
|
61
|
-
script: [...yarnInstall, "yarn
|
|
62
|
-
}
|
|
64
|
+
script: [...yarnInstall, "yarn test"],
|
|
65
|
+
},
|
|
63
66
|
},
|
|
64
67
|
];
|
|
65
68
|
};
|
|
66
69
|
|
|
67
70
|
const createNodeBuildJobs = (context: Context): GitlabJobs => {
|
|
71
|
+
const buildConfig = context.componentConfig.build;
|
|
72
|
+
if (!isOfType(buildConfig, "node")) {
|
|
73
|
+
// should not happen
|
|
74
|
+
throw new Error("deploy config is not kubernetes");
|
|
75
|
+
}
|
|
76
|
+
|
|
68
77
|
const buildInfo = [
|
|
69
78
|
". getCommitInfo", // TODO: inline
|
|
70
|
-
`echo '{"id":"'$BUILD_ID'","commit":"'$BUILD_COMMIT'","tag":"'$BUILD_TAG'","time":"'$BUILD_TIME'"}' > $
|
|
79
|
+
`echo '{"id":"'$BUILD_ID'","commit":"'$BUILD_COMMIT'","tag":"'$BUILD_TAG'","time":"'$BUILD_TIME'"}' > ${context.componentConfig.dir}/__build_info.json`,
|
|
71
80
|
];
|
|
72
81
|
|
|
82
|
+
const yarnInstall = getYarnInstall(context);
|
|
83
|
+
|
|
73
84
|
return [
|
|
74
85
|
{
|
|
75
86
|
name: "app-build",
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
variables: environment.variables,
|
|
87
|
+
job: {
|
|
88
|
+
needs: [],
|
|
89
|
+
variables: context.environment.variables,
|
|
79
90
|
retry: baseRetry,
|
|
80
91
|
interruptible: true,
|
|
81
92
|
stage: "build",
|
|
82
|
-
script: [...buildInfo, ...yarnInstall,
|
|
93
|
+
script: [...buildInfo, ...yarnInstall, buildConfig.buildCommand!],
|
|
83
94
|
artifacts: {
|
|
84
95
|
paths: [
|
|
85
96
|
context.componentConfig.dir + "/__build_info.json",
|
|
@@ -87,21 +98,33 @@ const createNodeBuildJobs = (context: Context): GitlabJobs => {
|
|
|
87
98
|
context.componentConfig.dir + "/.next",
|
|
88
99
|
],
|
|
89
100
|
},
|
|
90
|
-
}
|
|
101
|
+
},
|
|
91
102
|
},
|
|
92
103
|
{
|
|
93
104
|
name: "docker-build",
|
|
94
|
-
|
|
95
|
-
job:
|
|
96
|
-
...createDockerBuildJob({
|
|
97
|
-
script: [
|
|
105
|
+
|
|
106
|
+
job: {
|
|
107
|
+
...createDockerBuildJob(context, {
|
|
108
|
+
script: [
|
|
109
|
+
buildConfig.runtime === "static"
|
|
110
|
+
? "ensureNginxDockerfile"
|
|
111
|
+
: "ensureNodeDockerfile",
|
|
112
|
+
], // TOOD: inline
|
|
98
113
|
}),
|
|
99
114
|
needs: ["app-build"],
|
|
100
|
-
}
|
|
115
|
+
},
|
|
101
116
|
},
|
|
102
117
|
];
|
|
103
118
|
};
|
|
104
119
|
|
|
105
120
|
export const createNodeJobs = (context: Context): GitlabJobs => {
|
|
106
|
-
return [...
|
|
121
|
+
return [...createNodeTestJobs(context), ...createNodeBuildJobs(context)];
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export const getNodeDefaults = (): Partial<BuildConfigNode> => {
|
|
125
|
+
return {
|
|
126
|
+
startCommand: "yarn start",
|
|
127
|
+
runtime: "dynamic",
|
|
128
|
+
buildCommand: "yarn build",
|
|
129
|
+
};
|
|
107
130
|
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type BuildConfigBase = {
|
|
2
|
+
startCommand?: string;
|
|
3
|
+
};
|
|
4
|
+
export type BuildConfigNode = {
|
|
5
|
+
type: "node";
|
|
6
|
+
runtime?: "dynamic" | "static"; // defaults to dynamic
|
|
7
|
+
buildCommand?: string;
|
|
8
|
+
} & BuildConfigBase;
|
|
9
|
+
|
|
10
|
+
export type BuildConfig = BuildConfigNode;
|
|
11
|
+
|
|
12
|
+
export const isOfType = <T extends BuildConfig["type"]>(
|
|
13
|
+
t: BuildConfig,
|
|
14
|
+
type: T
|
|
15
|
+
): t is Extract<BuildConfig, { type: T }> => {
|
|
16
|
+
return t.type === type;
|
|
17
|
+
};
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { merge } from "lodash";
|
|
2
2
|
import slugify from "slugify";
|
|
3
3
|
import { GitlabJobDef, GitlabJobs } from "../types/gitlab-types";
|
|
4
|
-
|
|
5
|
-
import { Config } from "../types/config";
|
|
6
|
-
import { Context, Environment } from "../types/context";
|
|
7
|
-
import { deployJobCreators } from "../
|
|
4
|
+
|
|
5
|
+
import { Config, DefaultEnvConfig } from "../types/config";
|
|
6
|
+
import { CommitInfo, Context, Environment } from "../types/context";
|
|
7
|
+
import { deployJobCreators, getDeployDefaults } from "../deploy";
|
|
8
|
+
import { buildJobCreators, getBuildDefaults } from "../build";
|
|
9
|
+
import { BuildConfig } from "../build/types";
|
|
10
|
+
import { DeployConfig } from "../deploy/types";
|
|
8
11
|
|
|
9
12
|
const createRawJobs = (context: Context): GitlabJobs => {
|
|
10
13
|
const buildJobs =
|
|
@@ -18,7 +21,8 @@ const createRawJobs = (context: Context): GitlabJobs => {
|
|
|
18
21
|
const getEnvironment = (
|
|
19
22
|
config: Config,
|
|
20
23
|
componentName: string,
|
|
21
|
-
env: string
|
|
24
|
+
env: string,
|
|
25
|
+
commitInfo: CommitInfo
|
|
22
26
|
): Environment => {
|
|
23
27
|
const componentConfig = config.components[componentName];
|
|
24
28
|
if (!componentConfig) {
|
|
@@ -38,7 +42,7 @@ const getEnvironment = (
|
|
|
38
42
|
const referenced = Object.entries(referencedRaw).reduce(
|
|
39
43
|
(acc, [otherApp, mapping]) => {
|
|
40
44
|
// TODO: prevent infinit looop
|
|
41
|
-
const { variables } = getEnvironment(config, otherApp, env);
|
|
45
|
+
const { variables } = getEnvironment(config, otherApp, env, commitInfo);
|
|
42
46
|
|
|
43
47
|
return Object.fromEntries(
|
|
44
48
|
Object.entries(mapping).map(([ourKey, otherKey]) => [
|
|
@@ -53,27 +57,29 @@ const getEnvironment = (
|
|
|
53
57
|
const envType = componentConfig.env?.[env]?.type ?? env;
|
|
54
58
|
const environmentName =
|
|
55
59
|
envType === "review"
|
|
56
|
-
? `${env}-${componentName}/$
|
|
60
|
+
? `${env}-${componentName}/${commitInfo.refName}`
|
|
57
61
|
: `${env}-${componentName}`;
|
|
58
62
|
|
|
59
63
|
const KUBE_APP_NAME =
|
|
60
64
|
envType === "review"
|
|
61
|
-
? `${componentName}-$
|
|
65
|
+
? `${componentName}-${commitInfo.refSlug}`
|
|
62
66
|
: componentName;
|
|
63
67
|
|
|
64
68
|
const KUBE_NAMESPACE = `${config.customerName}-${config.appName}-${env}`;
|
|
65
69
|
|
|
66
70
|
const APP_SLUG = slugify(KUBE_APP_NAME);
|
|
67
71
|
|
|
68
|
-
const
|
|
72
|
+
const HOST_CANONICAL = `${config.appName}-${APP_SLUG}.${config.customerName}.panter.cloud`;
|
|
69
73
|
|
|
70
|
-
const hostname = mergedConfig.hostname ??
|
|
74
|
+
const hostname = mergedConfig.hostname ?? HOST_CANONICAL;
|
|
71
75
|
const url = `https://${hostname}`;
|
|
72
76
|
|
|
73
77
|
const predefinedVariables = {
|
|
74
|
-
|
|
78
|
+
HOST_CANONICAL,
|
|
75
79
|
ROOT_URL: url,
|
|
76
80
|
KUBE_NAMESPACE,
|
|
81
|
+
KUBE_APP_NAME,
|
|
82
|
+
ENV_SHORT: env,
|
|
77
83
|
};
|
|
78
84
|
const variables = {
|
|
79
85
|
...predefinedVariables,
|
|
@@ -83,6 +89,7 @@ const getEnvironment = (
|
|
|
83
89
|
};
|
|
84
90
|
|
|
85
91
|
return {
|
|
92
|
+
hostname,
|
|
86
93
|
fullName: environmentName,
|
|
87
94
|
shortName: env,
|
|
88
95
|
url: url,
|
|
@@ -92,16 +99,35 @@ const getEnvironment = (
|
|
|
92
99
|
|
|
93
100
|
export const createContext = (
|
|
94
101
|
componentName: string,
|
|
95
|
-
config: Config
|
|
102
|
+
config: Config,
|
|
103
|
+
env: string
|
|
96
104
|
): Context => {
|
|
97
|
-
const
|
|
98
|
-
if (!
|
|
105
|
+
const rawConfig = config.components[componentName];
|
|
106
|
+
if (!rawConfig) {
|
|
99
107
|
throw new Error("unknown component " + componentName);
|
|
100
108
|
}
|
|
109
|
+
// envs can override the config
|
|
110
|
+
const envConfig = rawConfig.env?.[env] ?? {};
|
|
111
|
+
const componentConfigWithoutDefaults = merge({}, rawConfig, envConfig);
|
|
112
|
+
// fill in defaults of build and deploy
|
|
113
|
+
const defaults: {
|
|
114
|
+
build: Partial<BuildConfig>;
|
|
115
|
+
deploy: Partial<DeployConfig>;
|
|
116
|
+
} = {
|
|
117
|
+
build: getBuildDefaults[componentConfigWithoutDefaults.build.type](),
|
|
118
|
+
deploy: getDeployDefaults[componentConfigWithoutDefaults.deploy.type](),
|
|
119
|
+
};
|
|
120
|
+
const componentConfig = merge({}, componentConfigWithoutDefaults, defaults);
|
|
121
|
+
const commit = {
|
|
122
|
+
refName: process.env.CI_COMMIT_REF_NAME ?? "unknown",
|
|
123
|
+
refSlug: process.env.CI_COMMIT_REF_SLUG ?? "unknown",
|
|
124
|
+
};
|
|
101
125
|
return {
|
|
102
126
|
fullConfig: config,
|
|
103
127
|
componentConfig,
|
|
104
128
|
componentName,
|
|
129
|
+
environment: getEnvironment(config, componentName, env, commit),
|
|
130
|
+
commit: commit,
|
|
105
131
|
};
|
|
106
132
|
};
|
|
107
133
|
|
|
@@ -131,31 +157,18 @@ export const createJobs = (
|
|
|
131
157
|
config: Config,
|
|
132
158
|
componentName: string
|
|
133
159
|
): Record<string, GitlabJobDef> => {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
getEnvironment(config, componentName, e)
|
|
138
|
-
);
|
|
139
|
-
return jobs.reduce((acc, job) => {
|
|
140
|
-
if (!job.env) {
|
|
141
|
-
const def = job.job();
|
|
142
|
-
|
|
143
|
-
return {
|
|
144
|
-
...acc,
|
|
145
|
-
[getFullJobName(job.name, componentName)]: replaceReferences(
|
|
146
|
-
def,
|
|
147
|
-
componentName
|
|
148
|
-
),
|
|
149
|
-
};
|
|
150
|
-
}
|
|
160
|
+
return envs.reduce((acc, env) => {
|
|
161
|
+
const context = createContext(componentName, config, env);
|
|
162
|
+
const jobs = createRawJobs(context);
|
|
151
163
|
return {
|
|
152
164
|
...acc,
|
|
153
|
-
...
|
|
154
|
-
const def = job
|
|
165
|
+
...jobs.reduce((acc, { name, job, perEnv = true }) => {
|
|
166
|
+
const def = job;
|
|
167
|
+
|
|
155
168
|
return {
|
|
156
169
|
...acc,
|
|
157
|
-
[getFullJobName(
|
|
158
|
-
replaceReferences(def, componentName,
|
|
170
|
+
[getFullJobName(name, componentName, perEnv ? env : undefined)]:
|
|
171
|
+
replaceReferences(def, componentName, perEnv ? env : undefined),
|
|
159
172
|
};
|
|
160
173
|
}, {}),
|
|
161
174
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createJobs } from "./createChildPipeline";
|
|
2
2
|
import { Config, ENV_TYPES, PipelineTrigger } from "./types/config";
|
|
3
|
+
import { GitlabJobDef } from "./types/gitlab-types";
|
|
3
4
|
|
|
4
5
|
export const createPipeline = async (
|
|
5
6
|
trigger: PipelineTrigger,
|
|
@@ -12,12 +13,15 @@ export const createPipeline = async (
|
|
|
12
13
|
|
|
13
14
|
// 2. write the triggering pipeline
|
|
14
15
|
|
|
15
|
-
const jobs = components.reduce
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
const jobs = components.reduce<Record<string, GitlabJobDef>>(
|
|
17
|
+
(acc, componentName) => {
|
|
18
|
+
return {
|
|
19
|
+
...acc,
|
|
20
|
+
...createJobs(envs, config, componentName),
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
{}
|
|
24
|
+
);
|
|
21
25
|
|
|
22
26
|
const rules = [
|
|
23
27
|
// same as rules "always", but with `changes` to only trigger changed branches
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { GitlabJobs } from "../types/gitlab-types";
|
|
2
|
+
import { ComponentConfig } from "../types/config";
|
|
3
|
+
import { Context } from "../types/context";
|
|
4
|
+
import { createKubernetesDeployJobs, getKubernetesDefault } from "./kubernetes";
|
|
5
|
+
import { DeployConfig } from "./types";
|
|
6
|
+
|
|
7
|
+
export const deployJobCreators: {
|
|
8
|
+
[type in DeployConfig["type"]]: (context: Context) => GitlabJobs;
|
|
9
|
+
} = {
|
|
10
|
+
kubernetes: createKubernetesDeployJobs,
|
|
11
|
+
serverless: () => [],
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const getDeployDefaults: {
|
|
15
|
+
[type in DeployConfig["type"]]: () => Partial<DeployConfig>;
|
|
16
|
+
} = {
|
|
17
|
+
kubernetes: getKubernetesDefault,
|
|
18
|
+
serverless: () => ({}),
|
|
19
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { GitlabJobs } from "../types/gitlab-types";
|
|
2
|
+
import { Context } from "../types/context";
|
|
3
|
+
import { DeployConfigKubernetes, isOfType } from "./types";
|
|
4
|
+
|
|
5
|
+
export const createKubernetesDeployJobs = (context: Context): GitlabJobs => {
|
|
6
|
+
const deployConfig = context.componentConfig.deploy;
|
|
7
|
+
if (!isOfType(deployConfig, "kubernetes")) {
|
|
8
|
+
// should not happen
|
|
9
|
+
throw new Error("deploy config is not kubernetes");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const defaltKubeValues = {
|
|
13
|
+
application: {
|
|
14
|
+
hostname: context.environment.hostname,
|
|
15
|
+
command: context.componentConfig.build.startCommand,
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
const kubeValues = {
|
|
19
|
+
...defaltKubeValues,
|
|
20
|
+
...deployConfig.values,
|
|
21
|
+
}; // TODO: merge with some defaults
|
|
22
|
+
return [
|
|
23
|
+
{
|
|
24
|
+
name: "deploy-to-kubernetes",
|
|
25
|
+
|
|
26
|
+
job: {
|
|
27
|
+
variables: {
|
|
28
|
+
...context.environment.variables,
|
|
29
|
+
|
|
30
|
+
// TODO: refactor and unify with other stages
|
|
31
|
+
HELM_EXPERIMENTAL_OCI: "1",
|
|
32
|
+
IMAGE_PULL_SECRET: `gitlab-registry-${context.componentName}`,
|
|
33
|
+
KUBE_VALUES: JSON.stringify(kubeValues),
|
|
34
|
+
HELM_GITLAB_CHART_PATH: "catladder/helm-charts",
|
|
35
|
+
HELM_GITLAB_CHART_NAME: "the-panter-chart",
|
|
36
|
+
COMPONENT_NAME: context.componentName,
|
|
37
|
+
// TODO: unify with docker build stage
|
|
38
|
+
IMAGE_TAG: "$CI_COMMIT_SHA",
|
|
39
|
+
HELM_GITLAB_CHART_VERSION: "3.2.0", // TODO, we could actually just ship the chart directly here
|
|
40
|
+
RELEASE_NAME: `${context.fullConfig.customerName}-${context.fullConfig.appName}-$CI_ENVIRONMENT_SLUG`,
|
|
41
|
+
},
|
|
42
|
+
stage: "deploy",
|
|
43
|
+
dependencies: [],
|
|
44
|
+
script: [
|
|
45
|
+
"kubernetesEnsureNamespace",
|
|
46
|
+
"kubernetesCreateSecret",
|
|
47
|
+
"kubernetesDeploy",
|
|
48
|
+
],
|
|
49
|
+
environment: {
|
|
50
|
+
name: context.environment.fullName,
|
|
51
|
+
url: context.environment.url,
|
|
52
|
+
kubernetes: {
|
|
53
|
+
namespace: context.environment.variables.KUBE_NAMESPACE,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const getKubernetesDefault = (): Partial<DeployConfigKubernetes> => {
|
|
62
|
+
return {
|
|
63
|
+
values: {},
|
|
64
|
+
};
|
|
65
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Config } from "../types";
|
|
2
|
+
import { createPipeline } from "../createPipeline";
|
|
3
|
+
|
|
4
|
+
describe("createPipeline", () => {
|
|
5
|
+
describe("node-app to kuberntes", () => {
|
|
6
|
+
const config: Config = {
|
|
7
|
+
appName: "test",
|
|
8
|
+
customerName: "pan",
|
|
9
|
+
components: {
|
|
10
|
+
myApp: {
|
|
11
|
+
dir: "myapp",
|
|
12
|
+
build: {
|
|
13
|
+
type: "node",
|
|
14
|
+
},
|
|
15
|
+
deploy: {
|
|
16
|
+
type: "kubernetes",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
it("creates a pipeline for a single app on the main branch", async () => {
|
|
23
|
+
const { image, stages, workflow, ...jobs } = await createPipeline(
|
|
24
|
+
"mainBranch",
|
|
25
|
+
config
|
|
26
|
+
);
|
|
27
|
+
expect(image).toEqual(
|
|
28
|
+
"git.panter.ch:5001/catladder/gitlab-ci/pipeline:$PIPELINE_IMAGE_TAG"
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
expect(Object.keys(jobs)).toEqual([
|
|
32
|
+
"myApp audit",
|
|
33
|
+
"myApp lint",
|
|
34
|
+
"myApp test",
|
|
35
|
+
"dev myApp app-build",
|
|
36
|
+
"dev myApp docker-build",
|
|
37
|
+
"dev myApp deploy-to-kubernetes",
|
|
38
|
+
]);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { BuildConfig } from "
|
|
2
|
-
import { DeployConfig } from "
|
|
1
|
+
import { BuildConfig } from "../build/types";
|
|
2
|
+
import { DeployConfig } from "../deploy/types";
|
|
3
3
|
|
|
4
4
|
export type PipelineTrigger = "mainBranch" | "mr" | "taggedRelease";
|
|
5
5
|
|
|
@@ -19,7 +19,7 @@ export const ENV_TYPES = {
|
|
|
19
19
|
} as const;
|
|
20
20
|
type EnvType = keyof typeof ENV_TYPES;
|
|
21
21
|
|
|
22
|
-
type DefaultEnvConfig = {
|
|
22
|
+
export type DefaultEnvConfig = {
|
|
23
23
|
deploy: DeployConfig;
|
|
24
24
|
build: BuildConfig;
|
|
25
25
|
vars?: {
|
|
@@ -1,13 +1,21 @@
|
|
|
1
|
-
import { ComponentConfig, Config
|
|
1
|
+
import { ComponentConfig, Config } from "./config";
|
|
2
2
|
|
|
3
3
|
export type Environment = {
|
|
4
|
+
hostname: string;
|
|
4
5
|
fullName: string;
|
|
5
6
|
shortName: string;
|
|
6
7
|
url: string;
|
|
7
8
|
variables: Record<string, string>;
|
|
8
9
|
};
|
|
10
|
+
|
|
11
|
+
export type CommitInfo = {
|
|
12
|
+
refName: string;
|
|
13
|
+
refSlug: string;
|
|
14
|
+
};
|
|
9
15
|
export type Context = {
|
|
10
16
|
componentConfig: ComponentConfig;
|
|
11
17
|
componentName: string;
|
|
12
18
|
fullConfig: Config;
|
|
19
|
+
environment: Environment;
|
|
20
|
+
commit: CommitInfo;
|
|
13
21
|
};
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { Environment } from "./context";
|
|
2
|
-
|
|
3
1
|
export type GitlabStage =
|
|
4
2
|
| "setup"
|
|
5
3
|
| "test"
|
|
@@ -52,22 +50,16 @@ export type GitlabJobDef = {
|
|
|
52
50
|
variables?: GitlabVariables;
|
|
53
51
|
dependencies?: string[];
|
|
54
52
|
environment?: GitlabEnvironment;
|
|
53
|
+
allow_failure?: boolean;
|
|
55
54
|
};
|
|
56
55
|
|
|
57
56
|
export type GitlabVariables = Record<string, string | undefined>;
|
|
58
57
|
|
|
59
|
-
export type
|
|
60
|
-
name: string;
|
|
61
|
-
env: true;
|
|
62
|
-
job: (environment: Environment) => GitlabJobDef;
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
export type JobNonEnv = {
|
|
58
|
+
export type GitlabJob = {
|
|
66
59
|
name: string;
|
|
67
|
-
|
|
68
|
-
job:
|
|
60
|
+
perEnv?: boolean;
|
|
61
|
+
job: GitlabJobDef;
|
|
69
62
|
};
|
|
70
|
-
export type GitlabJob = JobEnvDependent | JobNonEnv;
|
|
71
63
|
export type GitlabJobs = GitlabJob[];
|
|
72
64
|
|
|
73
65
|
export type GitlabPipeline = {
|
package/scripts/magic.ts
CHANGED
|
@@ -17,16 +17,16 @@ const isHotfixBranch = false; // TODO: $CI_COMMIT_BRANCH =~ /^[0-9]+\.([0-9]+|x
|
|
|
17
17
|
const isMergeRequest = Boolean(CI_MERGE_REQUEST_ID);
|
|
18
18
|
const isTaggedRelease = Boolean(CI_COMMIT_TAG);
|
|
19
19
|
|
|
20
|
-
const
|
|
20
|
+
const fullPath = (ext: string) => process.cwd() + "/catladder." + ext;
|
|
21
21
|
const readConfig = () => {
|
|
22
22
|
const found = ["ts", "js", "yml", "yaml"].find((extension) =>
|
|
23
|
-
existsSync(
|
|
23
|
+
existsSync(fullPath(extension))
|
|
24
24
|
);
|
|
25
25
|
if (found) {
|
|
26
26
|
if (found === "ts" || found === "js") {
|
|
27
|
-
return require(
|
|
27
|
+
return require(fullPath(found)).default;
|
|
28
28
|
} else {
|
|
29
|
-
return parse(
|
|
29
|
+
return parse(fullPath(found));
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { GitlabJobs } from "../gitlab-types";
|
|
2
|
-
import { Context } from "../context";
|
|
3
|
-
import { createNodeJobs } from "./node";
|
|
4
|
-
import { BuildConfig } from "./types";
|
|
5
|
-
|
|
6
|
-
export const buildJobCreators: {
|
|
7
|
-
[type in BuildConfig["type"]]: (context: Context) => GitlabJobs;
|
|
8
|
-
} = {
|
|
9
|
-
node: createNodeJobs,
|
|
10
|
-
"node-static": createNodeJobs, // TODO
|
|
11
|
-
};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { GitlabJobs } from "../gitlab-types";
|
|
2
|
-
import { ComponentConfig } from "../config";
|
|
3
|
-
import { Context } from "../context";
|
|
4
|
-
import { createKubernetesDeployJobs } from "./kubernetes";
|
|
5
|
-
|
|
6
|
-
export const deployJobCreators: {
|
|
7
|
-
[type in ComponentConfig["deploy"]["type"]]: (context: Context) => GitlabJobs;
|
|
8
|
-
} = {
|
|
9
|
-
kubernetes: createKubernetesDeployJobs,
|
|
10
|
-
serverless: () => [],
|
|
11
|
-
};
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { GitlabJobs } from "../gitlab-types";
|
|
2
|
-
import { Context } from "../context";
|
|
3
|
-
import { isOfType } from "./types";
|
|
4
|
-
|
|
5
|
-
export const createKubernetesDeployJobs = (context: Context): GitlabJobs => {
|
|
6
|
-
const deployConfig = context.componentConfig.deploy;
|
|
7
|
-
if (!isOfType(deployConfig, "kubernetes")) {
|
|
8
|
-
// should not happen
|
|
9
|
-
throw new Error("deploy config is not kubernetes");
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
return [
|
|
13
|
-
{
|
|
14
|
-
name: "deploy-to-kubernetes",
|
|
15
|
-
env: true,
|
|
16
|
-
job: (environment) => ({
|
|
17
|
-
variables: {
|
|
18
|
-
...environment.variables,
|
|
19
|
-
RELEASE_NAME: "${CUSTOMER_NAME}-${APP_NAME}-${CI_ENVIRONMENT_SLUG}",
|
|
20
|
-
},
|
|
21
|
-
stage: "deploy",
|
|
22
|
-
dependencies: [],
|
|
23
|
-
script: [
|
|
24
|
-
"kubernetesEnsureNamespace",
|
|
25
|
-
"kubernetesCreateSecret",
|
|
26
|
-
"kubernetesDeploy",
|
|
27
|
-
],
|
|
28
|
-
environment: {
|
|
29
|
-
name: environment.fullName,
|
|
30
|
-
url: environment.url,
|
|
31
|
-
kubernetes: {
|
|
32
|
-
namespace: environment.variables.KUBE_NAMESPACE,
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
}),
|
|
36
|
-
},
|
|
37
|
-
];
|
|
38
|
-
};
|