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