@catladder/cli 1.5.7 → 1.8.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/dist/apps/cli/commands/project/commandCloudSqlProxy.js +3 -0
- package/dist/apps/cli/commands/project/commandCloudSqlProxy.js.map +1 -1
- package/dist/apps/cli/commands/project/commandConfigSecrets.js +145 -105
- package/dist/apps/cli/commands/project/commandConfigSecrets.js.map +1 -1
- package/dist/apps/cli/commands/project/utils/autocompletions.js +1 -1
- package/dist/apps/cli/commands/project/utils/autocompletions.js.map +1 -1
- package/dist/config/getProjectConfig.d.ts +5 -2
- package/dist/config/getProjectConfig.js +24 -6
- package/dist/config/getProjectConfig.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/apps/cli/commands/project/commandCloudSqlProxy.ts +5 -0
- package/src/apps/cli/commands/project/commandConfigSecrets.ts +123 -126
- package/src/apps/cli/commands/project/utils/autocompletions.ts +2 -2
- package/src/config/getProjectConfig.ts +22 -6
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"catladder": "./bin/catladder"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@catladder/pipeline": "1.
|
|
19
|
+
"@catladder/pipeline": "1.8.0",
|
|
20
20
|
"@kubernetes/client-node": "^0.16.2",
|
|
21
21
|
"child-process-promise": "^2.2.1",
|
|
22
22
|
"command-exists-promise": "^2.0.2",
|
|
@@ -55,5 +55,5 @@
|
|
|
55
55
|
"eslint": "^8.7.0",
|
|
56
56
|
"typescript": "^4.5.4"
|
|
57
57
|
},
|
|
58
|
-
"version": "1.
|
|
58
|
+
"version": "1.8.0"
|
|
59
59
|
}
|
|
@@ -37,8 +37,13 @@ export default async (vorpal: Vorpal) =>
|
|
|
37
37
|
if (!isOfDeployType(context.componentConfig.deploy, "kubernetes")) {
|
|
38
38
|
throw new Error("currently only supported for kubernetes deployment");
|
|
39
39
|
}
|
|
40
|
+
this.log("");
|
|
40
41
|
this.log(`postgres-PW: ${POSTGRESQL_PASSWORD}`);
|
|
41
42
|
this.log("");
|
|
43
|
+
this.log(
|
|
44
|
+
`POSTGRESQL_URL=postgresql://postgres:${POSTGRESQL_PASSWORD}@localhost:${localPort}/${context.environment.envVars.KUBE_APP_NAME}?schema=public`
|
|
45
|
+
);
|
|
46
|
+
this.log("");
|
|
42
47
|
|
|
43
48
|
const values = context.componentConfig.deploy.values;
|
|
44
49
|
|
|
@@ -4,6 +4,7 @@ import { difference } from "lodash";
|
|
|
4
4
|
import Vorpal, { CommandInstance } from "vorpal";
|
|
5
5
|
import { GOOGLE_CLOUD_SQL_PASS_PATH } from "../../../../config/constants";
|
|
6
6
|
import {
|
|
7
|
+
getAllComponentsWithAllEnvsHierarchical,
|
|
7
8
|
getEnvironment,
|
|
8
9
|
getEnvVars,
|
|
9
10
|
getPipelineContextByChoice,
|
|
@@ -16,20 +17,30 @@ import { hasBitwarden, readPass } from "../../../../utils/passwordstore";
|
|
|
16
17
|
import { delay } from "../../../../utils/promise";
|
|
17
18
|
import { allEnvsAndAllComponents } from "./utils/autocompletions";
|
|
18
19
|
|
|
20
|
+
type Vars = {
|
|
21
|
+
[env: string]: {
|
|
22
|
+
[componentName: string]: Record<string, string>;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
19
25
|
/* for convenience, parse json objects. that makes it easier to edit secrets that are object */
|
|
20
|
-
const resolveJson = (v:
|
|
26
|
+
const resolveJson = (v: Vars) =>
|
|
21
27
|
Object.fromEntries(
|
|
22
|
-
Object.entries(v).map(([
|
|
28
|
+
Object.entries(v).map(([componentName, envs]) => {
|
|
23
29
|
return [
|
|
24
|
-
|
|
30
|
+
componentName,
|
|
25
31
|
Object.fromEntries(
|
|
26
|
-
Object.entries(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
Object.entries(envs).map(([env, secrets]) => [
|
|
33
|
+
env,
|
|
34
|
+
Object.fromEntries(
|
|
35
|
+
Object.entries(secrets).map(([key, value]) => {
|
|
36
|
+
try {
|
|
37
|
+
return [key, JSON.parse(value)];
|
|
38
|
+
} catch (e) {
|
|
39
|
+
return [key, value];
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
),
|
|
43
|
+
])
|
|
33
44
|
),
|
|
34
45
|
];
|
|
35
46
|
})
|
|
@@ -48,14 +59,22 @@ const getEnvVarsToEdit = async (
|
|
|
48
59
|
};
|
|
49
60
|
const doItFor = async (
|
|
50
61
|
instance: CommandInstance,
|
|
51
|
-
|
|
52
|
-
|
|
62
|
+
envAndComponents: {
|
|
63
|
+
[componentName: string]: string[];
|
|
64
|
+
}
|
|
53
65
|
) => {
|
|
54
|
-
let valuesToEdit:
|
|
66
|
+
let valuesToEdit: Vars = Object.fromEntries(
|
|
55
67
|
await Promise.all(
|
|
56
|
-
|
|
68
|
+
Object.entries(envAndComponents).map(async ([componentName, envs]) => [
|
|
57
69
|
componentName,
|
|
58
|
-
|
|
70
|
+
Object.fromEntries(
|
|
71
|
+
await Promise.all(
|
|
72
|
+
envs.map(async (env) => [
|
|
73
|
+
env,
|
|
74
|
+
await getEnvVarsToEdit(instance, env, componentName),
|
|
75
|
+
])
|
|
76
|
+
)
|
|
77
|
+
),
|
|
59
78
|
])
|
|
60
79
|
)
|
|
61
80
|
);
|
|
@@ -64,81 +83,90 @@ const doItFor = async (
|
|
|
64
83
|
valuesToEdit = await editAsFile(
|
|
65
84
|
resolveJson(valuesToEdit),
|
|
66
85
|
stripIndents`
|
|
67
|
-
Please fill in all secrets for:
|
|
86
|
+
Please fill in all secrets for:
|
|
87
|
+
|
|
88
|
+
${Object.entries(envAndComponents)
|
|
89
|
+
.map(
|
|
90
|
+
([componentName, envs]) => `- ${componentName}: ${envs.join(", ")}`
|
|
91
|
+
)
|
|
92
|
+
.join("\n")}
|
|
68
93
|
|
|
69
94
|
`
|
|
70
95
|
);
|
|
71
96
|
// check for errors
|
|
72
97
|
hasErrors = false;
|
|
73
|
-
for (const componentName of
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
98
|
+
for (const [componentName, envs] of Object.entries(envAndComponents)) {
|
|
99
|
+
for (const env of envs) {
|
|
100
|
+
const usedKeys = valuesToEdit[componentName][env]
|
|
101
|
+
? Object.keys(valuesToEdit[componentName][env])
|
|
102
|
+
: [];
|
|
103
|
+
// check whether newValues have the exact number of keys
|
|
104
|
+
const { secretEnvVarKeys } = await getEnvironment(env, componentName);
|
|
105
|
+
const extranous = difference(usedKeys, secretEnvVarKeys);
|
|
106
|
+
const missing = difference(secretEnvVarKeys, usedKeys);
|
|
81
107
|
|
|
82
|
-
|
|
83
|
-
instance.log("");
|
|
84
|
-
instance.log(
|
|
85
|
-
`😿 Oh no! There is something wrong with "${componentName}"`
|
|
86
|
-
);
|
|
87
|
-
instance.log("");
|
|
88
|
-
if (extranous.length > 0) {
|
|
89
|
-
instance.log("these secrets are not declared in the config");
|
|
90
|
-
extranous.forEach((key) => instance.log(key));
|
|
108
|
+
if (extranous.length > 0 || missing.length > 0) {
|
|
91
109
|
instance.log("");
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
missing.forEach((key) => instance.log(key));
|
|
110
|
+
instance.log(
|
|
111
|
+
`😿 Oh no! There is something wrong with "${componentName}"`
|
|
112
|
+
);
|
|
96
113
|
instance.log("");
|
|
97
|
-
|
|
114
|
+
if (extranous.length > 0) {
|
|
115
|
+
instance.log("these secrets are not declared in the config");
|
|
116
|
+
extranous.forEach((key) => instance.log(key));
|
|
117
|
+
instance.log("");
|
|
118
|
+
}
|
|
119
|
+
if (missing.length > 0) {
|
|
120
|
+
instance.log("these secrets have not been provided:");
|
|
121
|
+
missing.forEach((key) => instance.log(key));
|
|
122
|
+
instance.log("");
|
|
123
|
+
}
|
|
98
124
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
125
|
+
await delay(1000);
|
|
126
|
+
const { shouldContinue } = await instance.prompt({
|
|
127
|
+
default: true,
|
|
128
|
+
message: "Try again? 🤔",
|
|
129
|
+
name: "shouldContinue",
|
|
130
|
+
type: "confirm",
|
|
131
|
+
});
|
|
106
132
|
|
|
107
|
-
|
|
108
|
-
|
|
133
|
+
if (!shouldContinue) {
|
|
134
|
+
throw new Error("abort");
|
|
135
|
+
}
|
|
136
|
+
hasErrors = true;
|
|
109
137
|
}
|
|
110
|
-
hasErrors = true;
|
|
111
138
|
}
|
|
112
139
|
}
|
|
113
140
|
}
|
|
141
|
+
for (const [componentName, envs] of Object.entries(envAndComponents)) {
|
|
142
|
+
for (const env of envs) {
|
|
143
|
+
await upsertAllVariables(
|
|
144
|
+
instance,
|
|
145
|
+
valuesToEdit[componentName][env],
|
|
146
|
+
env,
|
|
147
|
+
componentName
|
|
148
|
+
);
|
|
114
149
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
valuesToEdit[componentName],
|
|
119
|
-
env,
|
|
120
|
-
componentName
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
if (hasBitwarden()) {
|
|
124
|
-
// add cloud sql secret if needed.
|
|
125
|
-
// TODO: this is legacy, in the future we want to have one service account per app
|
|
150
|
+
if (hasBitwarden()) {
|
|
151
|
+
// add cloud sql secret if needed.
|
|
152
|
+
// TODO: this is legacy, in the future we want to have one service account per app
|
|
126
153
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
154
|
+
const context = await getPipelineContextByChoice(env, componentName);
|
|
155
|
+
if (
|
|
156
|
+
context.componentConfig.deploy &&
|
|
157
|
+
context.componentConfig.deploy.values?.cloudsql?.enabled
|
|
158
|
+
) {
|
|
159
|
+
await upsertAllVariables(
|
|
160
|
+
instance,
|
|
161
|
+
{
|
|
162
|
+
cloudsqlProxyCredentials: await readPass(
|
|
163
|
+
GOOGLE_CLOUD_SQL_PASS_PATH
|
|
164
|
+
),
|
|
165
|
+
},
|
|
166
|
+
env,
|
|
167
|
+
componentName
|
|
168
|
+
);
|
|
169
|
+
}
|
|
142
170
|
}
|
|
143
171
|
}
|
|
144
172
|
}
|
|
@@ -147,62 +175,31 @@ const doItFor = async (
|
|
|
147
175
|
export default async (vorpal: Vorpal) => {
|
|
148
176
|
vorpal
|
|
149
177
|
.command(
|
|
150
|
-
"project-config-secrets
|
|
178
|
+
"project-config-secrets [envComponent]",
|
|
151
179
|
"setup/update secrets stored in pass"
|
|
152
180
|
)
|
|
153
181
|
.autocomplete(await allEnvsAndAllComponents())
|
|
154
182
|
.action(async function ({ envComponent }) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
if (componentName) {
|
|
163
|
-
await doItFor(this, env, [componentName]);
|
|
164
|
-
}
|
|
183
|
+
if (!envComponent) {
|
|
184
|
+
const allEnvAndcomponents =
|
|
185
|
+
await getAllComponentsWithAllEnvsHierarchical();
|
|
186
|
+
await doItFor(this, allEnvAndcomponents);
|
|
187
|
+
} else {
|
|
188
|
+
const { env, componentName } = parseChoice(envComponent);
|
|
165
189
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
{
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
this.log("");
|
|
180
|
-
this.log(
|
|
181
|
-
"⚠️ You need to delete/restart pods in order to make them pick up the new config"
|
|
182
|
-
);
|
|
183
|
-
this.log(`you can use project-delete-pods ${env} to do that`);
|
|
184
|
-
this.log("");
|
|
185
|
-
this.log("");
|
|
186
|
-
await delay(1000);
|
|
190
|
+
// componentName can be null. in this case, iterate over all components
|
|
191
|
+
if (!componentName) {
|
|
192
|
+
const components = await getProjectComponents();
|
|
193
|
+
await doItFor(
|
|
194
|
+
this,
|
|
195
|
+
Object.fromEntries(components.map((c) => [c, [env]]))
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
if (componentName) {
|
|
199
|
+
await doItFor(this, {
|
|
200
|
+
[componentName]: [env],
|
|
201
|
+
});
|
|
202
|
+
}
|
|
187
203
|
}
|
|
188
|
-
this.log("");
|
|
189
|
-
this.log("😻 success!!!!!");
|
|
190
|
-
this.log("");
|
|
191
|
-
*/
|
|
192
204
|
});
|
|
193
|
-
};
|
|
194
|
-
async function createNewEnvInPass(env: any, secretEnvVarsMapping: ISecrets) {
|
|
195
|
-
// const passPath = await getPassPath(env);
|
|
196
|
-
this.log(
|
|
197
|
-
"Your selected env is not yet in pass. Do you want to copy it from another env? "
|
|
198
|
-
);
|
|
199
|
-
const noAnswer = "No, I will create a new one from scratch.";
|
|
200
|
-
const { sourceEnv } = await this.prompt({
|
|
201
|
-
type: "list",
|
|
202
|
-
name: "sourceEnv",
|
|
203
|
-
choices: [...(await envAndComponents()).filter((e) => e !== env), noAnswer],
|
|
204
|
-
message: "Do you want to copy an env?",
|
|
205
|
-
});
|
|
206
|
-
// TODO: reimplenent
|
|
207
|
-
}
|
|
208
|
-
*/
|
|
205
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getAllEnvs, getAllEnvsInAllComponents } from "@catladder/pipeline";
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
getAllComponentsWithAllEnvsFlat,
|
|
4
4
|
getProjectConfig,
|
|
5
5
|
} from "../../../../../config/getProjectConfig";
|
|
6
6
|
|
|
@@ -13,7 +13,7 @@ export const allEnvs = async () => {
|
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
export const envAndComponents = async () => {
|
|
16
|
-
const allEnvAndcomponents = await
|
|
16
|
+
const allEnvAndcomponents = await getAllComponentsWithAllEnvsFlat();
|
|
17
17
|
|
|
18
18
|
return allEnvAndcomponents.reduce<string[]>(
|
|
19
19
|
(acc, { env, componentName }) => [...acc, env + ":" + componentName],
|
|
@@ -58,21 +58,37 @@ export const getPipelineContextByChoice = async (
|
|
|
58
58
|
const config = await getProjectConfig();
|
|
59
59
|
return createContext(config, componentName, env);
|
|
60
60
|
};
|
|
61
|
-
export const
|
|
61
|
+
export const getAllComponentsWithAllEnvsFlat = async (): Promise<
|
|
62
|
+
Array<{ env: string; componentName: string }>
|
|
63
|
+
> => {
|
|
62
64
|
const config = await getProjectConfig();
|
|
63
65
|
if (!config) {
|
|
64
66
|
return [];
|
|
65
67
|
}
|
|
66
|
-
return
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
return Object.keys(config.components).flatMap((componentName) =>
|
|
69
|
+
getAllEnvs(config, componentName).map((env) => ({ env, componentName }))
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const getAllComponentsWithAllEnvsHierarchical = async (): Promise<{
|
|
74
|
+
[componentName: string]: string[];
|
|
75
|
+
}> => {
|
|
76
|
+
const config = await getProjectConfig();
|
|
77
|
+
if (!config) {
|
|
78
|
+
return {};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return Object.fromEntries(
|
|
82
|
+
Object.keys(config.components).map((componentName) => [
|
|
83
|
+
componentName,
|
|
84
|
+
getAllEnvs(config, componentName),
|
|
85
|
+
])
|
|
70
86
|
);
|
|
71
87
|
};
|
|
72
88
|
|
|
73
89
|
export const getAllPipelineContexts = async () => {
|
|
74
90
|
return Promise.all(
|
|
75
|
-
(await
|
|
91
|
+
(await getAllComponentsWithAllEnvsFlat())
|
|
76
92
|
.filter((c) => c.env !== "local")
|
|
77
93
|
.map(({ env, componentName }) =>
|
|
78
94
|
getPipelineContextByChoice(env, componentName)
|