This repository has been archived by the owner on May 8, 2021. It is now read-only.
/
docker-secrets.ts
145 lines (116 loc) · 3.49 KB
/
docker-secrets.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/**
* docker-secrets
* FP loading of docker secrets, falling back on env vars
*/
import * as fs from 'fs'
import * as path from 'path'
import ow from 'ow'
import Debug from 'debug'
import { Maybe, Just } from 'purify-ts/Maybe'
import { Left, Right, Either } from 'purify-ts/Either'
import { FutureInstance } from 'fluture'
import * as Future from 'fluture'
const debug = {
secrets: Debug('docker-secrets')
}
const defaultSecretsDir = () => '/run/secrets'
const tryCatch = <L, R>(f: () => R): Either<L, R> => {
try {
return Right(f())
} catch (error) {
return Left(error)
}
}
const readFile: (path: string) => FutureInstance<NodeJS.ErrnoException, string> =
Future.encaseP(
async (file: string) => new Promise<string>(
(resolve, reject) => fs.readFile(
file,
'utf8',
(error, data) => error ? reject(error) : resolve(data)
)
)
)
const readFileSync = (file: string) =>
tryCatch<NodeJS.ErrnoException, string>(() => fs.readFileSync(file, 'utf8'))
function getEnvironmentVariable(secret: string): Maybe<string> {
return Maybe.fromNullable(process.env[secret.toUpperCase()])
}
function getSecret<T>(
secretGetter: (directory: string, secret: string) => T
) {
return function getSecretFromDirectory(directory: string) {
ow(directory, ow.string)
return function getSecretDescribedBy(secret: string) {
ow(secret, ow.string)
debug.secrets(`loading secret '${secret}'`)
return secretGetter(directory, secret)
}
}
}
async function getSecretAsync(
directory: string,
secret: string
): Promise<Maybe<string>> {
const secretFile = path.resolve(directory, secret)
return readFile(secretFile)
.map(text => text.trim())
.map(text => Just(text))
.chainRej(() => Future.of(getEnvironmentVariable(secret)))
.promise()
}
function getSecretSync(
directory: string,
secret: string
): Maybe<string> {
const secretFile = path.resolve(directory, secret)
return readFileSync(secretFile)
.map(text => text.trim())
.map(text => Just(text))
.mapLeft(() => getEnvironmentVariable(secret))
.extract()
}
/**
* Get a secret asynchronously, first checking /run/secrets and then
* falling-back to environment variables.
*/
async function get(secret: string) {
return getSecret (getSecretAsync) (defaultSecretsDir()) (secret)
}
/**
* Get a secret asynchronously, first checking `directory` and then
* falling-back to environment variables.
*/
function getFrom(directory: string) {
return getSecret (getSecretAsync) (directory)
}
/**
* Get a secret synchronously, first checking /run/secrets and then
* falling-back to environment variables.
*/
function getSync(secret: string) {
return getSecret (getSecretSync) (defaultSecretsDir()) (secret)
}
/**
* Get a secret synchronously, first checking `directory` and then
* falling-back to environment variables.
*/
function getFromSync(directory: string) {
return getSecret (getSecretSync) (directory)
}
/**
* Exposed API
*/
interface Secrets {
get: (secret: string) => Promise<Maybe<string>>;
getSync: (secret: string) => Maybe<string>;
getFrom: (directory: string) => (secret: string) => Promise<Maybe<string>>;
getFromSync: (directory: string) => (secret: string) => Maybe<string>;
}
const secrets: Secrets = {
get,
getFrom,
getSync,
getFromSync
}
export { secrets }