@catladder/cli 1.15.0 → 1.16.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/package.json CHANGED
@@ -16,7 +16,7 @@
16
16
  "catladder": "./bin/catladder"
17
17
  },
18
18
  "dependencies": {
19
- "@catladder/pipeline": "1.15.0",
19
+ "@catladder/pipeline": "1.16.1",
20
20
  "@kubernetes/client-node": "^0.16.2",
21
21
  "child-process-promise": "^2.2.1",
22
22
  "command-exists-promise": "^2.0.2",
@@ -55,5 +55,5 @@
55
55
  "eslint": "^8.7.0",
56
56
  "typescript": "^4.5.4"
57
57
  },
58
- "version": "1.15.0"
58
+ "version": "1.16.1"
59
59
  }
@@ -1,19 +1,5 @@
1
- import {
2
- getFullKubernetesClusterName,
3
- isOfDeployType,
4
- } from "@catladder/pipeline";
5
1
  import Vorpal from "vorpal";
6
- import { $ } from "zx";
7
- import { getAllPipelineContexts } from "../../../../config/getProjectConfig";
8
- import { connectToCluster } from "../../../../utils/cluster";
9
- import {
10
- doGitlabRequest,
11
- getProjectInfo,
12
- upsertAllVariables,
13
- } from "../../../../utils/gitlab";
14
- import ensureNamespace from "./utils/ensureNamespace";
15
- import open from "open";
16
- import { projectConfigSecrets } from "./commandConfigSecrets";
2
+ import { setupProject } from "./setup";
17
3
 
18
4
  export default async (vorpal: Vorpal) =>
19
5
  vorpal
@@ -22,219 +8,5 @@ export default async (vorpal: Vorpal) =>
22
8
  "Initializes all environments and creates requires resources, service accounts, etc."
23
9
  )
