@magda/scripts 4.2.2-alpha.0 → 4.2.3

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.
@@ -1,111 +0,0 @@
1
- #!/usr/bin/env node
2
- import { askQuestions, getEnvVarInfo } from "./askQuestions.js";
3
- import k8sExecution from "./k8sExecution.js";
4
- import preloadConfig from "./preloadConfig.js";
5
- import clear from "clear";
6
- import chalk from "chalk";
7
- import Configstore from "configstore";
8
- import { require } from "@magda/esm-utils";
9
-
10
- const appName = "magda-create-secrets";
11
- const pkg = require("../package.json");
12
- const config = new Configstore("magda-create-secrets", {});
13
-
14
- const program = require("commander");
15
-
16
- program
17
- .version(pkg.version)
18
- .usage("[options]")
19
- .description(`A tool for magda k8s secrets setup. Version: ${pkg.version}`)
20
- .option(
21
- "-E, --execute [configFilePath]",
22
- "Create k8s secrets in cluster using `${appName}` config file/data without asking any user input. \n" +
23
- " If you want to supply config data via STDIN, you can set `configFilePath` parameter to `-`. \n" +
24
- ` e.g. \`echo $CONFIG_CONTENT | ${appName} -E -\` or \`cat config.json | ${appName} --execute=-\`\n` +
25
- " If configFilePath is not specify, program will attempt to load config file from: \n" +
26
- ` either \`$XDG_CONFIG_HOME/configstore/${appName}.json\` \n or \`~/.config/configstore/${appName}.json\``
27
- )
28
- .option("-P, --print", "Print previously saved local config data to stdout")
29
- .option("-D, --delete", "Delete previously saved local config data");
30
-
31
- program.on("--help", function () {
32
- const envInfo = getEnvVarInfo();
33
- console.log(" Available Setting ENV Variables:");
34
- console.log("");
35
- envInfo.forEach((item) => {
36
- console.log(` ${item.name} : ${item.description}`);
37
- });
38
- console.log("");
39
- });
40
-
41
- program.parse(process.argv);
42
-
43
- const programOptions = program.opts();
44
-
45
- if (programOptions.print) {
46
- process.stdout.write(JSON.stringify(config.all), "utf-8", function () {
47
- process.exit();
48
- });
49
- } else if (programOptions.delete) {
50
- config.clear();
51
- console.log(chalk.green("All local saved config data has been removed!"));
52
- process.exit();
53
- } else if (programOptions.execute) {
54
- console.log(
55
- chalk.green(`${appName} tool version: ${pkg.version} Execute Mode`)
56
- );
57
- let configDataBak = {};
58
- let hasError = false;
59
- preloadConfig(config, programOptions.execute)
60
- .then(function (data) {
61
- if (programOptions.execute !== true) {
62
- //--- we only need to receovey user's local config
63
- //--- when the config is fed by STDIN or external file
64
- configDataBak = config.all;
65
- }
66
- config.all = data;
67
- return k8sExecution(config, true);
68
- })
69
- .catch(function (error) {
70
- hasError = true;
71
- console.log(chalk.red(`Failed to create secrets: ${error}`));
72
- console.error(error);
73
- })
74
- .then(function () {
75
- //--- can't use finally
76
- if (programOptions.execute !== true) {
77
- //--- recover origin config data
78
- config.all = configDataBak;
79
- }
80
- if (hasError) {
81
- process.exit(1);
82
- }
83
- });
84
- } else {
85
- clear();
86
- console.log("\n");
87
- console.log(chalk.green(`${appName} tool version: ${pkg.version}`));
88
- askQuestions(config).then(function (shouldCreateSecrets) {
89
- if (shouldCreateSecrets) {
90
- k8sExecution(config).then(
91
- function () {
92
- console.log(
93
- chalk.green(
94
- "All required secrets have been successfully created!"
95
- )
96
- );
97
- process.exit();
98
- },
99
- function (error) {
100
- console.log(
101
- chalk.red(`Failed to create required secrets: ${error}`)
102
- );
103
- console.error(error);
104
- process.exit(1);
105
- }
106
- );
107
- } else {
108
- process.exit();
109
- }
110
- });
111
- }
@@ -1,475 +0,0 @@
1
- import childProcess from "child_process";
2
- import process from "process";
3
- import chalk from "chalk";
4
- import trim from "lodash/trim.js";
5
- import {
6
- getEnvVarInfo,
7
- askIfCreateNamespace,
8
- settingNameToEnvVarName
9
- } from "./askQuestions.js";
10
- import { Base64 } from "js-base64";
11
- import pwgen from "./pwgen.js";
12
-
13
- const dbPasswordNames = [
14
- "authorization-db",
15
- "authorization-db-client",
16
- "combined-db",
17
- "combined-db-client",
18
- "content-db",
19
- "content-db-client",
20
- "registry-db",
21
- "registry-db-client",
22
- "session-db",
23
- "session-db-client",
24
- "tenant-db",
25
- "tenant-db-client"
26
- ];
27
-
28
- function k8sExecution(config, shouldNotAsk = false) {
29
- try {
30
- return doK8sExecution(config, shouldNotAsk);
31
- } catch (e) {
32
- return Promise.reject(e);
33
- }
34
- }
35
-
36
- function doK8sExecution(config, shouldNotAsk = false) {
37
- const env = getEnvByClusterType(config);
38
- let configData = Object.assign({}, config.all);
39
- const allowEnvVarOverride = configData["allow-env-override-settings"];
40
-
41
- configData = overrideSettingWithEnvVarsBasedOnQuestionAnswers(
42
- env,
43
- configData
44
- );
45
-
46
- if (allowEnvVarOverride) {
47
- configData = overrideSettingWithEnvVars(env, configData);
48
- }
49
-
50
- let promise = Promise.resolve().then(function () {
51
- /**
52
- * All errors / exceptions should be process through promise chain rather than stop program here.
53
- * There are different logic outside doK8sExecution requires some clean-up job to be done before exit program.
54
- */
55
- checkIfKubectlValid(env, configData);
56
- configData["cluster-namespace"] = trim(configData["cluster-namespace"]);
57
- if (!configData["cluster-namespace"]) {
58
- throw new Error(
59
- "Cluster namespace cannot be empty! \n " +
60
- "If you've set cluster namespace, make sure it's not overrided by env variable."
61
- );
62
- }
63
- });
64
-
65
- if (!checkNamespace(env, configData["cluster-namespace"], config)) {
66
- if (shouldNotAsk) {
67
- promise = promise.then(function () {
68
- // --- leave error to be handled at end of then chain. see above
69
- throw new Error(
70
- `Namespace ${configData["cluster-namespace"]} doesn't exist. Please create and try again.`
71
- );
72
- });
73
- }
74
- promise = promise
75
- .then(
76
- askIfCreateNamespace.bind(null, configData["cluster-namespace"])
77
- )
78
- .then(function (shouldCreateNamespace) {
79
- if (!shouldCreateNamespace) {
80
- throw new Error(
81
- `You need to create namespace \`${configData["cluster-namespace"]}\` before try again.`
82
- );
83
- } else {
84
- createNamespace(
85
- env,
86
- configData["cluster-namespace"],
87
- configData
88
- );
89
- }
90
- });
91
- }
92
- return promise.then(function () {
93
- const namespace = configData["cluster-namespace"];
94
-
95
- if (configData["use-cloudsql-instance-credentials"] === true) {
96
- createFileContentSecret(
97
- env,
98
- namespace,
99
- configData,
100
- "cloudsql-instance-credentials",
101
- "credentials.json",
102
- configData["cloudsql-instance-credentials"]["data"]
103
- );
104
- createSecret(
105
- env,
106
- namespace,
107
- configData,
108
- "cloudsql-db-credentials",
109
- {
110
- password: configData["cloudsql-db-credentials"]
111
- }
112
- );
113
- }
114
-
115
- if (configData["use-storage-account-credentials"] === true) {
116
- createFileContentSecret(
117
- env,
118
- namespace,
119
- configData,
120
- "storage-account-credentials",
121
- "db-service-account-private-key.json",
122
- configData["storage-account-credentials"]["data"]
123
- );
124
- }
125
-
126
- if (configData["use-smtp-secret"] === true) {
127
- createSecret(env, namespace, configData, "smtp-secret", {
128
- username: configData["smtp-secret-username"],
129
- password: configData["smtp-secret-password"]
130
- });
131
- }
132
-
133
- createDbPasswords(env, namespace, configData);
134
- createMinioCredentials(env, namespace, configData);
135
-
136
- createWebAccessPassword(env, namespace, configData);
137
-
138
- if (configData["use-regcred"] === true) {
139
- /**
140
- * always use `regcred-password`
141
- * `use-regcred-password-from-env` has been taken care seperately
142
- */
143
- createDockerRegistrySecret(
144
- env,
145
- namespace,
146
- configData,
147
- "regcred",
148
- configData["regcred-password"],
149
- "registry.gitlab.com",
150
- "gitlab-ci-token",
151
- configData["regcred-email"]
152
- );
153
- }
154
-
155
- if (
156
- configData["use-oauth-secrets-google"] === true ||
157
- configData["use-oauth-secrets-facebook"] === true ||
158
- configData["use-oauth-secrets-arcgis"] === true ||
159
- configData["use-oauth-secrets-aaf"] === true
160
- ) {
161
- const data = {};
162
-
163
- if (configData["use-oauth-secrets-google"]) {
164
- data["google-client-secret"] =
165
- configData["oauth-secrets-google"];
166
- }
167
-
168
- if (configData["use-oauth-secrets-facebook"]) {
169
- data["facebook-client-secret"] =
170
- configData["oauth-secrets-facebook"];
171
- }
172
-
173
- if (configData["use-oauth-secrets-arcgis"]) {
174
- data["arcgis-client-secret"] =
175
- configData["oauth-secrets-arcgis"];
176
- }
177
-
178
- if (configData["use-oauth-secrets-aaf"]) {
179
- data["aaf-client-secret"] = configData["oauth-secrets-aaf"];
180
- }
181
-
182
- createSecret(env, namespace, configData, "oauth-secrets", data);
183
- }
184
-
185
- createSecret(env, namespace, configData, "auth-secrets", {
186
- "jwt-secret": pwgen(64),
187
- "session-secret": pwgen()
188
- });
189
- });
190
- }
191
-
192
- function getEnvByClusterType(config) {
193
- const localClusterType = config.get("local-cluster-type");
194
-
195
- if (
196
- typeof localClusterType === "undefined" ||
197
- localClusterType !== "minikube"
198
- ) {
199
- return Object.assign({}, process.env);
200
- }
201
-
202
- const dockerEnvProcess = childProcess.execSync(
203
- "minikube docker-env --shell bash",
204
- { encoding: "utf8" }
205
- );
206
- const dockerEnv = dockerEnvProcess
207
- .split("\n")
208
- .filter((line) => line.indexOf("export ") === 0)
209
- .reduce(function (env, line) {
210
- const match = /^export (\w+)="(.*)"$/.exec(line);
211
- if (match) {
212
- env[match[1]] = match[2];
213
- }
214
- return env;
215
- }, {});
216
-
217
- const env = Object.assign({}, process.env, dockerEnv);
218
- return env;
219
- }
220
-
221
- /**
222
- * the difference between this function and `overrideSettingWithEnvVars` is:
223
- * `overrideSettingWithEnvVars` allows users to override any questions answers
224
- * and it will only be run when the answer to question
225
- * `Do you want to allow environment variables (see --help for full list) to override current settings at runtime?`
226
- * is `YES`.
227
- * This function will always be run so if user said YES to a specific question
228
- * (e.g. Do you want namespace to be overiden), that particular question answer will be overriden.
229
- */
230
- function overrideSettingWithEnvVarsBasedOnQuestionAnswers(env, configData) {
231
- if (
232
- configData["get-namespace-from-env"] === true &&
233
- env[settingNameToEnvVarName("cluster-namespace")]
234
- ) {
235
- configData["cluster-namespace"] =
236
- env[settingNameToEnvVarName("cluster-namespace")];
237
- }
238
-
239
- if (
240
- configData["use-regcred-password-from-env"] === true &&
241
- env["CI_JOB_TOKEN"]
242
- ) {
243
- configData["regcred-password"] = env["CI_JOB_TOKEN"];
244
- }
245
-
246
- if (
247
- typeof configData["manual-db-passwords"] === "object" &&
248
- configData["manual-db-passwords"]["answer"] === false &&
249
- configData["manual-db-passwords"]["password"]
250
- ) {
251
- configData["db-passwords"] =
252
- configData["manual-db-passwords"]["password"];
253
- }
254
-
255
- if (
256
- typeof configData["manual-web-access-password"] === "object" &&
257
- configData["manual-web-access-password"]["answer"] === false &&
258
- configData["manual-web-access-password"]["password"]
259
- ) {
260
- configData["web-access-password"] =
261
- configData["manual-web-access-password"]["password"];
262
- }
263
-
264
- return configData;
265
- }
266
-
267
- function overrideSettingWithEnvVars(env, configData) {
268
- getEnvVarInfo().forEach((item) => {
269
- const envVal = env[item.name];
270
- if (typeof envVal === "undefined") return;
271
- if (item.dataType === "boolean") {
272
- const value = envVal.toLowerCase().trim();
273
- if (value === "false" || value === "0") {
274
- configData[item.settingName] = false;
275
- } else {
276
- configData[item.settingName] = true;
277
- }
278
- } else if (item.dataType === "jsonfile") {
279
- configData[item.settingName] = {
280
- data: JSON.parse(envVal)
281
- };
282
- } else {
283
- configData[item.settingName] = envVal;
284
- }
285
- });
286
-
287
- return configData;
288
- }
289
-
290
- function checkIfKubectlValid(env, configData) {
291
- try {
292
- childProcess.execSync(getKubectlCommand(configData), {
293
- stdio: "ignore",
294
- env: env
295
- });
296
- } catch (e) {
297
- throw new Error(
298
- `Failed to execute \`kubectl\` utility: ${e}\n` +
299
- "Make sure you have install & config `kubectl` properly before try again."
300
- );
301
- }
302
- }
303
-
304
- function checkNamespace(env, namespace, configData) {
305
- try {
306
- childProcess.execSync(
307
- `${getKubectlCommand(configData)} get namespace ${namespace}`,
308
- {
309
- stdio: "ignore",
310
- env: env
311
- }
312
- );
313
- return true;
314
- } catch (e) {
315
- console.log(
316
- chalk.red(
317
- `Failed to get k8s namespace ${namespace} or namespace has not been created yet: ${e}`
318
- )
319
- );
320
- return false;
321
- }
322
- }
323
-
324
- function createNamespace(env, namespace, configData) {
325
- childProcess.execSync(
326
- `${getKubectlCommand(configData)} create namespace ${namespace}`,
327
- {
328
- stdio: "inherit",
329
- env: env
330
- }
331
- );
332
- }
333
-
334
- function buildTemplateObject(name, namespace) {
335
- return {
336
- apiVersion: "v1",
337
- kind: "Secret",
338
- type: "Opaque",
339
- metadata: {
340
- name,
341
- namespace,
342
- annotations: {
343
- "replicator.v1.mittwald.de/replication-allowed": "true",
344
- "replicator.v1.mittwald.de/replication-allowed-namespaces": `${namespace}-openfaas-fn`
345
- },
346
- creationTimestamp: null
347
- }
348
- };
349
- }
350
-
351
- function createDbPasswords(env, namespace, configData) {
352
- /**
353
- * dbPasswordNames is defined as const at top of the file
354
- */
355
- const data = {};
356
- dbPasswordNames.forEach((key) => {
357
- data[key] = configData["db-passwords"];
358
- });
359
- createSecret(env, namespace, configData, "db-passwords", data);
360
- }
361
-
362
- function createMinioCredentials(env, namespace, configData) {
363
- const data = {
364
- accesskey: configData["accesskey"],
365
- secretkey: configData["secretkey"]
366
- };
367
- createSecret(env, namespace, configData, "storage-secrets", data);
368
- }
369
-
370
- function createWebAccessPassword(env, namespace, configData) {
371
- if (configData["use-web-access-secret"] === false) {
372
- return;
373
- }
374
- const data = {
375
- username: configData["web-access-username"],
376
- password: configData["web-access-password"]
377
- };
378
- createSecret(env, namespace, configData, "web-access-secret", data);
379
- }
380
-
381
- function createFileContentSecret(
382
- env,
383
- namespace,
384
- configData,
385
- secretName,
386
- fileName,
387
- content
388
- ) {
389
- if (typeof content !== "string") {
390
- content = JSON.stringify(content);
391
- }
392
-
393
- createSecret(env, namespace, configData, secretName, {
394
- [fileName]: content
395
- });
396
- }
397
-
398
- function createDockerRegistrySecret(
399
- env,
400
- namespace,
401
- configData,
402
- secretName,
403
- password,
404
- dockerServer,
405
- username,
406
- email
407
- ) {
408
- const dockerConfig = {
409
- auths: {
410
- [dockerServer]: {
411
- username: username,
412
- password: password,
413
- email: email,
414
- auth: Base64.encode(`${username}:${password}`)
415
- }
416
- }
417
- };
418
-
419
- const data = {};
420
- data[".dockerconfigjson"] = JSON.stringify(dockerConfig);
421
- createSecret(
422
- env,
423
- namespace,
424
- configData,
425
- secretName,
426
- data,
427
- true,
428
- "kubernetes.io/dockerconfigjson"
429
- );
430
- }
431
-
432
- function createSecret(
433
- env,
434
- namespace,
435
- configData,
436
- secretName,
437
- data,
438
- encodeAllDataFields,
439
- type
440
- ) {
441
- const configObj = buildTemplateObject(secretName, namespace);
442
- configObj.data = data;
443
-
444
- if (type) configObj.type = type;
445
-
446
- if (encodeAllDataFields !== false) {
447
- Object.keys(configObj.data).forEach((key) => {
448
- configObj.data[key] = Base64.encode(configObj.data[key]);
449
- });
450
- }
451
-
452
- const configContent = JSON.stringify(configObj);
453
-
454
- childProcess.execSync(
455
- `${getKubectlCommand(configData)} apply --namespace ${namespace} -f -`,
456
- {
457
- input: configContent,
458
- env: env
459
- }
460
- );
461
-
462
- console.log(
463
- chalk.green(
464
- `Successfully created secret \`${secretName}\` in namespace \`${namespace}\`.`
465
- )
466
- );
467
- }
468
-
469
- function getKubectlCommand(configData) {
470
- const clusterType = configData["local-cluster-type"];
471
-
472
- return clusterType === "microk8s" ? "microk8s kubectl" : "kubectl";
473
- }
474
-
475
- export default k8sExecution;
@@ -1,103 +0,0 @@
1
- import fs from "fs";
2
- import chalk from "chalk";
3
- import trim from "lodash/trim.js";
4
-
5
- function preloadConfig(configStore, executeOption) {
6
- return Promise.resolve().then(function () {
7
- if (executeOption === true) {
8
- console.log(
9
- chalk.yellow(
10
- `Loading config data from \`${configStore.path}\`...`
11
- )
12
- );
13
- // --- trigger config file read
14
- const configData = configStore.all;
15
- if (!configStore.size) {
16
- throw new Error(`Error: loaded config object is empty!`);
17
- }
18
- console.log(
19
- chalk.green(
20
- `Successfully loaded config data from \`${configStore.path}\`.`
21
- )
22
- );
23
- return configData;
24
- } else if (executeOption === "-") {
25
- return readConfigFromStdin(configStore);
26
- } else {
27
- return readConfigFromFile(configStore, executeOption);
28
- }
29
- });
30
- }
31
-
32
- function readConfigFromStdin(configStore) {
33
- console.log(chalk.yellow(`Loading config data from STDIN ...`));
34
- let configContent = "";
35
- return new Promise(function (resolve, reject) {
36
- process.stdin.setEncoding("utf8");
37
-
38
- process.stdin.on("readable", () => {
39
- try {
40
- const chunk = process.stdin.read();
41
- if (chunk !== null) {
42
- configContent += chunk;
43
- }
44
- } catch (e) {
45
- reject(e);
46
- }
47
- });
48
-
49
- process.stdin.on("end", () => {
50
- try {
51
- const data = JSON.parse(configContent);
52
- if (
53
- !data ||
54
- typeof data !== "object" ||
55
- !Object.keys(data).length
56
- ) {
57
- throw new Error("Loaded config object is empty!");
58
- }
59
- console.log(
60
- chalk.green(`Successfully loaded config data from STDIN.`)
61
- );
62
- resolve(data);
63
- } catch (e) {
64
- reject(e);
65
- }
66
- });
67
- });
68
- }
69
-
70
- function readConfigFromFile(configStore, executeOption) {
71
- console.log(chalk.yellow(`Loading config data from ${executeOption}...`));
72
- return new Promise(function (resolve, reject) {
73
- try {
74
- const filePath = trim(executeOption);
75
- if (!fs.existsSync(filePath)) {
76
- throw new Error(
77
- "The config file path specified does not exist or cannot read."
78
- );
79
- }
80
- const content = fs.readFileSync(filePath, {
81
- encoding: "utf-8"
82
- });
83
- const data = JSON.parse(content);
84
- if (
85
- !data ||
86
- typeof data !== "object" ||
87
- !Object.keys(data).length
88
- ) {
89
- throw new Error("Loaded config object is empty!");
90
- }
91
- console.log(
92
- chalk.green(
93
- `Successfully loaded config data from \`${filePath}\`.`
94
- )
95
- );
96
- resolve(data);
97
- } catch (e) {
98
- reject(e);
99
- }
100
- });
101
- }
102
-
103
- export default preloadConfig;