deployhq 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|