24
10
  .action(async function () {
25
- const allContext = await getAllPipelineContexts();
26
-
27
- for (const context of allContext) {
28
- this.log("");
29
- this.log(
30
- "=================================================================================="
31
- );
32
-
33
- this.log(
34
- "setting up " +
35
- context.environment.shortName +
36
- ":" +
37
- context.componentName +
38
- "..."
39
- );
40
- this.log("");
41
- const deployConfig = context.componentConfig.deploy;
42
- if (isOfDeployType(deployConfig, "kubernetes")) {
43
- const fullName = getFullKubernetesClusterName(deployConfig.cluster);
44
- this.log(`cluster: ${fullName}`);
45
-
46
- await connectToCluster(fullName);
47
- this.log("");
48
- this.log("ensuring namespace ...");
49
- const namespace = await ensureNamespace(context);
50
- this.log("Namespace " + namespace + " created / updated!");
51
- this.log("");
52
- //$.verbose = true;
53
-
54
- // we name the service account and the role and the role binding with the same name
55
- // we currently create one per component to better separate them
56
- this.log("ensuring service accounts...");
57
- const serviceAccountName = `cl-${context.componentName}-deploy`;
58
- const KUBE_URL =
59
- await $`TERM=dumb kubectl cluster-info | grep -E 'Kubernetes master|Kubernetes control plane' | awk '/http/ {print $NF}'`.then(
60
- (s) => s.stdout.trim()
61
- );
62
-
63
- // first upsert service acount in the ns
64
- try {
65
- await $`kubectl delete serviceaccount --namespace ${namespace} ${serviceAccountName}`;
66
- await $`kubectl delete rolebinding --namespace ${namespace} ${serviceAccountName}`;
67
- await $`kubectl delete role --namespace ${namespace} ${serviceAccountName}`;
68
- } catch (e) {
69
- // ignore
70
- }
71
-
72
- await $`kubectl create serviceaccount --namespace ${namespace} ${serviceAccountName}`;
73
-
74
- // upsert role in the ns
75
-
76
- await $`cat <<EOF | kubectl apply -f -
77
- kind: Role
78
- apiVersion: rbac.authorization.k8s.io/v1
79
- metadata:
80
- namespace: ${namespace}
81
- name: ${serviceAccountName}
82
- rules:
83
- - apiGroups: ["", "extensions", "apps", "networking.k8s.io", "batch"]
84
- resources: ["deployments", "replicasets", "statefulsets", "pods", "secrets", "configmaps", "services", "ingresses", "serviceaccounts", "jobs", "cronjobs"]
85
- verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] # You can also use ["*"]
86
- ---
87
- kind: RoleBinding
88
- apiVersion: rbac.authorization.k8s.io/v1
89
- metadata:
90
- name: ${serviceAccountName}
91
- namespace: ${namespace}
92
- subjects:
93
- - kind: ServiceAccount
94
- name: ${serviceAccountName}
95
- namespace: ${namespace}
96
- roleRef:
97
- kind: Role
98
- name: ${serviceAccountName}
99
- apiGroup: rbac.authorization.k8s.io
100
- EOF
101
- `;
102
-
103
- // get token name
104
- const tokenName =
105
- await $`kubectl get serviceaccount --namespace ${namespace} ${serviceAccountName} -o jsonpath='{.secrets[0].name}'`;
106
-
107
- const KUBE_CA_PEM =
108
- await $`kubectl get secret ${tokenName} --namespace ${namespace} -o jsonpath="{['data']['ca\\.crt']}"`.then(
109
- (c) => c.stdout.trim()
110
- );
111
- const KUBE_TOKEN =
112
- await $`kubectl get secret ${tokenName} --namespace ${namespace} -o jsonpath="{['data']['token']}" | base64 --decode`.then(
113
- (c) => c.stdout.trim()
114
- );
115
-
116
- const vars = {
117
- KUBE_TOKEN,
118
- KUBE_CA_PEM,
119
- KUBE_URL,
120
- };
121
-
122
- this.log("service accounts created / updated!");
123
-
124
- this.log("");
125
- this.log("pusing secrets to gitlab...");
126
-
127
- await upsertAllVariables(
128
- this,
129
- vars,
130
- context.environment.shortName,
131
- context.componentName
132
- );
133
- this.log("done!");
134
- }
135
- this.log("");
136
- this.log(
137
- "✅ " +
138
- context.environment.shortName +
139
- ":" +
140
- context.componentName +
141
- " done!"
142
- );
143
-
144
- this.log("");
145
- }
146
-
147
- const { id: projectId, web_url: projectWebUrl } = await getProjectInfo(
148
- this
149
- );
150
- const variables = await doGitlabRequest(
151
- this,
152
- `projects/${projectId}/variables`
153
- );
154
-
155
- if (!variables.find((v: any) => v.key === "GL_TOKEN")) {
156
- this.log(
157
- "I need add a GL_TOKEN to the project, so that semantic release will work\n"
158
- );
159
- this.log(
160
- "👉 Please please create a project access token in gitlab and copy its value into clipboard\n\n - name: something like 'semantic-release'\n - expires: leave empty\n - scopes: api, read_repository"
161
- );
162
- this.log("\n");
163
-
164
- const { understood } = await this.prompt({
165
- default: true,
166
- message: "Understood and open gitlab now? 🤔",
167
- name: "understood",
168
- type: "confirm",
169
- });
170
- if (!understood) {
171
- this.log("continuing anyway...");
172
- }
173
- open(`${projectWebUrl}/-/settings/access_tokens`);
174
-
175
- this.log("\n");
176
-
177
- this.log("Enter your copied token now: ");
178
-
179
- this.log("\n");
180
- const { GL_TOKEN } = await this.prompt({
181
- type: "password",
182
- name: "GL_TOKEN",
183
- message: "Access Token: ",
184
- });
185
- await doGitlabRequest(this, `projects/${projectId}/variables`, {
186
- key: "GL_TOKEN",
187
- value: GL_TOKEN,
188
- });
189
- }
190
-
191
- const deploy_tokens = await doGitlabRequest(
192
- this,
193
- `projects/${projectId}/deploy_tokens`
194
- );
195
-
196
- if (
197
- !deploy_tokens.find(
198
- (v: { name: string }) => v.name === "gitlab-deploy-token"
199
- )
200
- ) {
201
- this.log(
202
- "I will setup the 'GitLab Deploy Token', so Kubernetes can pull images from this project."
203
- );
204
-
205
- await doGitlabRequest(this, `projects/${projectId}/deploy_tokens`, {
206
- id: projectId,
207
- name: "gitlab-deploy-token",
208
- scopes: ["read_registry"],
209
- });
210
- }
211
- this.log();
212
- const { configSecrets } = await this.prompt({
213
- default: true,
214
- message:
215
- "Before deployments work, you need to config secrets. Do it now?",
216
- name: "configSecrets",
217
- type: "confirm",
218
- });
219
- this.log();
220
- if (configSecrets) {
221
- await projectConfigSecrets(this);
222
- } else {
223
- this.log(
224
- "👆 don't forget to config secret using `project-config-secrets`"
225
- );
226
- }
227
- this.log();
228
- this.log("gitlab is ready! 🥂");
229
- this.log("\n");
230
- this.log("do not forget to make sure that:");
231
- [
232
- "you have __health route in place",
233
- "lint and test are defined",
234
- "secrets are configured (call project-config-secret)",
235
- "eat your vegetables",
236
- "be awesome 🤩",
237
- ].forEach((tip) => this.log(` - ${tip}`));
238
- this.log("\n");
239
- this.log("\n");
11
+ await setupProject(this);
240
12
  });
