-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontrol.ls
166 lines (131 loc) · 5.72 KB
/
control.ls
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
{find, filter, map, minimum, any, concat-map, unique, id} = require \prelude-ls
{bindP, from-error-value-callback, new-promise, returnP, rejectP, to-callback, with-cancel-and-dispose} = require \./async-ls
{exec} = require \shelljs
Docker = require \dockerode
read = (file) -> (require \fs).readFileSync "/Users/homam/.docker/machine/certs/#{file}"
docker = new Docker({host: "192.168.99.100", protocol: \https, port: 2376, ca: (read "ca.pem"), cert: (read "cert.pem"), key: (read "key.pem") })
# ContainerInfo -> Boolean
is-container-up = (container) ->
(container.Status.index-of "Up") == 0
# ContainerInfo -> Int16
get-public-port = (.Ports.0.PublicPort)
# [ContainerInfo] -> Int16
find-a-free-port = (containers) ->
taken-ports = containers
#|> filter (-> (it.Status.index-of "Up") != 0)
|> concat-map (.Ports)
|> map (.PublicPort)
taken-ports := unique (taken-ports ++ (containers |> map (.Names.0.split "_p_" .1) |> filter (-> !!it) |> map (-> parse-int it)))
f = (p) ->
if taken-ports |> any (== p) then f (p + 1) else p
f 10000
# docker run -d -p $(docker-machine ip default):4082:4081 --name homam -i -t homam/pipe /bin/sh ./start.sh
resume-container = (container) ->
resolve, reject <- new-promise
err, data <- container.start
return reject err if !!err
resolve container
get-containers = (options = {all: true}) ->
(from-error-value-callback docker.listContainers, docker) options
get-container-info = (username, options = {all: true}) ->
containers <- bindP (get-containers options)
returnP <| find-container-info containers, username
find-container-info = (containers, username) ->
containers |> find (-> "/#{username}" in (it.Names |> map (.split "_p_" .0)))
# wait-for-stream :: Container -> Promise ()
wait-for-stream = (container) ->
stream <- bindP (from-error-value-callback container.attach, container) {stream: true, stdout: true, stderr: true}
res, rej <- new-promise
cleanup = do ->
resolved = false
->
return if resolved
resolved := true
stream.removeListener \data, shandler
output = ""
shandler = (d) ->
s = d.to-string!
output += s
if (s.index-of 'listening for connections on port: 4081') > -1
cleanup!
res null
stream.on \data, shandler
<- set-timeout _, 20000 # 20 seconds
cleanup!
rej Error "Running a container timedout\nOutput:\n#{output}"
make-stopper = (username, container-info) -> ->
resolve, reject <- new-promise
console.log "really stopping #{username} - #{container-info.Id}"
container = docker.getContainer container-info.Id
err, res <- container.stop
if !!err
console.log err
reject err
else
console.log res
resolve "stopped #{username} - #{container-info.Id}"
# start-container :: String -> Promise ContainerData :: {state :: String, container :: Container, container-info, port: Int16}
start-container = (username) ->
containers <- bindP (get-containers {all: true})
container-info = find-container-info containers, username
if !!container-info
container = docker.getContainer container-info.Id
if is-container-up container-info
# existing running container
console.log "> existing #{username}"
return returnP {state: "running", container, container-info, port: (get-public-port container-info), stopper: (make-stopper username, container-info)}
else
# resume a container
console.log "> resuming #{username}"
container.PortBindings = "4081/tcp": [{HostPort: "#{free-port}"}]
container <- bindP (resume-container container)
container-info <- bindP (get-container-info username)
_ <- bindP (wait-for-stream container)
return returnP {state: "started", container, container-info, port: (get-public-port container-info), stopper: (make-stopper username, container-info)}
else
# run a new container from an image
console.log "> creating #{username}"
free-port = find-a-free-port containers
container <- bindP (from-error-value-callback docker.createContainer, docker) {
Image: 'homam/pipe'
Cmd: ['/bin/sh', './start.sh']
name: "#{username}_p_#{free-port}"
PortBindings: "4081/tcp": [{HostPort: "#{free-port}"}]
Tty: true
}
container <- bindP resume-container container
container-info <- bindP (get-container-info username)
_ <- bindP (wait-for-stream container)
returnP {state: "created", container, container-info, port: free-port, stopper: (make-stopper username, container-info)}
controller = do ->
promises = {}
retries = {}
# restart :: String -> Promise ContainerData
restart: (username) ->
delete promises[username]
retries[username] = (retries[username] ? 0) + 1
console.log "> retry #{retries[username]} #{username}"
if retries[username] > 2
rejectP Error "Maximum number of retries exhausted."
else
@start username
# start :: String -> ContainerData
start: (username) ->
if !!promises[username]
return promises[username]
else
p = start-container username
p
.then (result) ->
promises[username] = returnP result
delete retries[username]
.catch ->
delete promises[username]
p
module.exports = controller
return
err, container <- to-callback (start-container-single "wow4")
if err
console.log err
else
console.log "STARTED", container