UNPKG

11.2 kBtext/coffeescriptView Raw
1#===============================================================================
2# Huxley - Configuration Handlers
3#===============================================================================
4# This file specifies the helper functions that get called whenever a Huxley CLI
5# command makes a change to the application's or service's configuration. There
6# are several checks and conditionals that need to happen, so all the clutter is
7# gathered here to keep the main "cli.coffee" tidy.
8#===============================================================================
9# Modules
10#===============================================================================
11# Core Libraries
12{resolve} = require "path"
13
14# Panda Strike Libraries
15{map, pluck, remove, read, shuffle} = require "fairmont"
16{parse} = require "c50n"
17
18# Third Party Libraries
19async = (require "when/generator").lift # promise library
20
21
22#===============================================================================
23# Helpers
24#===============================================================================
25# Just to ensure good error handling, catch any errors from these helpers and
26# bubble them up through the CLI handlers.
27catch_fail = (f) ->
28 try
29 f()
30 catch e
31 throw e
32
33# This function selects and returns a random element from an input array.
34# TODO: This is a shitty implementation... fix this later.
35select_random = (list) ->
36 list = shuffle list
37 return list[0]
38
39
40#===============================================================================
41# Module Definition
42#===============================================================================
43module.exports =
44
45 #------------------------
46 # Huxley Cluster Create
47 #------------------------
48 # Construct an object that will be passed to the Huxley API to used by its panda-cluster library.
49 build_create_cluster: async (config, argv) ->
50 catch_fail ->
51 # Did the user input a cluster name?
52 if argv.length == 1
53 # The user gave us a name. Use it.
54 cluster_name = argv[0]
55 else
56 # The user didn't give us anything. Generate a cluster name from our list of ajectives and nouns.
57 {adjectives, nouns} = parse( yield read( resolve( __dirname, "names.cson")))
58 cluster_name = "#{select_random(adjectives)}-#{select_random(nouns)}"
59
60 # Return "options" object for panda-cluster's create function.
61 return {
62 # Required
63 aws: config.aws
64 key_name: config.aws.key_name
65 availability_zone: config.aws.availability_zone
66 cluster_name: cluster_name
67 public_domain: config.public_domain
68 private_domain: "#{cluster_name}.cluster"
69
70 # Optional
71 channel: config.channel || 'stable'
72 cluster_size: config.cluster_size || 3
73 instance_type: config.instance_type || "m1.medium"
74 public_keys: config.public_keys || []
75 region: config.region if config.region?
76 formation_service_templates:
77 config.extra_storage || true
78 spot_price: config.spot_price if config.spot_price?
79 virtualization: config.virtualization || "pv"
80
81 # Huxley Access
82 url: config.huxley.url
83 secret_token: config.huxley.secret_token
84 email: config.huxley.email
85 }
86
87
88 # Check the root level config to make sure what the user's requesting can be done.
89 check_create_cluster: async (config, argv) ->
90 catch_fail ->
91 if config.clusters?
92 clusters = yield map ((x) -> pluck "name", x), config.clusters
93 if argv[0] in clusters
94 message = "There is already a cluster named #{argv[0]} registered with your account \n " +
95 "Please use select another name or use \"huxley cluster delete #{argv[0]}\" to delete the current cluster."
96 throw message
97
98 # Update the root level config based on what "huxley cluster create" changes.
99 update_create_cluster: async (config, options, api_response) ->
100 catch_fail ->
101 unless config.data.clusters?
102 config.data.clusters = []
103
104 config.data.clusters.push {
105 name: options.cluster_name
106 id: api_response.cluster_id
107 }
108
109 yield config.save()
110
111
112
113 #------------------------
114 # Huxley Cluster Delete
115 #------------------------
116 # Construct an object that will be passed to the Huxley API to be used by its panda-cluster library.
117 build_delete_cluster: async (config, argv) ->
118 catch_fail ->
119 clusters = yield map ((x) -> pluck "name", x), config.clusters
120 index = clusters.indexOf argv[0]
121
122 return {
123 cluster_id: config.clusters[index].id
124 url: config.huxley.url
125 secret_token: config.huxley.secret_token
126 email: config.huxley.email
127 }
128
129
130 # Check the application level config to make sure what the user's requesting can be done.
131 check_delete_cluster: async (config, argv) ->
132 catch_fail ->
133 if config.clusters?
134 clusters = yield map ((x) -> pluck "name", x), config.clusters
135 unless argv[0] in clusters
136 throw "Error: The cluster \"#{argv[0]}\" is not registered with your account. Nothing to remove."
137 else
138 throw "Error: The cluster \"#{argv[0]}\" is not registered with your account. Nothing to remove."
139
140
141 # Update the application level config based on what "huxley cluster delete" changes.
142 update_delete_cluster: async (config, argv) ->
143 catch_fail ->
144 clusters = yield map ((x) -> pluck "name", x), config.data.clusters
145 index = clusters.indexOf argv[0]
146 config.data.clusters[index..index] = []
147
148 yield config.save()
149
150
151 #------------------------
152 # Huxley Cluster Poll
153 #------------------------
154 # Construct an object that will be passed to the Huxley API to be used by its panda-cluster library.
155 build_poll_cluster: async (config, argv) ->
156 catch_fail ->
157 clusters = yield map ((x) -> pluck "name", x), config.clusters
158 index = clusters.indexOf argv[0]
159
160 return {
161 cluster_id: config.clusters[index].id
162 url: config.huxley.url
163 secret_token: config.huxley.secret_token
164 email: config.huxley.email
165 }
166
167
168 # Check the application level config to make sure what the user's requesting can be done.
169 check_poll_cluster: async (config, argv) ->
170 catch_fail ->
171 if config.clusters?
172 clusters = yield map ((x) -> pluck "name", x), config.clusters
173 unless argv[0] in clusters
174 throw "Error: The cluster \"#{argv[0]}\" is not registered with your account. Nothing to poll."
175 else
176 throw "Error: The cluster \"#{argv[0]}\" is not registered with your account. Nothing to poll."
177
178
179 #------------------------
180 # Huxley Remote Add
181 #------------------------
182 # Construct an object that will be passed to the Huxley API to used by its panda-hook library.
183 build_add_remote: async (config, argv) ->
184 catch_fail ->
185 return yield {
186 cluster_name: argv[0]
187 cluster_address: "core@#{argv[0]}.#{config.public_domain}"
188 repo_name: config.app_name
189 hook_address: "root@#{argv[0]}.#{config.public_domain}:3000"
190 url: config.huxley.url
191 secret_token: config.huxley.secret_token
192 email: config.huxley.email
193 }
194
195 # Check the application level config to make sure what the user's requesting can be done.
196 check_add_remote: async (config, argv) ->
197 catch_fail ->
198 if config.remotes?
199 remotes = yield map ((x) -> pluck "name", x), config.remotes
200 if argv[0] in remotes
201 message = "There is already a remote named #{argv[0]} registered with this application. \n " +
202 "Please use \"huxley remote rm #{argv[0]}\" to delete and try again."
203 throw message
204
205
206 # Update the application level config based on what "huxley remote add" changes.
207 update_add_remote: async (config, argv, api_response) ->
208 catch_fail ->
209 unless config.data.remotes?
210 config.data.remotes = []
211
212 config.data.remotes.push {
213 name: argv[0]
214 id: api_response.remote_id
215 }
216
217 yield config.save()
218
219 #------------------------
220 # Huxley Remote Remove
221 #------------------------
222 # Construct an object that will be passed to the Huxley API to used by its panda-hook library.
223 build_rm_remote: async (config, argv) ->
224 catch_fail ->
225 remotes = yield map ((x) -> pluck "name", x), config.remotes
226 index = remotes.indexOf argv[0]
227
228 return {
229 remote_id: config.remotes[index].id
230 url: config.huxley.url
231 secret_token: config.huxley.secret_token
232 email: config.huxley.email
233 }
234
235
236 # Check the application level config to make sure what the user's requesting can be done.
237 check_rm_remote: async (config, argv) ->
238 catch_fail ->
239 if config.remotes?
240 remotes = yield map ((x) -> pluck "name", x), config.remotes
241 unless argv[0] in remotes
242 throw "Error: The remote #{argv[0]} is not registered with your account. Nothing to remove."
243 else
244 throw "Error: The remote #{argv[0]} is not registered with your account. Nothing to remove."
245
246 # Update the application level config based on what "huxley remote rm" changes.
247 update_rm_remote: async (config, argv) ->
248 catch_fail ->
249 remotes = yield map ((x) -> pluck "name", x), config.data.remotes
250 index = remotes.indexOf argv[0]
251 config.data.remotes[index..index] = []
252
253 yield config.save()
254
255 #------------------------
256 # Huxley Remote Passive
257 #------------------------
258 # For now, when Huxley adds "passive" repositories to the cluster, it doesn't get routed though
259 # the API server. For now, we just execute a series of shell commands.
260 run_passive_remote: async (config, argv) ->
261 catch_fail ->
262 # Shell to the specified cluster and create a bare repo. Overwrite anything in our path.
263 command =
264 "ssh -A -o \"StrictHostKeyChecking no\" -o \"UserKnownHostsFile=/dev/null\" " +
265 "-p 3000 root@#{argv[0]}.#{config.public_domain} << EOF\n" +
266 "cd /root/passive \n " +
267 "if [ -d #{config.repo_name}.git ]; then \n " +
268 " rm -rf #{config.repo_name}.git \n" +
269 "fi \n " +
270 "mkdir #{config.repo_name}.git \n " +
271 "cd #{config.repo_name}.git \n " +
272 "/usr/bin/git init --bare \n " +
273 "EOF"
274
275 yield shell command
276
277 # Next, we will add this repository to the user's git remotes. Allow "remote rm" to fail if neccessary, so do it separately.
278 yield force shell, "git remote rm #{argv[0]}"
279 yield shell "git remote add #{argv[0]} ssh://root@#{argv[0]}.#{config.public_domain}:3000/root/passive/#{config.repo_name}.git"
280
281 # Because this does not cause a deployment, we can actually go ahead and push the
282 # local repository to the passive remote. Initialize a git repo in the executable
283 # path, commit everything, and push.
284 command =
285 "cd #{process.cwd()} &&" +
286 "git init; " +
287 "git add -A; "+
288 "git commit -m 'Repo commited by Huxley to place on cluster.'; " +
289 "git push #{config.repo_name} master"
290
291 yield shell command