@catladder/cli 0.0.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 (74) hide show
  1. package/.nvmrc +1 -0
  2. package/CONTRIBUTING.md +83 -0
  3. package/README.md +31 -0
  4. package/bin/catenv.sh +1 -0
  5. package/bin/catladder +3 -0
  6. package/includes/envrc +35 -0
  7. package/package.json +65 -0
  8. package/src/apps/catenv/catenv.ts +41 -0
  9. package/src/apps/shell/commands/general/index.ts +132 -0
  10. package/src/apps/shell/commands/general/namespaceAutoCompletion.ts +7 -0
  11. package/src/apps/shell/commands/general/portForward.ts +47 -0
  12. package/src/apps/shell/commands/mongodb/index.ts +10 -0
  13. package/src/apps/shell/commands/mongodb/projectMongoDestroyMember.ts +134 -0
  14. package/src/apps/shell/commands/mongodb/projectMongoGetShell.ts +41 -0
  15. package/src/apps/shell/commands/mongodb/projectMongoPortForward.ts +42 -0
  16. package/src/apps/shell/commands/mongodb/utils/index.ts +84 -0
  17. package/src/apps/shell/commands/project/commandCloudSqlProxy.ts +65 -0
  18. package/src/apps/shell/commands/project/commandConfigSecrets.ts +245 -0
  19. package/src/apps/shell/commands/project/commandCopyDB.ts +93 -0
  20. package/src/apps/shell/commands/project/commandDeletePods.ts +50 -0
  21. package/src/apps/shell/commands/project/commandDeleteProject.ts +34 -0
  22. package/src/apps/shell/commands/project/commandEnvVars.ts +17 -0
  23. package/src/apps/shell/commands/project/commandGetMyTotalWorktime.ts +14 -0
  24. package/src/apps/shell/commands/project/commandGetShell.ts +35 -0
  25. package/src/apps/shell/commands/project/commandGitlabCi.ts +114 -0
  26. package/src/apps/shell/commands/project/commandInitGitlab.ts +157 -0
  27. package/src/apps/shell/commands/project/commandInitProject.ts +282 -0
  28. package/src/apps/shell/commands/project/commandListPods.ts +21 -0
  29. package/src/apps/shell/commands/project/commandMigrateHelm3.ts +53 -0
  30. package/src/apps/shell/commands/project/commandNamespace.ts +12 -0
  31. package/src/apps/shell/commands/project/commandOpenCostDashboard.ts +29 -0
  32. package/src/apps/shell/commands/project/commandOpenDashboard.ts +27 -0
  33. package/src/apps/shell/commands/project/commandOpenEnv.ts +18 -0
  34. package/src/apps/shell/commands/project/commandOpenGit.ts +12 -0
  35. package/src/apps/shell/commands/project/commandOpenGrafana.ts +31 -0
  36. package/src/apps/shell/commands/project/commandOpenGrafanaPod.ts +46 -0
  37. package/src/apps/shell/commands/project/commandOpenLogs.ts +23 -0
  38. package/src/apps/shell/commands/project/commandPauseProject.ts +31 -0
  39. package/src/apps/shell/commands/project/commandPortForward.ts +45 -0
  40. package/src/apps/shell/commands/project/commandTriggerCronjob.ts +61 -0
  41. package/src/apps/shell/commands/project/commandVariables.ts +13 -0
  42. package/src/apps/shell/commands/project/index.ts +62 -0
  43. package/src/apps/shell/commands/project/utils/autocompletions.ts +7 -0
  44. package/src/apps/shell/commands/project/utils/ensureCluster.ts +31 -0
  45. package/src/apps/shell/commands/project/utils/ensureNamespace.ts +32 -0
  46. package/src/apps/shell/commands/project/utils/monorepo.ts +45 -0
  47. package/src/apps/shell/commands/shared/index.ts +31 -0
  48. package/src/apps/shell/commands/theStuffThatReallyMatters/index.ts +51 -0
  49. package/src/apps/shell/shell.ts +30 -0
  50. package/src/apps/shell/utils/getGoogleAuthUserNumber.ts +23 -0
  51. package/src/config/clusters.ts +45 -0
  52. package/src/config/constants.ts +5 -0
  53. package/src/index.ts +17 -0
  54. package/src/k8sApi/index.ts +17 -0
  55. package/src/packageInfos.ts +4 -0
  56. package/src/types/child-process-promise.d.ts +1 -0
  57. package/src/types/command-exists-promise.d.ts +1 -0
  58. package/src/types/git-repo-name.d.ts +1 -0
  59. package/src/types/types.ts +20 -0
  60. package/src/types/yawn-yaml.d.ts +1 -0
  61. package/src/utils/cluster.ts +21 -0
  62. package/src/utils/dashboardToken.ts +20 -0
  63. package/src/utils/files.ts +18 -0
  64. package/src/utils/formatEnvVars.ts +7 -0
  65. package/src/utils/getEditor.ts +16 -0
  66. package/src/utils/gitlab.ts +80 -0
  67. package/src/utils/log.ts +13 -0
  68. package/src/utils/passwordstore/index.ts +192 -0
  69. package/src/utils/portForward.ts +52 -0
  70. package/src/utils/preferences/index.ts +33 -0
  71. package/src/utils/projects/index.ts +171 -0
  72. package/src/utils/promise.ts +11 -0
  73. package/src/utils/shell.ts +20 -0
  74. package/tsconfig.json +20 -0
