@catladder/cli 0.0.0-refactor-image-ca767657 → 0.0.0-refactor-gcloud-auth-no-file-b43bf0d9
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/CONTRIBUTING.md +17 -12
- package/README.md +19 -12
- package/dist/apps/cli/commands/cloudSQL/commandRestoreDb.js +12 -7
- package/dist/apps/cli/commands/cloudSQL/commandRestoreDb.js.map +1 -1
- package/dist/apps/cli/commands/project/cloudSql/commandProjectRestoreDb.d.ts +3 -0
- package/dist/apps/cli/commands/project/cloudSql/commandProjectRestoreDb.js +189 -0
- package/dist/apps/cli/commands/project/cloudSql/commandProjectRestoreDb.js.map +1 -0
- package/dist/apps/cli/commands/project/commandConfigSecrets.js +3 -0
- package/dist/apps/cli/commands/project/commandConfigSecrets.js.map +1 -1
- package/dist/apps/cli/commands/project/index.js +2 -0
- package/dist/apps/cli/commands/project/index.js.map +1 -1
- package/dist/bundles/catenv/index.js +549 -81
- package/dist/bundles/cli/index.js +1128 -237
- package/dist/gcloud/cloudSql/copyDb.d.ts +12 -0
- package/dist/gcloud/cloudSql/copyDb.js +64 -0
- package/dist/gcloud/cloudSql/copyDb.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +51 -2
- package/src/apps/cli/commands/cloudSQL/commandRestoreDb.ts +11 -29
- package/src/apps/cli/commands/project/cloudSql/commandProjectRestoreDb.ts +175 -0
- package/src/apps/cli/commands/project/commandConfigSecrets.ts +3 -0
- package/src/apps/cli/commands/project/index.ts +2 -0
- package/src/gcloud/cloudSql/copyDb.ts +57 -0
package/package.json
CHANGED
|
@@ -1,9 +1,58 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@catladder/cli",
|
|
3
|
+
"description": "Panter cli tool for Kubernetes",
|
|
3
4
|
"author": "Marco Wettstein <maw@panter.ch>",
|
|
4
5
|
"homepage": "https://git.panter.ch/catladder/catladder/#readme",
|
|
5
6
|
"bugs": "https://git.panter.ch/catladder/catladder/issues",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://git.panter.ch/catladder/catladder.git",
|
|
10
|
+
"directory": "cli"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"GitLab",
|
|
14
|
+
"cloud",
|
|
15
|
+
"cli",
|
|
16
|
+
"gitlab-ci",
|
|
17
|
+
"ci/cd",
|
|
18
|
+
"pipeline",
|
|
19
|
+
"deployment",
|
|
20
|
+
"devops",
|
|
21
|
+
"automation",
|
|
22
|
+
"typescript"
|
|
23
|
+
],
|
|
24
|
+
"contributors": [
|
|
25
|
+
{
|
|
26
|
+
"name": "Marco Wettstein",
|
|
27
|
+
"email": "maw@panter.ch"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "Michael Leu",
|
|
31
|
+
"email": "leu@panter.ch"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "Luboš Matejčík",
|
|
35
|
+
"email": "lma@panter.ch"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "Kaspar Vollenweider",
|
|
39
|
+
"email": "vok@panter.ch"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"name": "Silvan Adrian",
|
|
43
|
+
"email": "sia@panter.ch"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"name": "Stefan Bakocs",
|
|
47
|
+
"email": "stb@panter.ch"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": "Lukas Brezina",
|
|
51
|
+
"email": "brl@panter.ch"
|
|
52
|
+
}
|
|
53
|
+
],
|
|
6
54
|
"license": "MIT",
|
|
55
|
+
"version": "0.0.0-refactor-gcloud-auth-no-file-b43bf0d9",
|
|
7
56
|
"scripts": {
|
|
8
57
|
"lint": "eslint \"src/**/*.ts\"",
|
|
9
58
|
"lint:fix": "eslint \"src/**/*.ts\" --fix",
|
|
@@ -24,7 +73,7 @@
|
|
|
24
73
|
"node": ">=12.0.0"
|
|
25
74
|
},
|
|
26
75
|
"devDependencies": {
|
|
27
|
-
"@catladder/pipeline": "0.0.0-refactor-
|
|
76
|
+
"@catladder/pipeline": "0.0.0-refactor-gcloud-auth-no-file-b43bf0d9",
|
|
28
77
|
"@kubernetes/client-node": "^0.16.2",
|
|
29
78
|
"@tsconfig/node14": "^1.0.1",
|
|
30
79
|
"@types/common-tags": "^1.8.0",
|
|
@@ -43,6 +92,7 @@
|
|
|
43
92
|
"clipboardy": "^2.0.0",
|
|
44
93
|
"command-exists-promise": "^2.0.2",
|
|
45
94
|
"common-tags": "^1.8.0",
|
|
95
|
+
"connection-string-parser": "^1.0.4",
|
|
46
96
|
"dayjs": "^1.10.4",
|
|
47
97
|
"eslint": "^8.7.0",
|
|
48
98
|
"fs-extra": "^7.0.1",
|
|
@@ -57,6 +107,5 @@
|
|
|
57
107
|
"typescript": "^4.5.4",
|
|
58
108
|
"vorpal": "^1.12.0"
|
|
59
109
|
},
|
|
60
|
-
"version": "0.0.0-refactor-image-ca767657",
|
|
61
110
|
"stableVersion": "0.0.0"
|
|
62
111
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { spawn } from "child-process-promise";
|
|
2
|
-
|
|
3
1
|
import type Vorpal from "vorpal";
|
|
2
|
+
import { spawnCopyDb } from "../../../../gcloud/cloudSql/copyDb";
|
|
4
3
|
import type { CloudSqlBackgroundProxy } from "../../../../gcloud/cloudSql/startProxy";
|
|
5
4
|
import { startCloudSqlProxyInBackground } from "../../../../gcloud/cloudSql/startProxy";
|
|
6
5
|
|
|
@@ -119,34 +118,17 @@ export default async (vorpal: Vorpal) =>
|
|
|
119
118
|
return;
|
|
120
119
|
}
|
|
121
120
|
|
|
122
|
-
const targetPSQL = (command: string) =>
|
|
123
|
-
`PGPASSWORD=${targetPassword} psql -p ${targetPort} --host=localhost --user=${targetUsername} -q ${command}`;
|
|
124
|
-
|
|
125
|
-
const copyDBScript = `
|
|
126
|
-
set -e
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
dumptmp=$(mktemp /tmp/dump.XXXXXX)
|
|
131
|
-
|
|
132
|
-
echo "Dumping file to $dumptmp"
|
|
133
|
-
pg_dump --dbname=postgres://${sourceUsername}:${sourcePassword}@localhost:${sourcePort}/${sourceDbName} --no-owner --no-privileges > $dumptmp
|
|
134
|
-
echo "dump done"
|
|
135
|
-
${targetPSQL(
|
|
136
|
-
`-c 'drop database "${targetDbName}" WITH (FORCE)' 1> /dev/null || true`
|
|
137
|
-
)}
|
|
138
|
-
${targetPSQL(`-c 'create database "${targetDbName}"' 1> /dev/null`)}
|
|
139
|
-
echo "Restoring dump..."
|
|
140
|
-
${targetPSQL(`"${targetDbName}" < $dumptmp 1> /dev/null`)}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
echo "Clean up..."
|
|
144
|
-
set +e
|
|
145
|
-
rm $dumptmp
|
|
146
|
-
echo "\n🐱 Done!"
|
|
147
|
-
`;
|
|
148
121
|
try {
|
|
149
|
-
await
|
|
122
|
+
await spawnCopyDb({
|
|
123
|
+
targetPassword,
|
|
124
|
+
targetPort,
|
|
125
|
+
targetUsername,
|
|
126
|
+
sourceUsername,
|
|
127
|
+
sourcePassword,
|
|
128
|
+
sourcePort,
|
|
129
|
+
sourceDbName,
|
|
130
|
+
targetDbName,
|
|
131
|
+
});
|
|
150
132
|
} finally {
|
|
151
133
|
sourceProxy?.stop();
|
|
152
134
|
targetProxy?.stop();
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import type Vorpal from "vorpal";
|
|
2
|
+
import {
|
|
3
|
+
getEnvVarsResolved,
|
|
4
|
+
getPipelineContextByChoice,
|
|
5
|
+
} from "../../../../../config/getProjectConfig";
|
|
6
|
+
import type { CloudSqlBackgroundProxy } from "../../../../../gcloud/cloudSql/startProxy";
|
|
7
|
+
import { startCloudSqlProxyInBackground } from "../../../../../gcloud/cloudSql/startProxy";
|
|
8
|
+
import { envAndComponents } from "../utils/autocompletions";
|
|
9
|
+
|
|
10
|
+
import { ConnectionStringParser } from "connection-string-parser";
|
|
11
|
+
import { spawnCopyDb } from "../../../../../gcloud/cloudSql/copyDb";
|
|
12
|
+
|
|
13
|
+
export default async (vorpal: Vorpal) =>
|
|
14
|
+
vorpal
|
|
15
|
+
.command(
|
|
16
|
+
"project-cloud-sql-restore-db",
|
|
17
|
+
"restores a project db from one source to another target"
|
|
18
|
+
)
|
|
19
|
+
.action(async function restoreDb() {
|
|
20
|
+
const envs = await envAndComponents();
|
|
21
|
+
|
|
22
|
+
const { sourceEnvAndComponent } = await this.prompt({
|
|
23
|
+
type: "list",
|
|
24
|
+
name: "sourceEnvAndComponent",
|
|
25
|
+
choices: envs,
|
|
26
|
+
|
|
27
|
+
message: "Source instance (connection string or 'local')? 🤔 ",
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const [sourceEnv, sourceComponent] = sourceEnvAndComponent.split(":");
|
|
31
|
+
|
|
32
|
+
const sourceContext = await getPipelineContextByChoice(
|
|
33
|
+
sourceEnv,
|
|
34
|
+
sourceComponent
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const sourceEnvVars = await getEnvVarsResolved(
|
|
38
|
+
this,
|
|
39
|
+
sourceContext.environment.shortName,
|
|
40
|
+
sourceContext.componentName
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
let sourceProxy: CloudSqlBackgroundProxy;
|
|
44
|
+
let sourceDbName: string;
|
|
45
|
+
|
|
46
|
+
let sourceUsername: string;
|
|
47
|
+
let sourcePassword: string;
|
|
48
|
+
|
|
49
|
+
let targetUsername: string;
|
|
50
|
+
let targetPassword: string;
|
|
51
|
+
let targetProxy: CloudSqlBackgroundProxy;
|
|
52
|
+
|
|
53
|
+
let sourcePort: number;
|
|
54
|
+
let targetPort: number;
|
|
55
|
+
let targetDbName: string;
|
|
56
|
+
|
|
57
|
+
const closeAll = () => {
|
|
58
|
+
sourceProxy?.stop();
|
|
59
|
+
targetProxy?.stop();
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
if (sourceEnv === "local") {
|
|
63
|
+
const parser = new ConnectionStringParser({
|
|
64
|
+
scheme: "postgres",
|
|
65
|
+
hosts: [],
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const parsersResult = parser.parse(sourceEnvVars.DATABASE_URL);
|
|
69
|
+
sourcePort = parsersResult.hosts?.[0]?.port;
|
|
70
|
+
sourceUsername = parsersResult.username;
|
|
71
|
+
sourcePassword = parsersResult.password;
|
|
72
|
+
sourceDbName = parsersResult.endpoint;
|
|
73
|
+
} else {
|
|
74
|
+
sourcePort = 54399;
|
|
75
|
+
sourceProxy = await startCloudSqlProxyInBackground({
|
|
76
|
+
instanceName: sourceEnvVars.CLOUD_SQL_INSTANCE_CONNECTION_NAME,
|
|
77
|
+
localPort: sourcePort,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
sourceUsername = sourceEnvVars.DB_USER;
|
|
81
|
+
sourcePassword = sourceEnvVars.DB_PASSWORD;
|
|
82
|
+
sourceDbName = sourceEnvVars.DB_NAME;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const { targetEnvAndComponent } = await this.prompt({
|
|
86
|
+
type: "list",
|
|
87
|
+
name: "targetEnvAndComponent",
|
|
88
|
+
choices: envs,
|
|
89
|
+
|
|
90
|
+
message: "target env? 🤔 ",
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const [targetEnv, targetComponent] = targetEnvAndComponent.split(":");
|
|
94
|
+
|
|
95
|
+
const targetContext = await getPipelineContextByChoice(
|
|
96
|
+
targetEnv,
|
|
97
|
+
targetComponent
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const targetEnvVars = await getEnvVarsResolved(
|
|
101
|
+
this,
|
|
102
|
+
targetContext.environment.shortName,
|
|
103
|
+
targetContext.componentName
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
if (targetEnv === "local") {
|
|
107
|
+
const parser = new ConnectionStringParser({
|
|
108
|
+
scheme: "postgres",
|
|
109
|
+
hosts: [],
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const parsersResult = parser.parse(targetEnvVars.DATABASE_URL);
|
|
113
|
+
|
|
114
|
+
targetPort = parsersResult.hosts?.[0]?.port;
|
|
115
|
+
targetUsername = parsersResult.username;
|
|
116
|
+
targetPassword = parsersResult.password;
|
|
117
|
+
targetDbName = parsersResult.endpoint;
|
|
118
|
+
} else {
|
|
119
|
+
targetPort = 54499;
|
|
120
|
+
targetProxy = await startCloudSqlProxyInBackground({
|
|
121
|
+
instanceName: targetEnvVars.CLOUD_SQL_INSTANCE_CONNECTION_NAME,
|
|
122
|
+
localPort: targetPort,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
targetUsername = targetEnvVars.DB_USER;
|
|
126
|
+
targetPassword = targetEnvVars.DB_PASSWORD;
|
|
127
|
+
targetDbName = targetEnvVars.DB_NAME;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const { shouldContinue } = await this.prompt({
|
|
131
|
+
type: "confirm",
|
|
132
|
+
name: "shouldContinue",
|
|
133
|
+
message: `This will drop ${targetEnv}/${targetDbName} and replace it with ${sourceEnv}/${sourceDbName}. Continue? 🤔 `,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (!shouldContinue) {
|
|
137
|
+
this.log("abort");
|
|
138
|
+
closeAll();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (targetContext.environment.envType === "prod") {
|
|
143
|
+
this.log(
|
|
144
|
+
`\n🚨 You are overriding a production environment. Please type in ${targetEnvVars.CLOUD_SQL_INSTANCE_CONNECTION_NAME} to continue\n\n`
|
|
145
|
+
);
|
|
146
|
+
const { confirmInstance } = await this.prompt({
|
|
147
|
+
type: "input",
|
|
148
|
+
name: "confirmInstance",
|
|
149
|
+
message: "confirm: ",
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
if (
|
|
153
|
+
confirmInstance !== targetEnvVars.CLOUD_SQL_INSTANCE_CONNECTION_NAME
|
|
154
|
+
) {
|
|
155
|
+
this.log("abort");
|
|
156
|
+
closeAll();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
await spawnCopyDb({
|
|
163
|
+
targetPassword,
|
|
164
|
+
targetPort,
|
|
165
|
+
targetUsername,
|
|
166
|
+
sourceUsername,
|
|
167
|
+
sourcePassword,
|
|
168
|
+
sourcePort,
|
|
169
|
+
sourceDbName,
|
|
170
|
+
targetDbName,
|
|
171
|
+
});
|
|
172
|
+
} finally {
|
|
173
|
+
closeAll();
|
|
174
|
+
}
|
|
175
|
+
});
|
|
@@ -177,10 +177,12 @@ const doItFor = async (
|
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
|
+
instance.log("");
|
|
180
181
|
instance.log("upserting all variables, please wait...");
|
|
181
182
|
instance.log("");
|
|
182
183
|
for (const [componentName, envs] of Object.entries(envAndComponents)) {
|
|
183
184
|
for (const env of envs) {
|
|
185
|
+
instance.log("upserting " + env + ":" + componentName + "...\n");
|
|
184
186
|
await upsertAllVariables(
|
|
185
187
|
instance,
|
|
186
188
|
valuesToEdit[componentName][env],
|
|
@@ -189,6 +191,7 @@ const doItFor = async (
|
|
|
189
191
|
);
|
|
190
192
|
instance.log("");
|
|
191
193
|
instance.log("✅ " + env + ":" + componentName);
|
|
194
|
+
instance.log("--------------------------------\n");
|
|
192
195
|
}
|
|
193
196
|
}
|
|
194
197
|
instance.log("done! 😻");
|
|
@@ -25,6 +25,7 @@ import commandTriggerCronjob from "./commandTriggerCronjob";
|
|
|
25
25
|
|
|
26
26
|
import commandOpenGrafanaPod from "./commandOpenGrafanaPod";
|
|
27
27
|
import commandSecretsClearBackups from "./commandSecretsClearBackups";
|
|
28
|
+
import commandProjectRestoreDb from "./cloudSql/commandProjectRestoreDb";
|
|
28
29
|
|
|
29
30
|
export default async (vorpal: Vorpal) => {
|
|
30
31
|
commandSetup(vorpal);
|
|
@@ -45,6 +46,7 @@ export default async (vorpal: Vorpal) => {
|
|
|
45
46
|
commandOpenGrafana(vorpal);
|
|
46
47
|
commandOpenGrafanaPod(vorpal);
|
|
47
48
|
commandCloudSqlProxy(vorpal);
|
|
49
|
+
commandProjectRestoreDb(vorpal);
|
|
48
50
|
commandOpenGit(vorpal);
|
|
49
51
|
commandOpenEnv(vorpal);
|
|
50
52
|
commandTriggerCronjob(vorpal);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { spawn } from "child-process-promise";
|
|
2
|
+
type Opts = {
|
|
3
|
+
targetPassword: string;
|
|
4
|
+
targetPort: number;
|
|
5
|
+
targetUsername: string;
|
|
6
|
+
sourceUsername: string;
|
|
7
|
+
sourcePassword: string;
|
|
8
|
+
sourcePort: number;
|
|
9
|
+
sourceDbName: string;
|
|
10
|
+
targetDbName: string;
|
|
11
|
+
};
|
|
12
|
+
export const spawnCopyDb = async (opts: Opts) => {
|
|
13
|
+
const script = createCopyDbScript(opts);
|
|
14
|
+
|
|
15
|
+
return await spawn(script, [], {
|
|
16
|
+
shell: "bash",
|
|
17
|
+
stdio: "inherit",
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
const createCopyDbScript = ({
|
|
21
|
+
targetPassword,
|
|
22
|
+
targetPort,
|
|
23
|
+
targetUsername,
|
|
24
|
+
sourceUsername,
|
|
25
|
+
sourcePassword,
|
|
26
|
+
sourcePort,
|
|
27
|
+
sourceDbName,
|
|
28
|
+
targetDbName,
|
|
29
|
+
}: Opts) => {
|
|
30
|
+
const targetPSQL = (command: string) =>
|
|
31
|
+
`PGPASSWORD=${targetPassword} psql -p ${targetPort} --host=localhost --user=${targetUsername} -q ${command}`;
|
|
32
|
+
|
|
33
|
+
const copyDBScript = `
|
|
34
|
+
set -e
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
dumptmp=$(mktemp /tmp/dump.XXXXXX)
|
|
39
|
+
|
|
40
|
+
echo "Dumping file to $dumptmp"
|
|
41
|
+
pg_dump --dbname=postgres://${sourceUsername}:${sourcePassword}@localhost:${sourcePort}/${sourceDbName} --no-owner --no-privileges > $dumptmp
|
|
42
|
+
echo "dump done"
|
|
43
|
+
${targetPSQL(
|
|
44
|
+
`-c 'drop database "${targetDbName}" WITH (FORCE)' 1> /dev/null || true`
|
|
45
|
+
)}
|
|
46
|
+
${targetPSQL(`-c 'create database "${targetDbName}"' 1> /dev/null`)}
|
|
47
|
+
echo "Restoring dump..."
|
|
48
|
+
${targetPSQL(`"${targetDbName}" < $dumptmp 1> /dev/null`)}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
echo "Clean up..."
|
|
52
|
+
set +e
|
|
53
|
+
rm $dumptmp
|
|
54
|
+
echo "\n🐱 Done!"
|
|
55
|
+
`;
|
|
56
|
+
return copyDBScript;
|
|
57
|
+
};
|