@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
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { exec } from "child-process-promise";
|
|
2
|
+
import { last } from "lodash";
|
|
3
|
+
import open from "open";
|
|
4
|
+
import Vorpal, { CommandInstance } from "vorpal";
|
|
5
|
+
import {
|
|
6
|
+
doGitlabRequest,
|
|
7
|
+
getGitlabToken,
|
|
8
|
+
getProjectInfo,
|
|
9
|
+
} from "../../../../utils/gitlab";
|
|
10
|
+
|
|
11
|
+
const statusEmojiMap: any = {
|
|
12
|
+
failed: "🙀",
|
|
13
|
+
warning: "😿",
|
|
14
|
+
pending: "🍺",
|
|
15
|
+
running: "🏃",
|
|
16
|
+
manual: "🤚",
|
|
17
|
+
scheduled: "🍺",
|
|
18
|
+
canceled: "😽",
|
|
19
|
+
success: "😻",
|
|
20
|
+
skipped: "🤭",
|
|
21
|
+
created: "🍺",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const statusTxt = (status: any) =>
|
|
25
|
+
statusEmojiMap[status] ? `${statusEmojiMap[status]} ${status}` : status;
|
|
26
|
+
|
|
27
|
+
const getCurrentCommit = async () => {
|
|
28
|
+
const result = await exec("git rev-parse HEAD");
|
|
29
|
+
return result.stdout && result.stdout.replace(/\n$/, "");
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const delay = async (ms: any) =>
|
|
33
|
+
new Promise((resolve) => setTimeout(resolve, ms));
|
|
34
|
+
|
|
35
|
+
const promptJob = async (vorpal: CommandInstance, projectId: any, ctx: any) => {
|
|
36
|
+
const jobs = await doGitlabRequest(vorpal, `projects/${projectId}/jobs`);
|
|
37
|
+
|
|
38
|
+
const commitId = await getCurrentCommit();
|
|
39
|
+
|
|
40
|
+
const jobsToName = (jo: any[]) =>
|
|
41
|
+
jo.map(
|
|
42
|
+
(j: any) => `${j.ref}-${j.name}-${j.user.username}-${j.status}-${j.id}`
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const preferredJobs = jobs.filter((j: any) => j.commit.id === commitId);
|
|
46
|
+
const moreJobs = jobs.filter((j: any) => !preferredJobs.includes(j.ref));
|
|
47
|
+
const sortedJobs =
|
|
48
|
+
preferredJobs.length > 1
|
|
49
|
+
? [
|
|
50
|
+
...jobsToName(preferredJobs),
|
|
51
|
+
"========================================================",
|
|
52
|
+
...jobsToName(moreJobs),
|
|
53
|
+
]
|
|
54
|
+
: jobsToName([...preferredJobs, ...moreJobs]);
|
|
55
|
+
|
|
56
|
+
const { jobName } = await ctx.prompt({
|
|
57
|
+
type: "list",
|
|
58
|
+
name: "jobName",
|
|
59
|
+
choices: sortedJobs,
|
|
60
|
+
message: "Which job? 🤔",
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const jobId = Number(last(jobName.split("-")));
|
|
64
|
+
return jobs.find((j: any) => j.id === jobId);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export default (vorpal: Vorpal) => {
|
|
68
|
+
vorpal.command("project-ci-job-open", "Open a Job").action(async function () {
|
|
69
|
+
const { id: projectId } = await getProjectInfo(this);
|
|
70
|
+
|
|
71
|
+
const job = await promptJob(this, projectId, this);
|
|
72
|
+
open(job.web_url);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
vorpal
|
|
76
|
+
.command("project-ci-job-log", "Show a job's log")
|
|
77
|
+
.action(async function () {
|
|
78
|
+
const { id: projectId } = await getProjectInfo(this);
|
|
79
|
+
|
|
80
|
+
const { id } = await promptJob(this, projectId, this);
|
|
81
|
+
|
|
82
|
+
let finished = false;
|
|
83
|
+
while (!finished) {
|
|
84
|
+
const trace = await exec(
|
|
85
|
+
`curl -s --header "PRIVATE-TOKEN: ${await getGitlabToken(
|
|
86
|
+
this
|
|
87
|
+
)}" "https://git.panter.ch/api/v4/projects/${projectId}/jobs/${id}/trace"`
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const job = await doGitlabRequest(
|
|
91
|
+
this,
|
|
92
|
+
`projects/${projectId}/jobs/${id}`
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
if (trace.stdout) {
|
|
96
|
+
vorpal.ui.redraw(`${trace.stdout}`);
|
|
97
|
+
} else {
|
|
98
|
+
vorpal.ui.redraw(`
|
|
99
|
+
|
|
100
|
+
${statusTxt(job.status)}
|
|
101
|
+
${job.web_url}
|
|
102
|
+
|
|
103
|
+
`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
finished = !!job.finished_at;
|
|
107
|
+
if (!finished) {
|
|
108
|
+
await delay(5000);
|
|
109
|
+
} else {
|
|
110
|
+
vorpal.ui.redraw.done();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import open from "open";
|
|
2
|
+
import Vorpal from "vorpal";
|
|
3
|
+
import { getClusterByName } from "../../../../utils/cluster";
|
|
4
|
+
import { doGitlabRequest, getProjectInfo } from "../../../../utils/gitlab";
|
|
5
|
+
import { readPass, syncBitwarden } from "../../../../utils/passwordstore/";
|
|
6
|
+
import {
|
|
7
|
+
getLocalProjectVariables,
|
|
8
|
+
getProjectNamespace,
|
|
9
|
+
} from "../../../../utils/projects";
|
|
10
|
+
|
|
11
|
+
export default (vorpal: Vorpal) =>
|
|
12
|
+
vorpal
|
|
13
|
+
.command(
|
|
14
|
+
"project-init-gitlab",
|
|
15
|
+
"Initializes the gitlab repo, e.g. connects the cluster to it"
|
|
16
|
+
)
|
|
17
|
+
.action(async function () {
|
|
18
|
+
const { id: projectId, web_url: projectWebUrl } = await getProjectInfo(
|
|
19
|
+
this
|
|
20
|
+
);
|
|
21
|
+
await syncBitwarden();
|
|
22
|
+
const clusters = await doGitlabRequest(
|
|
23
|
+
this,
|
|
24
|
+
`projects/${projectId}/clusters`
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
if (clusters.length === 0) {
|
|
28
|
+
this.log("");
|
|
29
|
+
this.log("there is no cluster on the current project?");
|
|
30
|
+
} else {
|
|
31
|
+
this.log("there are already these clusters on the gitlab: ");
|
|
32
|
+
this.log("");
|
|
33
|
+
clusters.forEach((cluster: any) => this.log(` - ${cluster.name}`));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.log("");
|
|
37
|
+
this.log("your project specifies the following cluster:");
|
|
38
|
+
this.log("");
|
|
39
|
+
const { CLUSTER_NAME } = await getLocalProjectVariables();
|
|
40
|
+
const configuredCluster = await getClusterByName(CLUSTER_NAME);
|
|
41
|
+
if (!configuredCluster) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`there exist no cluster with the name '${CLUSTER_NAME}'`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
this.log(`${CLUSTER_NAME} (${configuredCluster.fullName})`);
|
|
47
|
+
this.log("");
|
|
48
|
+
const { shouldContinue } = await this.prompt({
|
|
49
|
+
type: "confirm",
|
|
50
|
+
name: "shouldContinue",
|
|
51
|
+
message: "Should I add the this cluster ? 🤔 ",
|
|
52
|
+
});
|
|
53
|
+
this.log("");
|
|
54
|
+
if (shouldContinue) {
|
|
55
|
+
const { api_url, passCredentials } = configuredCluster;
|
|
56
|
+
if (!api_url) {
|
|
57
|
+
throw new Error("no api_url on this cluster!");
|
|
58
|
+
}
|
|
59
|
+
if (!passCredentials) {
|
|
60
|
+
throw new Error("no passCredentials on this cluster!");
|
|
61
|
+
}
|
|
62
|
+
const token = await readPass(passCredentials.token);
|
|
63
|
+
const ca_cert = await readPass(passCredentials.ca_cert);
|
|
64
|
+
|
|
65
|
+
const postResult = await doGitlabRequest(
|
|
66
|
+
this,
|
|
67
|
+
`projects/${projectId}/clusters/user`,
|
|
68
|
+
{
|
|
69
|
+
name: configuredCluster.fullName,
|
|
70
|
+
managed: false,
|
|
71
|
+
platform_kubernetes_attributes: {
|
|
72
|
+
api_url,
|
|
73
|
+
ca_cert,
|
|
74
|
+
token,
|
|
75
|
+
namespace: await getProjectNamespace("prod"),
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
const { message } = postResult;
|
|
80
|
+
if (message) {
|
|
81
|
+
this.log(`Message from gitlab: ${message}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const variables = await doGitlabRequest(
|
|
86
|
+
this,
|
|
87
|
+
`projects/${projectId}/variables`
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
if (!variables.find((v: any) => v.key === "GL_TOKEN")) {
|
|
91
|
+
this.log(
|
|
92
|
+
"I need add a GL_TOKEN to the project, so that semantic release will work\n"
|
|
93
|
+
);
|
|
94
|
+
this.log(
|
|
95
|
+
"👉 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"
|
|
96
|
+
);
|
|
97
|
+
this.log("\n");
|
|
98
|
+
|
|
99
|
+
const { understood } = await this.prompt({
|
|
100
|
+
default: true,
|
|
101
|
+
message: "Understood and open gitlab now? 🤔",
|
|
102
|
+
name: "understood",
|
|
103
|
+
type: "confirm",
|
|
104
|
+
});
|
|
105
|
+
if (!understood) {
|
|
106
|
+
this.log("continuing anyway...");
|
|
107
|
+
}
|
|
108
|
+
open(`${projectWebUrl}/-/settings/access_tokens`);
|
|
109
|
+
|
|
110
|
+
this.log("\n");
|
|
111
|
+
|
|
112
|
+
this.log("Enter your copied token now: ");
|
|
113
|
+
|
|
114
|
+
this.log("\n");
|
|
115
|
+
const { GL_TOKEN } = await this.prompt({
|
|
116
|
+
type: "password",
|
|
117
|
+
name: "GL_TOKEN",
|
|
118
|
+
message: "Access Token: ",
|
|
119
|
+
});
|
|
120
|
+
await doGitlabRequest(this, `projects/${projectId}/variables`, {
|
|
121
|
+
key: "GL_TOKEN",
|
|
122
|
+
value: GL_TOKEN,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const deploy_tokens = await doGitlabRequest(
|
|
127
|
+
this,
|
|
128
|
+
`projects/${projectId}/deploy_tokens`
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
if (
|
|
132
|
+
!deploy_tokens.find(
|
|
133
|
+
(v: { name: string }) => v.name === "gitlab-deploy-token"
|
|
134
|
+
)
|
|
135
|
+
) {
|
|
136
|
+
this.log(
|
|
137
|
+
"I will setup the 'GitLab Deploy Token', so Kubernetes can pull images from this project."
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
await doGitlabRequest(this, `projects/${projectId}/deploy_tokens`, {
|
|
141
|
+
id: projectId,
|
|
142
|
+
name: "gitlab-deploy-token",
|
|
143
|
+
scopes: ["read_registry"],
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
this.log("gitlab is ready! 🥂");
|
|
147
|
+
this.log("\n");
|
|
148
|
+
this.log("do not forget to make sure that:");
|
|
149
|
+
[
|
|
150
|
+
"you have __health route in place",
|
|
151
|
+
"lint and test are defined",
|
|
152
|
+
"eat your vegetables",
|
|
153
|
+
"be awesome 🤩",
|
|
154
|
+
].forEach((tip) => this.log(` - ${tip}`));
|
|
155
|
+
this.log("\n");
|
|
156
|
+
this.log("\n");
|
|
157
|
+
});
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { readFile, writeFile } from "fs-extra";
|
|
2
|
+
import { safeDump } from "js-yaml";
|
|
3
|
+
import { mapKeys, snakeCase, toUpper } from "lodash";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import Vorpal from "vorpal";
|
|
6
|
+
import { hasGitlabCiFile } from "../../../../utils/projects";
|
|
7
|
+
|
|
8
|
+
const transformVar = (key: string) => toUpper(snakeCase(key));
|
|
9
|
+
type GitlabCiIncludeObj = {
|
|
10
|
+
project: string;
|
|
11
|
+
ref: string;
|
|
12
|
+
file: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type FileToCreate = {
|
|
16
|
+
filename: string;
|
|
17
|
+
content: string | (() => Promise<string>);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type PlatformDefinition = {
|
|
21
|
+
variables: {
|
|
22
|
+
[key: string]: string;
|
|
23
|
+
};
|
|
24
|
+
gitlabCiInclude: GitlabCiIncludeObj;
|
|
25
|
+
values: { [key: string]: unknown };
|
|
26
|
+
filesToCreate?: FileToCreate[];
|
|
27
|
+
};
|
|
28
|
+
const DEFAULT_VARIABLES = {
|
|
29
|
+
clusterName: "production",
|
|
30
|
+
};
|
|
31
|
+
// TODO: we should find a way how to fetch the variables of the inclues directly from gitlab
|
|
32
|
+
const DEFAULT_NODE_APPLICATION = {};
|
|
33
|
+
|
|
34
|
+
const DEFAULT_FILES_TO_CREATE: FileToCreate[] = [
|
|
35
|
+
{
|
|
36
|
+
filename: ".envrc",
|
|
37
|
+
content: async () =>
|
|
38
|
+
readFile(path.resolve(__dirname, "../includes/envrc"), "utf8"),
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
const PLATFORMS: { [platformName: string]: PlatformDefinition } = {
|
|
42
|
+
nextJS: {
|
|
43
|
+
variables: {},
|
|
44
|
+
gitlabCiInclude: {
|
|
45
|
+
project: "catladder/gitlab-ci",
|
|
46
|
+
ref: "v1",
|
|
47
|
+
file: "node-kubernetes.yml",
|
|
48
|
+
},
|
|
49
|
+
values: {
|
|
50
|
+
application: DEFAULT_NODE_APPLICATION,
|
|
51
|
+
},
|
|
52
|
+
filesToCreate: [
|
|
53
|
+
{
|
|
54
|
+
content: "v14",
|
|
55
|
+
filename: ".nvmrc",
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
nestJS: {
|
|
60
|
+
variables: {},
|
|
61
|
+
gitlabCiInclude: {
|
|
62
|
+
project: "catladder/gitlab-ci",
|
|
63
|
+
ref: "v1",
|
|
64
|
+
file: "node-kubernetes.yml",
|
|
65
|
+
},
|
|
66
|
+
values: {
|
|
67
|
+
application: {
|
|
68
|
+
command: "yarn start:prod --port $(PORT)",
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
filesToCreate: [
|
|
72
|
+
{
|
|
73
|
+
content: "v14",
|
|
74
|
+
filename: ".nvmrc",
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
node: {
|
|
79
|
+
variables: {},
|
|
80
|
+
gitlabCiInclude: {
|
|
81
|
+
project: "catladder/gitlab-ci",
|
|
82
|
+
ref: "v1",
|
|
83
|
+
file: "node-kubernetes.yml",
|
|
84
|
+
},
|
|
85
|
+
values: {
|
|
86
|
+
application: DEFAULT_NODE_APPLICATION,
|
|
87
|
+
},
|
|
88
|
+
filesToCreate: [
|
|
89
|
+
{
|
|
90
|
+
content: "v14",
|
|
91
|
+
filename: ".nvmrc",
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
staticJs: {
|
|
96
|
+
variables: {},
|
|
97
|
+
gitlabCiInclude: {
|
|
98
|
+
project: "catladder/gitlab-ci",
|
|
99
|
+
ref: "v1",
|
|
100
|
+
file: "static-js-kubernetes.yml",
|
|
101
|
+
},
|
|
102
|
+
values: {},
|
|
103
|
+
},
|
|
104
|
+
meteor: {
|
|
105
|
+
variables: {},
|
|
106
|
+
gitlabCiInclude: {
|
|
107
|
+
project: "gitlab-ci/meteor-kubernetes",
|
|
108
|
+
ref: "v1",
|
|
109
|
+
file: "meteor-kubernetes.yml",
|
|
110
|
+
},
|
|
111
|
+
values: {},
|
|
112
|
+
},
|
|
113
|
+
rails: {
|
|
114
|
+
variables: {},
|
|
115
|
+
gitlabCiInclude: {
|
|
116
|
+
project: "catladder/gitlab-ci",
|
|
117
|
+
ref: "v1",
|
|
118
|
+
file: "rails-kubernetes.yml",
|
|
119
|
+
},
|
|
120
|
+
values: {
|
|
121
|
+
env: {
|
|
122
|
+
public: {
|
|
123
|
+
RAILS_ENV: "production",
|
|
124
|
+
PORT: 8080,
|
|
125
|
+
},
|
|
126
|
+
secret: {
|
|
127
|
+
POSTGRESQL_PASSWORD: "app-secrets",
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
application: {
|
|
131
|
+
command: ["/cnb/process/web"],
|
|
132
|
+
livenessProbe: {
|
|
133
|
+
httpGet: {
|
|
134
|
+
path: "/robots.txt",
|
|
135
|
+
httpHeaders: null,
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
readinessProbe: {
|
|
139
|
+
httpGet: {
|
|
140
|
+
path: "/robots.txt",
|
|
141
|
+
httpHeaders: null,
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
cloudsql: { enabled: true },
|
|
146
|
+
jobs: {
|
|
147
|
+
"db-prepare": {
|
|
148
|
+
hook: "post-install,post-upgrade",
|
|
149
|
+
command: "/cnb/lifecycle/launcher bundle exec rake db:prepare",
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
filesToCreate: [
|
|
154
|
+
{
|
|
155
|
+
filename: "values-review.yml",
|
|
156
|
+
content: async () =>
|
|
157
|
+
safeDump({
|
|
158
|
+
jobs: {
|
|
159
|
+
"db-prepare-seed": {
|
|
160
|
+
hook: "post-install",
|
|
161
|
+
command:
|
|
162
|
+
"/cnb/lifecycle/launcher bundle exec rake db:prepare db:seed",
|
|
163
|
+
},
|
|
164
|
+
"db-prepare": {
|
|
165
|
+
hook: "post-upgrade",
|
|
166
|
+
command: "/cnb/lifecycle/launcher bundle exec rake db:prepare",
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
}),
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
other: {
|
|
174
|
+
variables: {},
|
|
175
|
+
gitlabCiInclude: {
|
|
176
|
+
project: "catladder/gitlab-ci",
|
|
177
|
+
ref: "v1",
|
|
178
|
+
file: "panter-kubernetes-base.yml",
|
|
179
|
+
},
|
|
180
|
+
values: {},
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
export default (vorpal: Vorpal) =>
|
|
184
|
+
vorpal
|
|
185
|
+
.command("project-init", "Inits a new project")
|
|
186
|
+
.action(async function () {
|
|
187
|
+
const hasGitlabFile = await hasGitlabCiFile();
|
|
188
|
+
if (hasGitlabFile) {
|
|
189
|
+
this.log("there is already a gitlab-ci.yml file. Skipping");
|
|
190
|
+
} else {
|
|
191
|
+
this.log("Alright. Let's do this! 😼");
|
|
192
|
+
this.log("");
|
|
193
|
+
|
|
194
|
+
const questions = [
|
|
195
|
+
{
|
|
196
|
+
type: "input",
|
|
197
|
+
name: "customerName",
|
|
198
|
+
default: "pan",
|
|
199
|
+
message: "Which customer?",
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
type: "input",
|
|
203
|
+
name: "appName",
|
|
204
|
+
message: "What is the app's name?",
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
type: "input",
|
|
208
|
+
name: "componentName",
|
|
209
|
+
default: "web",
|
|
210
|
+
message:
|
|
211
|
+
"And the componentName?\n(in multi-app projects, you have to chose a different componentName for each app, e.g. frontend and backend)\n\n",
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
type: "list",
|
|
215
|
+
name: "platform",
|
|
216
|
+
choices: Object.keys(PLATFORMS),
|
|
217
|
+
message: "What is your app platform?",
|
|
218
|
+
},
|
|
219
|
+
].map((q) => ({
|
|
220
|
+
...q,
|
|
221
|
+
validate: Boolean,
|
|
222
|
+
message: `${q.message} `,
|
|
223
|
+
}));
|
|
224
|
+
const { customerName, appName, componentName, platform } =
|
|
225
|
+
await this.prompt(questions);
|
|
226
|
+
|
|
227
|
+
const {
|
|
228
|
+
gitlabCiInclude,
|
|
229
|
+
variables: platformVariables,
|
|
230
|
+
values,
|
|
231
|
+
filesToCreate,
|
|
232
|
+
} = PLATFORMS[platform];
|
|
233
|
+
|
|
234
|
+
const allVariables = {
|
|
235
|
+
customerName,
|
|
236
|
+
appName,
|
|
237
|
+
componentName,
|
|
238
|
+
...DEFAULT_VARIABLES,
|
|
239
|
+
...platformVariables,
|
|
240
|
+
};
|
|
241
|
+
const allFilesToCreate = [
|
|
242
|
+
...DEFAULT_FILES_TO_CREATE,
|
|
243
|
+
...(filesToCreate ?? []),
|
|
244
|
+
];
|
|
245
|
+
const transformedVariables = mapKeys(allVariables, (value, key) =>
|
|
246
|
+
transformVar(key)
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
const gitlabCiContentObj = {
|
|
250
|
+
include: [gitlabCiInclude],
|
|
251
|
+
variables: transformedVariables,
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const gitlabCiContent = safeDump(gitlabCiContentObj);
|
|
255
|
+
await writeFile(".gitlab-ci.yml", gitlabCiContent);
|
|
256
|
+
|
|
257
|
+
allFilesToCreate.forEach(async ({ filename, content }) => {
|
|
258
|
+
const theContent =
|
|
259
|
+
typeof content === "function" ? await content() : content;
|
|
260
|
+
await writeFile(filename, theContent);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
await writeFile("values.yml", safeDump(values));
|
|
264
|
+
|
|
265
|
+
this.log("");
|
|
266
|
+
this.log("gitlab-ci created! 💪😻");
|
|
267
|
+
this.log("feel free to adjust it to your needs!");
|
|
268
|
+
this.log("");
|
|
269
|
+
this.log("we also create an .envrc for you which works with direnv");
|
|
270
|
+
this.log("");
|
|
271
|
+
}
|
|
272
|
+
const { shouldContinue } = await this.prompt({
|
|
273
|
+
default: true,
|
|
274
|
+
message:
|
|
275
|
+
"should we continue with setting up your gitlab? 🤔 (connecting cluster and stuff)",
|
|
276
|
+
name: "shouldContinue",
|
|
277
|
+
type: "confirm",
|
|
278
|
+
});
|
|
279
|
+
if (shouldContinue) {
|
|
280
|
+
await vorpal.execSync(`project-init-gitlab`);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import yaml from "js-yaml";
|
|
2
|
+
import { pick } from "lodash";
|
|
3
|
+
import Vorpal from "vorpal";
|
|
4
|
+
import { getProjectPods } from "../../../../utils/projects";
|
|
5
|
+
|
|
6
|
+
import { envAutocompletion } from "./utils/autocompletions";
|
|
7
|
+
import ensureCluster from "./utils/ensureCluster";
|
|
8
|
+
|
|
9
|
+
export default (vorpal: Vorpal) =>
|
|
10
|
+
vorpal
|
|
11
|
+
.command("project-list-pods <env>", "list pods of local project")
|
|
12
|
+
.autocomplete(envAutocompletion)
|
|
13
|
+
.action(async function({ env }) {
|
|
14
|
+
await ensureCluster.call(this);
|
|
15
|
+
const pods = await getProjectPods(env);
|
|
16
|
+
this.log(
|
|
17
|
+
yaml.safeDump(
|
|
18
|
+
pods.map(p => pick(p, ["metadata.name", "status.startTime"]))
|
|
19
|
+
)
|
|
20
|
+
);
|
|
21
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { exec } from "child-process-promise";
|
|
2
|
+
import Vorpal from "vorpal";
|
|
3
|
+
import { logError } from "../../../../utils/log";
|
|
4
|
+
import { getProjectHelmReleaseName } from "../../../../utils/projects";
|
|
5
|
+
import { envAutocompletion } from "./utils/autocompletions";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Error: rendered manifests contain a resource that already exists.
|
|
9
|
+
* Unable to continue with install: ConfigMap "web-app-env" in namespace "pvl-bike2school-dev" exists and cannot be imported into the current release: invalid ownership metadata; label validation error: missing key "app.kubernetes.io/managed-by": must be set to "Helm"; annotation validation error: missing key "meta.helm.sh/release-name": must be set to "pvl-bike2school-dev-web"; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "pvl-bike2school-dev"
|
|
10
|
+
*/
|
|
11
|
+
export default (vorpal: Vorpal) =>
|
|
12
|
+
vorpal
|
|
13
|
+
.command(
|
|
14
|
+
"project-migrate-helm3 <env>",
|
|
15
|
+
"Do manual step for helm2 to helm3 migration"
|
|
16
|
+
)
|
|
17
|
+
.autocomplete(
|
|
18
|
+
envAutocompletion.filter((e) => e !== "dev-local" && e !== "review")
|
|
19
|
+
)
|
|
20
|
+
.action(async function ({ env }) {
|
|
21
|
+
try {
|
|
22
|
+
const result = await exec("helm version --short");
|
|
23
|
+
const version = result.stdout as string;
|
|
24
|
+
this.log(`your helm version: ${version}`);
|
|
25
|
+
if (!version.startsWith("v3")) {
|
|
26
|
+
throw new Error("no helm3");
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
await exec(
|
|
30
|
+
"helm plugin install https://github.com/helm/helm-2to3.git"
|
|
31
|
+
);
|
|
32
|
+
this.log(
|
|
33
|
+
"successfully installed plugin https://github.com/helm/helm-2to3.git"
|
|
34
|
+
);
|
|
35
|
+
} catch (e) {
|
|
36
|
+
// ignore
|
|
37
|
+
}
|
|
38
|
+
const releaseName = await getProjectHelmReleaseName(env);
|
|
39
|
+
this.log(`helm release name: ${releaseName}`);
|
|
40
|
+
this.log("");
|
|
41
|
+
this.log("migrating now... 😼. This may take a moment");
|
|
42
|
+
this.log("");
|
|
43
|
+
const r = await exec(
|
|
44
|
+
`helm 2to3 convert --delete-v2-releases ${releaseName}`
|
|
45
|
+
);
|
|
46
|
+
this.log(r);
|
|
47
|
+
} catch (e) {
|
|
48
|
+
logError(this, e.message);
|
|
49
|
+
this.log("make sure that you have installed latest helm3 locally");
|
|
50
|
+
this.log("(e.g. brew upgrade helm)");
|
|
51
|
+
this.log("");
|
|
52
|
+
}
|
|
53
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import Vorpal from "vorpal";
|
|
2
|
+
import { Env } from "../../../../types/types";
|
|
3
|
+
import { getProjectNamespace } from "../../../../utils/projects";
|
|
4
|
+
import { envAutocompletion } from "./utils/autocompletions";
|
|
5
|
+
|
|
6
|
+
export default (vorpal: Vorpal) =>
|
|
7
|
+
vorpal
|
|
8
|
+
.command("project-namespace <env>", "show namespace of local project")
|
|
9
|
+
.autocomplete(envAutocompletion)
|
|
10
|
+
.action(async function({ env }) {
|
|
11
|
+
this.log(await getProjectNamespace(env as Env));
|
|
12
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import open from "open";
|
|
2
|
+
import Vorpal from "vorpal";
|
|
3
|
+
import {
|
|
4
|
+
GRAFANA_PROXY_LOCAL_PORT,
|
|
5
|
+
GRAFANA_PROXY_TARGET_PORT,
|
|
6
|
+
} from "../../../../config/constants";
|
|
7
|
+
|
|
8
|
+
import { startPortForward } from "../../../../utils/portForward";
|
|
9
|
+
|
|
10
|
+
import { getProjectNamespace } from "../../../../utils/projects";
|
|
11
|
+
|
|
12
|
+
import { envAutocompletion } from "./utils/autocompletions";
|
|
13
|
+
import ensureCluster from "./utils/ensureCluster";
|
|
14
|
+
export default (vorpal: Vorpal) =>
|
|
15
|
+
vorpal
|
|
16
|
+
.command("project-check-costs <env>", "Shows you how much you're spending")
|
|
17
|
+
.autocomplete(envAutocompletion)
|
|
18
|
+
.action(async function ({ env }) {
|
|
19
|
+
await ensureCluster.call(this);
|
|
20
|
+
const namespace = await getProjectNamespace(env);
|
|
21
|
+
const url = `http://localhost:${GRAFANA_PROXY_LOCAL_PORT}/namespace.html?name=${namespace}`;
|
|
22
|
+
await startPortForward(
|
|
23
|
+
"deployment/kubecost-cost-analyzer",
|
|
24
|
+
GRAFANA_PROXY_LOCAL_PORT,
|
|
25
|
+
GRAFANA_PROXY_TARGET_PORT,
|
|
26
|
+
"kubecost"
|
|
27
|
+
);
|
|
28
|
+
open(url);
|
|
29
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import Vorpal from "vorpal";
|
|
2
|
+
import { getCurrentConnectedClusterName } from "../../../../utils/cluster";
|
|
3
|
+
import { getProjectNamespace } from "../../../../utils/projects";
|
|
4
|
+
import { getGoogleAuthUserNumber } from "../../utils/getGoogleAuthUserNumber";
|
|
5
|
+
import { openGoogleCloudKubernetesDashboard } from "../shared";
|
|
6
|
+
import { envAutocompletion } from "./utils/autocompletions";
|
|
7
|
+
import ensureCluster from "./utils/ensureCluster";
|
|
8
|
+
|
|
9
|
+
export default (vorpal: Vorpal) =>
|
|
10
|
+
vorpal
|
|
11
|
+
.command(
|
|
12
|
+
"project-open-dashboard <env>",
|
|
13
|
+
"open kubernetes dashboard on google"
|
|
14
|
+
)
|
|
15
|
+
.autocomplete(envAutocompletion)
|
|
16
|
+
.action(async function ({ env }) {
|
|
17
|
+
await ensureCluster.call(this);
|
|
18
|
+
const clustername = await getCurrentConnectedClusterName();
|
|
19
|
+
const namespace = await getProjectNamespace(env);
|
|
20
|
+
const authGoogleNumber = await getGoogleAuthUserNumber.call(this, vorpal);
|
|
21
|
+
|
|
22
|
+
openGoogleCloudKubernetesDashboard(
|
|
23
|
+
authGoogleNumber,
|
|
24
|
+
clustername,
|
|
25
|
+
namespace
|
|
26
|
+
);
|
|
27
|
+
});
|