package/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ 14
@@ -0,0 +1,83 @@
1
+ ### Commit message guidelines
2
+
3
+ #### Atomic commits
4
+
5
+ If possible, make [atomic commits](https://en.wikipedia.org/wiki/Atomic_commit), which means:
6
+ - a commit should contain exactly one self-contained functional change
7
+ - a functional change should be contained in exactly one commit
8
+ - a commit should not create an inconsistent state (such as test errors, linting errors, partial fix, feature with documentation etc...)
9
+
10
+ A complex feature can be broken down into multiple commits as long as each one maintains a consistent state and consists of a self-contained change.
11
+
12
+ #### Commit message format
13
+
14
+ Each commit message consists of a **header**, a **body** and a **footer**. The header has a special format that includes a **type**, a **scope** and a **subject**:
15
+
16
+ ```commit
17
+ <type>(<scope>): <subject>
18
+ <BLANK LINE>
19
+ <body>
20
+ <BLANK LINE>
21
+ <footer>
22
+ ```
23
+
24
+ The **header** is mandatory and the **scope** of the header is optional.
25
+
26
+ The **footer** can contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages).
27
+
28
+ #### Revert
29
+
30
+ If the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit. In the body it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.
31
+
32
+ #### Type
33
+
34
+ The type must be one of the following:
35
+
36
+ | Type | Description |
37
+ | ------------ | ----------------------------------------------------------------------------------------------------------- |
38
+ | **build** | Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm) |
39
+ | **ci** | Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs) |
40
+ | **docs** | Documentation only changes |
41
+ | **feat** | A new feature |
42
+ | **fix** | A bug fix |
43
+ | **perf** | A code change that improves performance |
44
+ | **refactor** | A code change that neither fixes a bug nor adds a feature |
45
+ | **style** | Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) |
46
+ | **test** | Adding missing tests or correcting existing tests |
47
+
48
+ #### Subject
49
+
50
+ The subject contains succinct description of the change:
51
+
52
+ - use the imperative, present tense: "change" not "changed" nor "changes"
53
+ - don't capitalize first letter
54
+ - no dot (.) at the end
55
+
56
+ #### Body
57
+ Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes".
58
+ The body should include the motivation for the change and contrast this with previous behavior.
59
+
60
+ #### Footer
61
+ The footer should contain any information about **Breaking Changes** and is also the place to reference GitLab issues that this commit **Closes**.
62
+
63
+ **Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this.
64
+
65
+ #### Examples
66
+
67
+ ```commit
68
+ `fix(pencil): stop graphite breaking when too much pressure applied`
69
+ ```
70
+
71
+ ```commit
72
+ `feat(pencil): add 'graphiteWidth' option`
73
+
74
+ Fix #42
75
+ ```
76
+
77
+ ```commit
78
+ perf(pencil): remove graphiteWidth option`
79
+
80
+ BREAKING CHANGE: The graphiteWidth option has been removed.
81
+
82
+ The default graphite width of 10mm is always used for performance reasons.
83
+ ```
package/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # catladder 🐱 🔧
2
+
3
+ panter cli tool for kubernetes
4
+
5
+ ## Install
6
+
7
+ `yarn global add @panter/catladder`
8
+
9
+ or npm users
10
+
11
+ `npm install -g @panter/catladder`
12
+
13
+ ## Getting started
14
+ You'll need
15
+ - Google Cloud SDK ([see installation instructions](https://cloud.google.com/sdk/docs/install))
16
+ - Kubectl ([see installation instructions](https://kubernetes.io/docs/tasks/tools/))
17
+ - Cloud SQL Auth proxy ([see installation instructions](https://cloud.google.com/sql/docs/postgres/sql-proxy#install))
18
+ - Bitwarden CLI ([see installation instructions](https://bitwarden.com/help/article/cli/))
19
+
20
+ Afterwards you need to connect to your cluster, e.g. `gcloud container clusters get-credentials clustername --zone google-zone --project google-project-id`
21
+ In most cases you'll find the details on the Google Cloud [cluster overview ](https://console.cloud.google.com/kubernetes/list?project=skynet-164509)
22
+
23
+ It just works™
24
+
25
+ ## Preferences
26
+
27
+ catladder stores some preferences in `~/.catladder/preferences.yml` in case you want to change settings.
28
+
29
+ ## Contribution
30
+
31
+ Check the docu [here](/CONTRIBUTING.md).
package/bin/catenv.sh ADDED
@@ -0,0 +1 @@
1
+ eval $(catladder --catenv )
package/bin/catladder ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ // tslint:disable-next-line:no-var-requires
3
+ require("../dist/index.js");
package/includes/envrc ADDED
@@ -0,0 +1,35 @@
1
+ # adjust this file to your needs
2
+ # find a current recommended version here: https://git.panter.ch/panter/catladder2/-/blob/master/includes/envrc
3
+
4
+ # if catladder is available, invoke that
5
+ if hash catladder 2>/dev/null; then
6
+ watch_file values*.yml
7
+ eval "$(catladder --catenv)"
8
+ fi
9
+ # additionaly load env vars from .env files
10
+ watch_file .env
11
+ if [ -f .env ]; then
12
+
13
+ set -o allexport
14
+ source .env
15
+ set +o allexport
16
+ fi
17
+
18
+
19
+ # install nvm automatically and install correct node version
20
+ # remove this if you are not using node or have other concerns
21
+ # see https://github.com/direnv/direnv/wiki/Node#using-nvm for alternatives
22
+ use_nvm() {
23
+ local NVM_PATH="$HOME/.nvm/nvm.sh"
24
+ if ! [ -f "$NVM_PATH" ]; then
25
+ echo "Installing NVM" >&2
26
+ curl -o- https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash
27
+ fi
28
+ . "${NVM_PATH}"
29
+ nvm install
30
+ layout node
31
+ }
32
+ watch_file .nvmrc
33
+ if [ -f .nvmrc ]; then
34
+ use_nvm
35
+ fi
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@catladder/cli",
3
+ "author": "Marco Wettstein <maw@panter.ch>",
4
+ "homepage": "https://git.panter.ch/catladder/catladder/#readme",
5
+ "bugs": "https://git.panter.ch/catladder/catladder/issues",
6
+ "license": "MIT",
7
+ "scripts": {
8
+ "lint": "eslint \"src/**/*.ts\"",
9
+ "lint:fix": "eslint \"src/**/*.ts\" --fix",
10
+ "test": "npm run lint",
11
+ "build": "tsc",
12
+ "build:watch": "tsc -w"
13
+ },
14
+ "bin": {
15
+ "catladder": "./bin/catladder",
16
+ "catenv": "./bin/catenv.sh"
17
+ },
18
+ "dependencies": {
19
+ "@bitwarden/cli": "^1.8.0",
20
+ "@kubernetes/client-node": "^0.16.1",
21
+ "child-process-promise": "^2.2.1",
22
+ "clipboardy": "^2.2.0",
23
+ "command-exists-promise": "^2.0.2",
24
+ "common-tags": "^1.8.0",
25
+ "dayjs": "^1.10.4",
26
+ "fs-extra": "^7.0.1",
27
+ "git-repo-name": "^1.0.1",
28
+ "gitlab": "^4.4.1",
29
+ "js-yaml": "^3.12.0",
30
+ "lodash": "^4.17.11",
31
+ "memoizee": "^0.4.14",
32
+ "node-fetch": "^2.3.0",
33
+ "open": "^8.4.0",
34
+ "open-editor": "^2.0.1",
35
+ "open-in-editor": "^2.2.0",
36
+ "tmp-promise": "^2.0.2",
37
+ "update-notifier": "^2.5.0",
38
+ "vorpal": "^1.12.0",
39
+ "yargs": "^13.2.4",
40
+ "yawn-yaml": "^1.3.4"
41
+ },
42
+ "engines": {
43
+ "node": ">=12.0.0"
44
+ },
45
+ "devDependencies": {
46
+ "@semantic-release/git": "^10.0.1",
47
+ "@semantic-release/gitlab": "^7.0.4",
48
+ "@tsconfig/node14": "^1.0.1",
49
+ "@types/common-tags": "^1.8.0",
50
+ "@types/fs-extra": "^5.0.4",
51
+ "@types/js-yaml": "^3.11.2",
52
+ "@types/lodash": "^4.14.119",
53
+ "@types/memoizee": "^0.4.2",
54
+ "@types/node-fetch": "^2.1.4",
55
+ "@types/request": "^2.48.1",
56
+ "@types/update-notifier": "^2.5.0",
57
+ "@types/vorpal": "^1.12.0",
58
+ "@types/websocket": "^0.0.40",
59
+ "@types/yargs": "^13.0.0",
60
+ "@typescript-eslint/eslint-plugin": "^5.8.0",
61
+ "@typescript-eslint/parser": "^5.8.0",
62
+ "typescript": "^4.5.4"
63
+ },
64
+ "version": "v0.0.0"
65
+ }
@@ -0,0 +1,41 @@
1
+ import { getAllEnvVars } from "../../utils/projects";
2
+ import {
3
+ getCurrentSubApp,
4
+ getSubAppsInMonoRepo,
5
+ } from "../shell/commands/project/utils/monorepo";
6
+
7
+ const sanitizeEnvVarName = (name: string) => name.replace(/[\s\-.]+/g, "_");
8
+ export default async () => {
9
+ const subapps = await getSubAppsInMonoRepo();
10
+ const currentSubApp = await getCurrentSubApp();
11
+ let variables = {};
12
+ if (currentSubApp) {
13
+ variables = await getAllEnvVars("dev-local", currentSubApp.componentName);
14
+ } else if (subapps.length > 0) {
15
+ // when in a monorep and not in a subapp, merge all env vars.
16
+ // this is not 100% correct, but better than not exporting any vars at all
17
+ // so we also add prefixed variants
18
+ variables = await subapps.reduce(async (acc, subapp) => {
19
+ const subappvars = await getAllEnvVars("dev-local", subapp.componentName);
20
+ return {
21
+ ...(await acc),
22
+ ...subappvars,
23
+ // also add prefixed variants in case
24
+ ...Object.fromEntries(
25
+ Object.entries(subappvars).map(([key, value]) => [
26
+ `${sanitizeEnvVarName(subapp.componentName.toUpperCase())}_${key}`,
27
+ value,
28
+ ])
29
+ ),
30
+ };
31
+ }, {});
32
+ } else {
33
+ variables = await getAllEnvVars("dev-local");
34
+ }
35
+
36
+ console.log(
37
+ Object.entries(variables)
38
+ .map(([key, value]) => `export ${key}='${value}'`)
39
+ .join("\n")
40
+ );
41
+ };
@@ -0,0 +1,132 @@
1
+ import memoizee from "memoizee";
2
+ import Vorpal from "vorpal";
3
+ import k8sApi from "../../../../k8sApi";
4
+ import {
5
+ connectToCluster,
6
+ getAllClusterNames,
7
+ getCurrentConnectedClusterName,
8
+ getCurrentContext,
9
+ } from "../../../../utils/cluster";
10
+ import { logError } from "../../../../utils/log";
11
+ import { syncBitwarden } from "../../../../utils/passwordstore";
12
+ import {
13
+ getAllRunningPortForwards,
14
+ stopPortForward,
15
+ } from "../../../../utils/portForward";
16
+ import { getShell } from "../../../../utils/shell";
17
+ import { getGoogleAuthUserNumber } from "../../utils/getGoogleAuthUserNumber";
18
+ import {
19
+ openGoogleCloudKubernetesDashboard,
20
+ openGoogleCloudLogs,
21
+ } from "../shared";
22
+ import { namespaceAutoCompletion } from "./namespaceAutoCompletion";
23
+ import portForward from "./portForward";
24
+
25
+ const getAllNamespaces = memoizee(
26
+ async () => {
27
+ const res = await k8sApi.listNamespace();
28
+ return res.body.items;
29
+ },
30
+ { maxAge: 30000, promise: true }
31
+ );
32
+
33
+ export const getAllNamespacesNames = async () => {
34
+ const namespaces = await getAllNamespaces();
35
+ return namespaces.map((n) => n.metadata.name);
36
+ };
37
+ export default (vorpal: Vorpal) => {
38
+ vorpal
39
+ .command("connect-cluster <clustername>")
40
+ .autocomplete(getAllClusterNames())
41
+ .action(async function ({ clustername }) {
42
+ this.log(`connecting to ${clustername}`);
43
+ await connectToCluster(clustername);
44
+ });
45
+ vorpal.command("current-context").action(async function () {
46
+ this.log(await getCurrentContext());
47
+ });
48
+
49
+ vorpal
50
+ .command("list-namespaces", "list all namespaces")
51
+ .action(async function () {
52
+ const namespaces = await getAllNamespacesNames();
53
+ this.log(namespaces.join("\n"));
54
+ });
55
+
56
+ vorpal
57
+ .command("list-secrets <namespace>", "show secrets")
58
+ .autocomplete(namespaceAutoCompletion)
59
+ .action(async function ({ namespace }) {
60
+ const res = await k8sApi.listNamespacedSecret(namespace);
61
+
62
+ this.log(res.body.items.map((n) => n.metadata.name).join("\n"));
63
+ });
64
+
65
+ vorpal.command("bw-sync", "force sync bitwarden").action(async function () {
66
+ await syncBitwarden(true);
67
+ this.log("done");
68
+ });
69
+
70
+ vorpal
71
+ .command("list-pods <namespace>", "list all pods of namespace")
72
+ .autocomplete(namespaceAutoCompletion)
73
+ .action(async function ({ namespace }) {
74
+ const res = await k8sApi.listNamespacedPod(namespace);
75
+ this.log(res.body.items.map((n) => n.metadata.name).join("\n"));
76
+ });
77
+
78
+ vorpal
79
+ .command("stop-portforward <name>", "stop a running port forward")
80
+ .autocomplete({ data: async () => getAllRunningPortForwards() })
81
+ .action(async function ({ name }) {
82
+ stopPortForward(name.trim());
83
+ });
84
+
85
+ vorpal
86
+ .command("open-dashboard <namespace>", "open kubernetes dashboard")
87
+ .autocomplete(namespaceAutoCompletion)
88
+ .action(async function ({ namespace }) {
89
+ const clustername = await getCurrentConnectedClusterName();
90
+ const authGoogleNumber = await getGoogleAuthUserNumber.call(this, vorpal);
91
+
92
+ await openGoogleCloudKubernetesDashboard(
93
+ authGoogleNumber,
94
+ clustername,
95
+ namespace
96
+ );
97
+ });
98
+ vorpal
99
+ .command(
100
+ "open-logs <namespace>",
101
+ "open google cloud logs (stackdriver stuff)"
102
+ )
103
+ .autocomplete(namespaceAutoCompletion)
104
+ .action(async function ({ namespace }) {
105
+ const clustername = await getCurrentConnectedClusterName();
106
+ const authGoogleNumber = await getGoogleAuthUserNumber.call(this, vorpal);
107
+
108
+ await openGoogleCloudLogs(authGoogleNumber, clustername, namespace);
109
+ });
110
+
111
+ vorpal
112
+ .command("get-shell <namespace>", "get a shell to a pod in the environment")
113
+ .autocomplete(namespaceAutoCompletion)
114
+ .action(async function ({ namespace }) {
115
+ const res = await k8sApi.listNamespacedPod(namespace);
116
+ if (res.body.items.length === 0) {
117
+ logError(this, "sorry, no pods found");
118
+ return;
119
+ }
120
+ const podNames = res.body.items.map((i) => i.metadata.name);
121
+ const { podName } = await this.prompt({
122
+ type: "list",
123
+ name: "podName",
124
+ choices: podNames,
125
+ message: "Which pod? 🤔",
126
+ });
127
+
128
+ await getShell(namespace, podName);
129
+ });
130
+
131
+ portForward(vorpal);
132
+ };
@@ -0,0 +1,7 @@
1
+ import { getAllNamespacesNames } from "./index";
2
+ export const namespaceAutoCompletion = {
3
+ async data() {
4
+ const namespaces = await getAllNamespacesNames();
5
+ return namespaces;
6
+ }
7
+ };
@@ -0,0 +1,47 @@
1
+ import Vorpal from "vorpal";
2
+ import { logError } from "../../../../utils/log";
3
+
4
+ import { startPortForward } from "../../../../utils/portForward";
5
+
6
+ import k8sApi from "../../../../k8sApi";
7
+ import { namespaceAutoCompletion } from "./namespaceAutoCompletion";
8
+
9
+ export default (vorpal: Vorpal) =>
10
+ vorpal
11
+ .command("port-forward <namespace>", "start port-forwarding")
12
+ .autocomplete(namespaceAutoCompletion)
13
+ .action(async function ({ namespace }) {
14
+ const res = await k8sApi.listNamespacedPod(namespace);
15
+ if (res.body.items.length === 0) {
16
+ logError(this, "sorry, no pods found");
17
+ return;
18
+ }
19
+ const podNames = res.body.items.map((i) => i.metadata.name);
20
+
21
+ if (podNames.length === 0) {
22
+ logError(this, "sorry, no pods found");
23
+ return;
24
+ }
25
+ const { podName } = await this.prompt({
26
+ type: "list",
27
+ name: "podName",
28
+ choices: podNames,
29
+ message: "Which pod? 🤔",
30
+ });
31
+
32
+ const { localPort } = await this.prompt({
33
+ type: "number",
34
+ name: "localPort",
35
+
36
+ message: "Local port: ",
37
+ });
38
+
39
+ const { remotePort } = await this.prompt({
40
+ type: "number",
41
+ name: "remotePort",
42
+
43
+ message: "Remote port: ",
44
+ });
45
+
46
+ return startPortForward(podName, localPort, remotePort, namespace);
47
+ });
@@ -0,0 +1,10 @@
1
+ import Vorpal from "vorpal";
2
+ import projectMongoDestroyMember from "./projectMongoDestroyMember";
3
+ import projectMongoGetShell from "./projectMongoGetShell";
4
+ import projectMongoPortForward from "./projectMongoPortForward";
5
+
6
+ export default (vorpal: Vorpal) => {
7
+ projectMongoGetShell(vorpal);
8
+ projectMongoPortForward(vorpal);
9
+ projectMongoDestroyMember(vorpal);
10
+ };
@@ -0,0 +1,134 @@
1
+ import { exec } from "child-process-promise";
2
+ import Vorpal from "vorpal";
3
+ import { logError } from "../../../../utils/log";
4
+ import {
5
+ getProjectNamespace,
6
+ getProjectPvcs
7
+ } from "../../../../utils/projects";
8
+ import { envAutocompletion } from "../project/utils/autocompletions";
9
+ import ensureCluster from "../project/utils/ensureCluster";
10
+ import { getMongoDbPodsWithReplInfo } from "./utils";
11
+
12
+ const removeFinalizer = async (
13
+ namespace: string,
14
+ type: "pod" | "pv" | "pvc",
15
+ name: string
16
+ ) =>
17
+ exec(
18
+ `kubectl patch --namespace ${namespace} ${type} ${name} -p '{"metadata":{"finalizers":null}}'`
19
+ );
20
+
21
+ const deleteResource = async (
22
+ namespace: string,
23
+ type: "pod" | "pv" | "pvc",
24
+ name: string
25
+ ) => exec(`kubectl delete --namespace ${namespace} ${type} ${name}`);
26
+
27
+ const removeFinalizerAndDelete = async (
28
+ namespace: string,
29
+ type: "pod" | "pv" | "pvc",
30
+ name: string
31
+ ) => {
32
+ return Promise.all([
33
+ removeFinalizer(namespace, type, name),
34
+ await deleteResource(namespace, type, name)
35
+ ]);
36
+ };
37
+ export default (vorpal: Vorpal) =>
38
+ vorpal
39
+ .command(
40
+ "project-mongo-destroy-member <env>",
41
+ "DESTROY a member of a replicaset in order to reinitialize it"
42
+ )
43
+ .autocomplete(envAutocompletion)
44
+ .action(async function({ env }) {
45
+ await ensureCluster.call(this);
46
+ this.log(
47
+ "this command tries to delete a (secondary) member of the replicaset, it's persistent volume claim (pvc) and the volume"
48
+ );
49
+ this.log("");
50
+ this.log(
51
+ "this is useful, if you update the stateful set with new volume configuration (different size or storage class)"
52
+ );
53
+ this.log("");
54
+ this.log(
55
+ "Kubernetes will usually recreate the missing member with the updated config and mongodb will start synchronizing the new member."
56
+ );
57
+ this.log("");
58
+ this.log(
59
+ "This works without downtime, but should be done when load on the db is low. Also it has not been tested with large dbs (> 10gi)"
60
+ );
61
+ this.log("");
62
+ this.log(
63
+ "Deleting the volume and claim often stuck, just wait or cancel and restart."
64
+ );
65
+ this.log("");
66
+ const { understood } = await this.prompt({
67
+ default: true,
68
+ message: "DO YOU UNDERSTAND?",
69
+ name: "understood",
70
+ type: "confirm"
71
+ });
72
+
73
+ if (!understood) {
74
+ throw new Error("abort");
75
+ }
76
+
77
+ const namespace = await getProjectNamespace(env);
78
+ const mongodbPods = await getMongoDbPodsWithReplInfo(env);
79
+
80
+ const pvcs = await getProjectPvcs(env);
81
+ const secondaries = mongodbPods.filter(pod => !pod.isMaster);
82
+
83
+ if (secondaries.length === 0) {
84
+ logError(this, "sorry, no pods found");
85
+ return;
86
+ }
87
+
88
+ const podName = (
89
+ await this.prompt({
90
+ type: "list",
91
+ name: "podName",
92
+ choices: secondaries.map(p => ({
93
+ name: `[ secondary ] ${p.podName}`,
94
+ value: p.podName
95
+ })),
96
+ message: "Which pod? 🤔"
97
+ })
98
+ ).podName;
99
+
100
+ const thePvc = pvcs.find(
101
+ pvc => pvc.metadata.name === `datadir-${podName}`
102
+ );
103
+ if (!thePvc) {
104
+ logError(this, `sorry, no pvc found for ${podName}`);
105
+ return;
106
+ }
107
+ const { shouldContinue } = await this.prompt({
108
+ default: true,
109
+ message:
110
+ "THIS WILL DESTROY THE POD, ITS VOLUME AND ALL ITS DATA 🙀🙀🙀!!! continue? 🤔",
111
+ name: "shouldContinue",
112
+ type: "confirm"
113
+ });
114
+
115
+ if (!shouldContinue) {
116
+ throw new Error("abort");
117
+ }
118
+
119
+ // force delete, see https://github.com/kubernetes/kubernetes/issues/77258#issuecomment-502209800
120
+ this.log("destroying volume...");
121
+ try {
122
+ await removeFinalizerAndDelete(namespace, "pv", thePvc.spec.volumeName);
123
+ } catch (e) {
124
+ this.log(e.message);
125
+ }
126
+ this.log("destroying volume claim...");
127
+ try {
128
+ await removeFinalizerAndDelete(namespace, "pvc", thePvc.metadata.name);
129
+ } catch (e) {
130
+ this.log(e.message);
131
+ }
132
+ this.log("destroying pod...");
133
+ await removeFinalizerAndDelete(namespace, "pod", podName);
134
+ });
@@ -0,0 +1,41 @@
1
+ import Vorpal from "vorpal";
2
+ import { logError } from "../../../../utils/log";
3
+ import { getProjectNamespace } from "../../../../utils/projects";
4
+ import { envAutocompletion } from "../project/utils/autocompletions";
5
+ import ensureCluster from "../project/utils/ensureCluster";
6
+ import {
7
+ getMongodbShell,
8
+ getProjectMongodbAllPodsSortedWithLabel
9
+ } from "./utils";
10
+
11
+ export default (vorpal: Vorpal) =>
12
+ vorpal
13
+ .command(
14
+ "project-mongo-get-shell <env>",
15
+ "get a shell to a mongodb in the environment"
16
+ )
17
+ .autocomplete(envAutocompletion)
18
+ .action(async function({ env }) {
19
+ await ensureCluster.call(this);
20
+ const namespace = await getProjectNamespace(env);
21
+ const podNames = await getProjectMongodbAllPodsSortedWithLabel(env);
22
+ if (podNames.length === 0) {
23
+ logError(this, "sorry, no pods found");
24
+ return;
25
+ }
26
+ let podName;
27
+ if (podNames.length === 1) {
28
+ podName = podNames[0];
29
+ } else {
30
+ podName = (
31
+ await this.prompt({
32
+ type: "list",
33
+ name: "podName",
34
+ choices: podNames,
35
+ message: "Which pod? 🤔"
36
+ })
37
+ ).podName;
38
+ }
39
+
40
+ return getMongodbShell(namespace, podName);
41
+ });