@kustodian/plugin-doppler 1.1.0 → 2.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/dist/executor.d.ts +38 -0
- package/dist/executor.d.ts.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +489 -0
- package/dist/plugin.d.ts +12 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/resolver.d.ts +12 -0
- package/dist/resolver.d.ts.map +1 -0
- package/dist/types.d.ts +43 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +14 -11
- package/src/executor.ts +0 -238
- package/src/index.ts +0 -25
- package/src/plugin.ts +0 -145
- package/src/resolver.ts +0 -116
- package/src/types.ts +0 -49
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { KustodianErrorType } from '@kustodian/core';
|
|
2
|
+
import { type ResultType } from '@kustodian/core';
|
|
3
|
+
import { type DopplerPluginOptionsType } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Result of a command execution.
|
|
6
|
+
*/
|
|
7
|
+
export interface CommandResultType {
|
|
8
|
+
stdout: string;
|
|
9
|
+
stderr: string;
|
|
10
|
+
exit_code: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Options for command execution.
|
|
14
|
+
*/
|
|
15
|
+
export interface ExecOptionsType {
|
|
16
|
+
cwd?: string | undefined;
|
|
17
|
+
timeout?: number | undefined;
|
|
18
|
+
env?: Record<string, string> | undefined;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Executes a command and returns the result.
|
|
22
|
+
* Uses execFile for security (no shell invocation).
|
|
23
|
+
*/
|
|
24
|
+
export declare function exec_command(command: string, args?: string[], options?: ExecOptionsType): Promise<ResultType<CommandResultType, KustodianErrorType>>;
|
|
25
|
+
/**
|
|
26
|
+
* Checks if doppler CLI is available in the system PATH.
|
|
27
|
+
*/
|
|
28
|
+
export declare function check_doppler_available(): Promise<ResultType<string, KustodianErrorType>>;
|
|
29
|
+
/**
|
|
30
|
+
* Fetches all secrets for a project/config combination.
|
|
31
|
+
* Returns a JSON object of all secrets.
|
|
32
|
+
*/
|
|
33
|
+
export declare function doppler_secrets_download(project: string, config: string, options?: DopplerPluginOptionsType): Promise<ResultType<Record<string, string>, KustodianErrorType>>;
|
|
34
|
+
/**
|
|
35
|
+
* Fetches a single secret value from Doppler.
|
|
36
|
+
*/
|
|
37
|
+
export declare function doppler_secret_get(project: string, config: string, secret_name: string, options?: DopplerPluginOptionsType): Promise<ResultType<string, KustodianErrorType>>;
|
|
38
|
+
//# sourceMappingURL=executor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../src/executor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAU,KAAK,UAAU,EAAoB,MAAM,iBAAiB,CAAC;AAE5E,OAAO,EAAmB,KAAK,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAI5E;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;CAC1C;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,MAAM,EAAO,EACnB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,UAAU,CAAC,iBAAiB,EAAE,kBAAkB,CAAC,CAAC,CAwB5D;AA6BD;;GAEG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,UAAU,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAc/F;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAgEjE;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,UAAU,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,CA+CjD"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { type CommandResultType, check_doppler_available, doppler_secret_get, doppler_secrets_download, type ExecOptionsType, exec_command, } from './executor.js';
|
|
2
|
+
export { create_doppler_plugin, plugin, plugin as default } from './plugin.js';
|
|
3
|
+
export { resolve_doppler_substitutions } from './resolver.js';
|
|
4
|
+
export { create_cache_key, DEFAULT_TIMEOUT, type DopplerCacheKeyType, type DopplerPluginOptionsType, type DopplerRefType, } from './types.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,iBAAiB,EACtB,uBAAuB,EACvB,kBAAkB,EAClB,wBAAwB,EACxB,KAAK,eAAe,EACpB,YAAY,GACb,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,qBAAqB,EAAE,MAAM,EAAE,MAAM,IAAI,OAAO,EAAE,MAAM,aAAa,CAAC;AAG/E,OAAO,EAAE,6BAA6B,EAAE,MAAM,eAAe,CAAC;AAG9D,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,EAC7B,KAAK,cAAc,GACpB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
// src/executor.ts
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
|
|
5
|
+
// ../../packages/core/dist/index.js
|
|
6
|
+
function create_error(code, message, cause) {
|
|
7
|
+
return { code, message, cause };
|
|
8
|
+
}
|
|
9
|
+
var ErrorCodes = {
|
|
10
|
+
UNKNOWN: "UNKNOWN",
|
|
11
|
+
INVALID_ARGUMENT: "INVALID_ARGUMENT",
|
|
12
|
+
NOT_FOUND: "NOT_FOUND",
|
|
13
|
+
ALREADY_EXISTS: "ALREADY_EXISTS",
|
|
14
|
+
FILE_NOT_FOUND: "FILE_NOT_FOUND",
|
|
15
|
+
FILE_READ_ERROR: "FILE_READ_ERROR",
|
|
16
|
+
FILE_WRITE_ERROR: "FILE_WRITE_ERROR",
|
|
17
|
+
DIRECTORY_NOT_FOUND: "DIRECTORY_NOT_FOUND",
|
|
18
|
+
PARSE_ERROR: "PARSE_ERROR",
|
|
19
|
+
YAML_PARSE_ERROR: "YAML_PARSE_ERROR",
|
|
20
|
+
JSON_PARSE_ERROR: "JSON_PARSE_ERROR",
|
|
21
|
+
VALIDATION_ERROR: "VALIDATION_ERROR",
|
|
22
|
+
SCHEMA_VALIDATION_ERROR: "SCHEMA_VALIDATION_ERROR",
|
|
23
|
+
CONFIG_NOT_FOUND: "CONFIG_NOT_FOUND",
|
|
24
|
+
CONFIG_INVALID: "CONFIG_INVALID",
|
|
25
|
+
TEMPLATE_NOT_FOUND: "TEMPLATE_NOT_FOUND",
|
|
26
|
+
CLUSTER_NOT_FOUND: "CLUSTER_NOT_FOUND",
|
|
27
|
+
PROFILE_NOT_FOUND: "PROFILE_NOT_FOUND",
|
|
28
|
+
NETWORK_ERROR: "NETWORK_ERROR",
|
|
29
|
+
CONNECTION_REFUSED: "CONNECTION_REFUSED",
|
|
30
|
+
TIMEOUT: "TIMEOUT",
|
|
31
|
+
SSH_CONNECTION_ERROR: "SSH_CONNECTION_ERROR",
|
|
32
|
+
SSH_AUTH_ERROR: "SSH_AUTH_ERROR",
|
|
33
|
+
SSH_COMMAND_ERROR: "SSH_COMMAND_ERROR",
|
|
34
|
+
K8S_CONNECTION_ERROR: "K8S_CONNECTION_ERROR",
|
|
35
|
+
K8S_API_ERROR: "K8S_API_ERROR",
|
|
36
|
+
NODE_NOT_READY: "NODE_NOT_READY",
|
|
37
|
+
BOOTSTRAP_ERROR: "BOOTSTRAP_ERROR",
|
|
38
|
+
BOOTSTRAP_STATE_ERROR: "BOOTSTRAP_STATE_ERROR",
|
|
39
|
+
CLUSTER_PROVIDER_ERROR: "CLUSTER_PROVIDER_ERROR",
|
|
40
|
+
PLUGIN_NOT_FOUND: "PLUGIN_NOT_FOUND",
|
|
41
|
+
PLUGIN_LOAD_ERROR: "PLUGIN_LOAD_ERROR",
|
|
42
|
+
PLUGIN_EXECUTION_ERROR: "PLUGIN_EXECUTION_ERROR",
|
|
43
|
+
DEPENDENCY_CYCLE: "DEPENDENCY_CYCLE",
|
|
44
|
+
DEPENDENCY_MISSING: "DEPENDENCY_MISSING",
|
|
45
|
+
DEPENDENCY_SELF_REFERENCE: "DEPENDENCY_SELF_REFERENCE",
|
|
46
|
+
DEPENDENCY_VALIDATION_ERROR: "DEPENDENCY_VALIDATION_ERROR",
|
|
47
|
+
SECRET_CLI_NOT_FOUND: "SECRET_CLI_NOT_FOUND",
|
|
48
|
+
SECRET_NOT_FOUND: "SECRET_NOT_FOUND",
|
|
49
|
+
SECRET_AUTH_ERROR: "SECRET_AUTH_ERROR",
|
|
50
|
+
SECRET_TIMEOUT: "SECRET_TIMEOUT",
|
|
51
|
+
SOURCE_FETCH_ERROR: "SOURCE_FETCH_ERROR",
|
|
52
|
+
SOURCE_AUTH_ERROR: "SOURCE_AUTH_ERROR",
|
|
53
|
+
SOURCE_TIMEOUT: "SOURCE_TIMEOUT",
|
|
54
|
+
SOURCE_CHECKSUM_MISMATCH: "SOURCE_CHECKSUM_MISMATCH",
|
|
55
|
+
SOURCE_VERSION_NOT_FOUND: "SOURCE_VERSION_NOT_FOUND",
|
|
56
|
+
CACHE_READ_ERROR: "CACHE_READ_ERROR",
|
|
57
|
+
CACHE_WRITE_ERROR: "CACHE_WRITE_ERROR",
|
|
58
|
+
CACHE_CORRUPT: "CACHE_CORRUPT"
|
|
59
|
+
};
|
|
60
|
+
var Errors = {
|
|
61
|
+
unknown(message, cause) {
|
|
62
|
+
return create_error(ErrorCodes.UNKNOWN, message, cause);
|
|
63
|
+
},
|
|
64
|
+
invalid_argument(argument, reason) {
|
|
65
|
+
return create_error(ErrorCodes.INVALID_ARGUMENT, `Invalid argument '${argument}': ${reason}`);
|
|
66
|
+
},
|
|
67
|
+
not_found(resource, identifier) {
|
|
68
|
+
return create_error(ErrorCodes.NOT_FOUND, `${resource} '${identifier}' not found`);
|
|
69
|
+
},
|
|
70
|
+
already_exists(resource, identifier) {
|
|
71
|
+
return create_error(ErrorCodes.ALREADY_EXISTS, `${resource} '${identifier}' already exists`);
|
|
72
|
+
},
|
|
73
|
+
file_not_found(path) {
|
|
74
|
+
return create_error(ErrorCodes.FILE_NOT_FOUND, `File not found: ${path}`);
|
|
75
|
+
},
|
|
76
|
+
file_read_error(path, cause) {
|
|
77
|
+
return create_error(ErrorCodes.FILE_READ_ERROR, `Failed to read file: ${path}`, cause);
|
|
78
|
+
},
|
|
79
|
+
file_write_error(path, cause) {
|
|
80
|
+
return create_error(ErrorCodes.FILE_WRITE_ERROR, `Failed to write file: ${path}`, cause);
|
|
81
|
+
},
|
|
82
|
+
parse_error(format, message, cause) {
|
|
83
|
+
return create_error(ErrorCodes.PARSE_ERROR, `Failed to parse ${format}: ${message}`, cause);
|
|
84
|
+
},
|
|
85
|
+
yaml_parse_error(message, cause) {
|
|
86
|
+
return create_error(ErrorCodes.YAML_PARSE_ERROR, `YAML parse error: ${message}`, cause);
|
|
87
|
+
},
|
|
88
|
+
validation_error(message, cause) {
|
|
89
|
+
return create_error(ErrorCodes.VALIDATION_ERROR, message, cause);
|
|
90
|
+
},
|
|
91
|
+
schema_validation_error(errors) {
|
|
92
|
+
return create_error(ErrorCodes.SCHEMA_VALIDATION_ERROR, `Schema validation failed:
|
|
93
|
+
${errors.map((e) => ` - ${e}`).join(`
|
|
94
|
+
`)}`);
|
|
95
|
+
},
|
|
96
|
+
config_not_found(config_type, path) {
|
|
97
|
+
return create_error(ErrorCodes.CONFIG_NOT_FOUND, `${config_type} configuration not found at: ${path}`);
|
|
98
|
+
},
|
|
99
|
+
template_not_found(name) {
|
|
100
|
+
return create_error(ErrorCodes.TEMPLATE_NOT_FOUND, `Template '${name}' not found`);
|
|
101
|
+
},
|
|
102
|
+
cluster_not_found(name) {
|
|
103
|
+
return create_error(ErrorCodes.CLUSTER_NOT_FOUND, `Cluster '${name}' not found`);
|
|
104
|
+
},
|
|
105
|
+
profile_not_found(name) {
|
|
106
|
+
return create_error(ErrorCodes.PROFILE_NOT_FOUND, `Node profile '${name}' not found`);
|
|
107
|
+
},
|
|
108
|
+
ssh_connection_error(host, cause) {
|
|
109
|
+
return create_error(ErrorCodes.SSH_CONNECTION_ERROR, `Failed to connect to ${host} via SSH`, cause);
|
|
110
|
+
},
|
|
111
|
+
ssh_auth_error(host, cause) {
|
|
112
|
+
return create_error(ErrorCodes.SSH_AUTH_ERROR, `SSH authentication failed for ${host}`, cause);
|
|
113
|
+
},
|
|
114
|
+
bootstrap_error(message, cause) {
|
|
115
|
+
return create_error(ErrorCodes.BOOTSTRAP_ERROR, `Bootstrap failed: ${message}`, cause);
|
|
116
|
+
},
|
|
117
|
+
plugin_not_found(name) {
|
|
118
|
+
return create_error(ErrorCodes.PLUGIN_NOT_FOUND, `Plugin '${name}' not found`);
|
|
119
|
+
},
|
|
120
|
+
plugin_load_error(name, cause) {
|
|
121
|
+
return create_error(ErrorCodes.PLUGIN_LOAD_ERROR, `Failed to load plugin '${name}'`, cause);
|
|
122
|
+
},
|
|
123
|
+
dependency_cycle(cycle) {
|
|
124
|
+
const cycle_str = cycle.join(" → ");
|
|
125
|
+
return create_error(ErrorCodes.DEPENDENCY_CYCLE, `Dependency cycle detected: ${cycle_str}`);
|
|
126
|
+
},
|
|
127
|
+
dependency_missing(source, target) {
|
|
128
|
+
return create_error(ErrorCodes.DEPENDENCY_MISSING, `Kustomization '${source}' depends on '${target}' which does not exist`);
|
|
129
|
+
},
|
|
130
|
+
dependency_self_reference(node) {
|
|
131
|
+
return create_error(ErrorCodes.DEPENDENCY_SELF_REFERENCE, `Kustomization '${node}' cannot depend on itself`);
|
|
132
|
+
},
|
|
133
|
+
dependency_validation_error(errors) {
|
|
134
|
+
return create_error(ErrorCodes.DEPENDENCY_VALIDATION_ERROR, `Dependency validation failed:
|
|
135
|
+
${errors.map((e) => ` - ${e}`).join(`
|
|
136
|
+
`)}`);
|
|
137
|
+
},
|
|
138
|
+
secret_cli_not_found(provider, cli_name) {
|
|
139
|
+
return create_error(ErrorCodes.SECRET_CLI_NOT_FOUND, `${provider} CLI (${cli_name}) not found. Please install it first.`);
|
|
140
|
+
},
|
|
141
|
+
secret_not_found(provider, ref) {
|
|
142
|
+
return create_error(ErrorCodes.SECRET_NOT_FOUND, `Secret not found in ${provider}: ${ref}`);
|
|
143
|
+
},
|
|
144
|
+
secret_auth_error(provider, cause) {
|
|
145
|
+
return create_error(ErrorCodes.SECRET_AUTH_ERROR, `${provider} authentication failed. Check your credentials.`, cause);
|
|
146
|
+
},
|
|
147
|
+
secret_timeout(provider, timeout) {
|
|
148
|
+
return create_error(ErrorCodes.SECRET_TIMEOUT, `${provider} operation timed out after ${timeout}ms`);
|
|
149
|
+
},
|
|
150
|
+
source_fetch_error(source, cause) {
|
|
151
|
+
return create_error(ErrorCodes.SOURCE_FETCH_ERROR, `Failed to fetch template source '${source}'`, cause);
|
|
152
|
+
},
|
|
153
|
+
source_auth_error(source, cause) {
|
|
154
|
+
return create_error(ErrorCodes.SOURCE_AUTH_ERROR, `Authentication failed for source '${source}'`, cause);
|
|
155
|
+
},
|
|
156
|
+
source_timeout(source, timeout) {
|
|
157
|
+
return create_error(ErrorCodes.SOURCE_TIMEOUT, `Source '${source}' operation timed out after ${timeout}ms`);
|
|
158
|
+
},
|
|
159
|
+
source_checksum_mismatch(source, expected, actual) {
|
|
160
|
+
return create_error(ErrorCodes.SOURCE_CHECKSUM_MISMATCH, `Checksum mismatch for '${source}': expected ${expected}, got ${actual}`);
|
|
161
|
+
},
|
|
162
|
+
source_version_not_found(source, version) {
|
|
163
|
+
return create_error(ErrorCodes.SOURCE_VERSION_NOT_FOUND, `Version '${version}' not found for source '${source}'`);
|
|
164
|
+
},
|
|
165
|
+
cache_read_error(path, cause) {
|
|
166
|
+
return create_error(ErrorCodes.CACHE_READ_ERROR, `Failed to read cache at: ${path}`, cause);
|
|
167
|
+
},
|
|
168
|
+
cache_write_error(path, cause) {
|
|
169
|
+
return create_error(ErrorCodes.CACHE_WRITE_ERROR, `Failed to write cache at: ${path}`, cause);
|
|
170
|
+
},
|
|
171
|
+
cache_corrupt(path) {
|
|
172
|
+
return create_error(ErrorCodes.CACHE_CORRUPT, `Cache is corrupt at: ${path}`);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
function success(value) {
|
|
176
|
+
return { success: true, value };
|
|
177
|
+
}
|
|
178
|
+
function failure(error) {
|
|
179
|
+
return { success: false, error };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/types.ts
|
|
183
|
+
var DEFAULT_TIMEOUT = 30000;
|
|
184
|
+
function create_cache_key(project, config) {
|
|
185
|
+
return `${project}/${config}`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/executor.ts
|
|
189
|
+
var exec_file_async = promisify(execFile);
|
|
190
|
+
async function exec_command(command, args = [], options = {}) {
|
|
191
|
+
try {
|
|
192
|
+
const { stdout, stderr } = await exec_file_async(command, args, {
|
|
193
|
+
cwd: options.cwd,
|
|
194
|
+
timeout: options.timeout,
|
|
195
|
+
env: { ...process.env, ...options.env }
|
|
196
|
+
});
|
|
197
|
+
return success({
|
|
198
|
+
stdout,
|
|
199
|
+
stderr,
|
|
200
|
+
exit_code: 0
|
|
201
|
+
});
|
|
202
|
+
} catch (error) {
|
|
203
|
+
if (is_exec_error(error)) {
|
|
204
|
+
return success({
|
|
205
|
+
stdout: error.stdout ?? "",
|
|
206
|
+
stderr: error.stderr ?? "",
|
|
207
|
+
exit_code: error.code ?? 1
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
return failure(Errors.unknown(`Failed to execute command: ${command}`, error));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function is_exec_error(error) {
|
|
214
|
+
return typeof error === "object" && error !== null && (("stdout" in error) || ("stderr" in error) || ("code" in error));
|
|
215
|
+
}
|
|
216
|
+
function get_doppler_auth_env(options) {
|
|
217
|
+
const env = {};
|
|
218
|
+
const token = options.token ?? process.env["DOPPLER_TOKEN"];
|
|
219
|
+
if (token) {
|
|
220
|
+
env["DOPPLER_TOKEN"] = token;
|
|
221
|
+
}
|
|
222
|
+
return env;
|
|
223
|
+
}
|
|
224
|
+
async function check_doppler_available() {
|
|
225
|
+
const result = await exec_command("doppler", ["--version"]);
|
|
226
|
+
if (!result.success) {
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
if (result.value.exit_code !== 0) {
|
|
230
|
+
return failure(Errors.secret_cli_not_found("Doppler", "doppler"));
|
|
231
|
+
}
|
|
232
|
+
const version = result.value.stdout.trim();
|
|
233
|
+
return success(version);
|
|
234
|
+
}
|
|
235
|
+
async function doppler_secrets_download(project, config, options = {}) {
|
|
236
|
+
const timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
237
|
+
const env = get_doppler_auth_env(options);
|
|
238
|
+
const result = await exec_command("doppler", [
|
|
239
|
+
"secrets",
|
|
240
|
+
"download",
|
|
241
|
+
"--project",
|
|
242
|
+
project,
|
|
243
|
+
"--config",
|
|
244
|
+
config,
|
|
245
|
+
"--format",
|
|
246
|
+
"json",
|
|
247
|
+
"--no-file"
|
|
248
|
+
], {
|
|
249
|
+
timeout,
|
|
250
|
+
env
|
|
251
|
+
});
|
|
252
|
+
if (!result.success) {
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
255
|
+
const { stdout, stderr, exit_code } = result.value;
|
|
256
|
+
if (exit_code !== 0) {
|
|
257
|
+
const stderr_lower = stderr.toLowerCase();
|
|
258
|
+
if (stderr_lower.includes("not found") || stderr_lower.includes("doesn't exist") || stderr_lower.includes("no project") || stderr_lower.includes("no config")) {
|
|
259
|
+
return failure(Errors.secret_not_found("Doppler", `${project}/${config}`));
|
|
260
|
+
}
|
|
261
|
+
if (stderr_lower.includes("unauthorized") || stderr_lower.includes("authentication") || stderr_lower.includes("invalid token") || stderr_lower.includes("access denied")) {
|
|
262
|
+
return failure(Errors.secret_auth_error("Doppler", stderr));
|
|
263
|
+
}
|
|
264
|
+
if (stderr_lower.includes("timeout")) {
|
|
265
|
+
return failure(Errors.secret_timeout("Doppler", timeout));
|
|
266
|
+
}
|
|
267
|
+
return failure(Errors.unknown(`Doppler error: ${stderr}`));
|
|
268
|
+
}
|
|
269
|
+
try {
|
|
270
|
+
const secrets = JSON.parse(stdout);
|
|
271
|
+
return success(secrets);
|
|
272
|
+
} catch (error) {
|
|
273
|
+
return failure(Errors.unknown("Failed to parse Doppler secrets output", error));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
async function doppler_secret_get(project, config, secret_name, options = {}) {
|
|
277
|
+
const timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
278
|
+
const env = get_doppler_auth_env(options);
|
|
279
|
+
const result = await exec_command("doppler", ["secrets", "get", secret_name, "--project", project, "--config", config, "--plain"], {
|
|
280
|
+
timeout,
|
|
281
|
+
env
|
|
282
|
+
});
|
|
283
|
+
if (!result.success) {
|
|
284
|
+
return result;
|
|
285
|
+
}
|
|
286
|
+
const { stdout, stderr, exit_code } = result.value;
|
|
287
|
+
if (exit_code !== 0) {
|
|
288
|
+
const stderr_lower = stderr.toLowerCase();
|
|
289
|
+
if (stderr_lower.includes("not found") || stderr_lower.includes("doesn't exist") || stderr_lower.includes("no secret")) {
|
|
290
|
+
return failure(Errors.secret_not_found("Doppler", `${project}/${config}/${secret_name}`));
|
|
291
|
+
}
|
|
292
|
+
if (stderr_lower.includes("unauthorized") || stderr_lower.includes("authentication") || stderr_lower.includes("invalid token")) {
|
|
293
|
+
return failure(Errors.secret_auth_error("Doppler", stderr));
|
|
294
|
+
}
|
|
295
|
+
if (stderr_lower.includes("timeout")) {
|
|
296
|
+
return failure(Errors.secret_timeout("Doppler", timeout));
|
|
297
|
+
}
|
|
298
|
+
return failure(Errors.unknown(`Doppler error: ${stderr}`));
|
|
299
|
+
}
|
|
300
|
+
return success(stdout.trim());
|
|
301
|
+
}
|
|
302
|
+
// src/plugin.ts
|
|
303
|
+
var manifest = {
|
|
304
|
+
name: "@kustodian/plugin-doppler",
|
|
305
|
+
version: "0.1.0",
|
|
306
|
+
description: "Doppler secret provider for Kustodian",
|
|
307
|
+
capabilities: ["commands", "hooks"]
|
|
308
|
+
};
|
|
309
|
+
function create_doppler_plugin(options = {}) {
|
|
310
|
+
return {
|
|
311
|
+
manifest,
|
|
312
|
+
async activate() {
|
|
313
|
+
const check_result = await check_doppler_available();
|
|
314
|
+
if (!check_result.success) {
|
|
315
|
+
console.warn("Doppler CLI not found - secret resolution may fail");
|
|
316
|
+
}
|
|
317
|
+
return success(undefined);
|
|
318
|
+
},
|
|
319
|
+
async deactivate() {
|
|
320
|
+
return success(undefined);
|
|
321
|
+
},
|
|
322
|
+
get_commands() {
|
|
323
|
+
const doppler_command = {
|
|
324
|
+
name: "doppler",
|
|
325
|
+
description: "Doppler secret management commands",
|
|
326
|
+
subcommands: [
|
|
327
|
+
{
|
|
328
|
+
name: "check",
|
|
329
|
+
description: "Check Doppler CLI availability and authentication",
|
|
330
|
+
handler: async () => {
|
|
331
|
+
const result = await check_doppler_available();
|
|
332
|
+
if (result.success) {
|
|
333
|
+
console.log(`Doppler CLI version: ${result.value}`);
|
|
334
|
+
return success(undefined);
|
|
335
|
+
}
|
|
336
|
+
console.error("Doppler CLI not available");
|
|
337
|
+
return result;
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
name: "test",
|
|
342
|
+
description: "Test reading a secret from Doppler",
|
|
343
|
+
arguments: [
|
|
344
|
+
{ name: "project", description: "Doppler project", required: true },
|
|
345
|
+
{ name: "config", description: "Doppler config", required: true },
|
|
346
|
+
{ name: "secret", description: "Secret name", required: true }
|
|
347
|
+
],
|
|
348
|
+
handler: async (ctx) => {
|
|
349
|
+
const [project, config, secret] = ctx.args;
|
|
350
|
+
if (!project || !config || !secret) {
|
|
351
|
+
console.error("Missing required arguments: project, config, secret");
|
|
352
|
+
return success(undefined);
|
|
353
|
+
}
|
|
354
|
+
const result = await doppler_secret_get(project, config, secret, options);
|
|
355
|
+
if (result.success) {
|
|
356
|
+
console.log("Secret retrieved successfully (value hidden)");
|
|
357
|
+
console.log(`Length: ${result.value.length} characters`);
|
|
358
|
+
return success(undefined);
|
|
359
|
+
}
|
|
360
|
+
console.error(`Failed to read secret: ${result.error.message}`);
|
|
361
|
+
return result;
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
name: "list-secrets",
|
|
366
|
+
description: "List available secrets in a Doppler config",
|
|
367
|
+
arguments: [
|
|
368
|
+
{ name: "project", description: "Doppler project", required: true },
|
|
369
|
+
{ name: "config", description: "Doppler config", required: true }
|
|
370
|
+
],
|
|
371
|
+
handler: async (ctx) => {
|
|
372
|
+
const [project, config] = ctx.args;
|
|
373
|
+
if (!project || !config) {
|
|
374
|
+
console.error("Missing required arguments: project, config");
|
|
375
|
+
return success(undefined);
|
|
376
|
+
}
|
|
377
|
+
const result = await doppler_secrets_download(project, config, options);
|
|
378
|
+
if (result.success) {
|
|
379
|
+
console.log("Available secrets:");
|
|
380
|
+
for (const key of Object.keys(result.value)) {
|
|
381
|
+
console.log(` - ${key}`);
|
|
382
|
+
}
|
|
383
|
+
return success(undefined);
|
|
384
|
+
}
|
|
385
|
+
console.error(`Failed to list secrets: ${result.error.message}`);
|
|
386
|
+
return result;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
]
|
|
390
|
+
};
|
|
391
|
+
return [{ command: doppler_command }];
|
|
392
|
+
},
|
|
393
|
+
get_hooks() {
|
|
394
|
+
return [
|
|
395
|
+
{
|
|
396
|
+
event: "generator:after_resolve",
|
|
397
|
+
priority: 50,
|
|
398
|
+
handler: async (_event, ctx) => {
|
|
399
|
+
return success(ctx);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
];
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
var plugin = create_doppler_plugin();
|
|
407
|
+
// src/resolver.ts
|
|
408
|
+
async function resolve_doppler_substitutions(substitutions, options = {}) {
|
|
409
|
+
if (substitutions.length === 0) {
|
|
410
|
+
return success({});
|
|
411
|
+
}
|
|
412
|
+
const resolved_subs = [];
|
|
413
|
+
for (const sub of substitutions) {
|
|
414
|
+
const project = sub.project ?? options.cluster_defaults?.project;
|
|
415
|
+
const config = sub.config ?? options.cluster_defaults?.config;
|
|
416
|
+
if (!project || !config) {
|
|
417
|
+
return failure({
|
|
418
|
+
code: "MISSING_DOPPLER_CONFIG",
|
|
419
|
+
message: `Doppler substitution '${sub.name}' missing project/config and no cluster defaults configured`
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
resolved_subs.push({
|
|
423
|
+
...sub,
|
|
424
|
+
project,
|
|
425
|
+
config
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
const groups = new Map;
|
|
429
|
+
for (const sub of resolved_subs) {
|
|
430
|
+
const key = create_cache_key(sub.project, sub.config);
|
|
431
|
+
const group = groups.get(key) ?? [];
|
|
432
|
+
group.push(sub);
|
|
433
|
+
groups.set(key, group);
|
|
434
|
+
}
|
|
435
|
+
const secrets_cache = new Map;
|
|
436
|
+
const results = {};
|
|
437
|
+
for (const [key, subs] of groups) {
|
|
438
|
+
const first = subs[0];
|
|
439
|
+
if (!first) {
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
let secrets = secrets_cache.get(key);
|
|
443
|
+
if (!secrets) {
|
|
444
|
+
const download_result = await doppler_secrets_download(first.project, first.config, options);
|
|
445
|
+
if (!download_result.success) {
|
|
446
|
+
let all_have_defaults = true;
|
|
447
|
+
for (const sub of subs) {
|
|
448
|
+
if (sub.default !== undefined) {
|
|
449
|
+
results[sub.name] = sub.default;
|
|
450
|
+
} else if (options.fail_on_missing !== false) {
|
|
451
|
+
all_have_defaults = false;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
if (!all_have_defaults) {
|
|
455
|
+
return failure(download_result.error);
|
|
456
|
+
}
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
secrets = download_result.value;
|
|
460
|
+
secrets_cache.set(key, secrets);
|
|
461
|
+
}
|
|
462
|
+
for (const sub of subs) {
|
|
463
|
+
const value = secrets[sub.secret];
|
|
464
|
+
if (value !== undefined) {
|
|
465
|
+
results[sub.name] = value;
|
|
466
|
+
} else if (sub.default !== undefined) {
|
|
467
|
+
results[sub.name] = sub.default;
|
|
468
|
+
} else if (options.fail_on_missing !== false) {
|
|
469
|
+
return failure({
|
|
470
|
+
code: "SECRET_NOT_FOUND",
|
|
471
|
+
message: `Secret not found in Doppler: ${sub.project}/${sub.config}/${sub.secret}`
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return success(results);
|
|
477
|
+
}
|
|
478
|
+
export {
|
|
479
|
+
resolve_doppler_substitutions,
|
|
480
|
+
plugin,
|
|
481
|
+
exec_command,
|
|
482
|
+
doppler_secrets_download,
|
|
483
|
+
doppler_secret_get,
|
|
484
|
+
plugin as default,
|
|
485
|
+
create_doppler_plugin,
|
|
486
|
+
create_cache_key,
|
|
487
|
+
check_doppler_available,
|
|
488
|
+
DEFAULT_TIMEOUT
|
|
489
|
+
};
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { KustodianPluginType } from '@kustodian/plugins';
|
|
2
|
+
import type { DopplerPluginOptionsType } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Creates the Doppler plugin.
|
|
5
|
+
*/
|
|
6
|
+
export declare function create_doppler_plugin(options?: DopplerPluginOptionsType): KustodianPluginType;
|
|
7
|
+
/**
|
|
8
|
+
* Default plugin export.
|
|
9
|
+
*/
|
|
10
|
+
export declare const plugin: KustodianPluginType;
|
|
11
|
+
export default plugin;
|
|
12
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAIV,mBAAmB,EAIpB,MAAM,oBAAoB,CAAC;AAO5B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAY3D;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,wBAA6B,GAAG,mBAAmB,CA0GjG;AAED;;GAEG;AACH,eAAO,MAAM,MAAM,qBAA0B,CAAC;AAE9C,eAAe,MAAM,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { KustodianErrorType } from '@kustodian/core';
|
|
2
|
+
import { type ResultType } from '@kustodian/core';
|
|
3
|
+
import type { DopplerSubstitutionType } from '@kustodian/schema';
|
|
4
|
+
import { type DopplerPluginOptionsType } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Resolves Doppler substitutions to actual secret values.
|
|
7
|
+
* Groups by project/config to minimize API calls.
|
|
8
|
+
* Uses cluster-level defaults when project/config are not specified.
|
|
9
|
+
* Returns a map from substitution name to resolved value.
|
|
10
|
+
*/
|
|
11
|
+
export declare function resolve_doppler_substitutions(substitutions: DopplerSubstitutionType[], options?: DopplerPluginOptionsType): Promise<ResultType<Record<string, string>, KustodianErrorType>>;
|
|
12
|
+
//# sourceMappingURL=resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../src/resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,KAAK,UAAU,EAAoB,MAAM,iBAAiB,CAAC;AACpE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAGjE,OAAO,EAEL,KAAK,wBAAwB,EAE9B,MAAM,YAAY,CAAC;AAEpB;;;;;GAKG;AACH,wBAAsB,6BAA6B,CACjD,aAAa,EAAE,uBAAuB,EAAE,EACxC,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,kBAAkB,CAAC,CAAC,CA+FjE"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cluster-level Doppler defaults.
|
|
3
|
+
*/
|
|
4
|
+
export interface DopplerClusterDefaultsType {
|
|
5
|
+
/** Default project name */
|
|
6
|
+
project?: string | undefined;
|
|
7
|
+
/** Default config name */
|
|
8
|
+
config?: string | undefined;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Options for the Doppler plugin.
|
|
12
|
+
*/
|
|
13
|
+
export interface DopplerPluginOptionsType {
|
|
14
|
+
/** Doppler service token (can also be set via DOPPLER_TOKEN env var) */
|
|
15
|
+
token?: string | undefined;
|
|
16
|
+
/** Timeout for CLI operations in milliseconds (default: 30000) */
|
|
17
|
+
timeout?: number | undefined;
|
|
18
|
+
/** Whether to fail on missing secrets (default: true) */
|
|
19
|
+
fail_on_missing?: boolean | undefined;
|
|
20
|
+
/** Cluster-level defaults for project/config */
|
|
21
|
+
cluster_defaults?: DopplerClusterDefaultsType | undefined;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Parsed Doppler secret reference.
|
|
25
|
+
*/
|
|
26
|
+
export interface DopplerRefType {
|
|
27
|
+
project: string;
|
|
28
|
+
config: string;
|
|
29
|
+
secret: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Cache key for Doppler project/config combination.
|
|
33
|
+
*/
|
|
34
|
+
export type DopplerCacheKeyType = `${string}/${string}`;
|
|
35
|
+
/**
|
|
36
|
+
* Default timeout for Doppler CLI operations.
|
|
37
|
+
*/
|
|
38
|
+
export declare const DEFAULT_TIMEOUT = 30000;
|
|
39
|
+
/**
|
|
40
|
+
* Creates a cache key for a Doppler project/config combination.
|
|
41
|
+
*/
|
|
42
|
+
export declare function create_cache_key(project: string, config: string): DopplerCacheKeyType;
|
|
43
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC,2BAA2B;IAC3B,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,0BAA0B;IAC1B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,yDAAyD;IACzD,eAAe,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACtC,gDAAgD;IAChD,gBAAgB,CAAC,EAAE,0BAA0B,GAAG,SAAS,CAAC;CAC3D;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC;AAExD;;GAEG;AACH,eAAO,MAAM,eAAe,QAAQ,CAAC;AAErC;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,mBAAmB,CAErF"}
|
package/package.json
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kustodian/plugin-doppler",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Doppler secret provider plugin for Kustodian",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./
|
|
7
|
-
"types": "./
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
"types": "./
|
|
11
|
-
"import": "./
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
|
-
"
|
|
15
|
+
"dist"
|
|
16
16
|
],
|
|
17
17
|
"scripts": {
|
|
18
18
|
"test": "bun test",
|
|
19
19
|
"test:watch": "bun test --watch",
|
|
20
|
-
"typecheck": "bun run tsc --noEmit"
|
|
20
|
+
"typecheck": "bun run tsc --noEmit",
|
|
21
|
+
"build": "bun build src/index.ts --outdir dist --target node --format esm && tsc --emitDeclarationOnly --outDir dist",
|
|
22
|
+
"prepublishOnly": "bun run build"
|
|
21
23
|
},
|
|
22
24
|
"keywords": [
|
|
23
25
|
"kustodian",
|
|
@@ -34,12 +36,13 @@
|
|
|
34
36
|
"directory": "plugins/doppler"
|
|
35
37
|
},
|
|
36
38
|
"publishConfig": {
|
|
37
|
-
"
|
|
39
|
+
"access": "public",
|
|
40
|
+
"registry": "https://registry.npmjs.org"
|
|
38
41
|
},
|
|
39
42
|
"dependencies": {
|
|
40
|
-
"@kustodian/core": "
|
|
41
|
-
"@kustodian/plugins": "
|
|
42
|
-
"@kustodian/schema": "
|
|
43
|
+
"@kustodian/core": "2.0.0",
|
|
44
|
+
"@kustodian/plugins": "2.0.0",
|
|
45
|
+
"@kustodian/schema": "2.0.0"
|
|
43
46
|
},
|
|
44
47
|
"devDependencies": {}
|
|
45
48
|
}
|
package/src/executor.ts
DELETED
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
import { execFile } from 'node:child_process';
|
|
2
|
-
import { promisify } from 'node:util';
|
|
3
|
-
|
|
4
|
-
import { Errors, type ResultType, failure, success } from '@kustodian/core';
|
|
5
|
-
import type { KustodianErrorType } from '@kustodian/core';
|
|
6
|
-
|
|
7
|
-
import { DEFAULT_TIMEOUT, type DopplerPluginOptionsType } from './types.js';
|
|
8
|
-
|
|
9
|
-
const exec_file_async = promisify(execFile);
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Result of a command execution.
|
|
13
|
-
*/
|
|
14
|
-
export interface CommandResultType {
|
|
15
|
-
stdout: string;
|
|
16
|
-
stderr: string;
|
|
17
|
-
exit_code: number;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Options for command execution.
|
|
22
|
-
*/
|
|
23
|
-
export interface ExecOptionsType {
|
|
24
|
-
cwd?: string | undefined;
|
|
25
|
-
timeout?: number | undefined;
|
|
26
|
-
env?: Record<string, string> | undefined;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Executes a command and returns the result.
|
|
31
|
-
* Uses execFile for security (no shell invocation).
|
|
32
|
-
*/
|
|
33
|
-
export async function exec_command(
|
|
34
|
-
command: string,
|
|
35
|
-
args: string[] = [],
|
|
36
|
-
options: ExecOptionsType = {},
|
|
37
|
-
): Promise<ResultType<CommandResultType, KustodianErrorType>> {
|
|
38
|
-
try {
|
|
39
|
-
const { stdout, stderr } = await exec_file_async(command, args, {
|
|
40
|
-
cwd: options.cwd,
|
|
41
|
-
timeout: options.timeout,
|
|
42
|
-
env: { ...process.env, ...options.env },
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
return success({
|
|
46
|
-
stdout,
|
|
47
|
-
stderr,
|
|
48
|
-
exit_code: 0,
|
|
49
|
-
});
|
|
50
|
-
} catch (error) {
|
|
51
|
-
if (is_exec_error(error)) {
|
|
52
|
-
return success({
|
|
53
|
-
stdout: error.stdout ?? '',
|
|
54
|
-
stderr: error.stderr ?? '',
|
|
55
|
-
exit_code: error.code ?? 1,
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return failure(Errors.unknown(`Failed to execute command: ${command}`, error));
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Type guard for exec errors.
|
|
65
|
-
*/
|
|
66
|
-
function is_exec_error(
|
|
67
|
-
error: unknown,
|
|
68
|
-
): error is { stdout?: string; stderr?: string; code?: number } {
|
|
69
|
-
return (
|
|
70
|
-
typeof error === 'object' &&
|
|
71
|
-
error !== null &&
|
|
72
|
-
('stdout' in error || 'stderr' in error || 'code' in error)
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Gets environment variables for Doppler CLI authentication.
|
|
78
|
-
*/
|
|
79
|
-
function get_doppler_auth_env(options: DopplerPluginOptionsType): Record<string, string> {
|
|
80
|
-
const env: Record<string, string> = {};
|
|
81
|
-
|
|
82
|
-
const token = options.token ?? process.env['DOPPLER_TOKEN'];
|
|
83
|
-
if (token) {
|
|
84
|
-
env['DOPPLER_TOKEN'] = token;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return env;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Checks if doppler CLI is available in the system PATH.
|
|
92
|
-
*/
|
|
93
|
-
export async function check_doppler_available(): Promise<ResultType<string, KustodianErrorType>> {
|
|
94
|
-
const result = await exec_command('doppler', ['--version']);
|
|
95
|
-
|
|
96
|
-
if (!result.success) {
|
|
97
|
-
return result;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (result.value.exit_code !== 0) {
|
|
101
|
-
return failure(Errors.secret_cli_not_found('Doppler', 'doppler'));
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Parse version from output (e.g., "v3.68.0")
|
|
105
|
-
const version = result.value.stdout.trim();
|
|
106
|
-
return success(version);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Fetches all secrets for a project/config combination.
|
|
111
|
-
* Returns a JSON object of all secrets.
|
|
112
|
-
*/
|
|
113
|
-
export async function doppler_secrets_download(
|
|
114
|
-
project: string,
|
|
115
|
-
config: string,
|
|
116
|
-
options: DopplerPluginOptionsType = {},
|
|
117
|
-
): Promise<ResultType<Record<string, string>, KustodianErrorType>> {
|
|
118
|
-
const timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
119
|
-
const env = get_doppler_auth_env(options);
|
|
120
|
-
|
|
121
|
-
const result = await exec_command(
|
|
122
|
-
'doppler',
|
|
123
|
-
[
|
|
124
|
-
'secrets',
|
|
125
|
-
'download',
|
|
126
|
-
'--project',
|
|
127
|
-
project,
|
|
128
|
-
'--config',
|
|
129
|
-
config,
|
|
130
|
-
'--format',
|
|
131
|
-
'json',
|
|
132
|
-
'--no-file',
|
|
133
|
-
],
|
|
134
|
-
{
|
|
135
|
-
timeout,
|
|
136
|
-
env,
|
|
137
|
-
},
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
if (!result.success) {
|
|
141
|
-
return result;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const { stdout, stderr, exit_code } = result.value;
|
|
145
|
-
|
|
146
|
-
if (exit_code !== 0) {
|
|
147
|
-
// Parse specific error types from stderr
|
|
148
|
-
const stderr_lower = stderr.toLowerCase();
|
|
149
|
-
|
|
150
|
-
if (
|
|
151
|
-
stderr_lower.includes('not found') ||
|
|
152
|
-
stderr_lower.includes("doesn't exist") ||
|
|
153
|
-
stderr_lower.includes('no project') ||
|
|
154
|
-
stderr_lower.includes('no config')
|
|
155
|
-
) {
|
|
156
|
-
return failure(Errors.secret_not_found('Doppler', `${project}/${config}`));
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (
|
|
160
|
-
stderr_lower.includes('unauthorized') ||
|
|
161
|
-
stderr_lower.includes('authentication') ||
|
|
162
|
-
stderr_lower.includes('invalid token') ||
|
|
163
|
-
stderr_lower.includes('access denied')
|
|
164
|
-
) {
|
|
165
|
-
return failure(Errors.secret_auth_error('Doppler', stderr));
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (stderr_lower.includes('timeout')) {
|
|
169
|
-
return failure(Errors.secret_timeout('Doppler', timeout));
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return failure(Errors.unknown(`Doppler error: ${stderr}`));
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
try {
|
|
176
|
-
const secrets = JSON.parse(stdout) as Record<string, string>;
|
|
177
|
-
return success(secrets);
|
|
178
|
-
} catch (error) {
|
|
179
|
-
return failure(Errors.unknown('Failed to parse Doppler secrets output', error));
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Fetches a single secret value from Doppler.
|
|
185
|
-
*/
|
|
186
|
-
export async function doppler_secret_get(
|
|
187
|
-
project: string,
|
|
188
|
-
config: string,
|
|
189
|
-
secret_name: string,
|
|
190
|
-
options: DopplerPluginOptionsType = {},
|
|
191
|
-
): Promise<ResultType<string, KustodianErrorType>> {
|
|
192
|
-
const timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
193
|
-
const env = get_doppler_auth_env(options);
|
|
194
|
-
|
|
195
|
-
const result = await exec_command(
|
|
196
|
-
'doppler',
|
|
197
|
-
['secrets', 'get', secret_name, '--project', project, '--config', config, '--plain'],
|
|
198
|
-
{
|
|
199
|
-
timeout,
|
|
200
|
-
env,
|
|
201
|
-
},
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
if (!result.success) {
|
|
205
|
-
return result;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const { stdout, stderr, exit_code } = result.value;
|
|
209
|
-
|
|
210
|
-
if (exit_code !== 0) {
|
|
211
|
-
// Parse specific error types from stderr
|
|
212
|
-
const stderr_lower = stderr.toLowerCase();
|
|
213
|
-
|
|
214
|
-
if (
|
|
215
|
-
stderr_lower.includes('not found') ||
|
|
216
|
-
stderr_lower.includes("doesn't exist") ||
|
|
217
|
-
stderr_lower.includes('no secret')
|
|
218
|
-
) {
|
|
219
|
-
return failure(Errors.secret_not_found('Doppler', `${project}/${config}/${secret_name}`));
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
if (
|
|
223
|
-
stderr_lower.includes('unauthorized') ||
|
|
224
|
-
stderr_lower.includes('authentication') ||
|
|
225
|
-
stderr_lower.includes('invalid token')
|
|
226
|
-
) {
|
|
227
|
-
return failure(Errors.secret_auth_error('Doppler', stderr));
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if (stderr_lower.includes('timeout')) {
|
|
231
|
-
return failure(Errors.secret_timeout('Doppler', timeout));
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
return failure(Errors.unknown(`Doppler error: ${stderr}`));
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
return success(stdout.trim());
|
|
238
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
// Plugin exports
|
|
2
|
-
export { create_doppler_plugin, plugin } from './plugin.js';
|
|
3
|
-
export { plugin as default } from './plugin.js';
|
|
4
|
-
|
|
5
|
-
// Executor exports
|
|
6
|
-
export {
|
|
7
|
-
check_doppler_available,
|
|
8
|
-
exec_command,
|
|
9
|
-
doppler_secret_get,
|
|
10
|
-
doppler_secrets_download,
|
|
11
|
-
type CommandResultType,
|
|
12
|
-
type ExecOptionsType,
|
|
13
|
-
} from './executor.js';
|
|
14
|
-
|
|
15
|
-
// Resolver exports
|
|
16
|
-
export { resolve_doppler_substitutions } from './resolver.js';
|
|
17
|
-
|
|
18
|
-
// Types
|
|
19
|
-
export {
|
|
20
|
-
create_cache_key,
|
|
21
|
-
DEFAULT_TIMEOUT,
|
|
22
|
-
type DopplerCacheKeyType,
|
|
23
|
-
type DopplerPluginOptionsType,
|
|
24
|
-
type DopplerRefType,
|
|
25
|
-
} from './types.js';
|
package/src/plugin.ts
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import { success } from '@kustodian/core';
|
|
2
|
-
import type {
|
|
3
|
-
CommandType,
|
|
4
|
-
HookContextType,
|
|
5
|
-
HookEventType,
|
|
6
|
-
KustodianPluginType,
|
|
7
|
-
PluginCommandContributionType,
|
|
8
|
-
PluginHookContributionType,
|
|
9
|
-
PluginManifestType,
|
|
10
|
-
} from '@kustodian/plugins';
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
check_doppler_available,
|
|
14
|
-
doppler_secret_get,
|
|
15
|
-
doppler_secrets_download,
|
|
16
|
-
} from './executor.js';
|
|
17
|
-
import type { DopplerPluginOptionsType } from './types.js';
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Doppler plugin manifest.
|
|
21
|
-
*/
|
|
22
|
-
const manifest: PluginManifestType = {
|
|
23
|
-
name: '@kustodian/plugin-doppler',
|
|
24
|
-
version: '0.1.0',
|
|
25
|
-
description: 'Doppler secret provider for Kustodian',
|
|
26
|
-
capabilities: ['commands', 'hooks'],
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Creates the Doppler plugin.
|
|
31
|
-
*/
|
|
32
|
-
export function create_doppler_plugin(options: DopplerPluginOptionsType = {}): KustodianPluginType {
|
|
33
|
-
return {
|
|
34
|
-
manifest,
|
|
35
|
-
|
|
36
|
-
async activate() {
|
|
37
|
-
// Verify CLI availability on activation (warning only)
|
|
38
|
-
const check_result = await check_doppler_available();
|
|
39
|
-
if (!check_result.success) {
|
|
40
|
-
console.warn('Doppler CLI not found - secret resolution may fail');
|
|
41
|
-
}
|
|
42
|
-
return success(undefined);
|
|
43
|
-
},
|
|
44
|
-
|
|
45
|
-
async deactivate() {
|
|
46
|
-
return success(undefined);
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
get_commands(): PluginCommandContributionType[] {
|
|
50
|
-
const doppler_command: CommandType = {
|
|
51
|
-
name: 'doppler',
|
|
52
|
-
description: 'Doppler secret management commands',
|
|
53
|
-
subcommands: [
|
|
54
|
-
{
|
|
55
|
-
name: 'check',
|
|
56
|
-
description: 'Check Doppler CLI availability and authentication',
|
|
57
|
-
handler: async () => {
|
|
58
|
-
const result = await check_doppler_available();
|
|
59
|
-
if (result.success) {
|
|
60
|
-
console.log(`Doppler CLI version: ${result.value}`);
|
|
61
|
-
return success(undefined);
|
|
62
|
-
}
|
|
63
|
-
console.error('Doppler CLI not available');
|
|
64
|
-
return result;
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
name: 'test',
|
|
69
|
-
description: 'Test reading a secret from Doppler',
|
|
70
|
-
arguments: [
|
|
71
|
-
{ name: 'project', description: 'Doppler project', required: true },
|
|
72
|
-
{ name: 'config', description: 'Doppler config', required: true },
|
|
73
|
-
{ name: 'secret', description: 'Secret name', required: true },
|
|
74
|
-
],
|
|
75
|
-
handler: async (ctx) => {
|
|
76
|
-
const [project, config, secret] = ctx.args;
|
|
77
|
-
if (!project || !config || !secret) {
|
|
78
|
-
console.error('Missing required arguments: project, config, secret');
|
|
79
|
-
return success(undefined);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const result = await doppler_secret_get(project, config, secret, options);
|
|
83
|
-
if (result.success) {
|
|
84
|
-
console.log('Secret retrieved successfully (value hidden)');
|
|
85
|
-
console.log(`Length: ${result.value.length} characters`);
|
|
86
|
-
return success(undefined);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
console.error(`Failed to read secret: ${result.error.message}`);
|
|
90
|
-
return result;
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
name: 'list-secrets',
|
|
95
|
-
description: 'List available secrets in a Doppler config',
|
|
96
|
-
arguments: [
|
|
97
|
-
{ name: 'project', description: 'Doppler project', required: true },
|
|
98
|
-
{ name: 'config', description: 'Doppler config', required: true },
|
|
99
|
-
],
|
|
100
|
-
handler: async (ctx) => {
|
|
101
|
-
const [project, config] = ctx.args;
|
|
102
|
-
if (!project || !config) {
|
|
103
|
-
console.error('Missing required arguments: project, config');
|
|
104
|
-
return success(undefined);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const result = await doppler_secrets_download(project, config, options);
|
|
108
|
-
if (result.success) {
|
|
109
|
-
console.log('Available secrets:');
|
|
110
|
-
for (const key of Object.keys(result.value)) {
|
|
111
|
-
console.log(` - ${key}`);
|
|
112
|
-
}
|
|
113
|
-
return success(undefined);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
console.error(`Failed to list secrets: ${result.error.message}`);
|
|
117
|
-
return result;
|
|
118
|
-
},
|
|
119
|
-
},
|
|
120
|
-
],
|
|
121
|
-
};
|
|
122
|
-
return [{ command: doppler_command }];
|
|
123
|
-
},
|
|
124
|
-
|
|
125
|
-
get_hooks(): PluginHookContributionType[] {
|
|
126
|
-
return [
|
|
127
|
-
{
|
|
128
|
-
event: 'generator:after_resolve',
|
|
129
|
-
priority: 50, // Run before default (100) to inject secrets early
|
|
130
|
-
handler: async (_event: HookEventType, ctx: HookContextType) => {
|
|
131
|
-
// Hook for secret injection will be implemented in generator integration
|
|
132
|
-
return success(ctx);
|
|
133
|
-
},
|
|
134
|
-
},
|
|
135
|
-
];
|
|
136
|
-
},
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Default plugin export.
|
|
142
|
-
*/
|
|
143
|
-
export const plugin = create_doppler_plugin();
|
|
144
|
-
|
|
145
|
-
export default plugin;
|
package/src/resolver.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { type ResultType, failure, success } from '@kustodian/core';
|
|
2
|
-
import type { KustodianErrorType } from '@kustodian/core';
|
|
3
|
-
import type { DopplerSubstitutionType } from '@kustodian/schema';
|
|
4
|
-
|
|
5
|
-
import { doppler_secrets_download } from './executor.js';
|
|
6
|
-
import {
|
|
7
|
-
type DopplerCacheKeyType,
|
|
8
|
-
type DopplerPluginOptionsType,
|
|
9
|
-
create_cache_key,
|
|
10
|
-
} from './types.js';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Resolves Doppler substitutions to actual secret values.
|
|
14
|
-
* Groups by project/config to minimize API calls.
|
|
15
|
-
* Uses cluster-level defaults when project/config are not specified.
|
|
16
|
-
* Returns a map from substitution name to resolved value.
|
|
17
|
-
*/
|
|
18
|
-
export async function resolve_doppler_substitutions(
|
|
19
|
-
substitutions: DopplerSubstitutionType[],
|
|
20
|
-
options: DopplerPluginOptionsType = {},
|
|
21
|
-
): Promise<ResultType<Record<string, string>, KustodianErrorType>> {
|
|
22
|
-
if (substitutions.length === 0) {
|
|
23
|
-
return success({});
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Validate and apply cluster defaults
|
|
27
|
-
const resolved_subs: Array<DopplerSubstitutionType & { project: string; config: string }> = [];
|
|
28
|
-
|
|
29
|
-
for (const sub of substitutions) {
|
|
30
|
-
const project = sub.project ?? options.cluster_defaults?.project;
|
|
31
|
-
const config = sub.config ?? options.cluster_defaults?.config;
|
|
32
|
-
|
|
33
|
-
if (!project || !config) {
|
|
34
|
-
return failure({
|
|
35
|
-
code: 'MISSING_DOPPLER_CONFIG',
|
|
36
|
-
message: `Doppler substitution '${sub.name}' missing project/config and no cluster defaults configured`,
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
resolved_subs.push({
|
|
41
|
-
...sub,
|
|
42
|
-
project,
|
|
43
|
-
config,
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Group substitutions by project/config to minimize API calls
|
|
48
|
-
const groups = new Map<DopplerCacheKeyType, typeof resolved_subs>();
|
|
49
|
-
|
|
50
|
-
for (const sub of resolved_subs) {
|
|
51
|
-
const key = create_cache_key(sub.project, sub.config);
|
|
52
|
-
const group = groups.get(key) ?? [];
|
|
53
|
-
group.push(sub);
|
|
54
|
-
groups.set(key, group);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Cache for downloaded secrets per project/config
|
|
58
|
-
const secrets_cache = new Map<DopplerCacheKeyType, Record<string, string>>();
|
|
59
|
-
|
|
60
|
-
const results: Record<string, string> = {};
|
|
61
|
-
|
|
62
|
-
// Fetch secrets for each group
|
|
63
|
-
for (const [key, subs] of groups) {
|
|
64
|
-
// Get project and config from the first substitution in the group
|
|
65
|
-
const first = subs[0];
|
|
66
|
-
if (!first) {
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Check if we already have the secrets cached
|
|
71
|
-
let secrets = secrets_cache.get(key);
|
|
72
|
-
|
|
73
|
-
if (!secrets) {
|
|
74
|
-
const download_result = await doppler_secrets_download(first.project, first.config, options);
|
|
75
|
-
|
|
76
|
-
if (!download_result.success) {
|
|
77
|
-
// If we can't download secrets, try to use defaults
|
|
78
|
-
let all_have_defaults = true;
|
|
79
|
-
for (const sub of subs) {
|
|
80
|
-
if (sub.default !== undefined) {
|
|
81
|
-
results[sub.name] = sub.default;
|
|
82
|
-
} else if (options.fail_on_missing !== false) {
|
|
83
|
-
all_have_defaults = false;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (!all_have_defaults) {
|
|
88
|
-
return failure(download_result.error);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
secrets = download_result.value;
|
|
95
|
-
secrets_cache.set(key, secrets);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Extract the requested secrets
|
|
99
|
-
for (const sub of subs) {
|
|
100
|
-
const value = secrets[sub.secret];
|
|
101
|
-
|
|
102
|
-
if (value !== undefined) {
|
|
103
|
-
results[sub.name] = value;
|
|
104
|
-
} else if (sub.default !== undefined) {
|
|
105
|
-
results[sub.name] = sub.default;
|
|
106
|
-
} else if (options.fail_on_missing !== false) {
|
|
107
|
-
return failure({
|
|
108
|
-
code: 'SECRET_NOT_FOUND',
|
|
109
|
-
message: `Secret not found in Doppler: ${sub.project}/${sub.config}/${sub.secret}`,
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return success(results);
|
|
116
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cluster-level Doppler defaults.
|
|
3
|
-
*/
|
|
4
|
-
export interface DopplerClusterDefaultsType {
|
|
5
|
-
/** Default project name */
|
|
6
|
-
project?: string | undefined;
|
|
7
|
-
/** Default config name */
|
|
8
|
-
config?: string | undefined;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Options for the Doppler plugin.
|
|
13
|
-
*/
|
|
14
|
-
export interface DopplerPluginOptionsType {
|
|
15
|
-
/** Doppler service token (can also be set via DOPPLER_TOKEN env var) */
|
|
16
|
-
token?: string | undefined;
|
|
17
|
-
/** Timeout for CLI operations in milliseconds (default: 30000) */
|
|
18
|
-
timeout?: number | undefined;
|
|
19
|
-
/** Whether to fail on missing secrets (default: true) */
|
|
20
|
-
fail_on_missing?: boolean | undefined;
|
|
21
|
-
/** Cluster-level defaults for project/config */
|
|
22
|
-
cluster_defaults?: DopplerClusterDefaultsType | undefined;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Parsed Doppler secret reference.
|
|
27
|
-
*/
|
|
28
|
-
export interface DopplerRefType {
|
|
29
|
-
project: string;
|
|
30
|
-
config: string;
|
|
31
|
-
secret: string;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Cache key for Doppler project/config combination.
|
|
36
|
-
*/
|
|
37
|
-
export type DopplerCacheKeyType = `${string}/${string}`;
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Default timeout for Doppler CLI operations.
|
|
41
|
-
*/
|
|
42
|
-
export const DEFAULT_TIMEOUT = 30000;
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Creates a cache key for a Doppler project/config combination.
|
|
46
|
-
*/
|
|
47
|
-
export function create_cache_key(project: string, config: string): DopplerCacheKeyType {
|
|
48
|
-
return `${project}/${config}`;
|
|
49
|
-
}
|