capistrano-data_plane_api 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.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
|