UNPKG

7.12 kBtext/coffeescriptView Raw
1###
2CLI to automatically deploy stuff, kind of like heroku.
3Ubuntu only! (upstart)
4
5TODO remember last command again
6TODO multiple services
7TODO multiple cron
8
9###
10
11CONFIG = "ggg"
12
13LOGS_LINES = 40
14COMMIT_HISTORY = 5
15VERSION = require("./package.json").version
16
17{exec} = require "child_process"
18fs = require 'fs'
19path = require 'path'
20
21program = require 'commander'
22
23MainConfig = require "./lib/MainConfig"
24Layer = require "./lib/Layer"
25
26parseTargetAndProcessName = (arg) ->
27 [target, processName] = arg.split ':'
28 {target, processName}
29
30program
31 .version(VERSION)
32 .option("-l, --local <user>", "deploy locally for bootstrapping")
33 .option("-n, --noPlugin", "disable plugins")
34
35program
36 .command("init")
37 .description("creates a ggg.js config file for you")
38 .action ->
39 init finish
40
41
42program
43 .command("deploy <name> [branch]")
44 .description("deploys a branch (defaults to origin/master) to named target")
45 .action (name, branch) ->
46 getLayer name, (err, layer) ->
47 return finish err if err?
48
49 branch = branch || "origin/master"
50
51 layer.deploy branch, finish
52
53runProcessSpecificCommandOnLayer = (command) ->
54 (name) ->
55 {target, processName} = parseTargetAndProcessName name
56 getLayer target, (err, layer) ->
57 return finish err if err?
58 if processName
59 layer[command] processName, finish
60 else
61 layer[command] finish
62
63program
64 .command("restart <name:process>")
65 .description("restarts all processes associated with target.\nIf process is provided, restarts only the named process.\n`ggg restart prod` would restart all processes under the prod target\n`ggg restart prod:web` would restart only the `web` process in the `prod` target.")
66 .action runProcessSpecificCommandOnLayer('restart')
67
68program
69 .command("start <name:process>")
70 .description("starts all processes associated with target. if process is provided, starts only the named process")
71 .action runProcessSpecificCommandOnLayer('start')
72
73program
74 .command("stop <name:process>")
75 .description("stops all processes associated with name. if process is provided, stops only the named process")
76 .action runProcessSpecificCommandOnLayer('stop')
77
78program
79 .command("logs <name:process>")
80 .description("Logs #{LOGS_LINES} lines from target and process. When no process is supplied, defaults to the first process.")
81 .option("-l, --lines <num>", "the number of lines to log")
82 .action (name) ->
83 {target, processName} = parseTargetAndProcessName name
84 getLayer target, (err, layer) ->
85 return finish err if err?
86 lines = program.lines || LOGS_LINES
87
88 if processName
89 layer.serverLogs lines, processName, finish
90 else
91 layer.serverLogs lines, finish
92
93program
94 .command("history <name>")
95 .description("Shows a history of #{COMMIT_HISTORY} last commits deployed")
96 .option("-r, --revisions <num>", "the number of commits to show")
97 .action (name) ->
98 getLayer name, (err, layer) ->
99 return finish err if err?
100 revisions = program.args.revisions || COMMIT_HISTORY
101 layer.commitHistory revisions, finish
102
103program
104 .command("command <name> <command>")
105 .description("run a command over ssh in the root of your project directory")
106 .action (name, command) ->
107 getLayer name, (err, layer) ->
108 return finish err if err?
109 layer.runCommand command, finish
110
111program
112 .command("list")
113 .description("lists all deploy targets")
114 .action ->
115 getConfigRepo (err, repoName, mainConfig) ->
116 return finish err if err?
117
118 list mainConfig, finish
119
120program
121 .command("servers <name>")
122 .description("lists deploy server locations")
123 .action (name) ->
124 getLayer name, finish
125
126program
127 .command("help")
128 .description("display this help")
129 .action ->
130 console.log program.helpInformation()
131 finish()
132
133program
134 .command("*")
135 .action ->
136 finish new Error "bad command!"
137
138## ACTIONS #########################################################
139
140# creates the init file for you
141init = (cb) ->
142 initConfigContent = """
143 // example ggg.js. Delete what you don't need
144 module.exports = {
145
146 // services
147 // can either be a string or an object with mutiple processes to start up
148 start: "node app.js",
149 /* or
150 start: {
151 web: 'node app.js',
152 worker 'node worker.js',
153 montior: 'node monitor.js'
154 },
155 */
156
157 // install
158 install: "npm install",
159
160 // cron jobs (from your app folder)
161 cron: {
162 someTask: { time: "0 3 * * *", command: "node sometask.js"},
163 },
164
165 // servers to deploy to
166 servers: {
167 dev: "deploy@dev.mycompany.com",
168 staging: ["deploy@staging.mycompany.com", "deploy@staging2.mycompany.com"],
169 prod: {
170 hosts: ["deploy@mycompany.com", "deploy@backup.mycompany.com"],
171 cron: {
172 someTask: {time: "0 3 * * *", command: "node sometask.js"},
173 anotherTask: {time: "0 3 * * *", command: "node secondTask.js"}
174 },
175 start: "prodstart app.js"
176 }
177 }
178 }
179 """
180
181 console.log "GOGOGO INITIALIZING!"
182 console.log "*** Written to ggg.js ***"
183 console.log initConfigContent
184
185 fs.writeFile mainConfigPath() + ".js", initConfigContent, cb
186
187list = (mainConfig, cb) ->
188 console.log "GOGOGO servers (see ggg.js)"
189 console.log " - " + mainConfig.getLayerNames().join("\n - ")
190
191## HELPERS #################################################
192# gets the repo url for the current directory
193# if it doesn't exist, use the directory name
194reponame = (dir, cb) ->
195 exec "git config --get remote.origin.url", {cwd:dir}, (err, stdout, stderr) ->
196 if err?
197 cb null, path.basename(dir)
198 else
199 url = stdout.replace("\n","")
200 cb null, path.basename(url).replace(".git","")
201
202
203# gets the main path for config
204mainConfigPath = -> path.join process.cwd(), CONFIG
205
206# returns the config object and the repoName
207getConfigRepo = (cb) ->
208 reponame process.cwd(), (err, repoName) ->
209 return cb err if err?
210 MainConfig.loadFromFile mainConfigPath(), (err, mainConfig) ->
211 if err
212 errString = "Bad gogogo config file, ggg.js. Run 'gogogo init' to" +
213 " create one. Err=#{err.message}"
214 return cb new Error errString
215
216 cb null, repoName, mainConfig
217
218#returns the layer object
219getLayer = (name, cb) ->
220 getConfigRepo (err, repoName, mainConfig) ->
221 return cb err if err?
222
223 layerConfig = mainConfig.getLayerByName name
224 if !layerConfig then return cb new Error("Invalid layer Name: #{name}")
225
226 if program.noPlugin
227 layerConfig.plugins = null
228 mainConfig.disablePlugins()
229
230 if program.local
231 layerConfig.hosts = ["#{program.local}@localhost"]
232
233 layer = new Layer name, layerConfig, repoName, mainConfig, program.local
234 layer.on "error", (err) -> return cb err
235 layer.on "ready", ->
236 cb null, layer
237
238# our handler on the finish
239finish = (err) ->
240 if err?
241 console.log "!!! " + err.message
242 console.log "stack follows:\n\n #{err.stack}"
243 process.exit 1
244 console.log "OK"
245
246# just export our program object
247module.exports = program