@catladder/cli 0.0.0-fix-migrate-string-cmd-508a469f → 0.0.0-fix-values-as-files-73f39da5

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 (90) hide show
  1. package/README.md +2 -1
  2. package/dist/apps/cli/commands/general/index.js +17 -9
  3. package/dist/apps/cli/commands/general/index.js.map +1 -1
  4. package/dist/apps/cli/commands/general/portForward.js +5 -6
  5. package/dist/apps/cli/commands/general/portForward.js.map +1 -1
  6. package/dist/apps/cli/commands/project/commandCloudSqlProxy.js +27 -19
  7. package/dist/apps/cli/commands/project/commandCloudSqlProxy.js.map +1 -1
  8. package/dist/apps/cli/commands/project/commandConfigSecrets.d.ts +2 -1
  9. package/dist/apps/cli/commands/project/commandConfigSecrets.js +175 -110
  10. package/dist/apps/cli/commands/project/commandConfigSecrets.js.map +1 -1
  11. package/dist/apps/cli/commands/project/commandDeletePods.js +4 -3
  12. package/dist/apps/cli/commands/project/commandDeletePods.js.map +1 -1
  13. package/dist/apps/cli/commands/project/{commandInitGitlab.d.ts → commandInitProject.old.d.ts} +0 -0
  14. package/dist/apps/cli/commands/project/{commandInitProject.js → commandInitProject.old.js} +1 -1
  15. package/dist/apps/cli/commands/project/commandInitProject.old.js.map +1 -0
  16. package/dist/apps/cli/commands/project/{commandInitProject.d.ts → commandSetup.d.ts} +0 -0
  17. package/dist/apps/cli/commands/project/{commandReloadConfig.js → commandSetup.js} +4 -8
  18. package/dist/apps/cli/commands/project/commandSetup.js.map +1 -0
  19. package/dist/apps/cli/commands/project/commandTriggerCronjob.js +2 -2
  20. package/dist/apps/cli/commands/project/commandTriggerCronjob.js.map +1 -1
  21. package/dist/apps/cli/commands/project/index.js +3 -6
  22. package/dist/apps/cli/commands/project/index.js.map +1 -1
  23. package/dist/apps/cli/commands/project/setup/index.d.ts +2 -0
  24. package/dist/apps/cli/commands/project/setup/index.js +103 -0
  25. package/dist/apps/cli/commands/project/setup/index.js.map +1 -0
  26. package/dist/apps/cli/commands/project/setup/setupAccessTokens.d.ts +2 -0
  27. package/dist/apps/cli/commands/project/setup/setupAccessTokens.js +115 -0
  28. package/dist/apps/cli/commands/project/setup/setupAccessTokens.js.map +1 -0
  29. package/dist/apps/cli/commands/project/setup/setupContext.d.ts +3 -0
  30. package/dist/apps/cli/commands/project/setup/setupContext.js +74 -0
  31. package/dist/apps/cli/commands/project/setup/setupContext.js.map +1 -0
  32. package/dist/apps/cli/commands/project/setup/setupKubernetes.d.ts +3 -0
  33. package/dist/apps/cli/commands/project/setup/setupKubernetes.js +132 -0
  34. package/dist/apps/cli/commands/project/setup/setupKubernetes.js.map +1 -0
  35. package/dist/apps/cli/commands/project/utils/autocompletions.js +1 -1
  36. package/dist/apps/cli/commands/project/utils/autocompletions.js.map +1 -1
  37. package/dist/apps/cli/commands/project/utils/ensureNamespace.js +6 -8
  38. package/dist/apps/cli/commands/project/utils/ensureNamespace.js.map +1 -1
  39. package/dist/apps/cli/config/writeConfig.js +1 -1
  40. package/dist/apps/cli/config/writeConfig.js.map +1 -1
  41. package/dist/apps/cli/verify/migration/fromv2.js +8 -6
  42. package/dist/apps/cli/verify/migration/fromv2.js.map +1 -1
  43. package/dist/config/getProjectConfig.d.ts +8 -5
  44. package/dist/config/getProjectConfig.js +81 -36
  45. package/dist/config/getProjectConfig.js.map +1 -1
  46. package/dist/k8sApi/index.d.ts +7 -5
  47. package/dist/k8sApi/index.js +23 -9
  48. package/dist/k8sApi/index.js.map +1 -1
  49. package/dist/tsconfig.tsbuildinfo +1 -1
  50. package/dist/utils/cluster.js +0 -2
  51. package/dist/utils/cluster.js.map +1 -1
  52. package/dist/utils/gitlab.d.ts +1 -0
  53. package/dist/utils/gitlab.js +17 -1
  54. package/dist/utils/gitlab.js.map +1 -1
  55. package/dist/utils/projects/index.js +7 -8
  56. package/dist/utils/projects/index.js.map +1 -1
  57. package/package.json +3 -3
  58. package/src/apps/cli/commands/general/index.ts +6 -7
  59. package/src/apps/cli/commands/general/portForward.ts +2 -1
  60. package/src/apps/cli/commands/project/commandCloudSqlProxy.ts +24 -6
  61. package/src/apps/cli/commands/project/commandConfigSecrets.ts +139 -128
  62. package/src/apps/cli/commands/project/commandDeletePods.ts +2 -1
  63. package/src/apps/cli/commands/project/{commandInitProject.ts → commandInitProject.old.ts} +0 -0
  64. package/src/apps/cli/commands/project/commandSetup.ts +12 -0
  65. package/src/apps/cli/commands/project/commandTriggerCronjob.ts +3 -3
  66. package/src/apps/cli/commands/project/index.ts +4 -6
  67. package/src/apps/cli/commands/project/setup/index.ts +42 -0
  68. package/src/apps/cli/commands/project/setup/setupAccessTokens.ts +72 -0
  69. package/src/apps/cli/commands/project/setup/setupContext.ts +37 -0
  70. package/src/apps/cli/commands/project/setup/setupKubernetes.ts +112 -0
  71. package/src/apps/cli/commands/project/utils/autocompletions.ts +2 -2
  72. package/src/apps/cli/commands/project/utils/ensureNamespace.ts +2 -1
  73. package/src/apps/cli/config/writeConfig.ts +1 -1
  74. package/src/apps/cli/verify/migration/fromv2.ts +4 -2
  75. package/src/config/getProjectConfig.ts +74 -34
  76. package/src/k8sApi/index.ts +20 -8
  77. package/src/utils/cluster.ts +0 -2
  78. package/src/utils/gitlab.ts +11 -0
  79. package/src/utils/projects/index.ts +3 -1
  80. package/dist/apps/cli/commands/project/commandInitGitlab.js +0 -136
  81. package/dist/apps/cli/commands/project/commandInitGitlab.js.map +0 -1
  82. package/dist/apps/cli/commands/project/commandInitProject.js.map +0 -1
  83. package/dist/apps/cli/commands/project/commandReloadConfig.d.ts +0 -3
  84. package/dist/apps/cli/commands/project/commandReloadConfig.js.map +0 -1
  85. package/dist/utils/formatEnvVars.d.ts +0 -2
  86. package/dist/utils/formatEnvVars.js +0 -10
  87. package/dist/utils/formatEnvVars.js.map +0 -1
  88. package/src/apps/cli/commands/project/commandInitGitlab.ts +0 -248
  89. package/src/apps/cli/commands/project/commandReloadConfig.ts +0 -12
  90. package/src/utils/formatEnvVars.ts +0 -7
