@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.
- package/.nvmrc +1 -0
- package/CONTRIBUTING.md +83 -0
- package/README.md +31 -0
- package/bin/catenv.sh +1 -0
- package/bin/catladder +3 -0
- package/includes/envrc +35 -0
- package/package.json +65 -0
- package/src/apps/catenv/catenv.ts +41 -0
- package/src/apps/shell/commands/general/index.ts +132 -0
- package/src/apps/shell/commands/general/namespaceAutoCompletion.ts +7 -0
- package/src/apps/shell/commands/general/portForward.ts +47 -0
- package/src/apps/shell/commands/mongodb/index.ts +10 -0
- package/src/apps/shell/commands/mongodb/projectMongoDestroyMember.ts +134 -0
- package/src/apps/shell/commands/mongodb/projectMongoGetShell.ts +41 -0
- package/src/apps/shell/commands/mongodb/projectMongoPortForward.ts +42 -0
- package/src/apps/shell/commands/mongodb/utils/index.ts +84 -0
- package/src/apps/shell/commands/project/commandCloudSqlProxy.ts +65 -0
- package/src/apps/shell/commands/project/commandConfigSecrets.ts +245 -0
- package/src/apps/shell/commands/project/commandCopyDB.ts +93 -0
- package/src/apps/shell/commands/project/commandDeletePods.ts +50 -0
- package/src/apps/shell/commands/project/commandDeleteProject.ts +34 -0
- package/src/apps/shell/commands/project/commandEnvVars.ts +17 -0
- package/src/apps/shell/commands/project/commandGetMyTotalWorktime.ts +14 -0
- package/src/apps/shell/commands/project/commandGetShell.ts +35 -0
- package/src/apps/shell/commands/project/commandGitlabCi.ts +114 -0
- package/src/apps/shell/commands/project/commandInitGitlab.ts +157 -0
- package/src/apps/shell/commands/project/commandInitProject.ts +282 -0
- package/src/apps/shell/commands/project/commandListPods.ts +21 -0
- package/src/apps/shell/commands/project/commandMigrateHelm3.ts +53 -0
- package/src/apps/shell/commands/project/commandNamespace.ts +12 -0
- package/src/apps/shell/commands/project/commandOpenCostDashboard.ts +29 -0
- package/src/apps/shell/commands/project/commandOpenDashboard.ts +27 -0
- package/src/apps/shell/commands/project/commandOpenEnv.ts +18 -0
- package/src/apps/shell/commands/project/commandOpenGit.ts +12 -0
- package/src/apps/shell/commands/project/commandOpenGrafana.ts +31 -0
- package/src/apps/shell/commands/project/commandOpenGrafanaPod.ts +46 -0
- package/src/apps/shell/commands/project/commandOpenLogs.ts +23 -0
- package/src/apps/shell/commands/project/commandPauseProject.ts +31 -0
- package/src/apps/shell/commands/project/commandPortForward.ts +45 -0
- package/src/apps/shell/commands/project/commandTriggerCronjob.ts +61 -0
- package/src/apps/shell/commands/project/commandVariables.ts +13 -0
- package/src/apps/shell/commands/project/index.ts +62 -0
- package/src/apps/shell/commands/project/utils/autocompletions.ts +7 -0
- package/src/apps/shell/commands/project/utils/ensureCluster.ts +31 -0
- package/src/apps/shell/commands/project/utils/ensureNamespace.ts +32 -0
- package/src/apps/shell/commands/project/utils/monorepo.ts +45 -0
- package/src/apps/shell/commands/shared/index.ts +31 -0
- package/src/apps/shell/commands/theStuffThatReallyMatters/index.ts +51 -0
- package/src/apps/shell/shell.ts +30 -0
- package/src/apps/shell/utils/getGoogleAuthUserNumber.ts +23 -0
- package/src/config/clusters.ts +45 -0
- package/src/config/constants.ts +5 -0
- package/src/index.ts +17 -0
- package/src/k8sApi/index.ts +17 -0
- package/src/packageInfos.ts +4 -0
- package/src/types/child-process-promise.d.ts +1 -0
- package/src/types/command-exists-promise.d.ts +1 -0
- package/src/types/git-repo-name.d.ts +1 -0
- package/src/types/types.ts +20 -0
- package/src/types/yawn-yaml.d.ts +1 -0
- package/src/utils/cluster.ts +21 -0
- package/src/utils/dashboardToken.ts +20 -0
- package/src/utils/files.ts +18 -0
- package/src/utils/formatEnvVars.ts +7 -0
- package/src/utils/getEditor.ts +16 -0
- package/src/utils/gitlab.ts +80 -0
- package/src/utils/log.ts +13 -0
- package/src/utils/passwordstore/index.ts +192 -0
- package/src/utils/portForward.ts +52 -0
- package/src/utils/preferences/index.ts +33 -0
- package/src/utils/projects/index.ts +171 -0
- package/src/utils/promise.ts +11 -0
- package/src/utils/shell.ts +20 -0
- package/tsconfig.json +20 -0
package/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
14
|
package/CONTRIBUTING.md
ADDED
|
@@ -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
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,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
|
+
});
|