deployhq 1.1.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/deployhq +5 -0
- data/lib/deploy/cli.rb +194 -0
- data/lib/deploy/deployment.rb +22 -5
- data/lib/deploy/deployment_status_poll.rb +34 -0
- data/lib/deploy/deployment_tap.rb +17 -0
- data/lib/deploy/project.rb +19 -8
- data/lib/deploy/request.rb +1 -1
- data/lib/deploy/server.rb +12 -0
- data/lib/deploy/server_group.rb +35 -0
- data/lib/deploy/version.rb +3 -0
- data/lib/deploy.rb +4 -2
- metadata +43 -23
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 889f38a4af08698a39c5f067aaa861ba7df6ef6b
|
4
|
+
data.tar.gz: 77791a4866fb8c7ede8a40f3d843679763e06283
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fbb92bf30ef4ec9699c37f2fac34b818a284ca60c8b1dd26b76b874f78402b5af24170f08519247ef30a2547b9b689f2c67e91f8357eff870e92257b2d268fd8
|
7
|
+
data.tar.gz: c2901bef7a41b21b7fbb51de8b70790eadd15a71f85ddb112a070c37b14c0b67317c063af0dc8198e5dd6c300104948d880fc81978d96f89a0e9d6ad4b6c518d
|
data/bin/deployhq
ADDED
data/lib/deploy/cli.rb
ADDED
@@ -0,0 +1,194 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'highline/import'
|
3
|
+
require 'deploy'
|
4
|
+
|
5
|
+
HighLine.colorize_strings
|
6
|
+
|
7
|
+
module Deploy
|
8
|
+
class CLI
|
9
|
+
|
10
|
+
## Constants for formatting output
|
11
|
+
TAP_COLOURS = {:info => :yellow, :error => :red, :success => :green}
|
12
|
+
PROTOCOL_NAME = {:ssh => "SSH/SFTP", :ftp => "FTP", :s3 => "Amazon S3", :rackspace => "Rackspace CloudFiles"}
|
13
|
+
|
14
|
+
class Config
|
15
|
+
AVAILABLE_CONFIG = [:account, :username, :api_key, :project]
|
16
|
+
|
17
|
+
def initialize(config_file=nil)
|
18
|
+
@config_file_path = config_file || File.join(Dir.pwd, 'Deployfile')
|
19
|
+
@config = JSON.parse(File.read(@config_file_path))
|
20
|
+
rescue Errno::ENOENT => e
|
21
|
+
puts "Couldn't find configuration file at #{@config_file_path}"
|
22
|
+
exit 1
|
23
|
+
end
|
24
|
+
|
25
|
+
def method_missing(meth, *args, &block)
|
26
|
+
if AVAILABLE_CONFIG.include?(meth.to_sym)
|
27
|
+
@config[meth.to_s]
|
28
|
+
else
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class << self
|
35
|
+
|
36
|
+
def invoke(*args)
|
37
|
+
options = {}
|
38
|
+
OptionParser.new do |opts|
|
39
|
+
opts.banner = "Usage: deployhq [command]"
|
40
|
+
opts.on("-c", "--config", 'Configuration file path') do |v|
|
41
|
+
options[:config_file] = v
|
42
|
+
end
|
43
|
+
end.parse!
|
44
|
+
|
45
|
+
@configuration = Config.new(options[:config_file])
|
46
|
+
Deploy.site = @configuration.account
|
47
|
+
Deploy.email = @configuration.username
|
48
|
+
Deploy.api_key = @configuration.api_key
|
49
|
+
@project = Deploy::Project.find(@configuration.project)
|
50
|
+
|
51
|
+
case args[0]
|
52
|
+
when 'deploy'
|
53
|
+
deploy
|
54
|
+
when 'servers'
|
55
|
+
server_list
|
56
|
+
else
|
57
|
+
puts "Usage: deployhq [command]"
|
58
|
+
return
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def server_list
|
63
|
+
@server_groups ||= @project.server_groups
|
64
|
+
if @server_groups.count > 0
|
65
|
+
@server_groups.each do |group|
|
66
|
+
puts "Group: #{group.name}".bold
|
67
|
+
puts group.servers.map {|server| format_server(server) }.join("\n\n")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
@ungrouped_servers ||= @project.servers
|
72
|
+
if @ungrouped_servers.count > 0
|
73
|
+
puts "\n" if @server_groups.count > 0
|
74
|
+
puts "Ungrouped Servers".bold
|
75
|
+
puts @ungrouped_servers.map {|server| format_server(server) }.join("\n\n")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def deploy
|
80
|
+
@ungrouped_servers = @project.servers
|
81
|
+
@server_groups = @project.server_groups
|
82
|
+
|
83
|
+
parent = nil
|
84
|
+
while parent.nil?
|
85
|
+
parent = choose do |menu|
|
86
|
+
menu.prompt = "Please choose a server or group to deploy to:"
|
87
|
+
|
88
|
+
menu.choices(*(@ungrouped_servers + @server_groups))
|
89
|
+
menu.choice("List Server Details") do
|
90
|
+
server_list
|
91
|
+
nil
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
latest_revision = @project.latest_revision(parent.preferred_branch)
|
97
|
+
@deployment = @project.deploy(parent.identifier, parent.last_revision, latest_revision)
|
98
|
+
|
99
|
+
@server_names = @deployment.servers.each_with_object({}) do |server, hsh|
|
100
|
+
hsh[server['id']] = server['name']
|
101
|
+
end
|
102
|
+
@longest_server_name = @server_names.values.map(&:length).max
|
103
|
+
|
104
|
+
last_tap = nil
|
105
|
+
current_status = 'pending'
|
106
|
+
previous_status = ''
|
107
|
+
print "Waiting for deployment capacity..."
|
108
|
+
while ['running', 'pending'].include?(current_status) do
|
109
|
+
sleep 1
|
110
|
+
|
111
|
+
poll = @deployment.status_poll(:since => last_tap, :status => current_status)
|
112
|
+
|
113
|
+
# Status only gets returned from the API if it has changed
|
114
|
+
current_status = poll.status if poll.status
|
115
|
+
|
116
|
+
if current_status == 'pending'
|
117
|
+
print "."
|
118
|
+
elsif current_status == 'running' && previous_status == 'pending'
|
119
|
+
puts "\n"
|
120
|
+
end
|
121
|
+
|
122
|
+
if current_status != 'pending'
|
123
|
+
poll.taps.each do |tap|
|
124
|
+
puts format_tap(tap)
|
125
|
+
last_tap = tap.id.to_i
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
previous_status = current_status
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def deployment
|
134
|
+
@deployment = @project.deployments.first
|
135
|
+
@server_names = @deployment.servers.each_with_object({}) do |obj, hsh|
|
136
|
+
hsh[obj.delete("id")] = obj["name"]
|
137
|
+
end
|
138
|
+
@longest_server_name = @server_names.values.map(&:length).max
|
139
|
+
|
140
|
+
@deployment.taps.reverse.each do |tap|
|
141
|
+
puts format_tap(tap)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
## Data formatters
|
147
|
+
|
148
|
+
def format_tap(tap)
|
149
|
+
server_name = @server_names[tap.server_id]
|
150
|
+
|
151
|
+
if server_name
|
152
|
+
padding = (@longest_server_name - server_name.length) / 2.0
|
153
|
+
server_name = "[#{' ' * padding.ceil} #{server_name} #{' ' * padding.floor}]"
|
154
|
+
else
|
155
|
+
server_name = ' '
|
156
|
+
end
|
157
|
+
|
158
|
+
text_colour = TAP_COLOURS[tap.tap_type.to_sym] || :white
|
159
|
+
|
160
|
+
String.new.tap do |s|
|
161
|
+
s << "#{server_name} ".color(text_colour, :bold)
|
162
|
+
s << tap.message.color(text_colour)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def format_server(server)
|
167
|
+
server_params = {
|
168
|
+
"Name" => server.name,
|
169
|
+
"Type" => PROTOCOL_NAME[server.protocol_type.to_sym],
|
170
|
+
"Path" => server.server_path,
|
171
|
+
"Branch" => server.preferred_branch,
|
172
|
+
"Current Revision" => server.last_revision,
|
173
|
+
}
|
174
|
+
server_params["Hostname"] = [server.hostname, server.port].join(':') if server.hostname
|
175
|
+
server_params["Bucket"] = server.bucket_name if server.bucket_name
|
176
|
+
server_params["Region"] = server.region if server.region
|
177
|
+
server_params["Container"] = server.container_name if server.container_name
|
178
|
+
|
179
|
+
Array.new.tap do |a|
|
180
|
+
a << format_kv_pair(server_params)
|
181
|
+
end.join("\n")
|
182
|
+
end
|
183
|
+
|
184
|
+
def format_kv_pair(hash)
|
185
|
+
longest_key = hash.keys.map(&:length).max + 2
|
186
|
+
hash.each_with_index.map do |(k,v), i|
|
187
|
+
str = sprintf("%#{longest_key}s : %s", k,v)
|
188
|
+
i == 0 ? str.color(:bold) : str
|
189
|
+
end.join("\n")
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
data/lib/deploy/deployment.rb
CHANGED
@@ -1,19 +1,36 @@
|
|
1
1
|
module Deploy
|
2
2
|
class Deployment < Base
|
3
|
-
|
3
|
+
|
4
4
|
class << self
|
5
5
|
def collection_path(params = {})
|
6
6
|
"projects/#{params[:project].permalink}/deployments"
|
7
7
|
end
|
8
|
-
|
8
|
+
|
9
9
|
def member_path(id, params = {})
|
10
10
|
"projects/#{params[:project].permalink}/deployments/#{id}"
|
11
11
|
end
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
def default_params
|
15
15
|
{:project => self.project}
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
|
+
def project
|
19
|
+
if self.attributes['project'].is_a?(Hash)
|
20
|
+
self.attributes['project'] = Project.send(:create_object, self.attributes['project'])
|
21
|
+
end
|
22
|
+
self.attributes['project']
|
23
|
+
end
|
24
|
+
|
25
|
+
def taps(params={})
|
26
|
+
params = {:deployment => self, :project => self.project}.merge(params)
|
27
|
+
DeploymentTap.find(:all, params)
|
28
|
+
end
|
29
|
+
|
30
|
+
def status_poll(params = {})
|
31
|
+
params = {:deployment => self, :project => self.project}.merge(params)
|
32
|
+
DeploymentStatusPoll.poll(params)
|
33
|
+
end
|
34
|
+
|
18
35
|
end
|
19
|
-
end
|
36
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Deploy
|
2
|
+
class DeploymentStatusPoll
|
3
|
+
attr_accessor :attributes
|
4
|
+
|
5
|
+
def initialize(parsed_json)
|
6
|
+
self.attributes = parsed_json
|
7
|
+
end
|
8
|
+
|
9
|
+
def status
|
10
|
+
@status ||= attributes['status']
|
11
|
+
end
|
12
|
+
|
13
|
+
def taps
|
14
|
+
return [] unless attributes['taps']
|
15
|
+
@taps ||= attributes['taps'].map { |t| DeploymentTap.send(:create_object, t) }
|
16
|
+
end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
def poll_url(params)
|
20
|
+
base = "projects/#{params[:project].permalink}/deployments/#{params[:deployment].identifier}/logs/poll"
|
21
|
+
base += "?status=#{params[:status]}"
|
22
|
+
base += "&since=#{params[:since]}" if params[:since]
|
23
|
+
base
|
24
|
+
end
|
25
|
+
|
26
|
+
def poll(params = {})
|
27
|
+
req = Request.new(poll_url(params)).make
|
28
|
+
parsed = JSON.parse(req.output)
|
29
|
+
|
30
|
+
new(parsed)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Deploy
|
2
|
+
class DeploymentTap < Base
|
3
|
+
|
4
|
+
class << self
|
5
|
+
def collection_path(params = {})
|
6
|
+
base = "projects/#{params[:project].permalink}/deployments/#{params[:deployment].identifier}.js"
|
7
|
+
base += "?since=#{params[:since]}" if params[:since]
|
8
|
+
base
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def default_params
|
13
|
+
{:deployment => self.deployment, :project => self.deployment.project}
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
data/lib/deploy/project.rb
CHANGED
@@ -1,23 +1,30 @@
|
|
1
1
|
module Deploy
|
2
2
|
class Project < Base
|
3
|
-
|
3
|
+
|
4
4
|
## Return all deployments for this project
|
5
5
|
def deployments
|
6
6
|
Deployment.find(:all, :project => self)
|
7
7
|
end
|
8
|
-
|
8
|
+
|
9
9
|
## Return a deployment
|
10
10
|
def deployment(identifier)
|
11
11
|
Deployment.find(identifier, :project => self)
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
|
+
def latest_revision(branch = '')
|
15
|
+
branch ||= 'master'
|
16
|
+
req = Request.new(self.class.member_path(self.permalink) + "/repository/latest_revision?branch=#{branch}").make
|
17
|
+
parsed = JSON.parse(req.output)
|
18
|
+
parsed['ref']
|
19
|
+
end
|
20
|
+
|
14
21
|
## Create a deployment in this project (and queue it to run)
|
15
22
|
def deploy(server, start_revision, end_revision)
|
16
23
|
run_deployment(server, start_revision, end_revision) do |d|
|
17
24
|
d.mode = 'queue'
|
18
25
|
end
|
19
26
|
end
|
20
|
-
|
27
|
+
|
21
28
|
##
|
22
29
|
def preview(server, start_revision, end_revision)
|
23
30
|
run_deployment(server, start_revision, end_revision) do |d|
|
@@ -29,9 +36,13 @@ module Deploy
|
|
29
36
|
def servers
|
30
37
|
Server.find(:all, :project => self)
|
31
38
|
end
|
32
|
-
|
39
|
+
|
40
|
+
def server_groups
|
41
|
+
ServerGroup.find(:all, :project => self)
|
42
|
+
end
|
43
|
+
|
33
44
|
private
|
34
|
-
|
45
|
+
|
35
46
|
def run_deployment(server, start_revision, end_revision, &block)
|
36
47
|
d = Deployment.new
|
37
48
|
d.project = self
|
@@ -44,6 +55,6 @@ module Deploy
|
|
44
55
|
d.save
|
45
56
|
d
|
46
57
|
end
|
47
|
-
|
58
|
+
|
48
59
|
end
|
49
|
-
end
|
60
|
+
end
|
data/lib/deploy/request.rb
CHANGED
@@ -21,7 +21,7 @@ module Deploy
|
|
21
21
|
## Hashes will be converted to JSON before being sent to the remote service.
|
22
22
|
def make
|
23
23
|
uri = URI.parse([Deploy.site, @path].join('/'))
|
24
|
-
http_request = http_class.new(uri.
|
24
|
+
http_request = http_class.new(uri.request_uri)
|
25
25
|
http_request.basic_auth(Deploy.email, Deploy.api_key)
|
26
26
|
http_request["Accept"] = "application/json"
|
27
27
|
http_request["Content-type"] = "application/json"
|
data/lib/deploy/server.rb
CHANGED
@@ -14,6 +14,18 @@ module Deploy
|
|
14
14
|
def default_params
|
15
15
|
{:project => self.project}
|
16
16
|
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
Array.new.tap do |a|
|
20
|
+
a << self.name
|
21
|
+
a << "(branch: #{self.preferred_branch})" if self.preferred_branch
|
22
|
+
if self.last_revision
|
23
|
+
a << "(currently: #{self.last_revision})"
|
24
|
+
else
|
25
|
+
a << "(currently undeployed)"
|
26
|
+
end
|
27
|
+
end.join(' ')
|
28
|
+
end
|
17
29
|
|
18
30
|
end
|
19
31
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Deploy
|
2
|
+
class ServerGroup < Base
|
3
|
+
|
4
|
+
class << self
|
5
|
+
def collection_path(params = {})
|
6
|
+
"projects/#{params[:project].permalink}/server_groups"
|
7
|
+
end
|
8
|
+
|
9
|
+
def member_path(id, params = {})
|
10
|
+
"projects/#{params[:project].permalink}/server_groups/#{identifier}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def default_params
|
15
|
+
{:project => self.project}
|
16
|
+
end
|
17
|
+
|
18
|
+
def servers
|
19
|
+
@servers ||= self.attributes['servers'].map {|server_attr| Deploy::Server.send(:create_object, server_attr) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
Array.new.tap do |a|
|
24
|
+
a << self.name
|
25
|
+
a << "(branch: #{self.preferred_branch})" if self.preferred_branch
|
26
|
+
if self.last_revision
|
27
|
+
a << "(currently: #{self.last_revision})"
|
28
|
+
else
|
29
|
+
a << "(currently undeployed)"
|
30
|
+
end
|
31
|
+
end.join(' ')
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
data/lib/deploy.rb
CHANGED
@@ -17,12 +17,14 @@ require 'deploy/base'
|
|
17
17
|
|
18
18
|
require 'deploy/project'
|
19
19
|
require 'deploy/deployment'
|
20
|
+
require 'deploy/deployment_tap'
|
21
|
+
require 'deploy/deployment_status_poll'
|
20
22
|
require 'deploy/server'
|
23
|
+
require 'deploy/server_group'
|
24
|
+
require 'deploy/version'
|
21
25
|
|
22
26
|
|
23
27
|
module Deploy
|
24
|
-
VERSION = '1.0.0'
|
25
|
-
|
26
28
|
class << self
|
27
29
|
## Domain which you wish to access (e.g. atech.deployhq.com)
|
28
30
|
attr_accessor :site
|
metadata
CHANGED
@@ -1,67 +1,87 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: deployhq
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
5
|
-
prerelease:
|
4
|
+
version: 1.3.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
|
-
-
|
7
|
+
- Dan Wentworth
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2016-04-14 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: json
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
|
-
- -
|
17
|
+
- - "~>"
|
20
18
|
- !ruby/object:Gem::Version
|
21
|
-
version: 1.
|
19
|
+
version: '1.8'
|
22
20
|
type: :runtime
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
|
-
- -
|
24
|
+
- - "~>"
|
28
25
|
- !ruby/object:Gem::Version
|
29
|
-
version: 1.
|
30
|
-
|
31
|
-
|
32
|
-
|
26
|
+
version: '1.8'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: highline
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.7'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.7'
|
41
|
+
description: |2
|
42
|
+
API and CLI client for the DeployHQ deployment platform. Provides the
|
43
|
+
deployhq executable.
|
44
|
+
email: dan@atech.io
|
45
|
+
executables:
|
46
|
+
- deployhq
|
33
47
|
extensions: []
|
34
48
|
extra_rdoc_files: []
|
35
49
|
files:
|
50
|
+
- bin/deployhq
|
51
|
+
- lib/deploy.rb
|
36
52
|
- lib/deploy/base.rb
|
53
|
+
- lib/deploy/cli.rb
|
37
54
|
- lib/deploy/deployment.rb
|
55
|
+
- lib/deploy/deployment_status_poll.rb
|
56
|
+
- lib/deploy/deployment_tap.rb
|
38
57
|
- lib/deploy/errors.rb
|
39
58
|
- lib/deploy/project.rb
|
40
59
|
- lib/deploy/request.rb
|
41
60
|
- lib/deploy/server.rb
|
42
|
-
- lib/deploy.rb
|
43
|
-
|
44
|
-
|
61
|
+
- lib/deploy/server_group.rb
|
62
|
+
- lib/deploy/version.rb
|
63
|
+
homepage: https://www.deployhq.com
|
64
|
+
licenses:
|
65
|
+
- MIT
|
66
|
+
metadata: {}
|
45
67
|
post_install_message:
|
46
68
|
rdoc_options: []
|
47
69
|
require_paths:
|
48
70
|
- lib
|
49
71
|
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
-
none: false
|
51
72
|
requirements:
|
52
|
-
- -
|
73
|
+
- - ">="
|
53
74
|
- !ruby/object:Gem::Version
|
54
75
|
version: '0'
|
55
76
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
-
none: false
|
57
77
|
requirements:
|
58
|
-
- -
|
78
|
+
- - ">="
|
59
79
|
- !ruby/object:Gem::Version
|
60
80
|
version: '0'
|
61
81
|
requirements: []
|
62
82
|
rubyforge_project:
|
63
|
-
rubygems_version:
|
83
|
+
rubygems_version: 2.4.5.1
|
64
84
|
signing_key:
|
65
|
-
specification_version:
|
66
|
-
summary: API client for the DeployHQ
|
85
|
+
specification_version: 4
|
86
|
+
summary: API and CLI client for the DeployHQ
|
67
87
|
test_files: []
|