capistrano-data_plane_api 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rubocop.yml +5 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +128 -0
- data/LICENSE.txt +21 -0
- data/README.md +197 -0
- data/Rakefile +16 -0
- data/capistrano-data_plane_api.gemspec +41 -0
- data/exe/cap_data_plane_api +37 -0
- data/lib/capistrano/data_plane_api/configuration/backend.rb +18 -0
- data/lib/capistrano/data_plane_api/configuration/server.rb +22 -0
- data/lib/capistrano/data_plane_api/configuration/symbol.rb +16 -0
- data/lib/capistrano/data_plane_api/configuration.rb +33 -0
- data/lib/capistrano/data_plane_api/deploy/args.rb +241 -0
- data/lib/capistrano/data_plane_api/deploy/deployment_stats.rb +117 -0
- data/lib/capistrano/data_plane_api/deploy/group.rb +100 -0
- data/lib/capistrano/data_plane_api/deploy/helper.rb +51 -0
- data/lib/capistrano/data_plane_api/deploy/server_stats.rb +110 -0
- data/lib/capistrano/data_plane_api/deploy.rb +27 -0
- data/lib/capistrano/data_plane_api/diggable.rb +31 -0
- data/lib/capistrano/data_plane_api/equatable.rb +32 -0
- data/lib/capistrano/data_plane_api/helper.rb +56 -0
- data/lib/capistrano/data_plane_api/hooks.rb +7 -0
- data/lib/capistrano/data_plane_api/show_state.rb +86 -0
- data/lib/capistrano/data_plane_api/tasks.rb +30 -0
- data/lib/capistrano/data_plane_api/terminal_print_loop.rb +43 -0
- data/lib/capistrano/data_plane_api/type.rb +29 -0
- data/lib/capistrano/data_plane_api/version.rb +8 -0
- data/lib/capistrano/data_plane_api.rb +296 -0
- data/readme/failed_deployment_summary.png +0 -0
- data/readme/haproxy_state.png +0 -0
- data/sig/capistrano/data_plane_api.rbs +6 -0
- data/templates/bin/deploy +5 -0
- data/templates/bin/deploy.rb +6 -0
- data/templates/config/data_plane_api.rb +6 -0
- data/templates/config/data_plane_api.yml +51 -0
- metadata +177 -0
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module DataPlaneApi
|
5
|
+
# Provides helper methods
|
6
|
+
module Helper
|
7
|
+
# @return [Hash{String => Symbol}]
|
8
|
+
ADMIN_STATE_COLORS = {
|
9
|
+
'drain' => :on_blue,
|
10
|
+
'ready' => :on_green,
|
11
|
+
'maint' => :on_yellow
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
# @return [Hash{String => Symbol}]
|
15
|
+
OPERATIONAL_STATE_COLORS = {
|
16
|
+
'up' => :on_green,
|
17
|
+
'down' => :on_red,
|
18
|
+
'stopping' => :on_yellow
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
# @return [Boolean]
|
22
|
+
def no_haproxy?
|
23
|
+
!::ENV['NO_HAPROXY'].nil? && !::ENV['NO_HAPROXY'].empty?
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [Boolean]
|
27
|
+
def force_haproxy?
|
28
|
+
!::ENV['FORCE_HAPROXY'].nil? && !::ENV['FORCE_HAPROXY'].empty?
|
29
|
+
end
|
30
|
+
|
31
|
+
# @param state [String, Symbol, nil]
|
32
|
+
# @return [String, nil]
|
33
|
+
def humanize_admin_state(state)
|
34
|
+
return unless state
|
35
|
+
|
36
|
+
state = state.to_s
|
37
|
+
COLORS.decorate(" #{state.upcase} ", :bold, ADMIN_STATE_COLORS[state.downcase])
|
38
|
+
end
|
39
|
+
|
40
|
+
# @param state [String, Symbol, nil]
|
41
|
+
# @return [String, nil]
|
42
|
+
def humanize_operational_state(state)
|
43
|
+
return unless state
|
44
|
+
|
45
|
+
state = state.to_s
|
46
|
+
COLORS.decorate(" #{state.upcase} ", :bold, OPERATIONAL_STATE_COLORS[state.downcase])
|
47
|
+
end
|
48
|
+
|
49
|
+
# @param backend [Capistrano::DataPlaneApi::Configuration::Backend]
|
50
|
+
# @return [String]
|
51
|
+
def humanize_backend_name(backend)
|
52
|
+
COLORS.decorate(" #{backend.name} ", *backend.styles)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pastel'
|
4
|
+
require 'tty/box'
|
5
|
+
require 'data_plane_api'
|
6
|
+
|
7
|
+
module Capistrano
|
8
|
+
module DataPlaneApi
|
9
|
+
# Creates a human readable summary of the state of
|
10
|
+
# HAProxy backends and servers to stdout.
|
11
|
+
module ShowState
|
12
|
+
class << self
|
13
|
+
# @return [String]
|
14
|
+
def call # rubocop:disable Metrics/MethodLength
|
15
|
+
pastel = ::Pastel.new
|
16
|
+
result = ::String.new
|
17
|
+
|
18
|
+
result << pastel.blue.bold('HAProxy State')
|
19
|
+
result << "\n"
|
20
|
+
result << pastel.bold.yellow(::Time.now.to_s)
|
21
|
+
result << "\n\n"
|
22
|
+
config = ::DataPlaneApi::Configuration.new.tap do |c|
|
23
|
+
c.logger = ::Logger.new($stdout)
|
24
|
+
c.logger.level = ::Logger::FATAL
|
25
|
+
c.timeout = 2
|
26
|
+
end
|
27
|
+
|
28
|
+
::Capistrano::DataPlaneApi.configuration.backends.each do |backend|
|
29
|
+
result << ::TTY::Box.frame(title: { top_left: backend_name(backend) }) do
|
30
|
+
b = ::String.new
|
31
|
+
servers = ::Capistrano::DataPlaneApi.get_backend_servers_settings(backend.name, config: config).body
|
32
|
+
servers.each do |server|
|
33
|
+
operational_state = operational_state(server)
|
34
|
+
admin_state = admin_state(server)
|
35
|
+
b << ::TTY::Box.frame(title: { top_left: server_name(server) }, border: :thick) do
|
36
|
+
s = ::String.new
|
37
|
+
s << " admin_state: #{admin_state}\n"
|
38
|
+
s << "operational_state: #{operational_state}\n"
|
39
|
+
s
|
40
|
+
end
|
41
|
+
end
|
42
|
+
b
|
43
|
+
|
44
|
+
rescue Error, ::Faraday::ConnectionFailed, ::Faraday::TimeoutError
|
45
|
+
b << pastel.bold.bright_red('Unavailable!')
|
46
|
+
b << "\n"
|
47
|
+
b
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
result
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# @param server [Hash{String => Object}]
|
57
|
+
# @return [String]
|
58
|
+
def operational_state(server)
|
59
|
+
::Capistrano::DataPlaneApi.humanize_operational_state(server['operational_state'])
|
60
|
+
end
|
61
|
+
|
62
|
+
# @param server [Hash{String => Object}]
|
63
|
+
# @return [String]
|
64
|
+
def admin_state(server)
|
65
|
+
::Capistrano::DataPlaneApi.humanize_admin_state(server['admin_state'])
|
66
|
+
end
|
67
|
+
|
68
|
+
# @param server [Hash{String => Object}]
|
69
|
+
# @return [String]
|
70
|
+
def server_name(server)
|
71
|
+
pastel = ::Pastel.new
|
72
|
+
pastel.bold server['name']
|
73
|
+
end
|
74
|
+
|
75
|
+
# @param backend [Capistrano::DataPlaneApi::Configuration::Backend]
|
76
|
+
# @return [String]
|
77
|
+
def backend_name(backend)
|
78
|
+
pastel = ::Pastel.new
|
79
|
+
pastel.decorate(" #{backend.name} ", *backend.styles)
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
|
5
|
+
namespace :data_plane_api do
|
6
|
+
namespace :server do
|
7
|
+
desc "Set the server's admin state to DRAIN through the HAProxy Data Plane API"
|
8
|
+
task :set_drain do
|
9
|
+
next if no_haproxy?
|
10
|
+
|
11
|
+
::Capistrano::DataPlaneApi.server_set_drain fetch(:stage), force: force_haproxy?
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "Set the server's admin state to READY through the HAProxy Data Plane API"
|
15
|
+
task :set_ready do
|
16
|
+
next if no_haproxy?
|
17
|
+
|
18
|
+
sleep 3
|
19
|
+
::Capistrano::DataPlaneApi.server_set_ready fetch(:stage)
|
20
|
+
end
|
21
|
+
|
22
|
+
desc "Set the server's admin state to MAINT through the HAProxy Data Plane API"
|
23
|
+
task :set_maint do
|
24
|
+
next if no_haproxy?
|
25
|
+
|
26
|
+
sleep 3
|
27
|
+
::Capistrano::DataPlaneApi.server_set_maint fetch(:stage), force: true
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'tty/cursor'
|
4
|
+
|
5
|
+
module Capistrano
|
6
|
+
module DataPlaneApi
|
7
|
+
module TerminalPrintLoop
|
8
|
+
class << self
|
9
|
+
# Calls the passed block in an endless loop with a given interval
|
10
|
+
# between calls.
|
11
|
+
# It prints the `String` returned from the block and clears it
|
12
|
+
# before another frame is printed.
|
13
|
+
#
|
14
|
+
# @yieldparam content [String]
|
15
|
+
# @param interval [Integer] Number of seconds between each screen refresh
|
16
|
+
def call(interval: 2)
|
17
|
+
previous_line_count = 0
|
18
|
+
previous_max_line_length = 0
|
19
|
+
loop do
|
20
|
+
content = ::String.new
|
21
|
+
yielded = yield(content)
|
22
|
+
|
23
|
+
print ::TTY::Cursor.clear_lines(previous_line_count + 1)
|
24
|
+
|
25
|
+
content = yielded if yielded.is_a?(::String) && content.length.zero?
|
26
|
+
line_count = 0
|
27
|
+
max_line_length = 0
|
28
|
+
content.each_line do |line|
|
29
|
+
line_count += 1
|
30
|
+
max_line_length = line.length if line.length > max_line_length
|
31
|
+
end
|
32
|
+
previous_line_count = line_count
|
33
|
+
previous_max_line_length = max_line_length
|
34
|
+
|
35
|
+
puts(content)
|
36
|
+
sleep(interval)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'shale'
|
4
|
+
|
5
|
+
require_relative 'diggable'
|
6
|
+
require_relative 'equatable'
|
7
|
+
|
8
|
+
module Capistrano
|
9
|
+
module DataPlaneApi
|
10
|
+
class Type < ::Shale::Mapper
|
11
|
+
include Diggable
|
12
|
+
include Equatable
|
13
|
+
|
14
|
+
alias to_h to_hash
|
15
|
+
|
16
|
+
# @param key [Symbol, String]
|
17
|
+
# @return [Object, nil]
|
18
|
+
def [](key)
|
19
|
+
public_send(key) if respond_to?(key)
|
20
|
+
end
|
21
|
+
|
22
|
+
# @param key [Symbol, String]
|
23
|
+
# @param val [Object]
|
24
|
+
def []=(key, val)
|
25
|
+
public_send(:"#{key}=", val)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,296 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'data_plane_api'
|
4
|
+
require 'pastel'
|
5
|
+
require 'pathname'
|
6
|
+
require 'json'
|
7
|
+
require 'logger'
|
8
|
+
|
9
|
+
require_relative 'data_plane_api/version'
|
10
|
+
require_relative 'data_plane_api/helper'
|
11
|
+
require_relative 'data_plane_api/terminal_print_loop'
|
12
|
+
require_relative 'data_plane_api/configuration'
|
13
|
+
require_relative 'data_plane_api/show_state'
|
14
|
+
|
15
|
+
module Capistrano
|
16
|
+
# Main module/namespace of the `capistrano-data_plane_api` gem.
|
17
|
+
module DataPlaneApi
|
18
|
+
extend Helper
|
19
|
+
|
20
|
+
class Error < ::StandardError; end
|
21
|
+
class NotConfiguredError < Error; end
|
22
|
+
class QueryError < Error; end
|
23
|
+
class NoOtherServerReadyError < Error; end
|
24
|
+
class UpdateServerStateError < Error; end
|
25
|
+
class NoSuchBackendError < Error; end
|
26
|
+
class NoBackendForThisStageError < Error; end
|
27
|
+
|
28
|
+
# @return [Pastel::Delegator]
|
29
|
+
COLORS = ::Pastel.new
|
30
|
+
|
31
|
+
class << self
|
32
|
+
# @return [Capistrano::DataPlaneApi::Configuration]
|
33
|
+
def configuration
|
34
|
+
raise NotConfiguredError, <<~ERR unless @configuration
|
35
|
+
`Capistrano::DataPlaneApi` is not configured!
|
36
|
+
You should register a configuration file like so:
|
37
|
+
Capistrano::DataPlaneApi.configuration = '/path/to/your/file.yaml'
|
38
|
+
ERR
|
39
|
+
|
40
|
+
@configuration
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param val [Capistrano::DataPlaneApi::Configuration, Hash{String, Symbol => Object}, String, Pathname]
|
44
|
+
def configuration=(val)
|
45
|
+
case val
|
46
|
+
when ::Hash
|
47
|
+
# as of now `shale` does not support
|
48
|
+
# symbol keys in hashes, so
|
49
|
+
# we convert it to JSON an back
|
50
|
+
# to a Hash to convert all Symbols
|
51
|
+
# to Strings
|
52
|
+
@configuration = Configuration.from_json(val.to_json)
|
53
|
+
when Configuration
|
54
|
+
@configuration = val
|
55
|
+
when ::String, ::Pathname
|
56
|
+
@configuration = Configuration.from_file(val.to_s)
|
57
|
+
@configuration.file_path = val
|
58
|
+
else
|
59
|
+
raise ::ArgumentError,
|
60
|
+
"Configuration should be a `#{::Hash}`, `#{Configuration}`, #{::String} or #{::Pathname}" \
|
61
|
+
", received: #{val.inspect} (#{val.class.inspect})"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Prints the current configuration in a human readable format.
|
66
|
+
#
|
67
|
+
# @return [void]
|
68
|
+
def show_config
|
69
|
+
puts ::JSON.pretty_generate(configuration.to_h)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Prints the current state of all backends and
|
73
|
+
# their servers in a human readable format.
|
74
|
+
#
|
75
|
+
# @return [void]
|
76
|
+
def show_state
|
77
|
+
TerminalPrintLoop.call do
|
78
|
+
ShowState.call
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Set server's admin_state to `drain`.
|
83
|
+
#
|
84
|
+
# @param deployment_stage [Symbol]
|
85
|
+
# @param force [Boolean] Change the server's state even when no other server is `up`
|
86
|
+
# @param config [::DataPlaneApi::Configuration, nil]
|
87
|
+
# @return [Hash, FalseClass] Server state after the change, or `false`
|
88
|
+
# when no change happened
|
89
|
+
# @raise [Error] The process failed due to some reason
|
90
|
+
def server_set_drain(deployment_stage, force: false, config: nil)
|
91
|
+
haproxy_server, haproxy_backend = find_server_and_backend(deployment_stage)
|
92
|
+
return false if haproxy_backend.servers.length < 2 # skip HAProxy if there is only a single server
|
93
|
+
|
94
|
+
validate_backend_state(haproxy_backend, haproxy_server) unless force
|
95
|
+
|
96
|
+
conf = ::DataPlaneApi::Configuration.new(
|
97
|
+
basic_user: haproxy_backend.basic_user || @configuration.basic_user,
|
98
|
+
basic_password: haproxy_backend.basic_password || @configuration.basic_password,
|
99
|
+
parent: config,
|
100
|
+
url: configuration.api_url
|
101
|
+
)
|
102
|
+
|
103
|
+
# set the target server's state to `drain`
|
104
|
+
response =
|
105
|
+
::DataPlaneApi::Server.update_transient_settings(
|
106
|
+
backend: haproxy_backend.name,
|
107
|
+
name: haproxy_server.name,
|
108
|
+
settings: { admin_state: :drain },
|
109
|
+
config: conf
|
110
|
+
)
|
111
|
+
|
112
|
+
unless response.status.between?(200, 299) && response.body['admin_state'] == 'drain'
|
113
|
+
raise UpdateServerStateError,
|
114
|
+
"HAProxy mutation failed! Couldn't set server's `admin_state` to `drain`."
|
115
|
+
end
|
116
|
+
|
117
|
+
response.body
|
118
|
+
end
|
119
|
+
|
120
|
+
# Set server's admin_state to `maint`.
|
121
|
+
#
|
122
|
+
# @param deployment_stage [Symbol]
|
123
|
+
# @param force [Boolean] Change the server's state even when no other server is `up`
|
124
|
+
# @param config [::DataPlaneApi::Configuration, nil]
|
125
|
+
# @return [Hash, FalseClass] Server state after the change, or `false`
|
126
|
+
# when no change happened
|
127
|
+
# @raise [Error] The process failed due to some reason
|
128
|
+
def server_set_maint(deployment_stage, force: false, config: nil)
|
129
|
+
haproxy_server, haproxy_backend = find_server_and_backend(deployment_stage)
|
130
|
+
return false if haproxy_backend.servers.length < 2 # skip HAProxy if there is only a single server
|
131
|
+
|
132
|
+
validate_backend_state(haproxy_backend, haproxy_server) unless force
|
133
|
+
|
134
|
+
conf = ::DataPlaneApi::Configuration.new(
|
135
|
+
basic_user: haproxy_backend.basic_user || @configuration.basic_user,
|
136
|
+
basic_password: haproxy_backend.basic_password || @configuration.basic_password,
|
137
|
+
parent: config,
|
138
|
+
url: configuration.api_url
|
139
|
+
)
|
140
|
+
|
141
|
+
# set the target server's state to `maint`
|
142
|
+
response =
|
143
|
+
::DataPlaneApi::Server.update_transient_settings(
|
144
|
+
backend: haproxy_backend.name,
|
145
|
+
name: haproxy_server.name,
|
146
|
+
settings: { admin_state: :maint },
|
147
|
+
config: conf
|
148
|
+
)
|
149
|
+
|
150
|
+
unless response.status.between?(200, 299) && response.body['admin_state'] == 'maint'
|
151
|
+
raise UpdateServerStateError,
|
152
|
+
"HAProxy mutation failed! Couldn't set server's `admin_state` to `drain`."
|
153
|
+
end
|
154
|
+
|
155
|
+
response.body
|
156
|
+
end
|
157
|
+
|
158
|
+
# Set server's admin_state to `ready`
|
159
|
+
#
|
160
|
+
# @param deployment_stage [Symbol]
|
161
|
+
# @param config [::DataPlaneApi::Configuration, nil]
|
162
|
+
# @return [Hash, FalseClass] Server state after the change, or `false`
|
163
|
+
# when no change happened
|
164
|
+
# @raise [Error] The process failed due to some reason
|
165
|
+
def server_set_ready(deployment_stage, config: nil)
|
166
|
+
haproxy_server, haproxy_backend = find_server_and_backend(deployment_stage)
|
167
|
+
return false if haproxy_backend.servers.length < 2 # skip HAProxy if there is only a single server
|
168
|
+
|
169
|
+
conf = ::DataPlaneApi::Configuration.new(
|
170
|
+
basic_user: haproxy_backend.basic_user || @configuration.basic_user,
|
171
|
+
basic_password: haproxy_backend.basic_password || @configuration.basic_password,
|
172
|
+
parent: config,
|
173
|
+
url: configuration.api_url
|
174
|
+
)
|
175
|
+
|
176
|
+
# set the target server's state to `drain`
|
177
|
+
response =
|
178
|
+
::DataPlaneApi::Server.update_transient_settings(
|
179
|
+
backend: haproxy_backend.name,
|
180
|
+
name: haproxy_server.name,
|
181
|
+
settings: { admin_state: :ready },
|
182
|
+
config: conf
|
183
|
+
)
|
184
|
+
|
185
|
+
unless response.status.between?(200, 299) &&
|
186
|
+
response.body['admin_state'] == 'ready' &&
|
187
|
+
response.body['operational_state'] == 'up'
|
188
|
+
|
189
|
+
raise UpdateServerStateError,
|
190
|
+
"HAProxy mutation failed! Couldn't set server's `admin_state` to `ready`."
|
191
|
+
end
|
192
|
+
|
193
|
+
response.body
|
194
|
+
end
|
195
|
+
|
196
|
+
# Find the HAProxy backend config with a particular name.
|
197
|
+
#
|
198
|
+
# @param backend_name [String]
|
199
|
+
# @return [Capistrano::DataPlaneApi::Configuration::Backend] HAProxy backend config.
|
200
|
+
# @raise [NoSuchBackendError] There is no backend with this name.
|
201
|
+
def find_backend(backend_name)
|
202
|
+
backend = configuration.backends.find { _1.name == backend_name }
|
203
|
+
if backend.nil?
|
204
|
+
raise NoSuchBackendError,
|
205
|
+
'There is no HAProxy backend with this name! ' \
|
206
|
+
"`#{backend_name.inspect}`"
|
207
|
+
end
|
208
|
+
|
209
|
+
backend
|
210
|
+
end
|
211
|
+
|
212
|
+
# Find the server and backend config for a particular
|
213
|
+
# deployment stage.
|
214
|
+
#
|
215
|
+
# @param deployment_stage [Symbol, String]
|
216
|
+
# @return [Capistrano::DataPlaneApi::Configuration::Server, Capistrano::DataPlaneApi::Configuration::Backend]
|
217
|
+
# Two-element Array
|
218
|
+
# where the first element is the HAProxy server config and the second one
|
219
|
+
# is the HAProxy backend config
|
220
|
+
def find_server_and_backend(deployment_stage)
|
221
|
+
# @type [Capistrano::DataPlaneApi::Configuration::Server, nil]
|
222
|
+
haproxy_server = nil
|
223
|
+
deployment_stage_str = deployment_stage.to_s
|
224
|
+
# find the HAProxy backend that the
|
225
|
+
# current deployment target is a part of
|
226
|
+
# @type [Capistrano::DataPlaneApi::Configuration::Backend]
|
227
|
+
haproxy_backend =
|
228
|
+
configuration.backends.each do |backend|
|
229
|
+
haproxy_server = backend.servers.find { _1.stage == deployment_stage_str }
|
230
|
+
break backend if haproxy_server
|
231
|
+
end
|
232
|
+
|
233
|
+
unless haproxy_backend.is_a?(Configuration::Backend)
|
234
|
+
raise NoBackendForThisStageError,
|
235
|
+
'There are no HAProxy backends for this deployment stage! ' \
|
236
|
+
"#{deployment_stage.inspect} `#{configuration.file_path.inspect}`"
|
237
|
+
end
|
238
|
+
|
239
|
+
[haproxy_server, haproxy_backend]
|
240
|
+
end
|
241
|
+
|
242
|
+
# @param backend_name [String]
|
243
|
+
# @param config [::DataPlaneApi::Configuration, nil]
|
244
|
+
# @return [Faraday::Response]
|
245
|
+
def get_backend_servers_settings(backend_name, config: nil)
|
246
|
+
haproxy_backend = find_backend(backend_name)
|
247
|
+
conf = ::DataPlaneApi::Configuration.new(
|
248
|
+
basic_user: haproxy_backend.basic_user || @configuration.basic_user,
|
249
|
+
basic_password: haproxy_backend.basic_password || @configuration.basic_password,
|
250
|
+
parent: config,
|
251
|
+
url: configuration.api_url
|
252
|
+
)
|
253
|
+
response = ::DataPlaneApi::Server.get_runtime_settings(backend: backend_name, config: conf)
|
254
|
+
unless response.status.between?(200, 299)
|
255
|
+
raise QueryError,
|
256
|
+
"HAProxy query failed! Couldn't fetch servers' states"
|
257
|
+
end
|
258
|
+
|
259
|
+
response
|
260
|
+
end
|
261
|
+
|
262
|
+
private
|
263
|
+
|
264
|
+
# @param haproxy_backend [Capistrano::DataPlaneApi::Configuration::Backend]
|
265
|
+
# @param haproxy_server [Capistrano::DataPlaneApi::Configuration::Server]
|
266
|
+
# @return [void]
|
267
|
+
def validate_backend_state(haproxy_backend, haproxy_server)
|
268
|
+
response = get_backend_servers_settings(haproxy_backend.name)
|
269
|
+
unless haproxy_backend.servers.length == response.body.length
|
270
|
+
raise QueryError,
|
271
|
+
'HAProxy query failed! Configured servers for this backend' \
|
272
|
+
"don't match the configuration file! `#{configuration.file_path}`"
|
273
|
+
end
|
274
|
+
|
275
|
+
# @type [Array<Hash>]
|
276
|
+
server_statuses = response.body
|
277
|
+
# check if there are any servers other than this one that are `ready` and `up`
|
278
|
+
other_servers_ready = server_statuses.any? do |server_status|
|
279
|
+
server_status['admin_state'] == 'ready' &&
|
280
|
+
server_status['operational_state'] == 'up' &&
|
281
|
+
server_status['name'] != haproxy_server.name
|
282
|
+
end
|
283
|
+
|
284
|
+
unless other_servers_ready # rubocop:disable Style/GuardClause
|
285
|
+
raise NoOtherServerReadyError,
|
286
|
+
'No other server is `ready`' \
|
287
|
+
"in this backend `#{haproxy_backend.name}`"
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
end
|
292
|
+
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
require_relative 'data_plane_api/tasks' if defined?(::Rake)
|
Binary file
|
Binary file
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# We recommend to keep your credentials outside of Git.
|
2
|
+
# You can achieve that by reading ENV variables using ERB in this file,
|
3
|
+
# or add this file to .gitignore and provide an example file with fake data
|
4
|
+
# called `config/data_plane_api.yml.example` which will serve as
|
5
|
+
# an example of how it should be configured.
|
6
|
+
|
7
|
+
# Root URL of the HAProxy Data Plane API
|
8
|
+
api_url: http://example.com/haproxy-data-plane-api
|
9
|
+
logger_level: <%= Logger::DEBUG %>
|
10
|
+
# Default Data Plane API BasicAuth credentials
|
11
|
+
basic_user: my_user # Data Plane API BasicAuth username
|
12
|
+
basic_password: my_password # Data Plane API BasicAuth password
|
13
|
+
|
14
|
+
# List of HAProxy backends
|
15
|
+
backends:
|
16
|
+
- name: back_production # your HAProxy backend name
|
17
|
+
# styles defined in the `Pastel` gem, used in the terminal to uniquely identify this backend
|
18
|
+
# by coloring its name.
|
19
|
+
# Read more: https://github.com/piotrmurach/pastel#3-supported-colors
|
20
|
+
styles:
|
21
|
+
- :bold
|
22
|
+
- :on_red
|
23
|
+
# List of servers under this backend
|
24
|
+
servers:
|
25
|
+
- name: production1 # HAProxy server name and Capistrano stage name (config/deploy/production1.rb)
|
26
|
+
- name: production2
|
27
|
+
# when the HAProxy server name is different from the Capistrano stage name.
|
28
|
+
- name: production3_haproxy_server # HAProxy server name
|
29
|
+
stage: production3_capistrano_stage # Capistrano stage name (config/deploy/production3_capistrano_stage.rb)
|
30
|
+
|
31
|
+
- name: back_staging
|
32
|
+
styles:
|
33
|
+
- :bold
|
34
|
+
- :on_blue
|
35
|
+
# This backend has its own credentials
|
36
|
+
basic_user: some_other_user # Data Plane API BasicAuth user for this password
|
37
|
+
basic_password: some_other_password # Data Plane API BasicAuth password for this backend
|
38
|
+
servers:
|
39
|
+
- name: staging1
|
40
|
+
- name: staging2
|
41
|
+
|
42
|
+
- name: back_edge
|
43
|
+
styles:
|
44
|
+
- :bold
|
45
|
+
- :on_green
|
46
|
+
# You can use ERB to read data from Environment Variables.
|
47
|
+
basic_user: <%= ENV['USER'] %>
|
48
|
+
basic_password: <%= ENV['PASS'] %>
|
49
|
+
servers:
|
50
|
+
- name: edge1
|
51
|
+
stage: edge2
|