| | | 1 | | /** |
| | | 2 | | * Whether a secret declared in the map file can be resolved, as checked by |
| | | 3 | | * `envilder validate` without reading its value: |
| | | 4 | | * |
| | | 5 | | * - `exists`: the secret is there and the identity can see it. |
| | | 6 | | * - `missing`: the provider answered that there is no such secret. |
| | | 7 | | * - `access_denied`: the identity is not allowed to check the secret, so |
| | | 8 | | * whether it exists is unknown. Never reported as `missing`. |
| | | 9 | | * - `authentication_failed`: the provider rejected the credentials. |
| | | 10 | | * - `provider_error`: anything else, for example an unreachable provider. |
| | | 11 | | */ |
| | | 12 | | export type SecretExistenceStatus = |
| | | 13 | | | 'exists' |
| | | 14 | | | 'missing' |
| | | 15 | | | 'access_denied' |
| | | 16 | | | 'authentication_failed' |
| | | 17 | | | 'provider_error'; |
| | | 18 | | |
| | | 19 | | /** |
| | | 20 | | * The outcome of checking one secret path. `reason` explains a status other |
| | | 21 | | * than `exists` or `missing` when the provider gave one; it never carries a |
| | | 22 | | * secret value. |
| | | 23 | | */ |
| | | 24 | | export type SecretExistence = { |
| | | 25 | | readonly status: SecretExistenceStatus; |
| | | 26 | | readonly reason?: string; |
| | | 27 | | }; |
| | | 28 | | |
| | | 29 | | /** One variable of the map file with the outcome for the path it maps to. */ |
| | | 30 | | export type RemoteKeyCheck = SecretExistence & { |
| | | 31 | | readonly variable: string; |
| | | 32 | | readonly path: string; |
| | | 33 | | }; |
| | | 34 | | |
| | | 35 | | /** Every failing status, in the order a summary lists them. */ |
| | 6 | 36 | | const FAILING_STATUSES: readonly SecretExistenceStatus[] = [ |
| | | 37 | | 'missing', |
| | | 38 | | 'access_denied', |
| | | 39 | | 'authentication_failed', |
| | | 40 | | 'provider_error', |
| | | 41 | | ]; |
| | | 42 | | |
| | | 43 | | /** How a status reads in text output: `access denied`, not `access_denied`. */ |
| | | 44 | | export function describeSecretExistenceStatus( |
| | | 45 | | status: SecretExistenceStatus, |
| | | 46 | | ): string { |
| | 40 | 47 | | return status.replace(/_/g, ' '); |
| | | 48 | | } |
| | | 49 | | |
| | | 50 | | /** |
| | | 51 | | * Counts the failing checks by status, for example `2 missing, 1 access |
| | | 52 | | * denied`. Empty when every secret exists. |
| | | 53 | | */ |
| | | 54 | | export function summarizeFailures(checks: readonly RemoteKeyCheck[]): string { |
| | 96 | 55 | | return FAILING_STATUSES.map((status) => ({ |
| | | 56 | | status, |
| | 200 | 57 | | count: checks.filter((check) => check.status === status).length, |
| | | 58 | | })) |
| | 96 | 59 | | .filter(({ count }) => count > 0) |
| | | 60 | | .map( |
| | | 61 | | ({ status, count }) => |
| | 18 | 62 | | `${count} ${describeSecretExistenceStatus(status)}`, |
| | | 63 | | ) |
| | | 64 | | .join(', '); |
| | | 65 | | } |