@catladder/cli 1.105.1 → 1.106.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/package.json CHANGED
@@ -24,7 +24,7 @@
24
24
  "node": ">=12.0.0"
25
25
  },
26
26
  "devDependencies": {
27
- "@catladder/pipeline": "1.105.1",
27
+ "@catladder/pipeline": "1.106.0",
28
28
  "@kubernetes/client-node": "^0.16.2",
29
29
  "@tsconfig/node14": "^1.0.1",
30
30
  "@types/common-tags": "^1.8.0",
@@ -57,5 +57,5 @@
57
57
  "typescript": "^4.5.4",
58
58
  "vorpal": "^1.12.0"
59
59
  },
60
- "version": "1.105.1"
60
+ "version": "1.106.0"
61
61
  }
@@ -13,7 +13,7 @@ const getAllVariablesToPrint = async (config: Config, choice?: Choice) => {
13
13
  choice
14
14
  );
15
15
 
16
- let variables = {};
16
+ let variables: Record<string, string> = {};
17
17
  if (currentComponent) {
18
18
  variables = await getEnvVarsResolved(null, env, currentComponent);
19
19
  } else {
@@ -23,6 +23,7 @@ const getAllVariablesToPrint = async (config: Config, choice?: Choice) => {
23
23
  variables = await Object.keys(config.components).reduce(
24
24
  async (acc, componentName) => {
25
25
  const subappvars = await getEnvVarsResolved(null, env, componentName);
26
+ delete subappvars["_ALL_ENV_VAR_KEYS"];
26
27
  return {
27
28
  ...(await acc),
28
29
  ...subappvars,
@@ -38,6 +39,7 @@ const getAllVariablesToPrint = async (config: Config, choice?: Choice) => {
38
39
  {}
39
40
  );
40
41
  }
42
+
41
43
  return variables;
42
44
  };
43
45
 
@@ -32,6 +32,7 @@ export const writeDotEnvFiles = async (config: Config, choice?: Choice) => {
32
32
 
33
33
  for (const componentName of componentsToActuallyWriteDotEnvNow) {
34
34
  const variables = await getEnvVarsResolved(null, env, componentName);
35
+ delete variables["_ALL_ENV_VAR_KEYS"];
35
36
  const componentDir = getComponentFullPath(gitRoot, config, componentName);
36
37
  const filePath = join(componentDir, ".env");
37
38
  // many .dotenv don't like multiline values, so we sanitize them here
@@ -1,29 +1,8 @@
1
1
  import { spawn } from "child-process-promise";
2
- import type Vorpal from "vorpal";
3
2
 
4
- const createProxy = async (instance: string, port: number) => {
5
- const proxy = spawn(
6
- `cloud_sql_proxy -instances ${instance}=tcp:${port}`,
7
- [],
8
- { shell: "bash" }
9
- );
10
- // wait until it starts
11
- await spawn(
12
- `echo -n "Waiting for proxy"
13
- until echo > /dev/tcp/localhost/${port}; do
14
- sleep 0.2
15
- echo -n "."
16
- done 2>/dev/null`,
17
- [],
18
- { shell: "bash" }
19
- );
20
- const stop = () => proxy.childProcess.kill();
21
- process.on("beforeExit", stop);
22
-
23
- return {
24
- stop,
25
- };
26
- };
3
+ import type Vorpal from "vorpal";
4
+ import type { CloudSqlBackgroundProxy } from "../../../../gcloud/cloudSql/startProxy";
5
+ import { startCloudSqlProxyInBackground } from "../../../../gcloud/cloudSql/startProxy";
27
6
 
28
7
  export default async (vorpal: Vorpal) =>
29
8
  vorpal
@@ -39,9 +18,9 @@ export default async (vorpal: Vorpal) =>
39
18
  message: "Source instance (connection string or 'local')? 🤔 ",
40
19
  });
41
20
 
42
- let sourceProxy: { stop: () => void };
21
+ let sourceProxy: CloudSqlBackgroundProxy;
43
22
 
44
- let targetProxy: { stop: () => void };
23
+ let targetProxy: CloudSqlBackgroundProxy;
45
24
 
46
25
  let sourcePort: number;
47
26
  let targetPort: number;
@@ -57,7 +36,10 @@ export default async (vorpal: Vorpal) =>
57
36
  sourcePort = sourceLocalPort;
58
37
  } else {
59
38
  sourcePort = 54399;
60
- sourceProxy = await createProxy(sourceInstance, sourcePort);
39
+ sourceProxy = await startCloudSqlProxyInBackground({
40
+ instanceName: sourceInstance,
41
+ localPort: sourcePort,
42
+ });
61
43
  }
62
44
 
63
45
  const { sourceUsername } = await this.prompt({
@@ -86,7 +68,7 @@ export default async (vorpal: Vorpal) =>
86
68
  type: "input",
87
69
  name: "targetInstance",
88
70
 
89
- message: "Targe INSTANCE (connection string or 'local')? 🤔 ",
71
+ message: "Target INSTANCE (connection string or 'local')? 🤔 ",
90
72
  });
91
73
 
92
74
  if (targetInstance === "local") {
@@ -100,7 +82,10 @@ export default async (vorpal: Vorpal) =>
100
82
  targetPort = targetLocalPort;
101
83
  } else {
102
84
  targetPort = 54499;
103
- targetProxy = await createProxy(targetInstance, targetPort);
85
+ targetProxy = await startCloudSqlProxyInBackground({
86
+ instanceName: targetInstance,
87
+ localPort: targetPort,
88
+ });
104
89
  }
105
90
 
106
91
  const { targetUsername } = await this.prompt({
@@ -3,7 +3,6 @@ import {
3
3
  createKubernetesCloudsqlBaseValues,
4
4
  isOfDeployType,
5
5
  } from "@catladder/pipeline";
6
- import { spawn } from "child-process-promise";
7
6
  import type Vorpal from "vorpal";
8
7
  import type { CommandInstance } from "vorpal";
9
8
  import {
@@ -12,6 +11,7 @@ import {
12
11
  parseChoice,
13
12
  } from "../../../../config/getProjectConfig";
14
13
  import { envAndComponents } from "./utils/autocompletions";
14
+ import { startCloudSqlProxyInCurrentShell } from "../../../../gcloud/cloudSql/startProxy";
15
15
 
16
16
  type ProxyInfo = {
17
17
  instanceName: string;
@@ -70,14 +70,11 @@ export default async (vorpal: Vorpal) =>
70
70
  `DATABASE_JDBC_URL=jdbc:postgresql://localhost:${localPort}/${DB_NAME}?schema=public&user=${DB_USER}&password=${DB_PASSWORD}`
71
71
  );
72
72
  this.log("");
73
- await spawn(
74
- "cloud_sql_proxy",
75
- ["-instances", `${instanceName}=tcp:${localPort}`],
76
- {
77
- stdio: "inherit",
78
- shell: true,
79
- }
80
- );
73
+
74
+ await startCloudSqlProxyInCurrentShell({
75
+ instanceName,
76
+ localPort,
77
+ });
81
78
  });
82
79
 
83
80
  const getProxyInfoForKubernetes = async (
@@ -1,4 +1,4 @@
1
- import type { Config } from "@catladder/pipeline";
1
+ import type { Config, EnvironmentEnvVars } from "@catladder/pipeline";
2
2
  import {
3
3
  readConfigSync,
4
4
  getAllEnvs,
@@ -124,24 +124,32 @@ export const getGitlabVar = async (
124
124
 
125
125
  const resolveSecrets = async (
126
126
  vorpal: CommandInstance | null,
127
- allEnvVars: Record<string, string>
127
+ varSets: EnvironmentEnvVars[]
128
128
  ) => {
129
129
  const allVariablesInGitlab = await getAllVariables(vorpal);
130
130
 
131
131
  return Object.fromEntries(
132
- Object.entries(allEnvVars).map(([key, value]) => {
133
- const containsSecret = String(value)?.includes?.("$CL_");
134
- if (containsSecret) {
135
- for (const variable of allVariablesInGitlab) {
136
- value = value.replace(
137
- new RegExp("\\$" + variable.key, "g"),
138
- variable.value
139
- );
140
- }
141
- return [key, value];
142
- }
143
- return [key, value];
144
- })
132
+ varSets.flatMap((set) =>
133
+ Object.entries(set.envVars)
134
+ .map(([key, value]) => {
135
+ const secretKey = set.secretEnvVarKeys.find((k) => k.key === key);
136
+
137
+ if (secretKey) {
138
+ if (secretKey.hidden) {
139
+ return null;
140
+ }
141
+ for (const variable of allVariablesInGitlab) {
142
+ value = value.replace(
143
+ new RegExp("\\$" + variable.key, "g"),
144
+ variable.value
145
+ );
146
+ }
147
+ return [key, value];
148
+ }
149
+ return [key, value];
150
+ })
151
+ .filter(Boolean)
152
+ )
145
153
  );
146
154
  };
147
155
 
@@ -155,9 +163,15 @@ export const getEnvVarsResolved = async (
155
163
  }
156
164
  try {
157
165
  const envionment = await getEnvironment(env, componentName);
166
+
158
167
  // in the pipeline the secrets alreadyy exists and bash will expand them
159
168
  // but here we need to manually load them
160
- return resolveSecrets(vorpal, envionment.envVars);
169
+ return resolveSecrets(vorpal, [
170
+ {
171
+ envVars: envionment.envVars,
172
+ secretEnvVarKeys: envionment.secretEnvVarKeys,
173
+ },
174
+ ]);
161
175
  } catch (e) {
162
176
  // env is disabled
163
177
  return {};
@@ -175,10 +189,10 @@ export const getJobOnlyEnvVarsResolved = async (
175
189
  ) => {
176
190
  try {
177
191
  const envionment = await getEnvironment(env, componentName);
178
- return resolveSecrets(vorpal, {
179
- ...envionment.jobOnlyVars.build.envVars,
180
- ...envionment.jobOnlyVars.deploy.envVars,
181
- });
192
+ return resolveSecrets(vorpal, [
193
+ envionment.jobOnlyVars.build,
194
+ envionment.jobOnlyVars.deploy,
195
+ ]);
182
196
  } catch (e) {
183
197
  // env is disabled
184
198
  return {};
@@ -0,0 +1,74 @@
1
+ import { spawn } from "child-process-promise";
2
+ import commandExists from "command-exists-promise";
3
+
4
+ export const ERROR_NOT_INSTALLED = "cloud-sql-proxy not installed";
5
+
6
+ export type CloudSqlBackgroundProxy = {
7
+ stop: () => void;
8
+ };
9
+
10
+ export type CloudSqlProxyOptions = {
11
+ instanceName: string;
12
+ localPort: number;
13
+ };
14
+
15
+ const getProxyCommandSpawnArgs = async ({
16
+ localPort,
17
+ instanceName,
18
+ }: CloudSqlProxyOptions) => {
19
+ const commandString = (await commandExists("cloud-sql-proxy"))
20
+ ? `cloud-sql-proxy --port ${localPort} ${instanceName}`
21
+ : (await commandExists(
22
+ "cloud_sql_proxy" // v1
23
+ ))
24
+ ? `cloud_sql_proxy -instances ${instanceName}=tcp:${localPort}`
25
+ : null;
26
+ if (!commandString) {
27
+ throw new Error(ERROR_NOT_INSTALLED);
28
+ }
29
+
30
+ const [cmd, ...args] = commandString.split(" ");
31
+ return { cmd, args };
32
+ };
33
+
34
+ export const startCloudSqlProxyInCurrentShell = async (
35
+ opts: CloudSqlProxyOptions
36
+ ) => {
37
+ const { cmd, args } = await getProxyCommandSpawnArgs(opts);
38
+
39
+ await spawn(cmd, args, {
40
+ stdio: "inherit",
41
+ shell: true,
42
+ });
43
+ };
44
+
45
+ export const startCloudSqlProxyInBackground = async (
46
+ opts: CloudSqlProxyOptions
47
+ ): Promise<CloudSqlBackgroundProxy> => {
48
+ const { cmd, args } = await getProxyCommandSpawnArgs(opts);
49
+
50
+ const proxyPromise = spawn(cmd, args, { shell: "bash" });
51
+
52
+ // wait until it starts
53
+ await spawn(
54
+ `echo -n "Waiting for proxy"
55
+ until echo > /dev/tcp/localhost/${opts.localPort}; do
56
+ sleep 0.2
57
+ echo -n "."
58
+ done 2>/dev/null`,
59
+ [],
60
+ { shell: "bash" }
61
+ );
62
+ const stop = () => {
63
+ proxyPromise.catch(() => {
64
+ // ignore
65
+ });
66
+ proxyPromise.childProcess.kill();
67
+ };
68
+ // stop if catladder i stopped
69
+ process.on("beforeExit", stop);
70
+
71
+ return {
72
+ stop,
73
+ };
74
+ };
@@ -13,7 +13,7 @@ declare module "child-process-promise" {
13
13
  type Stdio = "inherit" | "pipe";
14
14
  type SpawnOptions = {
15
15
  stdio?: Stdio | Stdio[];
16
- shell?: boolean;
16
+ shell?: boolean | string;
17
17
  env?: Record<string, string>;
18
18
  };
19
19
 
@@ -31,6 +31,7 @@ declare module "child-process-promise" {
31
31
  ): Promise<unknown> & {
32
32
  childProcess: {
33
33
  stdout: ReadStream;
34
+ kill: () => void;
34
35
  };
35
36
  };
36
37
  }