@@ -1,5 +1,5 @@
1
1
  import Vorpal from "vorpal";
2
- import k8sApi from "../../../../k8sApi";
2
+ import { getk8sApi } from "../../../../k8sApi";
3
3
 
4
4
  import { logError } from "../../../../utils/log";
5
5
  import {
@@ -40,6 +40,7 @@ export default async (vorpal: Vorpal) =>
40
40
  message: "Continue ? 🤔 ",
41
41
  });
42
42
  this.log("");
43
+ const k8sApi = getk8sApi();
43
44
  if (shouldContinue) {
44
45
  for (const podName of selectedPodNames) {
45
46
  await k8sApi.deleteNamespacedPod(podName, namespace, "true");
@@ -0,0 +1,12 @@
1
+ import Vorpal from "vorpal";
2
+ import { setupProject } from "./setup";
3
+
4
+ export default async (vorpal: Vorpal) =>
5
+ vorpal
6
+ .command(
7
+ "project-setup",
8
+ "Initializes all environments and creates requires resources, service accounts, etc."
9
+ )
10
+ .action(async function () {
11
+ await setupProject(this);
12
+ });
@@ -1,7 +1,7 @@
1
1
  import { V1Job, V1ObjectMeta } from "@kubernetes/client-node";
2
2
 
3
3
  import Vorpal from "vorpal";
4
- import { k8sApiBatch, k8sApiBatchBeta } from "../../../../k8sApi";
4
+ import { getk8sApiBatch, getk8sApiBatchBeta } from "../../../../k8sApi";
5
5
  import { logError } from "../../../../utils/log";
6
6
  import { namespaceAutoCompletion } from "../general/namespaceAutoCompletion";
7
7
 
@@ -12,7 +12,7 @@ import ensureCluster from "./utils/ensureCluster";
12
12
  async function triggerCronjob(namespace: string) {
13
13
  const {
14
14
  body: { items: jobs },
15
- } = await k8sApiBatchBeta.listNamespacedCronJob(namespace);
15
+ } = await getk8sApiBatchBeta().listNamespacedCronJob(namespace);
16
16
 
17
17
  const jobNames = jobs.map((j) => j.metadata.name);
18
18
 
@@ -34,7 +34,7 @@ async function triggerCronjob(namespace: string) {
34
34
  job.metadata = metadata as V1ObjectMeta;
35
35
  job.spec = jobSpec;
36
36
  try {
37
- const result = await k8sApiBatch.createNamespacedJob(namespace, job);
37
+ const result = await getk8sApiBatch().createNamespacedJob(namespace, job);
38
38
 
39
39
  this.log("");
40
40
  this.log(`yeah, you got a job, man. 😺 ${result.body.metadata.name}`);
@@ -8,8 +8,8 @@ import commandEnvVars from "./commandEnvVars";
8
8
  import commandGetMyTotalWorktime from "./commandGetMyTotalWorktime";
9
9
  import commandGetShell from "./commandGetShell";
10
10
  import commandGitlabCi from "./commandGitlabCi";
11
- import commandInitGitlab from "./commandInitGitlab";
12
- import commandInitProject from "./commandInitProject";
11
+ import commandSetup from "./commandSetup";
12
+ //import commandInitProject from "./commandInitProject.old";
13
13
  import commandListPods from "./commandListPods";
14
14
  import commandMigrateHelm3 from "./commandMigrateHelm3";
15
15
  import commandNamespace from "./commandNamespace";
@@ -24,12 +24,10 @@ import commandPortForward from "./commandPortForward";
24
24
  import commandTriggerCronjob from "./commandTriggerCronjob";
25
25
 
26
26
  import commandOpenGrafanaPod from "./commandOpenGrafanaPod";
27
- import commandReloadConfig from "./commandReloadConfig";
28
27
 
29
28
  export default async (vorpal: Vorpal) => {
30
- commandReloadConfig(vorpal);
31
- commandInitProject(vorpal);
32
- commandInitGitlab(vorpal);
29
+ commandSetup(vorpal);
30
+
33
31
  commandEnvVars(vorpal);
34
32
 
35
33
  commandNamespace(vorpal);
@@ -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,72 @@
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
+ try {
10
+ await doGitlabRequest(instance, `projects/${projectId}/variables/GL_TOKEN`);
11
+ } catch (e) {
12
+ if (e.message !== "not found") {
13
+ throw e;
14
+ }
15
+ // not found
16
+
17
+ instance.log(
18
+ "I need add a GL_TOKEN to the project, so that semantic release will work\n"
19
+ );
20
+ instance.log(
21
+ "👉 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"
22
+ );
23
+ instance.log("\n");
24
+
25
+ const { understood } = await instance.prompt({
26
+ default: true,
27
+ message: "Understood and open gitlab now? 🤔",
28
+ name: "understood",
29
+ type: "confirm",
30
+ });
31
+ if (!understood) {
32
+ instance.log("continuing anyway...");
33
+ }
34
+ open(`${projectWebUrl}/-/settings/access_tokens`);
35
+
36
+ instance.log("\n");
37
+
38
+ instance.log("Enter your copied token now: ");
39
+
40
+ instance.log("\n");
41
+ const { GL_TOKEN } = await instance.prompt({
42
+ type: "password",
43
+ name: "GL_TOKEN",
44
+ message: "Access Token: ",
45
+ });
46
+ await doGitlabRequest(instance, `projects/${projectId}/variables`, {
47
+ key: "GL_TOKEN",
48
+ value: GL_TOKEN,
49
+ });
50
+ }
51
+
52
+ const deploy_tokens = await doGitlabRequest(
53
+ instance,
54
+ `projects/${projectId}/deploy_tokens`
55
+ );
56
+
57
+ if (
58
+ !deploy_tokens.find(
59
+ (v: { name: string }) => v.name === "gitlab-deploy-token"
60
+ )
61
+ ) {
62
+ instance.log(
63
+ "I will setup the 'GitLab Deploy Token', so Kubernetes can pull images from this project."
64
+ );
65
+
66
+ await doGitlabRequest(instance, `projects/${projectId}/deploy_tokens`, {
67
+ id: projectId,
68
+ name: "gitlab-deploy-token",
69
+ scopes: ["read_registry"],
70
+ });
71
+ }
72
+ };
@@ -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
+ };
@@ -1,6 +1,6 @@
1
1
  import { getAllEnvs, getAllEnvsInAllComponents } from "@catladder/pipeline";
2
2
  import {
3
- getAllComponentsWithAllEnvs,
3
+ getAllComponentsWithAllEnvsFlat,
4
4
  getProjectConfig,
5
5
  } from "../../../../../config/getProjectConfig";
6
6
 
@@ -13,7 +13,7 @@ export const allEnvs = async () => {
13
13
  };
14
14
 
15
15
  export const envAndComponents = async () => {
16
- const allEnvAndcomponents = await getAllComponentsWithAllEnvs();
16
+ const allEnvAndcomponents = await getAllComponentsWithAllEnvsFlat();
17
17
 
18
18
  return allEnvAndcomponents.reduce<string[]>(
19
19
  (acc, { env, componentName }) => [...acc, env + ":" + componentName],
@@ -1,6 +1,6 @@
1
1
  import { Context, getKubernetesNamespace } from "@catladder/pipeline";
2
2
  import { V1Namespace, V1ObjectMeta } from "@kubernetes/client-node";
3
- import k8sApi from "../../../../../k8sApi";
3
+ import { getk8sApi } from "../../../../../k8sApi";
4
4
 
5
5
  export default async function (context: Context) {
6
6
  const namespace = getKubernetesNamespace(
@@ -31,6 +31,7 @@ export default async function (context: Context) {
31
31
  };
32
32
 
33
33
  namespaceBody.metadata = metadata;
34
+ const k8sApi = getk8sApi();
34
35
  try {
35
36
  await k8sApi.readNamespace(namespace);
36
37
 
@@ -45,7 +45,7 @@ export const writeConfig = async (
45
45
  encoding: "utf-8",
46
46
  });
47
47
  vorpal.log("adding type @catladder/pipeline....");
48
- await spawn("yarn add @catladder/pipeline -DW", {
48
+ await spawn("yarn add --non-interactive @catladder/pipeline -DW", {
49
49
  shell: true,
50
50
  });
51
51
  await exec("git add " + file);
@@ -94,7 +94,7 @@ export const migrateV2 = async (vorpal: Vorpal) => {
94
94
  const {
95
95
  CUSTOMER_NAME,
96
96
  APP_NAME,
97
- COMPONENT_NAME,
97
+ COMPONENT_NAME = "web",
98
98
  APP_DIR = ".",
99
99
  STAGING_ENABLED,
100
100
  } = gitlabCi.variables;
@@ -147,7 +147,7 @@ export const migrateV2 = async (vorpal: Vorpal) => {
147
147
 
148
148
  const startCommand = Array.isArray(baseValues?.application?.command)
149
149
  ? baseValues?.application?.command.join(" ")
150
- : baseValues?.application?.command
150
+ : baseValues?.application?.command;
151
151
  return {
152
152
  vars: env ? transformVars(env) : undefined,
153
153
  dir: dir,
@@ -249,6 +249,8 @@ export const migrateV2 = async (vorpal: Vorpal) => {
249
249
  this.log("-------------------");
250
250
 
251
251
  this.log("done!");
252
+ this.log("");
253
+ this.log("You can remove the values*.yml files now.");
252
254
 
253
255
  this.log("-------------------");
254
256
  } else {
@@ -3,29 +3,42 @@ import {
3
3
  getAllEnvs,
4
4
  getEnvironment as _getEnvironment,
5
5
  createContext,
6
+ getSecretVarName,
7
+ Config,
6
8
  } from "@catladder/pipeline";
7
9
 
8
10
  import { CommandInstance } from "vorpal";
9
- import { getAllVariables } from "../utils/gitlab";
10
- import memoizee from "memoizee";
11
+ import { getAllVariables, getVariableValueByRawName } from "../utils/gitlab";
12
+
11
13
  import { getGitRoot } from "../utils/projects";
12
14
  import { readYaml } from "../utils/files";
13
- // currently cant change
14
-
15
- export const getProjectConfig = memoizee(
16
- async () => {
17
- try {
18
- const gitRoot = await getGitRoot();
19
- return readConfigSync(gitRoot);
20
- } catch (e) {
21
- // ignore
22
- return null;
23
- }
24
- },
25
- { promise: true }
26
- );
27
-
28
- export const reloadConfig = () => getProjectConfig.clear();
15
+ import { watch } from "fs";
16
+
17
+ let currentConfig: Config = null;
18
+
19
+ // reload the config on change
20
+ const reloadConfigAndObserve = async () => {
21
+ const gitRoot = await getGitRoot();
22
+ const result = readConfigSync(gitRoot);
23
+ if (!result) {
24
+ // can't do anything, there is no config
25
+ return;
26
+ }
27
+ const { config, path } = result;
28
+ const watcher = watch(path, () => {
29
+ watcher.close();
30
+ reloadConfigAndObserve();
31
+ });
32
+ currentConfig = config;
33
+ };
34
+
35
+ export const getProjectConfig = async () => {
36
+ if (!currentConfig) {
37
+ // initially
38
+ await reloadConfigAndObserve();
39
+ }
40
+ return currentConfig;
41
+ };
29
42
 
30
43
  export const getGitlabCiFilePath = async () => {
31
44
  const gitRoot = await getGitRoot();
@@ -58,21 +71,37 @@ export const getPipelineContextByChoice = async (
58
71
  const config = await getProjectConfig();
59
72
  return createContext(config, componentName, env);
60
73
  };
61
- export const getAllComponentsWithAllEnvs = async () => {
74
+ export const getAllComponentsWithAllEnvsFlat = async (): Promise<
75
+ Array<{ env: string; componentName: string }>
76
+ > => {
62
77
  const config = await getProjectConfig();
63
78
  if (!config) {
64
79
  return [];
65
80
  }
66
- return Promise.all(
67
- Object.keys(config.components).flatMap((componentName) =>
68
- getAllEnvs(config, componentName).map((env) => ({ env, componentName }))
69
- )
81
+ return Object.keys(config.components).flatMap((componentName) =>
82
+ getAllEnvs(config, componentName).map((env) => ({ env, componentName }))
83
+ );
84
+ };
85
+
86
+ export const getAllComponentsWithAllEnvsHierarchical = async (): Promise<{
87
+ [componentName: string]: string[];
88
+ }> => {
89
+ const config = await getProjectConfig();
90
+ if (!config) {
91
+ return {};
92
+ }
93
+
94
+ return Object.fromEntries(
95
+ Object.keys(config.components).map((componentName) => [
96
+ componentName,
97
+ getAllEnvs(config, componentName),
98
+ ])
70
99
  );
71
100
  };
72
101
 
73
102
  export const getAllPipelineContexts = async () => {
74
103
  return Promise.all(
75
- (await getAllComponentsWithAllEnvs())
104
+ (await getAllComponentsWithAllEnvsFlat())
76
105
  .filter((c) => c.env !== "local")
77
106
  .map(({ env, componentName }) =>
78
107
  getPipelineContextByChoice(env, componentName)
@@ -82,9 +111,20 @@ export const getAllPipelineContexts = async () => {
82
111
 
83
112
  export const getEnvironment = async (env: string, componentName: string) => {
84
113
  const config = await getProjectConfig();
114
+
85
115
  return _getEnvironment(config, componentName, env);
86
116
  };
87
117
 
118
+ export const getGitlabVar = async (
119
+ vorpal: CommandInstance,
120
+ env: string,
121
+ componentName: string,
122
+ variableName: string
123
+ ) => {
124
+ const rawVariableName = getSecretVarName(env, componentName, variableName);
125
+ return await getVariableValueByRawName(vorpal, rawVariableName);
126
+ };
127
+
88
128
  const resolveSecrets = async (
89
129
  vorpal: CommandInstance,
90
130
  allEnvVars: Record<string, string>
@@ -93,16 +133,15 @@ const resolveSecrets = async (
93
133
 
94
134
  return Object.fromEntries(
95
135
  Object.entries(allEnvVars).map(([key, value]) => {
96
- const isSecret = String(value)?.startsWith?.("$CL_");
97
- if (isSecret) {
98
- // secrets have CL_XXXX structure
99
- const found = allVariablesInGitlab.find((v) => "$" + v.key === value);
100
-
101
- if (found) {
102
- return [key, found.value];
103
- } else {
104
- return [key, ""];
136
+ const containsSecret = String(value)?.includes?.("$CL_");
137
+ if (containsSecret) {
138
+ for (const variable of allVariablesInGitlab) {
139
+ value = value.replace(
140
+ new RegExp("\\$" + variable.key, "g"),
141
+ variable.value
142
+ );
105
143
  }
144
+ return [key, value];
106
145
  }
107
146
  return [key, value];
108
147
  })
@@ -115,6 +154,7 @@ export const getEnvVars = async (
115
154
  componentName: string
116
155
  ) => {
117
156
  const envionment = await getEnvironment(env, componentName);
118
-
157
+ // in the pipeline the secrets alreadyy exists and bash will expand them
158
+ // but here we need to manually load them
119
159
  return resolveSecrets(vorpal, envionment.envVars);
120
160
  };
@@ -5,17 +5,29 @@ import {
5
5
  BatchV1beta1Api,
6
6
  } from "@kubernetes/client-node";
7
7
 
8
- const kc = new KubeConfig();
9
-
10
- export const reload = () => {
8
+ const getKubeConfig = () => {
9
+ const kc = new KubeConfig();
11
10
  kc.loadFromDefault();
11
+ return kc;
12
12
  };
13
+ /**
14
+ * get kubernetes client. avoid reusing the instance when context get changed
15
+ * @returns kuberenetes client
16
+ */
17
+ export const getk8sApi = () => {
18
+ const kc = getKubeConfig();
13
19
 
14
- reload();
20
+ return kc.makeApiClient(CoreV1Api);
21
+ };
15
22
 
16
- const k8sApi = kc.makeApiClient(CoreV1Api);
23
+ export const getk8sApiBatch = () => {
24
+ const kc = getKubeConfig();
17
25
 
18
- export const k8sApiBatch = kc.makeApiClient(BatchV1Api);
19
- export const k8sApiBatchBeta = kc.makeApiClient(BatchV1beta1Api);
26
+ return kc.makeApiClient(BatchV1Api);
27
+ };
20
28
 
21
- export default k8sApi;
29
+ export const getk8sApiBatchBeta = () => {
30
+ const kc = getKubeConfig();
31
+
32
+ return kc.makeApiClient(BatchV1beta1Api);
33
+ };
@@ -1,5 +1,4 @@
1
1
  import { $ } from "zx";
2
- import { reload } from "../k8sApi";
3
2
 
4
3
  export const getCurrentContext = async () =>
5
4
  (await $`kubectl config current-context`).stdout.trim();
@@ -10,5 +9,4 @@ export const getCurrentConnectedClusterName = async () => {
10
9
 
11
10
  export const connectToCluster = async (fullname: string) => {
12
11
  await $`kubectl config use-context ${fullname}`;
13
- reload();
14
12
  };
@@ -73,6 +73,9 @@ export const doGitlabRequest = async <T = any>(
73
73
  if (result.status >= 200 && result.status < 400) {
74
74
  return result.json();
75
75
  }
76
+ if (result.status === 404) {
77
+ throw new Error("not found");
78
+ }
76
79
 
77
80
  throw new Error(
78
81
  `Could not send request to gitlab api ${path}: ${result.status} "${
@@ -125,6 +128,14 @@ export const getAllVariables = memoizee(
125
128
  { promise: true }
126
129
  );
127
130
 
131
+ export const getVariableValueByRawName = async (
132
+ vorpal: CommandInstance,
133
+ rawName: string
134
+ ) => {
135
+ const allVariables = await getAllVariables(vorpal);
136
+ return allVariables.find((v) => v.key === rawName)?.value;
137
+ };
138
+
128
139
  const maskableRegex = new RegExp("^[a-zA-Z0-9_+=/@:.~-]{8,}$"); // SEE https://gitlab.com/gitlab-org/gitlab-foss/-/blob/master/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js#L20
129
140
  const isMaskable = (value: string): boolean => maskableRegex.test(value);
130
141