deployhq 2.0.3 → 2.1.2
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 +5 -5
- data/bin/deployhq +3 -1
- data/lib/deploy/cli/deployment_progress_output.rb +18 -8
- data/lib/deploy/cli/websocket_client.rb +19 -10
- data/lib/deploy/cli.rb +65 -48
- data/lib/deploy/configuration.rb +9 -2
- data/lib/deploy/errors.rb +5 -1
- data/lib/deploy/request.rb +22 -13
- data/lib/deploy/resource.rb +42 -32
- data/lib/deploy/resources/deployment.rb +9 -5
- data/lib/deploy/resources/deployment_step.rb +5 -1
- data/lib/deploy/resources/deployment_step_log.rb +9 -1
- data/lib/deploy/resources/project.rb +9 -7
- data/lib/deploy/resources/server.rb +12 -8
- data/lib/deploy/resources/server_group.rb +13 -9
- data/lib/deploy/version.rb +10 -1
- data/lib/deploy.rb +6 -0
- metadata +16 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7822d0dc6c5d8d2ba5b4dc65e955badf2e80fb3634897c5671155035ba6dbbe3
|
4
|
+
data.tar.gz: 17493bebc4b52686ce52bbbec87736084746eb97392fa7942913f31b508b1e5d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7b533389d7fa5604510a5ced8f9b1e22187df73cc23a6d79a2e2e9b98c0ea2ee1f0875816babbd7a5e3c6b66928274e14ac5777397283d5e6e3f7a1ba8c6c753
|
7
|
+
data.tar.gz: b5d3ec552dd78c0caceed18f64b6a1753480c54665cf50ebbf28c8aa26249ed144133915f02c645a147e14b20d9cf0662cf6de2991125da694764519fc0480dc
|
data/bin/deployhq
CHANGED
@@ -1,9 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Deploy
|
2
4
|
class CLI
|
5
|
+
|
3
6
|
class DeploymentProgressOutput
|
7
|
+
|
4
8
|
SERVER_TAG_COLOURS = %w[32 33 34 35 36].cycle
|
5
9
|
|
6
|
-
attr_reader :deployment
|
10
|
+
attr_reader :deployment
|
11
|
+
attr_reader :step_index
|
12
|
+
attr_reader :server_tags
|
7
13
|
|
8
14
|
def initialize(deployment)
|
9
15
|
@deployment = deployment
|
@@ -26,13 +32,14 @@ module Deploy
|
|
26
32
|
|
27
33
|
private
|
28
34
|
|
35
|
+
# rubocop:disable Metrics/AbcSize
|
29
36
|
def handle_log_entry(payload)
|
30
37
|
step = step_index[payload['step']]
|
31
38
|
server_tag = server_tags[step.server]
|
32
39
|
|
33
|
-
|
34
|
-
|
35
|
-
|
40
|
+
lines = ["\n"]
|
41
|
+
lines << server_tag
|
42
|
+
lines << payload['message']
|
36
43
|
|
37
44
|
if payload['detail']
|
38
45
|
padding_width = 0
|
@@ -40,22 +47,25 @@ module Deploy
|
|
40
47
|
padding = ' ' * padding_width
|
41
48
|
|
42
49
|
payload['detail'].split("\n").each do |detail_line|
|
43
|
-
|
50
|
+
lines << "\n#{padding}| #{detail_line}"
|
44
51
|
end
|
45
52
|
end
|
46
53
|
|
47
|
-
|
54
|
+
$stdout.print lines.join
|
48
55
|
end
|
56
|
+
# rubocop:enable Metrics/AbcSize
|
49
57
|
|
50
58
|
def handle_status_change(payload)
|
51
59
|
if payload['status'] == 'completed'
|
52
|
-
|
60
|
+
$stdout.print "\nDeployment has finished successfully!\n"
|
53
61
|
elsif payload['status'] == 'failed'
|
54
|
-
|
62
|
+
$stdout.print "\nDeployment has failed!\n"
|
55
63
|
end
|
56
64
|
|
57
65
|
throw(:finished) if %w[completed failed].include?(payload['status'])
|
58
66
|
end
|
67
|
+
|
59
68
|
end
|
69
|
+
|
60
70
|
end
|
61
71
|
end
|
@@ -1,14 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'websocket-eventmachine-client'
|
2
4
|
require 'logger'
|
3
5
|
|
4
6
|
module Deploy
|
5
7
|
class CLI
|
8
|
+
|
6
9
|
# Manages a connection and associated subscriptions to DeployHQ's websocket
|
7
10
|
class WebsocketClient
|
11
|
+
|
8
12
|
attr_reader :subscriptions
|
9
13
|
|
10
14
|
class Subscription
|
11
|
-
|
15
|
+
|
16
|
+
attr_reader :exchange
|
17
|
+
attr_reader :routing_key
|
12
18
|
|
13
19
|
def initialize(exchange, routing_key)
|
14
20
|
@exchange = exchange
|
@@ -36,6 +42,7 @@ module Deploy
|
|
36
42
|
def subscribed!
|
37
43
|
@subscribed = true
|
38
44
|
end
|
45
|
+
|
39
46
|
end
|
40
47
|
|
41
48
|
def initialize
|
@@ -51,10 +58,10 @@ module Deploy
|
|
51
58
|
catch(:finished) do
|
52
59
|
EM.run do
|
53
60
|
connection.onopen do
|
54
|
-
logger.info
|
61
|
+
logger.info 'connected'
|
55
62
|
end
|
56
63
|
|
57
|
-
connection.onmessage do |msg,
|
64
|
+
connection.onmessage do |msg, _type|
|
58
65
|
receive(msg)
|
59
66
|
end
|
60
67
|
|
@@ -68,7 +75,7 @@ module Deploy
|
|
68
75
|
|
69
76
|
private
|
70
77
|
|
71
|
-
def dispatch(event, payload,
|
78
|
+
def dispatch(event, payload, rmq = nil)
|
72
79
|
case event
|
73
80
|
when 'Welcome'
|
74
81
|
authenticate
|
@@ -79,7 +86,7 @@ module Deploy
|
|
79
86
|
when 'Error', 'InternalError'
|
80
87
|
websocket_error(payload)
|
81
88
|
else
|
82
|
-
subscription_event(event, payload,
|
89
|
+
subscription_event(event, payload, rmq) if rmq
|
83
90
|
end
|
84
91
|
end
|
85
92
|
|
@@ -96,17 +103,17 @@ module Deploy
|
|
96
103
|
def successful_subscription(payload)
|
97
104
|
key = subscription_key(payload['exchange'], payload['routing_key'])
|
98
105
|
subscription = subscriptions[key]
|
99
|
-
subscription
|
106
|
+
subscription&.subscribed!
|
100
107
|
end
|
101
108
|
|
102
109
|
def websocket_error(payload)
|
103
110
|
raise Deploy::Errors::WebsocketError, payload['error']
|
104
111
|
end
|
105
112
|
|
106
|
-
def subscription_event(event, payload,
|
107
|
-
key = subscription_key(
|
113
|
+
def subscription_event(event, payload, rmq)
|
114
|
+
key = subscription_key(rmq['e'], rmq['rk'])
|
108
115
|
subscription = subscriptions[key]
|
109
|
-
subscription
|
116
|
+
subscription&.dispatch(event, payload)
|
110
117
|
end
|
111
118
|
|
112
119
|
def receive(msg)
|
@@ -131,7 +138,7 @@ module Deploy
|
|
131
138
|
end
|
132
139
|
|
133
140
|
def logger
|
134
|
-
@logger ||= Logger.new(
|
141
|
+
@logger ||= Logger.new($stdout)
|
135
142
|
@logger.level = Logger::ERROR
|
136
143
|
@logger
|
137
144
|
end
|
@@ -139,6 +146,8 @@ module Deploy
|
|
139
146
|
def subscription_key(exchange, routing_key)
|
140
147
|
[exchange, routing_key].join('-')
|
141
148
|
end
|
149
|
+
|
142
150
|
end
|
151
|
+
|
143
152
|
end
|
144
153
|
end
|
data/lib/deploy/cli.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'optparse'
|
2
4
|
require 'highline/import'
|
3
5
|
|
@@ -5,43 +7,50 @@ require 'deploy'
|
|
5
7
|
require 'deploy/cli/websocket_client'
|
6
8
|
require 'deploy/cli/deployment_progress_output'
|
7
9
|
|
8
|
-
|
10
|
+
OptionsStruct = Struct.new(:config_file, :project)
|
9
11
|
|
12
|
+
# rubocop:disable Metrics/ClassLength
|
13
|
+
# rubocop:disable Metrics/AbcSize
|
14
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
15
|
+
# rubocop:disable Metrics/MethodLength
|
16
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
10
17
|
module Deploy
|
11
18
|
class CLI
|
19
|
+
|
12
20
|
## Constants for formatting output
|
13
|
-
PROTOCOL_NAME = {:
|
21
|
+
PROTOCOL_NAME = { ssh: 'SSH/SFTP', ftp: 'FTP', s3: 'Amazon S3', rackspace: 'Rackspace CloudFiles' }.freeze
|
14
22
|
|
15
23
|
class << self
|
24
|
+
|
16
25
|
def invoke(args)
|
17
|
-
@options =
|
26
|
+
@options = OptionsStruct.new
|
18
27
|
|
19
28
|
parser = OptionParser.new do |opts|
|
20
|
-
opts.banner =
|
21
|
-
opts.separator
|
22
|
-
opts.separator
|
29
|
+
opts.banner = 'Usage: deployhq [options] command'
|
30
|
+
opts.separator ''
|
31
|
+
opts.separator 'Commands:'
|
23
32
|
opts.separator "deploy\t\t Start a new deployment"
|
24
33
|
opts.separator "servers\t\t List configured servers and server groups"
|
25
34
|
opts.separator "configure\t\t Create a new configuration file for this tool"
|
26
|
-
opts.separator
|
27
|
-
opts.separator
|
35
|
+
opts.separator ''
|
36
|
+
opts.separator 'Common Options:'
|
28
37
|
|
29
38
|
@options.config_file = './Deployfile'
|
30
|
-
opts.on(
|
39
|
+
opts.on('-c', '--config path', 'Configuration file path') do |config_file_path|
|
31
40
|
@options.config_file = config_file_path
|
32
41
|
end
|
33
42
|
|
34
|
-
opts.on(
|
35
|
-
|
43
|
+
opts.on('-p', '--project project',
|
44
|
+
'Project to operate on (default is read from project: in config file)') do |project_permalink|
|
36
45
|
@options.project = project_permalink
|
37
46
|
end
|
38
47
|
|
39
|
-
opts.on_tail('-v', '--version',
|
48
|
+
opts.on_tail('-v', '--version', 'Shows Version') do
|
40
49
|
puts Deploy::VERSION
|
41
50
|
exit
|
42
51
|
end
|
43
52
|
|
44
|
-
opts.on_tail(
|
53
|
+
opts.on_tail('-h', '--help', 'Displays Help') do
|
45
54
|
puts opts
|
46
55
|
exit
|
47
56
|
end
|
@@ -51,7 +60,7 @@ module Deploy
|
|
51
60
|
parser.parse!(args)
|
52
61
|
command = args.pop
|
53
62
|
rescue OptionParser::InvalidOption
|
54
|
-
|
63
|
+
warn parser
|
55
64
|
exit 1
|
56
65
|
end
|
57
66
|
|
@@ -59,13 +68,13 @@ module Deploy
|
|
59
68
|
begin
|
60
69
|
Deploy.configuration_file = @options.config_file
|
61
70
|
rescue Errno::ENOENT
|
62
|
-
|
71
|
+
warn "Couldn't find configuration file at #{@options.config_file.inspect}"
|
63
72
|
exit 1
|
64
73
|
end
|
65
74
|
|
66
75
|
project_permalink = @options.project || Deploy.configuration.project
|
67
76
|
if project_permalink.nil?
|
68
|
-
|
77
|
+
warn 'Project must be specified in config file or as --project argument'
|
69
78
|
exit 1
|
70
79
|
end
|
71
80
|
|
@@ -80,25 +89,25 @@ module Deploy
|
|
80
89
|
when 'configure'
|
81
90
|
configure
|
82
91
|
else
|
83
|
-
|
92
|
+
warn parser
|
84
93
|
end
|
85
94
|
end
|
86
95
|
|
87
96
|
def server_list
|
88
97
|
@server_groups ||= @project.server_groups
|
89
|
-
if @server_groups.count
|
98
|
+
if @server_groups.count.positive?
|
90
99
|
@server_groups.each do |group|
|
91
|
-
puts "Group: #{group.name}"
|
92
|
-
puts group.servers.map {|server| format_server(server) }.join("\n\n")
|
100
|
+
puts "Group: #{group.name}"
|
101
|
+
puts group.servers.map { |server| format_server(server) }.join("\n\n")
|
93
102
|
end
|
94
103
|
end
|
95
104
|
|
96
105
|
@ungrouped_servers ||= @project.servers
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
106
|
+
return unless @ungrouped_servers.count.positive?
|
107
|
+
|
108
|
+
puts "\n" if @server_groups.count.positive?
|
109
|
+
puts 'Ungrouped Servers'
|
110
|
+
puts @ungrouped_servers.map { |server| format_server(server) }.join("\n\n")
|
102
111
|
end
|
103
112
|
|
104
113
|
def deploy
|
@@ -108,10 +117,10 @@ module Deploy
|
|
108
117
|
parent = nil
|
109
118
|
while parent.nil?
|
110
119
|
parent = choose do |menu|
|
111
|
-
menu.prompt =
|
120
|
+
menu.prompt = 'Please choose a server or group to deploy to:'
|
112
121
|
|
113
122
|
menu.choices(*(@ungrouped_servers + @server_groups))
|
114
|
-
menu.choice(
|
123
|
+
menu.choice('List Server Details') do
|
115
124
|
server_list
|
116
125
|
nil
|
117
126
|
end
|
@@ -121,21 +130,21 @@ module Deploy
|
|
121
130
|
latest_revision = @project.latest_revision(parent.preferred_branch)
|
122
131
|
deployment = @project.deploy(parent.identifier, parent.last_revision, latest_revision)
|
123
132
|
|
124
|
-
|
133
|
+
$stdout.print 'Waiting for an available deployment slot...'
|
125
134
|
DeploymentProgressOutput.new(deployment).monitor
|
126
135
|
end
|
127
136
|
|
128
137
|
def configure
|
129
138
|
configuration = {
|
130
|
-
account: ask_config_question(
|
131
|
-
|
132
|
-
username: ask_config_question(
|
133
|
-
api_key: ask_config_question(
|
134
|
-
project: ask_config_question(
|
139
|
+
account: ask_config_question('Account Domain (e.g. https://atech.deployhq.com)',
|
140
|
+
/\Ahttps?:\/\/[a-z0-9.-]+.deployhq.com\z/),
|
141
|
+
username: ask_config_question('Username or e-mail address'),
|
142
|
+
api_key: ask_config_question('API key (You can find this in Settings -> Security)'),
|
143
|
+
project: ask_config_question('Default project to use (please use permalink from web URL)')
|
135
144
|
}
|
136
145
|
|
137
146
|
confirmation = true
|
138
|
-
if File.
|
147
|
+
if File.exist?(@options.config_file)
|
139
148
|
confirmation = agree("File already exists at #{@options.config_file}. Overwrite? ")
|
140
149
|
end
|
141
150
|
|
@@ -150,7 +159,7 @@ module Deploy
|
|
150
159
|
question_text = "#{question_text}: "
|
151
160
|
ask(question_text) do |q|
|
152
161
|
q.whitespace = :remove
|
153
|
-
q.responses[:not_valid] =
|
162
|
+
q.responses[:not_valid] = 'That answer is not valid'
|
154
163
|
q.responses[:ask_on_error] = :question
|
155
164
|
q.validate = valid_format
|
156
165
|
end
|
@@ -161,30 +170,38 @@ module Deploy
|
|
161
170
|
## Data formatters
|
162
171
|
def format_server(server)
|
163
172
|
server_params = {
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
173
|
+
'Name' => server.name,
|
174
|
+
'Type' => PROTOCOL_NAME[server.protocol_type.to_sym],
|
175
|
+
'Path' => server.server_path,
|
176
|
+
'Branch' => server.preferred_branch,
|
177
|
+
'Current Revision' => server.last_revision
|
169
178
|
}
|
170
|
-
server_params[
|
171
|
-
server_params[
|
172
|
-
server_params[
|
173
|
-
server_params[
|
179
|
+
server_params['Hostname'] = [server.hostname, server.port].join(':') if server.hostname
|
180
|
+
server_params['Bucket'] = server.bucket_name if server.bucket_name
|
181
|
+
server_params['Region'] = server.region if server.region
|
182
|
+
server_params['Container'] = server.container_name if server.container_name
|
174
183
|
|
175
|
-
|
184
|
+
[].tap do |a|
|
176
185
|
a << format_kv_pair(server_params)
|
177
186
|
end.join("\n")
|
178
187
|
end
|
179
188
|
|
189
|
+
# rubocop:disable Lint/FormatParameterMismatch
|
180
190
|
def format_kv_pair(hash)
|
181
191
|
longest_key = hash.keys.map(&:length).max + 2
|
182
|
-
hash.each_with_index.map do |(k,v),
|
183
|
-
str =
|
184
|
-
|
192
|
+
hash.each_with_index.map do |(k, v), _i|
|
193
|
+
str = format("%#{longest_key}s : %s", k, v)
|
194
|
+
str
|
185
195
|
end.join("\n")
|
186
196
|
end
|
197
|
+
# rubocop:enable Lint/FormatParameterMismatch
|
187
198
|
|
188
199
|
end
|
200
|
+
|
189
201
|
end
|
190
202
|
end
|
203
|
+
# rubocop:enable Metrics/ClassLength
|
204
|
+
# rubocop:enable Metrics/AbcSize
|
205
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
206
|
+
# rubocop:enable Metrics/MethodLength
|
207
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
data/lib/deploy/configuration.rb
CHANGED
@@ -1,6 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Deploy
|
2
4
|
class Configuration
|
3
|
-
|
5
|
+
|
6
|
+
attr_accessor :account
|
7
|
+
attr_accessor :username
|
8
|
+
attr_accessor :api_key
|
9
|
+
attr_accessor :project
|
4
10
|
attr_writer :websocket_hostname
|
5
11
|
|
6
12
|
def websocket_hostname
|
@@ -11,7 +17,7 @@ module Deploy
|
|
11
17
|
file_contents = File.read(path)
|
12
18
|
parsed_contents = JSON.parse(file_contents)
|
13
19
|
|
14
|
-
|
20
|
+
new.tap do |config|
|
15
21
|
config.account = parsed_contents['account']
|
16
22
|
config.username = parsed_contents['username']
|
17
23
|
config.api_key = parsed_contents['api_key']
|
@@ -19,5 +25,6 @@ module Deploy
|
|
19
25
|
config.websocket_hostname = parsed_contents['websocket_hostname']
|
20
26
|
end
|
21
27
|
end
|
28
|
+
|
22
29
|
end
|
23
30
|
end
|
data/lib/deploy/errors.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Deploy
|
2
4
|
|
3
5
|
## Base level error which all other deploy errors will inherit from. It may also be
|
@@ -13,10 +15,12 @@ module Deploy
|
|
13
15
|
## Access was denied to the remote service
|
14
16
|
class AccessDenied < Error; end
|
15
17
|
|
16
|
-
## A communication error
|
18
|
+
## A communication error occurred while talking to the Deploy API
|
17
19
|
class CommunicationError < Error; end
|
18
20
|
|
19
21
|
# Raised from the websocket client when we receive an error event
|
20
22
|
class WebsocketError < Error; end
|
23
|
+
|
21
24
|
end
|
25
|
+
|
22
26
|
end
|
data/lib/deploy/request.rb
CHANGED
@@ -1,7 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
# rubocop:disable Metrics/AbcSize
|
6
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
7
|
+
# rubocop:disable Metrics/MethodLength
|
1
8
|
module Deploy
|
2
9
|
class Request
|
3
10
|
|
4
|
-
attr_reader :path
|
11
|
+
attr_reader :path
|
12
|
+
attr_reader :method
|
5
13
|
attr_accessor :data
|
6
14
|
|
7
15
|
def initialize(path, method = :get)
|
@@ -23,30 +31,28 @@ module Deploy
|
|
23
31
|
uri = URI.parse([Deploy.configuration.account, @path].join('/'))
|
24
32
|
http_request = http_class.new(uri.request_uri)
|
25
33
|
http_request.basic_auth(Deploy.configuration.username, Deploy.configuration.api_key)
|
26
|
-
http_request[
|
27
|
-
http_request[
|
34
|
+
http_request['Accept'] = 'application/json'
|
35
|
+
http_request['Content-Type'] = 'application/json'
|
28
36
|
|
29
37
|
http = Net::HTTP.new(uri.host, uri.port)
|
30
|
-
if uri.scheme == 'https'
|
31
|
-
http.use_ssl = true
|
32
|
-
end
|
38
|
+
http.use_ssl = true if uri.scheme == 'https'
|
33
39
|
|
34
40
|
data = self.data.to_json if self.data.is_a?(Hash) && self.data.respond_to?(:to_json)
|
35
41
|
http_result = http.request(http_request, data)
|
36
42
|
@output = http_result.body
|
37
|
-
|
43
|
+
case http_result
|
38
44
|
when Net::HTTPSuccess
|
39
|
-
true
|
45
|
+
@success = true
|
40
46
|
when Net::HTTPServiceUnavailable
|
41
|
-
raise Deploy::Errors::ServiceUnavailable
|
47
|
+
@success = raise Deploy::Errors::ServiceUnavailable
|
42
48
|
when Net::HTTPForbidden, Net::HTTPUnauthorized
|
43
|
-
raise Deploy::Errors::AccessDenied, "Access Denied for '#{Deploy.configuration.username}'"
|
49
|
+
@success = raise Deploy::Errors::AccessDenied, "Access Denied for '#{Deploy.configuration.username}'"
|
44
50
|
when Net::HTTPNotFound
|
45
|
-
raise Deploy::Errors::CommunicationError, "Not Found at #{uri
|
51
|
+
@success = raise Deploy::Errors::CommunicationError, "Not Found at #{uri}"
|
46
52
|
when Net::HTTPClientError
|
47
|
-
false
|
53
|
+
@success = false
|
48
54
|
else
|
49
|
-
raise Deploy::Errors::CommunicationError, http_result.body
|
55
|
+
@success = raise Deploy::Errors::CommunicationError, http_result.body
|
50
56
|
end
|
51
57
|
self
|
52
58
|
end
|
@@ -65,3 +71,6 @@ module Deploy
|
|
65
71
|
|
66
72
|
end
|
67
73
|
end
|
74
|
+
# rubocop:enable Metrics/AbcSize
|
75
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
76
|
+
# rubocop:enable Metrics/MethodLength
|
data/lib/deploy/resource.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Deploy
|
2
4
|
class Resource
|
3
5
|
|
@@ -9,14 +11,18 @@ module Deploy
|
|
9
11
|
def method_missing(method, *params)
|
10
12
|
set = method.to_s.include?('=')
|
11
13
|
key = method.to_s.sub('=', '')
|
12
|
-
self.attributes =
|
14
|
+
self.attributes = ({}) unless attributes.is_a?(Hash)
|
13
15
|
if set
|
14
|
-
|
16
|
+
attributes[key] = params.first
|
15
17
|
else
|
16
|
-
|
18
|
+
attributes[key]
|
17
19
|
end
|
18
20
|
end
|
19
21
|
|
22
|
+
def respond_to_missing?(method_name, include_private = false)
|
23
|
+
method_name.to_s.start_with?('user_') || super
|
24
|
+
end
|
25
|
+
|
20
26
|
class << self
|
21
27
|
|
22
28
|
## Find a record or set of records. Passing :all will return all records and passing an integer
|
@@ -30,11 +36,13 @@ module Deploy
|
|
30
36
|
|
31
37
|
## Find all objects and return an array of objects with the attributes set.
|
32
38
|
def find_all(params)
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
39
|
+
request = Request.new(collection_path(params))
|
40
|
+
output = request.make.output
|
41
|
+
output = JSON.parse(output)
|
42
|
+
|
43
|
+
output = output['records'] if output.is_a?(Hash) && output['records'] && output['pagination']
|
37
44
|
return [] unless output.is_a?(Array)
|
45
|
+
|
38
46
|
output.map do |o|
|
39
47
|
create_object(o, params)
|
40
48
|
end
|
@@ -42,12 +50,13 @@ module Deploy
|
|
42
50
|
|
43
51
|
## Find a single object and return an object for it.
|
44
52
|
def find_single(id, params = {})
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
53
|
+
request = Request.new(member_path(id, params))
|
54
|
+
output = request.make.output
|
55
|
+
output = JSON.parse(output)
|
56
|
+
|
57
|
+
raise Deploy::Errors::NotFound, 'Record not found' unless output.is_a?(Hash)
|
58
|
+
|
59
|
+
create_object(output, params)
|
51
60
|
end
|
52
61
|
|
53
62
|
## Post to the specified object on the collection path
|
@@ -58,18 +67,18 @@ module Deploy
|
|
58
67
|
## Return the collection path for this model. Very lazy pluralizion here
|
59
68
|
## at the moment, nothing in Deploy needs to be pluralized with anything
|
60
69
|
## other than just adding an 's'.
|
61
|
-
def collection_path(
|
62
|
-
class_name.downcase
|
70
|
+
def collection_path(_params = {})
|
71
|
+
"#{class_name.downcase}s"
|
63
72
|
end
|
64
73
|
|
65
74
|
## Return the member path for the passed ID & attributes
|
66
|
-
def member_path(id,
|
75
|
+
def member_path(id, _params = {})
|
67
76
|
[collection_path, id].join('/')
|
68
77
|
end
|
69
78
|
|
70
79
|
## Return the deploy class name
|
71
80
|
def class_name
|
72
|
-
|
81
|
+
name.to_s.split('::').last.downcase
|
73
82
|
end
|
74
83
|
|
75
84
|
private
|
@@ -77,20 +86,21 @@ module Deploy
|
|
77
86
|
## Create a new object with the specified attributes and getting and ID.
|
78
87
|
## Returns the newly created object
|
79
88
|
def create_object(attributes, objects = [])
|
80
|
-
o =
|
89
|
+
o = new
|
81
90
|
o.attributes = attributes
|
82
91
|
o.id = attributes['id']
|
83
|
-
|
92
|
+
objects.select { |_k, v| v.is_a?(Deploy::Resource) }.each do |key, object|
|
84
93
|
o.attributes[key.to_s] = object
|
85
94
|
end
|
86
95
|
o
|
87
96
|
end
|
97
|
+
|
88
98
|
end
|
89
99
|
|
90
100
|
## Run a post on the member path. Returns the ouput from the post, false if a conflict or raises
|
91
101
|
## a Deploy::Error. Optionally, pass a second 'data' parameter to send data to the post action.
|
92
102
|
def post(action, data = nil)
|
93
|
-
path = self.class.member_path(
|
103
|
+
path = "#{self.class.member_path(id, default_params)}/#{action}"
|
94
104
|
request = Request.new(path, :post)
|
95
105
|
request.data = data
|
96
106
|
request.make
|
@@ -99,20 +109,21 @@ module Deploy
|
|
99
109
|
## Delete this record from the remote service. Returns true or false depending on the success
|
100
110
|
## status of the destruction.
|
101
111
|
def destroy
|
102
|
-
Request.new(self.class.member_path(
|
112
|
+
Request.new(self.class.member_path(id, default_params), :delete).make.success?
|
103
113
|
end
|
104
114
|
|
105
115
|
def new_record?
|
106
|
-
|
116
|
+
id.nil?
|
107
117
|
end
|
108
118
|
|
109
119
|
def save
|
110
120
|
new_record? ? create : update
|
111
121
|
end
|
112
122
|
|
123
|
+
# rubocop:disable Metrics/AbcSize
|
113
124
|
def create
|
114
125
|
request = Request.new(self.class.collection_path(default_params), :post)
|
115
|
-
request.data = {self.class.class_name.downcase.to_sym => attributes_to_post}
|
126
|
+
request.data = { self.class.class_name.downcase.to_sym => attributes_to_post }
|
116
127
|
if request.make && request.success?
|
117
128
|
new_record = JSON.parse(request.output)
|
118
129
|
self.attributes = new_record
|
@@ -123,13 +134,14 @@ module Deploy
|
|
123
134
|
false
|
124
135
|
end
|
125
136
|
end
|
137
|
+
# rubocop:enable Metrics/AbcSize
|
126
138
|
|
127
139
|
## Push the updated attributes to the remote. Returns true if the record was saved successfully
|
128
140
|
## other false if not. If not saved successfully, the errors hash will be updated with an array
|
129
141
|
## of all errors with the submission.
|
130
142
|
def update
|
131
|
-
request = Request.new(self.class.member_path(
|
132
|
-
request.data = {self.class.class_name.downcase.to_sym => attributes_to_post}
|
143
|
+
request = Request.new(self.class.member_path(id, default_params), :put)
|
144
|
+
request.data = { self.class.class_name.downcase.to_sym => attributes_to_post }
|
133
145
|
if request.make && request.success?
|
134
146
|
true
|
135
147
|
else
|
@@ -142,23 +154,21 @@ module Deploy
|
|
142
154
|
|
143
155
|
## Populate the errors hash from the given raw JSON output
|
144
156
|
def populate_errors(json)
|
145
|
-
self.errors =
|
146
|
-
JSON.parse(json).
|
157
|
+
self.errors = ({})
|
158
|
+
JSON.parse(json).each_with_object(errors) do |e, r|
|
147
159
|
r[e.first] = e.last
|
148
|
-
r
|
149
160
|
end
|
150
161
|
end
|
151
162
|
|
152
163
|
## An array of params which should always be sent with this instances requests
|
153
164
|
def default_params
|
154
|
-
|
165
|
+
{}
|
155
166
|
end
|
156
167
|
|
157
168
|
## Attributes which can be passed for update & creation
|
158
169
|
def attributes_to_post
|
159
|
-
|
160
|
-
r[key] = value if value.is_a?(String) || value.is_a?(Integer)
|
161
|
-
r
|
170
|
+
attributes.each_with_object({}) do |(key, value), r|
|
171
|
+
r[key] = value if value.is_a?(String) || value.is_a?(Integer)
|
162
172
|
end
|
163
173
|
end
|
164
174
|
|
@@ -1,6 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Deploy
|
2
4
|
class Deployment < Resource
|
5
|
+
|
3
6
|
class << self
|
7
|
+
|
4
8
|
def collection_path(params = {})
|
5
9
|
"projects/#{params[:project].permalink}/deployments"
|
6
10
|
end
|
@@ -8,17 +12,16 @@ module Deploy
|
|
8
12
|
def member_path(id, params = {})
|
9
13
|
"projects/#{params[:project].permalink}/deployments/#{id}"
|
10
14
|
end
|
15
|
+
|
11
16
|
end
|
12
17
|
|
13
18
|
def default_params
|
14
|
-
{:project
|
19
|
+
{ project: project }
|
15
20
|
end
|
16
21
|
|
17
22
|
def project
|
18
|
-
|
19
|
-
|
20
|
-
end
|
21
|
-
self.attributes['project']
|
23
|
+
attributes['project'] = Project.send(:create_object, attributes['project']) if attributes['project'].is_a?(Hash)
|
24
|
+
attributes['project']
|
22
25
|
end
|
23
26
|
|
24
27
|
def servers
|
@@ -47,5 +50,6 @@ module Deploy
|
|
47
50
|
[]
|
48
51
|
end
|
49
52
|
end
|
53
|
+
|
50
54
|
end
|
51
55
|
end
|
@@ -1,12 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Deploy
|
2
4
|
class DeploymentStep < Resource
|
5
|
+
|
3
6
|
def default_params
|
4
|
-
{ deployment:
|
7
|
+
{ deployment: deployment, project: deployment.project }
|
5
8
|
end
|
6
9
|
|
7
10
|
def logs(params = {})
|
8
11
|
params = default_params.merge(step: self).merge(params)
|
9
12
|
DeploymentStepLog.find(:all, params)
|
10
13
|
end
|
14
|
+
|
11
15
|
end
|
12
16
|
end
|
@@ -1,7 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Deploy
|
2
4
|
class DeploymentStepLog < Resource
|
5
|
+
|
3
6
|
def self.collection_path(params = {})
|
4
|
-
|
7
|
+
permalink = params[:project].permalink
|
8
|
+
project_identifier = params[:project].identifier
|
9
|
+
step_identifier = params[:step].identifier
|
10
|
+
|
11
|
+
"projects/#{permalink}/deployments/#{project_identifier}/steps/#{step_identifier}/logs"
|
5
12
|
end
|
13
|
+
|
6
14
|
end
|
7
15
|
end
|
@@ -1,31 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Deploy
|
2
4
|
class Project < Resource
|
3
5
|
|
4
6
|
## Return all deployments for this project
|
5
7
|
def deployments
|
6
|
-
Deployment.find(:all, :
|
8
|
+
Deployment.find(:all, project: self)
|
7
9
|
end
|
8
10
|
|
9
11
|
## Return a deployment
|
10
12
|
def deployment(identifier)
|
11
|
-
Deployment.find(identifier, :
|
13
|
+
Deployment.find(identifier, project: self)
|
12
14
|
end
|
13
15
|
|
14
16
|
def latest_revision(branch = '')
|
15
17
|
branch ||= 'master'
|
16
|
-
req = Request.new(self.class.member_path(
|
18
|
+
req = Request.new(self.class.member_path(permalink) + "/repository/latest_revision?branch=#{branch}").make
|
17
19
|
parsed = JSON.parse(req.output)
|
18
20
|
parsed['ref']
|
19
21
|
end
|
20
22
|
|
21
|
-
|
23
|
+
# Create a deployment in this project (and queue it to run)
|
22
24
|
def deploy(server, start_revision, end_revision)
|
23
25
|
run_deployment(server, start_revision, end_revision) do |d|
|
24
26
|
d.mode = 'queue'
|
25
27
|
end
|
26
28
|
end
|
27
29
|
|
28
|
-
|
30
|
+
# Create a deployment preview
|
29
31
|
def preview(server, start_revision, end_revision)
|
30
32
|
run_deployment(server, start_revision, end_revision) do |d|
|
31
33
|
d.mode = 'preview'
|
@@ -34,11 +36,11 @@ module Deploy
|
|
34
36
|
|
35
37
|
## Return all servers for this project
|
36
38
|
def servers
|
37
|
-
Server.find(:all, :
|
39
|
+
Server.find(:all, project: self)
|
38
40
|
end
|
39
41
|
|
40
42
|
def server_groups
|
41
|
-
ServerGroup.find(:all, :
|
43
|
+
ServerGroup.find(:all, project: self)
|
42
44
|
end
|
43
45
|
|
44
46
|
private
|
@@ -1,28 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Deploy
|
2
4
|
class Server < Resource
|
3
5
|
|
4
6
|
class << self
|
7
|
+
|
5
8
|
def collection_path(params = {})
|
6
9
|
"projects/#{params[:project].permalink}/servers"
|
7
10
|
end
|
8
11
|
|
9
|
-
def member_path(
|
12
|
+
def member_path(_id, params = {})
|
10
13
|
"projects/#{params[:project].permalink}/servers/#{identifier}"
|
11
14
|
end
|
15
|
+
|
12
16
|
end
|
13
17
|
|
14
18
|
def default_params
|
15
|
-
{:project
|
19
|
+
{ project: project }
|
16
20
|
end
|
17
21
|
|
18
22
|
def to_s
|
19
|
-
|
20
|
-
a <<
|
21
|
-
a << "(branch: #{
|
22
|
-
if
|
23
|
-
a << "(currently: #{
|
23
|
+
[].tap do |a|
|
24
|
+
a << name
|
25
|
+
a << "(branch: #{preferred_branch})" if preferred_branch
|
26
|
+
if last_revision
|
27
|
+
a << "(currently: #{last_revision})"
|
24
28
|
else
|
25
|
-
a <<
|
29
|
+
a << '(currently undeployed)'
|
26
30
|
end
|
27
31
|
end.join(' ')
|
28
32
|
end
|
@@ -1,32 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Deploy
|
2
4
|
class ServerGroup < Resource
|
3
5
|
|
4
6
|
class << self
|
7
|
+
|
5
8
|
def collection_path(params = {})
|
6
9
|
"projects/#{params[:project].permalink}/server_groups"
|
7
10
|
end
|
8
11
|
|
9
|
-
def member_path(
|
12
|
+
def member_path(_id, params = {})
|
10
13
|
"projects/#{params[:project].permalink}/server_groups/#{identifier}"
|
11
14
|
end
|
15
|
+
|
12
16
|
end
|
13
17
|
|
14
18
|
def default_params
|
15
|
-
{:project
|
19
|
+
{ project: project }
|
16
20
|
end
|
17
21
|
|
18
22
|
def servers
|
19
|
-
@servers ||=
|
23
|
+
@servers ||= attributes['servers'].map { |server_attr| Deploy::Server.send(:create_object, server_attr) }
|
20
24
|
end
|
21
25
|
|
22
26
|
def to_s
|
23
|
-
|
24
|
-
a <<
|
25
|
-
a << "(branch: #{
|
26
|
-
if
|
27
|
-
a << "(currently: #{
|
27
|
+
[].tap do |a|
|
28
|
+
a << name
|
29
|
+
a << "(branch: #{preferred_branch})" if preferred_branch
|
30
|
+
if last_revision
|
31
|
+
a << "(currently: #{last_revision})"
|
28
32
|
else
|
29
|
-
a <<
|
33
|
+
a << '(currently undeployed)'
|
30
34
|
end
|
31
35
|
end.join(' ')
|
32
36
|
end
|
data/lib/deploy/version.rb
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Deploy
|
2
|
-
|
4
|
+
|
5
|
+
VERSION_FILE_ROOT = File.expand_path('../../VERSION', __dir__)
|
6
|
+
if File.file?(VERSION_FILE_ROOT)
|
7
|
+
VERSION = File.read(VERSION_FILE_ROOT).strip.sub(/\Av/, '')
|
8
|
+
else
|
9
|
+
VERSION = '0.0.0.dev'
|
10
|
+
end
|
11
|
+
|
3
12
|
end
|
data/lib/deploy.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rubygems'
|
2
4
|
require 'bundler'
|
3
5
|
|
@@ -25,7 +27,9 @@ require 'deploy/resources/server_group'
|
|
25
27
|
require 'deploy/version'
|
26
28
|
|
27
29
|
module Deploy
|
30
|
+
|
28
31
|
class << self
|
32
|
+
|
29
33
|
def configure
|
30
34
|
@configuration ||= Configuration.new
|
31
35
|
yield @configuration if block_given?
|
@@ -39,5 +43,7 @@ module Deploy
|
|
39
43
|
def configuration_file=(file_location)
|
40
44
|
@configuration = Configuration.from_file(file_location)
|
41
45
|
end
|
46
|
+
|
42
47
|
end
|
48
|
+
|
43
49
|
end
|
metadata
CHANGED
@@ -1,43 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: deployhq
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
7
|
+
- Adam Cooke
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-08-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: highline
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1
|
19
|
+
version: '2.1'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '1
|
26
|
+
version: '2.1'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: json
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '2.6'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '2.6'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: websocket-eventmachine-client
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,10 +52,10 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '1.2'
|
55
|
-
description:
|
56
|
-
|
57
|
-
|
58
|
-
|
55
|
+
description: API and CLI client for the DeployHQ deployment platform. Provides the
|
56
|
+
deployhq executable.
|
57
|
+
email:
|
58
|
+
- adam@k.io
|
59
59
|
executables:
|
60
60
|
- deployhq
|
61
61
|
extensions: []
|
@@ -77,7 +77,7 @@ files:
|
|
77
77
|
- lib/deploy/resources/server.rb
|
78
78
|
- lib/deploy/resources/server_group.rb
|
79
79
|
- lib/deploy/version.rb
|
80
|
-
homepage: https://
|
80
|
+
homepage: https://github.com/krystal/deployhq-lib
|
81
81
|
licenses:
|
82
82
|
- MIT
|
83
83
|
metadata: {}
|
@@ -89,15 +89,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
89
89
|
requirements:
|
90
90
|
- - ">="
|
91
91
|
- !ruby/object:Gem::Version
|
92
|
-
version: '
|
92
|
+
version: '2.7'
|
93
93
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
94
|
requirements:
|
95
95
|
- - ">="
|
96
96
|
- !ruby/object:Gem::Version
|
97
97
|
version: '0'
|
98
98
|
requirements: []
|
99
|
-
|
100
|
-
rubygems_version: 2.6.14.3
|
99
|
+
rubygems_version: 3.3.26
|
101
100
|
signing_key:
|
102
101
|
specification_version: 4
|
103
102
|
summary: API and CLI client for the DeployHQ
|