@@ -0,0 +1,42 @@
1
+ import { CommandInstance } from "vorpal";
2
+ import { getAllPipelineContexts } from "../../../../../config/getProjectConfig";
3
+ import { projectConfigSecrets } from "../commandConfigSecrets";
4
+ import { setupAccessTokens } from "./setupAccessTokens";
5
+ import { setupContext } from "./setupContext";
6
+
7
+ export const setupProject = async (instance: CommandInstance) => {
8
+ const allContext = await getAllPipelineContexts();
9
+
10
+ for (const context of allContext) {
11
+ await setupContext(instance, context);
12
+ }
13
+ await setupAccessTokens(instance);
14
+ instance.log("");
15
+ const { configSecrets } = await instance.prompt({
16
+ default: true,
17
+ message: "Before deployments work, you need to config secrets. Do it now?",
18
+ name: "configSecrets",
19
+ type: "confirm",
20
+ });
21
+ instance.log("");
22
+ if (configSecrets) {
23
+ await projectConfigSecrets(instance);
24
+ } else {
25
+ instance.log(
26
+ "👆 don't forget to config secret using `project-config-secrets`"
27
+ );
28
+ }
29
+ instance.log("");
30
+ instance.log("gitlab is ready! 🥂");
31
+ instance.log("\n");
32
+ instance.log("do not forget to make sure that:");
33
+ [
34
+ "you have __health route in place",
35
+ "lint and test are defined",
36
+ "secrets are configured (call project-config-secret)",
37
+ "eat your vegetables",
38
+ "be awesome 🤩",
39
+ ].forEach((tip) => instance.log(` - ${tip}`));
40
+ instance.log("\n");
41
+ instance.log("\n");
42
+ };
@@ -0,0 +1,70 @@
1
+ import open from "open";
2
+ import { CommandInstance } from "vorpal";
3
+ import { doGitlabRequest, getProjectInfo } from "../../../../../utils/gitlab";
4
+
5
+ export const setupAccessTokens = async (instance: CommandInstance) => {
6
+ const { id: projectId, web_url: projectWebUrl } = await getProjectInfo(
7
+ instance
8
+ );
9
+ const variables = await doGitlabRequest(
10
+ instance,
11
+ `projects/${projectId}/variables`
12
+ );
13
+
14
+ if (!variables.find((v: any) => v.key === "GL_TOKEN")) {
15
+ instance.log(
16
+ "I need add a GL_TOKEN to the project, so that semantic release will work\n"
17
+ );
18
+ instance.log(
19
+ "👉 Please please create a project access token in gitlab and copy its value into clipboard\n\n - name: something like 'semantic-release'\n - expires: leave empty\n - scopes: api, read_repository"
20
+ );
21
+ instance.log("\n");
22
+
23
+ const { understood } = await instance.prompt({
24
+ default: true,
25
+ message: "Understood and open gitlab now? 🤔",
26
+ name: "understood",
27
+ type: "confirm",
28
+ });
29
+ if (!understood) {
30
+ instance.log("continuing anyway...");
31
+ }
32
+ open(`${projectWebUrl}/-/settings/access_tokens`);
33
+
34
+ instance.log("\n");
35
+
36
+ instance.log("Enter your copied token now: ");
37
+
38
+ instance.log("\n");
39
+ const { GL_TOKEN } = await instance.prompt({
40
+ type: "password",
41
+ name: "GL_TOKEN",
42
+ message: "Access Token: ",
43
+ });
44
+ await doGitlabRequest(instance, `projects/${projectId}/variables`, {
45
+ key: "GL_TOKEN",
46
+ value: GL_TOKEN,
47
+ });
48
+ }
49
+
50
+ const deploy_tokens = await doGitlabRequest(
51
+ instance,
52
+ `projects/${projectId}/deploy_tokens`
53
+ );
54
+
55
+ if (
56
+ !deploy_tokens.find(
57
+ (v: { name: string }) => v.name === "gitlab-deploy-token"
58
+ )
59
+ ) {
60
+ instance.log(
61
+ "I will setup the 'GitLab Deploy Token', so Kubernetes can pull images from this project."
62
+ );
63
+
64
+ await doGitlabRequest(instance, `projects/${projectId}/deploy_tokens`, {
65
+ id: projectId,
66
+ name: "gitlab-deploy-token",
67
+ scopes: ["read_registry"],
68
+ });
69
+ }
70
+ };
@@ -0,0 +1,37 @@
1
+ import { Context, isOfDeployType } from "@catladder/pipeline";
2
+ import { CommandInstance } from "vorpal";
3
+ import { setupKubernetes } from "./setupKubernetes";
4
+
5
+ export const setupContext = async (
6
+ instance: CommandInstance,
7
+ context: Context
8
+ ) => {
9
+ instance.log("");
10
+ instance.log(
11
+ "=================================================================================="
12
+ );
13
+
14
+ instance.log(
15
+ "🐱 🔧 setting up " +
16
+ context.environment.shortName +
17
+ ":" +
18
+ context.componentName +
19
+ "..."
20
+ );
21
+ instance.log("");
22
+ const deployConfig = context.componentConfig.deploy;
23
+ if (isOfDeployType(deployConfig, "kubernetes")) {
24
+ await setupKubernetes(instance, context);
25
+ }
26
+
27
+ instance.log("");
28
+ instance.log(
29
+ "✅ " +
30
+ context.environment.shortName +
31
+ ":" +
32
+ context.componentName +
33
+ " done!"
34
+ );
35
+
36
+ instance.log("");
37
+ };
@@ -0,0 +1,112 @@
1
+ import {
2
+ Context,
3
+ getFullKubernetesClusterName,
4
+ isOfDeployType,
5
+ } from "@catladder/pipeline";
6
+ import { CommandInstance } from "vorpal";
7
+ import { $ } from "zx";
8
+ import { connectToCluster } from "../../../../../utils/cluster";
9
+ import { upsertAllVariables } from "../../../../../utils/gitlab";
10
+ import ensureNamespace from "../utils/ensureNamespace";
11
+
12
+ export const setupKubernetes = async (
13
+ instance: CommandInstance,
14
+ context: Context
15
+ ) => {
16
+ const deployConfig = context.componentConfig.deploy;
17
+ if (!isOfDeployType(deployConfig, "kubernetes")) {
18
+ throw new Error("cannot run setupKubernetes on non-kubernetes deployments");
19
+ }
20
+
21
+ const fullName = getFullKubernetesClusterName(deployConfig.cluster);
22
+ instance.log(`cluster: ${fullName}`);
23
+
24
+ await connectToCluster(fullName);
25
+ instance.log("");
26
+ instance.log("ensuring namespace ...");
27
+ const namespace = await ensureNamespace(context);
28
+ instance.log("Namespace " + namespace + " created / updated!");
29
+ instance.log("");
30
+ //$.verbose = true;
31
+
32
+ // we name the service account and the role and the role binding with the same name
33
+ // we currently create one per component to better separate them
34
+ instance.log("ensuring service accounts...");
35
+ const serviceAccountName = `cl-${context.componentName}-deploy`;
36
+ const KUBE_URL =
37
+ await $`TERM=dumb kubectl cluster-info | grep -E 'Kubernetes master|Kubernetes control plane' | awk '/http/ {print $NF}'`.then(
38
+ (s) => s.stdout.trim()
39
+ );
40
+
41
+ // first upsert service acount in the ns
42
+ try {
43
+ await $`kubectl delete serviceaccount --namespace ${namespace} ${serviceAccountName}`;
44
+ await $`kubectl delete rolebinding --namespace ${namespace} ${serviceAccountName}`;
45
+ await $`kubectl delete role --namespace ${namespace} ${serviceAccountName}`;
46
+ } catch (e) {
47
+ // ignore
48
+ }
49
+
50
+ await $`kubectl create serviceaccount --namespace ${namespace} ${serviceAccountName}`;
51
+
52
+ // upsert role in the ns
53
+
54
+ await $`cat <<EOF | kubectl apply -f -
55
+ kind: Role
56
+ apiVersion: rbac.authorization.k8s.io/v1
57
+ metadata:
58
+ namespace: ${namespace}
59
+ name: ${serviceAccountName}
60
+ rules:
61
+ - apiGroups: ["", "extensions", "apps", "networking.k8s.io", "batch"]
62
+ resources: ["deployments", "replicasets", "statefulsets", "pods", "secrets", "configmaps", "services", "ingresses", "serviceaccounts", "jobs", "cronjobs"]
63
+ verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] # You can also use ["*"]
64
+ ---
65
+ kind: RoleBinding
66
+ apiVersion: rbac.authorization.k8s.io/v1
67
+ metadata:
68
+ name: ${serviceAccountName}
69
+ namespace: ${namespace}
70
+ subjects:
71
+ - kind: ServiceAccount
72
+ name: ${serviceAccountName}
73
+ namespace: ${namespace}
74
+ roleRef:
75
+ kind: Role
76
+ name: ${serviceAccountName}
77
+ apiGroup: rbac.authorization.k8s.io
78
+ EOF
79
+ `;
80
+
81
+ // get token name
82
+ const tokenName =
83
+ await $`kubectl get serviceaccount --namespace ${namespace} ${serviceAccountName} -o jsonpath='{.secrets[0].name}'`;
84
+
85
+ const KUBE_CA_PEM =
86
+ await $`kubectl get secret ${tokenName} --namespace ${namespace} -o jsonpath="{['data']['ca\\.crt']}"`.then(
87
+ (c) => c.stdout.trim()
88
+ );
89
+ const KUBE_TOKEN =
90
+ await $`kubectl get secret ${tokenName} --namespace ${namespace} -o jsonpath="{['data']['token']}" | base64 --decode`.then(
91
+ (c) => c.stdout.trim()
92
+ );
93
+
94
+ const vars = {
95
+ KUBE_TOKEN,
96
+ KUBE_CA_PEM,
97
+ KUBE_URL,
98
+ };
99
+
100
+ instance.log("service accounts created / updated!");
101
+
102
+ instance.log("");
103
+ instance.log("pusing secrets to gitlab...");
104
+
105
+ await upsertAllVariables(
106
+ instance,
107
+ vars,
108
+ context.environment.shortName,
109
+ context.componentName
110
+ );
111
+ instance.log("done!");
112
+ };