deployhq 1.3.4 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/deployhq +1 -1
- data/lib/deploy.rb +23 -15
- data/lib/deploy/cli.rb +87 -115
- data/lib/deploy/cli/deployment_progress_output.rb +61 -0
- data/lib/deploy/cli/websocket_client.rb +144 -0
- data/lib/deploy/configuration.rb +23 -0
- data/lib/deploy/errors.rb +9 -7
- data/lib/deploy/request.rb +14 -14
- data/lib/deploy/{base.rb → resource.rb} +26 -26
- data/lib/deploy/resources/deployment.rb +51 -0
- data/lib/deploy/resources/deployment_step.rb +12 -0
- data/lib/deploy/resources/deployment_step_log.rb +7 -0
- data/lib/deploy/{project.rb → resources/project.rb} +1 -1
- data/lib/deploy/{server.rb → resources/server.rb} +6 -6
- data/lib/deploy/{server_group.rb → resources/server_group.rb} +6 -6
- data/lib/deploy/version.rb +1 -1
- metadata +26 -9
- data/lib/deploy/deployment.rb +0 -36
- data/lib/deploy/deployment_status_poll.rb +0 -34
- data/lib/deploy/deployment_tap.rb +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: da343b392a7afa950b197f8181112b114b05d6e0
|
4
|
+
data.tar.gz: 1b7b875b6bb5a77a4b9672836df13cdbdb7278bd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3c60eaf8aa6aeca85fb367d04071198c77abeabad94d72b757588153baf928e2fa4021af1585acad20bfeea345433e38a36ce6dcdcce8fe69aeedd0880ce9e98
|
7
|
+
data.tar.gz: 71a2b3e4179e7893bbb643b172f049125caa9c218f197cbcbed4df12a68e93209b73f1ef7a3b638f468e96168396220911ac3aa070f6eb5132580827238c40aa
|
data/bin/deployhq
CHANGED
data/lib/deploy.rb
CHANGED
@@ -11,25 +11,33 @@ require 'time'
|
|
11
11
|
## This is a ruby API library for the DeployHQ deployment platform.
|
12
12
|
|
13
13
|
require 'deploy/errors'
|
14
|
+
require 'deploy/configuration'
|
14
15
|
require 'deploy/request'
|
15
|
-
require 'deploy/
|
16
|
-
|
17
|
-
require 'deploy/project'
|
18
|
-
require 'deploy/deployment'
|
19
|
-
require 'deploy/
|
20
|
-
require 'deploy/
|
21
|
-
require 'deploy/server'
|
22
|
-
require 'deploy/server_group'
|
23
|
-
require 'deploy/version'
|
16
|
+
require 'deploy/resource'
|
17
|
+
|
18
|
+
require 'deploy/resources/project'
|
19
|
+
require 'deploy/resources/deployment'
|
20
|
+
require 'deploy/resources/deployment_step'
|
21
|
+
require 'deploy/resources/deployment_step_log'
|
22
|
+
require 'deploy/resources/server'
|
23
|
+
require 'deploy/resources/server_group'
|
24
24
|
|
25
|
+
require 'deploy/version'
|
25
26
|
|
26
27
|
module Deploy
|
27
28
|
class << self
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
29
|
+
def configure
|
30
|
+
@configuration ||= Configuration.new
|
31
|
+
yield @configuration if block_given?
|
32
|
+
@configuration
|
33
|
+
end
|
34
|
+
|
35
|
+
def configuration
|
36
|
+
@configuration ||= Configuration.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def configuration_file=(file_location)
|
40
|
+
@configuration = Configuration.from_file(file_location)
|
41
|
+
end
|
34
42
|
end
|
35
43
|
end
|
data/lib/deploy/cli.rb
CHANGED
@@ -1,61 +1,86 @@
|
|
1
1
|
require 'optparse'
|
2
2
|
require 'highline/import'
|
3
|
+
|
3
4
|
require 'deploy'
|
5
|
+
require 'deploy/cli/websocket_client'
|
6
|
+
require 'deploy/cli/deployment_progress_output'
|
4
7
|
|
5
8
|
HighLine.colorize_strings
|
6
9
|
|
7
10
|
module Deploy
|
8
11
|
class CLI
|
9
|
-
|
10
12
|
## Constants for formatting output
|
11
|
-
TAP_COLOURS = {:info => :yellow, :error => :red, :success => :green}
|
12
13
|
PROTOCOL_NAME = {:ssh => "SSH/SFTP", :ftp => "FTP", :s3 => "Amazon S3", :rackspace => "Rackspace CloudFiles"}
|
13
14
|
|
14
|
-
class
|
15
|
-
|
15
|
+
class << self
|
16
|
+
def invoke(args)
|
17
|
+
@options = OpenStruct.new
|
18
|
+
|
19
|
+
parser = OptionParser.new do |opts|
|
20
|
+
opts.banner = "Usage: deployhq [options] command"
|
21
|
+
opts.separator ""
|
22
|
+
opts.separator "Commands:"
|
23
|
+
opts.separator "deploy\t\t Start a new deployment"
|
24
|
+
opts.separator "servers\t\t List configured servers and server groups"
|
25
|
+
opts.separator "configure\t\t Create a new configuration file for this tool"
|
26
|
+
opts.separator ""
|
27
|
+
opts.separator "Common Options:"
|
28
|
+
|
29
|
+
@options.config_file = './Deployfile'
|
30
|
+
opts.on("-c", "--config path", 'Configuration file path') do |config_file_path|
|
31
|
+
@options.config_file = config_file_path
|
32
|
+
end
|
16
33
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
puts "Couldn't find configuration file at #{@config_file_path}"
|
22
|
-
exit 1
|
23
|
-
end
|
34
|
+
opts.on("-p", "--project project",
|
35
|
+
"Project to operate on (default is read from project: in config file)") do |project_permalink|
|
36
|
+
@options.project = project_permalink
|
37
|
+
end
|
24
38
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
39
|
+
opts.on_tail('-v', '--version', "Shows Version") do
|
40
|
+
puts Deploy::VERSION
|
41
|
+
exit
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.on_tail("-h", "--help", "Displays Help") do
|
45
|
+
puts opts
|
46
|
+
exit
|
47
|
+
end
|
30
48
|
end
|
31
|
-
end
|
32
|
-
end
|
33
49
|
|
34
|
-
|
50
|
+
begin
|
51
|
+
parser.parse!(args)
|
52
|
+
command = args.pop
|
53
|
+
rescue OptionParser::InvalidOption
|
54
|
+
STDERR.puts parser.to_s
|
55
|
+
exit 1
|
56
|
+
end
|
57
|
+
|
58
|
+
unless command == 'configure'
|
59
|
+
begin
|
60
|
+
Deploy.configuration_file = @options.config_file
|
61
|
+
rescue Errno::ENOENT
|
62
|
+
STDERR.puts "Couldn't find configuration file at #{@options.config_file.inspect}"
|
63
|
+
exit 1
|
64
|
+
end
|
35
65
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
opts.on("-c", "--config", 'Configuration file path') do |v|
|
41
|
-
options[:config_file] = v
|
66
|
+
project_permalink = @options.project || Deploy.configuration.project
|
67
|
+
if project_permalink.nil?
|
68
|
+
STDERR.puts "Project must be specified in config file or as --project argument"
|
69
|
+
exit 1
|
42
70
|
end
|
43
|
-
end.parse!
|
44
71
|
|
45
|
-
|
46
|
-
|
47
|
-
Deploy.email = @configuration.username
|
48
|
-
Deploy.api_key = @configuration.api_key
|
49
|
-
@project = Deploy::Project.find(@configuration.project)
|
72
|
+
@project = Deploy::Project.find(project_permalink)
|
73
|
+
end
|
50
74
|
|
51
|
-
case
|
75
|
+
case command
|
52
76
|
when 'deploy'
|
53
77
|
deploy
|
54
78
|
when 'servers'
|
55
79
|
server_list
|
80
|
+
when 'configure'
|
81
|
+
configure
|
56
82
|
else
|
57
|
-
puts
|
58
|
-
return
|
83
|
+
STDERR.puts parser.to_s
|
59
84
|
end
|
60
85
|
end
|
61
86
|
|
@@ -94,98 +119,45 @@ module Deploy
|
|
94
119
|
end
|
95
120
|
|
96
121
|
latest_revision = @project.latest_revision(parent.preferred_branch)
|
97
|
-
|
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
|
-
last_tap_lines = 0
|
106
|
-
current_status = 'pending'
|
107
|
-
previous_status = ''
|
108
|
-
STDOUT.print "Waiting for deployment capacity..."
|
109
|
-
while ['running', 'pending'].include?(current_status)
|
110
|
-
sleep 1
|
122
|
+
deployment = @project.deploy(parent.identifier, parent.last_revision, latest_revision)
|
111
123
|
|
112
|
-
|
113
|
-
|
114
|
-
# Status only gets returned from the API if it has changed
|
115
|
-
current_status = poll.status if poll.status
|
116
|
-
|
117
|
-
if current_status == 'pending'
|
118
|
-
STDOUT.print "."
|
119
|
-
elsif current_status == 'running' && previous_status == 'pending'
|
120
|
-
STDOUT.puts "\n"
|
121
|
-
end
|
122
|
-
|
123
|
-
if current_status != 'pending'
|
124
|
-
poll.taps.each do |tap|
|
125
|
-
# Delete most recent tap and redraw it if it's been updated
|
126
|
-
if tap.id.to_i == last_tap
|
127
|
-
if tap.updated
|
128
|
-
# Restore the cursor to the start of the last entry so we can overwrite
|
129
|
-
STDOUT.print "\e[#{last_tap_lines}A\r"
|
130
|
-
else
|
131
|
-
next
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
tap_output = format_tap(tap)
|
136
|
-
last_tap_lines = tap_output.count("\n")
|
137
|
-
last_tap = tap.id.to_i
|
138
|
-
|
139
|
-
STDOUT.print tap_output
|
140
|
-
STDOUT.flush
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
previous_status = current_status
|
145
|
-
end
|
124
|
+
STDOUT.print "Waiting for an available deployment slot..."
|
125
|
+
DeploymentProgressOutput.new(deployment).monitor
|
146
126
|
end
|
147
127
|
|
148
|
-
def
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
128
|
+
def configure
|
129
|
+
configuration = {
|
130
|
+
account: ask_config_question("Account Domain (e.g. atech.deployhq.com)", /\A[a-z0-9\.\-]+.deployhq.com\z/),
|
131
|
+
username: ask_config_question("Username or e-mail address"),
|
132
|
+
api_key: ask_config_question("API key (You can find this in Settings -> Security)"),
|
133
|
+
project: ask_config_question("Default project to use (please use permalink from web URL)")
|
134
|
+
}
|
154
135
|
|
155
|
-
|
156
|
-
|
136
|
+
confirmation = true
|
137
|
+
if File.exists?(@options.config_file)
|
138
|
+
confirmation = agree("File already exists at #{@options.config_file}. Overwrite? ")
|
157
139
|
end
|
158
|
-
end
|
159
|
-
|
160
|
-
## Data formatters
|
161
140
|
|
162
|
-
|
163
|
-
server_name = @server_names[tap.server_id]
|
141
|
+
return unless confirmation
|
164
142
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
server_name = ' '
|
170
|
-
end
|
143
|
+
file_data = JSON.pretty_generate(configuration)
|
144
|
+
File.write(@options.config_file, file_data)
|
145
|
+
say("File written to #{@options.config_file}")
|
146
|
+
end
|
171
147
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
s << "\n"
|
180
|
-
s << " " * server_name.length
|
181
|
-
s << " "
|
182
|
-
s << backend_line.color(text_colour).gsub(/\<[^\>]*\>/, '')
|
183
|
-
end
|
184
|
-
end
|
185
|
-
s << "\n"
|
148
|
+
def ask_config_question(question_text, valid_format = /.+/)
|
149
|
+
question_text = "#{question_text}: "
|
150
|
+
ask(question_text) do |q|
|
151
|
+
q.whitespace = :remove
|
152
|
+
q.responses[:not_valid] = "That answer is not valid"
|
153
|
+
q.responses[:ask_on_error] = :question
|
154
|
+
q.validate = valid_format
|
186
155
|
end
|
187
156
|
end
|
188
157
|
|
158
|
+
private
|
159
|
+
|
160
|
+
## Data formatters
|
189
161
|
def format_server(server)
|
190
162
|
server_params = {
|
191
163
|
"Name" => server.name,
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Deploy
|
2
|
+
class CLI
|
3
|
+
class DeploymentProgressOutput
|
4
|
+
SERVER_TAG_COLOURS = %w[32 33 34 35 36].cycle
|
5
|
+
|
6
|
+
attr_reader :deployment, :step_index, :server_tags
|
7
|
+
|
8
|
+
def initialize(deployment)
|
9
|
+
@deployment = deployment
|
10
|
+
|
11
|
+
@step_index = @deployment.steps.each_with_object({}) { |s, hsh| hsh[s.identifier] = s }
|
12
|
+
@server_tags = @deployment.servers.each_with_object({}) do |s, hsh|
|
13
|
+
hsh[s.id] = "\e[#{SERVER_TAG_COLOURS.next};1m[#{s.name}]\e[0m "
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def monitor
|
18
|
+
websocket_client = Deploy::CLI::WebsocketClient.new
|
19
|
+
|
20
|
+
subscription = websocket_client.subscribe('deployment', @deployment.identifier)
|
21
|
+
subscription.on('log-entry', &method(:handle_log_entry))
|
22
|
+
subscription.on('status-change', &method(:handle_status_change))
|
23
|
+
|
24
|
+
websocket_client.run
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def handle_log_entry(payload)
|
30
|
+
step = step_index[payload['step']]
|
31
|
+
server_tag = server_tags[step.server]
|
32
|
+
|
33
|
+
line = "\n"
|
34
|
+
line << server_tag if server_tag
|
35
|
+
line << payload['message']
|
36
|
+
|
37
|
+
if payload['detail']
|
38
|
+
padding_width = 0
|
39
|
+
padding_width += (server_tag.length - 11) if server_tag
|
40
|
+
padding = ' ' * padding_width
|
41
|
+
|
42
|
+
payload['detail'].split("\n").each do |detail_line|
|
43
|
+
line << "\n#{padding}| #{detail_line}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
STDOUT.print line
|
48
|
+
end
|
49
|
+
|
50
|
+
def handle_status_change(payload)
|
51
|
+
if payload['status'] == 'completed'
|
52
|
+
STDOUT.print "\nDeployment has finished successfully!\n"
|
53
|
+
elsif payload['status'] == 'failed'
|
54
|
+
STDOUT.print "\nDeployment has failed!\n"
|
55
|
+
end
|
56
|
+
|
57
|
+
throw(:finished) if %w[completed failed].include?(payload['status'])
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'websocket-eventmachine-client'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module Deploy
|
5
|
+
class CLI
|
6
|
+
# Manages a connection and associated subscriptions to DeployHQ's websocket
|
7
|
+
class WebsocketClient
|
8
|
+
attr_reader :subscriptions
|
9
|
+
|
10
|
+
class Subscription
|
11
|
+
attr_reader :exchange, :routing_key
|
12
|
+
|
13
|
+
def initialize(exchange, routing_key)
|
14
|
+
@exchange = exchange
|
15
|
+
@routing_key = routing_key
|
16
|
+
@events = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def on(event, &block)
|
20
|
+
@events[event] ||= []
|
21
|
+
@events[event] << block
|
22
|
+
end
|
23
|
+
|
24
|
+
def dispatch(event, payload)
|
25
|
+
return unless @events[event]
|
26
|
+
|
27
|
+
@events[event].each do |block|
|
28
|
+
block.call(payload)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def subscribed?
|
33
|
+
@subscribed == true
|
34
|
+
end
|
35
|
+
|
36
|
+
def subscribed!
|
37
|
+
@subscribed = true
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize
|
42
|
+
@subscriptions = {}
|
43
|
+
end
|
44
|
+
|
45
|
+
def subscribe(exchange, routing_key)
|
46
|
+
key = subscription_key(exchange, routing_key)
|
47
|
+
subscriptions[key] ||= Subscription.new(exchange, routing_key)
|
48
|
+
end
|
49
|
+
|
50
|
+
def run
|
51
|
+
catch(:finished) do
|
52
|
+
EM.run do
|
53
|
+
connection.onopen do
|
54
|
+
logger.info "connected"
|
55
|
+
end
|
56
|
+
|
57
|
+
connection.onmessage do |msg, type|
|
58
|
+
receive(msg)
|
59
|
+
end
|
60
|
+
|
61
|
+
connection.onclose do |code, reason|
|
62
|
+
logger.info "disconnected #{code} #{reason}"
|
63
|
+
reset_connection
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def dispatch(event, payload, mq = nil)
|
72
|
+
case event
|
73
|
+
when 'Welcome'
|
74
|
+
authenticate
|
75
|
+
when 'Authenticated'
|
76
|
+
request_subscriptions
|
77
|
+
when 'Subscribed'
|
78
|
+
successful_subscription(payload)
|
79
|
+
when 'Error', 'InternalError'
|
80
|
+
websocket_error(payload)
|
81
|
+
else
|
82
|
+
subscription_event(event, payload, mq) if mq
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def authenticate
|
87
|
+
send('Authenticate', api_key: Deploy.configuration.api_key)
|
88
|
+
end
|
89
|
+
|
90
|
+
def request_subscriptions
|
91
|
+
subscriptions.each do |_key, subscription|
|
92
|
+
send('Subscribe', exchange: subscription.exchange, routing_key: subscription.routing_key)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def successful_subscription(payload)
|
97
|
+
key = subscription_key(payload['exchange'], payload['routing_key'])
|
98
|
+
subscription = subscriptions[key]
|
99
|
+
subscription.subscribed! if subscription
|
100
|
+
end
|
101
|
+
|
102
|
+
def websocket_error(payload)
|
103
|
+
raise Deploy::Errors::WebsocketError, payload['error']
|
104
|
+
end
|
105
|
+
|
106
|
+
def subscription_event(event, payload, mq)
|
107
|
+
key = subscription_key(mq["e"], mq["rk"])
|
108
|
+
subscription = subscriptions[key]
|
109
|
+
subscription.dispatch(event, payload) if subscription
|
110
|
+
end
|
111
|
+
|
112
|
+
def receive(msg)
|
113
|
+
logger.debug "< #{msg}"
|
114
|
+
decoded = JSON.parse(msg)
|
115
|
+
dispatch(decoded['event'], decoded['payload'], decoded['mq'])
|
116
|
+
end
|
117
|
+
|
118
|
+
def send(action, payload = {})
|
119
|
+
msg = JSON.dump(action: action, payload: payload)
|
120
|
+
logger.debug "> #{msg}"
|
121
|
+
connection.send(msg)
|
122
|
+
end
|
123
|
+
|
124
|
+
def connection
|
125
|
+
uri = "#{Deploy.configuration.websocket_hostname}/pushwss"
|
126
|
+
@connection ||= WebSocket::EventMachine::Client.connect(uri: uri)
|
127
|
+
end
|
128
|
+
|
129
|
+
def reset_connection
|
130
|
+
@connection = nil
|
131
|
+
end
|
132
|
+
|
133
|
+
def logger
|
134
|
+
@logger ||= Logger.new(STDOUT)
|
135
|
+
@logger.level = Logger::ERROR
|
136
|
+
@logger
|
137
|
+
end
|
138
|
+
|
139
|
+
def subscription_key(exchange, routing_key)
|
140
|
+
[exchange, routing_key].join('-')
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Deploy
|
2
|
+
class Configuration
|
3
|
+
attr_accessor :account, :username, :api_key, :project
|
4
|
+
attr_writer :websocket_hostname
|
5
|
+
|
6
|
+
def websocket_hostname
|
7
|
+
@websocket_hostname || 'wss://websocket.deployhq.com'
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.from_file(path)
|
11
|
+
file_contents = File.read(path)
|
12
|
+
parsed_contents = JSON.parse(file_contents)
|
13
|
+
|
14
|
+
self.new.tap do |config|
|
15
|
+
config.account = parsed_contents['account']
|
16
|
+
config.username = parsed_contents['username']
|
17
|
+
config.api_key = parsed_contents['api_key']
|
18
|
+
config.project = parsed_contents['project']
|
19
|
+
config.websocket_hostname = parsed_contents['websocket_hostname']
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/deploy/errors.rb
CHANGED
@@ -1,20 +1,22 @@
|
|
1
1
|
module Deploy
|
2
|
-
|
2
|
+
|
3
3
|
## Base level error which all other deploy errors will inherit from. It may also be
|
4
4
|
## invoked for errors which don't directly relate to other errors below.
|
5
5
|
class Error < StandardError; end
|
6
|
-
|
6
|
+
|
7
7
|
module Errors
|
8
|
-
|
8
|
+
|
9
9
|
## The service is currently unavailable. This may be caused by rate limiting or the API
|
10
10
|
## or the service has been disabled by the system
|
11
11
|
class ServiceUnavailable < Error; end
|
12
|
-
|
12
|
+
|
13
13
|
## Access was denied to the remote service
|
14
14
|
class AccessDenied < Error; end
|
15
|
-
|
15
|
+
|
16
16
|
## A communication error occured while talking to the Deploy API
|
17
17
|
class CommunicationError < Error; end
|
18
|
-
|
18
|
+
|
19
|
+
# Raised from the websocket client when we receive an error event
|
20
|
+
class WebsocketError < Error; end
|
19
21
|
end
|
20
|
-
end
|
22
|
+
end
|
data/lib/deploy/request.rb
CHANGED
@@ -1,36 +1,36 @@
|
|
1
1
|
module Deploy
|
2
2
|
class Request
|
3
|
-
|
3
|
+
|
4
4
|
attr_reader :path, :method
|
5
5
|
attr_accessor :data
|
6
|
-
|
6
|
+
|
7
7
|
def initialize(path, method = :get)
|
8
8
|
@path = path
|
9
9
|
@method = method
|
10
10
|
end
|
11
|
-
|
11
|
+
|
12
12
|
def success?
|
13
13
|
@success || false
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
def output
|
17
17
|
@output || nil
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
## Make a request to the Deploy API using net/http. Data passed can be a hash or a string
|
21
21
|
## Hashes will be converted to JSON before being sent to the remote service.
|
22
22
|
def make
|
23
|
-
uri = URI.parse([Deploy.
|
23
|
+
uri = URI.parse([Deploy.configuration.account, @path].join('/'))
|
24
24
|
http_request = http_class.new(uri.request_uri)
|
25
|
-
http_request.basic_auth(Deploy.
|
25
|
+
http_request.basic_auth(Deploy.configuration.username, Deploy.configuration.api_key)
|
26
26
|
http_request["Accept"] = "application/json"
|
27
27
|
http_request["Content-type"] = "application/json"
|
28
|
-
|
28
|
+
|
29
29
|
http = Net::HTTP.new(uri.host, uri.port)
|
30
30
|
if uri.scheme == 'https'
|
31
31
|
http.use_ssl = true
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
34
|
data = self.data.to_json if self.data.is_a?(Hash) && self.data.respond_to?(:to_json)
|
35
35
|
http_result = http.request(http_request, data)
|
36
36
|
@output = http_result.body
|
@@ -50,10 +50,10 @@ module Deploy
|
|
50
50
|
end
|
51
51
|
self
|
52
52
|
end
|
53
|
-
|
53
|
+
|
54
54
|
private
|
55
|
-
|
56
|
-
def http_class
|
55
|
+
|
56
|
+
def http_class
|
57
57
|
case @method
|
58
58
|
when :post then Net::HTTP::Post
|
59
59
|
when :put then Net::HTTP::Put
|
@@ -62,6 +62,6 @@ module Deploy
|
|
62
62
|
Net::HTTP::Get
|
63
63
|
end
|
64
64
|
end
|
65
|
-
|
65
|
+
|
66
66
|
end
|
67
|
-
end
|
67
|
+
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
module Deploy
|
2
|
-
class
|
3
|
-
|
2
|
+
class Resource
|
3
|
+
|
4
4
|
## Store all attributes for the model we're working with.
|
5
5
|
attr_accessor :id, :attributes, :errors
|
6
|
-
|
6
|
+
|
7
7
|
## Pass any methods via. the attributes hash to see if they exist
|
8
8
|
## before resuming normal method_missing behaviour
|
9
9
|
def method_missing(method, *params)
|
@@ -16,9 +16,9 @@ module Deploy
|
|
16
16
|
self.attributes[key]
|
17
17
|
end
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
class << self
|
21
|
-
|
21
|
+
|
22
22
|
## Find a record or set of records. Passing :all will return all records and passing an integer
|
23
23
|
## will return the individual record for the ID passed.
|
24
24
|
def find(type, params = {})
|
@@ -27,7 +27,7 @@ module Deploy
|
|
27
27
|
else find_single(type, params)
|
28
28
|
end
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
## Find all objects and return an array of objects with the attributes set.
|
32
32
|
def find_all(params)
|
33
33
|
output = JSON.parse(Request.new(collection_path(params)).make.output)
|
@@ -39,7 +39,7 @@ module Deploy
|
|
39
39
|
create_object(o, params)
|
40
40
|
end
|
41
41
|
end
|
42
|
-
|
42
|
+
|
43
43
|
## Find a single object and return an object for it.
|
44
44
|
def find_single(id, params = {})
|
45
45
|
o = JSON.parse(Request.new(member_path(id, params)).make.output)
|
@@ -49,44 +49,44 @@ module Deploy
|
|
49
49
|
raise Deploy::Errors::NotFound, "Record not found"
|
50
50
|
end
|
51
51
|
end
|
52
|
-
|
52
|
+
|
53
53
|
## Post to the specified object on the collection path
|
54
54
|
def post(path)
|
55
55
|
Request.new(path.to_s, :post).make
|
56
56
|
end
|
57
|
-
|
57
|
+
|
58
58
|
## Return the collection path for this model. Very lazy pluralizion here
|
59
59
|
## at the moment, nothing in Deploy needs to be pluralized with anything
|
60
60
|
## other than just adding an 's'.
|
61
61
|
def collection_path(params = {})
|
62
62
|
class_name.downcase + 's'
|
63
63
|
end
|
64
|
-
|
64
|
+
|
65
65
|
## Return the member path for the passed ID & attributes
|
66
66
|
def member_path(id, params = {})
|
67
67
|
[collection_path, id].join('/')
|
68
68
|
end
|
69
|
-
|
69
|
+
|
70
70
|
## Return the deploy class name
|
71
71
|
def class_name
|
72
72
|
self.name.to_s.split('::').last.downcase
|
73
73
|
end
|
74
|
-
|
74
|
+
|
75
75
|
private
|
76
|
-
|
77
|
-
## Create a new object with the specified attributes and getting and ID.
|
76
|
+
|
77
|
+
## Create a new object with the specified attributes and getting and ID.
|
78
78
|
## Returns the newly created object
|
79
79
|
def create_object(attributes, objects = [])
|
80
80
|
o = self.new
|
81
81
|
o.attributes = attributes
|
82
82
|
o.id = attributes['id']
|
83
|
-
for key, object in objects.select{|k,v| v.kind_of?(Deploy::
|
83
|
+
for key, object in objects.select{|k,v| v.kind_of?(Deploy::Resource)}
|
84
84
|
o.attributes[key.to_s] = object
|
85
85
|
end
|
86
86
|
o
|
87
87
|
end
|
88
88
|
end
|
89
|
-
|
89
|
+
|
90
90
|
## Run a post on the member path. Returns the ouput from the post, false if a conflict or raises
|
91
91
|
## a Deploy::Error. Optionally, pass a second 'data' parameter to send data to the post action.
|
92
92
|
def post(action, data = nil)
|
@@ -95,17 +95,17 @@ module Deploy
|
|
95
95
|
request.data = data
|
96
96
|
request.make
|
97
97
|
end
|
98
|
-
|
98
|
+
|
99
99
|
## Delete this record from the remote service. Returns true or false depending on the success
|
100
100
|
## status of the destruction.
|
101
101
|
def destroy
|
102
102
|
Request.new(self.class.member_path(self.id, default_params), :delete).make.success?
|
103
103
|
end
|
104
|
-
|
104
|
+
|
105
105
|
def new_record?
|
106
106
|
self.id.nil?
|
107
107
|
end
|
108
|
-
|
108
|
+
|
109
109
|
def save
|
110
110
|
new_record? ? create : update
|
111
111
|
end
|
@@ -126,7 +126,7 @@ module Deploy
|
|
126
126
|
|
127
127
|
## Push the updated attributes to the remote. Returns true if the record was saved successfully
|
128
128
|
## other false if not. If not saved successfully, the errors hash will be updated with an array
|
129
|
-
## of all errors with the submission.
|
129
|
+
## of all errors with the submission.
|
130
130
|
def update
|
131
131
|
request = Request.new(self.class.member_path(self.id, default_params), :put)
|
132
132
|
request.data = {self.class.class_name.downcase.to_sym => attributes_to_post}
|
@@ -137,9 +137,9 @@ module Deploy
|
|
137
137
|
false
|
138
138
|
end
|
139
139
|
end
|
140
|
-
|
140
|
+
|
141
141
|
private
|
142
|
-
|
142
|
+
|
143
143
|
## Populate the errors hash from the given raw JSON output
|
144
144
|
def populate_errors(json)
|
145
145
|
self.errors = Hash.new
|
@@ -148,12 +148,12 @@ module Deploy
|
|
148
148
|
r
|
149
149
|
end
|
150
150
|
end
|
151
|
-
|
151
|
+
|
152
152
|
## An array of params which should always be sent with this instances requests
|
153
153
|
def default_params
|
154
154
|
Hash.new
|
155
155
|
end
|
156
|
-
|
156
|
+
|
157
157
|
## Attributes which can be passed for update & creation
|
158
158
|
def attributes_to_post
|
159
159
|
self.attributes.inject(Hash.new) do |r,(key,value)|
|
@@ -161,6 +161,6 @@ module Deploy
|
|
161
161
|
r
|
162
162
|
end
|
163
163
|
end
|
164
|
-
|
164
|
+
|
165
165
|
end
|
166
|
-
end
|
166
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Deploy
|
2
|
+
class Deployment < Resource
|
3
|
+
class << self
|
4
|
+
def collection_path(params = {})
|
5
|
+
"projects/#{params[:project].permalink}/deployments"
|
6
|
+
end
|
7
|
+
|
8
|
+
def member_path(id, params = {})
|
9
|
+
"projects/#{params[:project].permalink}/deployments/#{id}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def default_params
|
14
|
+
{:project => self.project}
|
15
|
+
end
|
16
|
+
|
17
|
+
def project
|
18
|
+
if self.attributes['project'].is_a?(Hash)
|
19
|
+
self.attributes['project'] = Project.send(:create_object, self.attributes['project'])
|
20
|
+
end
|
21
|
+
self.attributes['project']
|
22
|
+
end
|
23
|
+
|
24
|
+
def servers
|
25
|
+
if attributes['servers'].is_a?(Array)
|
26
|
+
@servers ||= attributes['servers'].map do |server_params|
|
27
|
+
Server.new.tap do |server|
|
28
|
+
server.id = server_params['id']
|
29
|
+
server.attributes = server_params
|
30
|
+
end
|
31
|
+
end
|
32
|
+
else
|
33
|
+
[]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def steps
|
38
|
+
if attributes['steps'].is_a?(Array)
|
39
|
+
@steps ||= attributes['steps'].map do |step_params|
|
40
|
+
DeploymentStep.new.tap do |step|
|
41
|
+
step.id = step_params['identifier']
|
42
|
+
step.attributes = step_params
|
43
|
+
step.attributes['deployment'] = self
|
44
|
+
end
|
45
|
+
end
|
46
|
+
else
|
47
|
+
[]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Deploy
|
2
|
+
class DeploymentStep < Resource
|
3
|
+
def default_params
|
4
|
+
{ deployment: self.deployment, project: self.deployment.project }
|
5
|
+
end
|
6
|
+
|
7
|
+
def logs(params = {})
|
8
|
+
params = default_params.merge(step: self).merge(params)
|
9
|
+
DeploymentStepLog.find(:all, params)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -1,16 +1,16 @@
|
|
1
1
|
module Deploy
|
2
|
-
class Server <
|
3
|
-
|
2
|
+
class Server < Resource
|
3
|
+
|
4
4
|
class << self
|
5
5
|
def collection_path(params = {})
|
6
6
|
"projects/#{params[:project].permalink}/servers"
|
7
7
|
end
|
8
|
-
|
8
|
+
|
9
9
|
def member_path(id, params = {})
|
10
10
|
"projects/#{params[:project].permalink}/servers/#{identifier}"
|
11
11
|
end
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
def default_params
|
15
15
|
{:project => self.project}
|
16
16
|
end
|
@@ -26,6 +26,6 @@ module Deploy
|
|
26
26
|
end
|
27
27
|
end.join(' ')
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
end
|
31
|
-
end
|
31
|
+
end
|
@@ -1,20 +1,20 @@
|
|
1
1
|
module Deploy
|
2
|
-
class ServerGroup <
|
3
|
-
|
2
|
+
class ServerGroup < Resource
|
3
|
+
|
4
4
|
class << self
|
5
5
|
def collection_path(params = {})
|
6
6
|
"projects/#{params[:project].permalink}/server_groups"
|
7
7
|
end
|
8
|
-
|
8
|
+
|
9
9
|
def member_path(id, params = {})
|
10
10
|
"projects/#{params[:project].permalink}/server_groups/#{identifier}"
|
11
11
|
end
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
def default_params
|
15
15
|
{:project => self.project}
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
def servers
|
19
19
|
@servers ||= self.attributes['servers'].map {|server_attr| Deploy::Server.send(:create_object, server_attr) }
|
20
20
|
end
|
@@ -32,4 +32,4 @@ module Deploy
|
|
32
32
|
end
|
33
33
|
|
34
34
|
end
|
35
|
-
end
|
35
|
+
end
|
data/lib/deploy/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: deployhq
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dan Wentworth
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-10-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '1.7'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: websocket-eventmachine-client
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.2'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.2'
|
41
55
|
description: |2
|
42
56
|
API and CLI client for the DeployHQ deployment platform. Provides the
|
43
57
|
deployhq executable.
|
@@ -49,16 +63,19 @@ extra_rdoc_files: []
|
|
49
63
|
files:
|
50
64
|
- bin/deployhq
|
51
65
|
- lib/deploy.rb
|
52
|
-
- lib/deploy/base.rb
|
53
66
|
- lib/deploy/cli.rb
|
54
|
-
- lib/deploy/
|
55
|
-
- lib/deploy/
|
56
|
-
- lib/deploy/
|
67
|
+
- lib/deploy/cli/deployment_progress_output.rb
|
68
|
+
- lib/deploy/cli/websocket_client.rb
|
69
|
+
- lib/deploy/configuration.rb
|
57
70
|
- lib/deploy/errors.rb
|
58
|
-
- lib/deploy/project.rb
|
59
71
|
- lib/deploy/request.rb
|
60
|
-
- lib/deploy/
|
61
|
-
- lib/deploy/
|
72
|
+
- lib/deploy/resource.rb
|
73
|
+
- lib/deploy/resources/deployment.rb
|
74
|
+
- lib/deploy/resources/deployment_step.rb
|
75
|
+
- lib/deploy/resources/deployment_step_log.rb
|
76
|
+
- lib/deploy/resources/project.rb
|
77
|
+
- lib/deploy/resources/server.rb
|
78
|
+
- lib/deploy/resources/server_group.rb
|
62
79
|
- lib/deploy/version.rb
|
63
80
|
homepage: https://www.deployhq.com
|
64
81
|
licenses:
|
data/lib/deploy/deployment.rb
DELETED
@@ -1,36 +0,0 @@
|
|
1
|
-
module Deploy
|
2
|
-
class Deployment < Base
|
3
|
-
|
4
|
-
class << self
|
5
|
-
def collection_path(params = {})
|
6
|
-
"projects/#{params[:project].permalink}/deployments"
|
7
|
-
end
|
8
|
-
|
9
|
-
def member_path(id, params = {})
|
10
|
-
"projects/#{params[:project].permalink}/deployments/#{id}"
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
def default_params
|
15
|
-
{:project => self.project}
|
16
|
-
end
|
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
|
-
|
35
|
-
end
|
36
|
-
end
|
@@ -1,34 +0,0 @@
|
|
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
|
@@ -1,17 +0,0 @@
|
|
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
|