kdeploy 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/.editorconfig +12 -0
- data/.rspec +3 -0
- data/.rubocop.yml +100 -0
- data/LICENSE +21 -0
- data/README.md +1030 -0
- data/Rakefile +45 -0
- data/bin/kdeploy +7 -0
- data/kdeploy.gemspec +49 -0
- data/lib/kdeploy/banner.rb +28 -0
- data/lib/kdeploy/cli.rb +1452 -0
- data/lib/kdeploy/command.rb +182 -0
- data/lib/kdeploy/configuration.rb +83 -0
- data/lib/kdeploy/dsl.rb +566 -0
- data/lib/kdeploy/host.rb +85 -0
- data/lib/kdeploy/inventory.rb +243 -0
- data/lib/kdeploy/logger.rb +100 -0
- data/lib/kdeploy/pipeline.rb +249 -0
- data/lib/kdeploy/runner.rb +190 -0
- data/lib/kdeploy/ssh_connection.rb +187 -0
- data/lib/kdeploy/statistics.rb +439 -0
- data/lib/kdeploy/task.rb +240 -0
- data/lib/kdeploy/template.rb +173 -0
- data/lib/kdeploy/version.rb +6 -0
- data/lib/kdeploy.rb +106 -0
- data/scripts/common_tasks.rb +218 -0
- data/scripts/deploy.rb +50 -0
- metadata +178 -0
@@ -0,0 +1,243 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kdeploy
|
4
|
+
# Inventory class for managing host inventory and configuration
|
5
|
+
class Inventory
|
6
|
+
attr_reader :hosts, :groups, :vars
|
7
|
+
|
8
|
+
def initialize(inventory_file = nil)
|
9
|
+
@hosts = {}
|
10
|
+
@groups = {}
|
11
|
+
@vars = {}
|
12
|
+
load_from_file(inventory_file) if inventory_file && File.exist?(inventory_file)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Load inventory from YAML file
|
16
|
+
# @param inventory_file [String] Path to inventory file
|
17
|
+
# @raise [ConfigurationError] If inventory file is invalid
|
18
|
+
def load_from_file(inventory_file)
|
19
|
+
inventory_data = YAML.load_file(inventory_file)
|
20
|
+
parse_inventory(inventory_data)
|
21
|
+
rescue Psych::SyntaxError => e
|
22
|
+
raise ConfigurationError, "Invalid YAML syntax in inventory file: #{e.message}"
|
23
|
+
rescue StandardError => e
|
24
|
+
raise ConfigurationError, "Failed to load inventory file: #{e.message}"
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get all hosts in a group
|
28
|
+
# @param group_name [String, Symbol] Group name
|
29
|
+
# @return [Array<Host>] Hosts in the group
|
30
|
+
def hosts_in_group(group_name)
|
31
|
+
group_name = group_name.to_s
|
32
|
+
return [] unless @groups[group_name]
|
33
|
+
|
34
|
+
@groups[group_name][:hosts].map { |hostname| @hosts[hostname] }.compact
|
35
|
+
end
|
36
|
+
|
37
|
+
# Get all hosts with specific role
|
38
|
+
# @param role [String, Symbol] Role name
|
39
|
+
# @return [Array<Host>] Hosts with the role
|
40
|
+
def hosts_with_role(role)
|
41
|
+
@hosts.values.select { |host| host.has_role?(role) }
|
42
|
+
end
|
43
|
+
|
44
|
+
# Get all hosts
|
45
|
+
# @return [Array<Host>] All hosts
|
46
|
+
def all_hosts
|
47
|
+
@hosts.values
|
48
|
+
end
|
49
|
+
|
50
|
+
# Get host by hostname
|
51
|
+
# @param hostname [String] Hostname
|
52
|
+
# @return [Host, nil] Host object or nil
|
53
|
+
def host(hostname)
|
54
|
+
@hosts[hostname]
|
55
|
+
end
|
56
|
+
|
57
|
+
# Get group variable
|
58
|
+
# @param group_name [String, Symbol] Group name
|
59
|
+
# @param var_name [String, Symbol] Variable name
|
60
|
+
# @return [Object] Variable value
|
61
|
+
def group_var(group_name, var_name)
|
62
|
+
group_name = group_name.to_s
|
63
|
+
return nil unless @groups[group_name]
|
64
|
+
|
65
|
+
@groups[group_name][:vars][var_name.to_s] || @groups[group_name][:vars][var_name.to_sym]
|
66
|
+
end
|
67
|
+
|
68
|
+
# Get global variable
|
69
|
+
# @param var_name [String, Symbol] Variable name
|
70
|
+
# @return [Object] Variable value
|
71
|
+
def global_var(var_name)
|
72
|
+
@vars[var_name.to_s] || @vars[var_name.to_sym]
|
73
|
+
end
|
74
|
+
|
75
|
+
# Export inventory summary
|
76
|
+
# @return [Hash] Inventory summary
|
77
|
+
def summary
|
78
|
+
{
|
79
|
+
total_hosts: @hosts.size,
|
80
|
+
total_groups: @groups.size,
|
81
|
+
hosts: @hosts.keys,
|
82
|
+
groups: @groups.keys
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def parse_inventory(inventory_data)
|
89
|
+
return unless inventory_data.is_a?(Hash)
|
90
|
+
|
91
|
+
@vars = extract_vars(inventory_data)
|
92
|
+
parse_groups(inventory_data)
|
93
|
+
parse_hosts(inventory_data)
|
94
|
+
apply_group_variables
|
95
|
+
end
|
96
|
+
|
97
|
+
def extract_vars(data)
|
98
|
+
data['vars'] || data[:vars] || {}
|
99
|
+
end
|
100
|
+
|
101
|
+
def parse_groups(inventory_data)
|
102
|
+
groups_data = inventory_data['groups'] || inventory_data[:groups] || {}
|
103
|
+
|
104
|
+
groups_data.each do |group_name, group_config|
|
105
|
+
process_group(group_name.to_s, group_config || {})
|
106
|
+
end
|
107
|
+
|
108
|
+
resolve_group_children
|
109
|
+
end
|
110
|
+
|
111
|
+
def process_group(group_name, group_config)
|
112
|
+
@groups[group_name] = {
|
113
|
+
hosts: extract_group_hosts(group_config),
|
114
|
+
vars: extract_group_vars(group_config),
|
115
|
+
children: extract_group_children(group_config)
|
116
|
+
}
|
117
|
+
end
|
118
|
+
|
119
|
+
def extract_group_hosts(config)
|
120
|
+
Array(config['hosts'] || config[:hosts] || [])
|
121
|
+
end
|
122
|
+
|
123
|
+
def extract_group_vars(config)
|
124
|
+
config['vars'] || config[:vars] || {}
|
125
|
+
end
|
126
|
+
|
127
|
+
def extract_group_children(config)
|
128
|
+
Array(config['children'] || config[:children] || [])
|
129
|
+
end
|
130
|
+
|
131
|
+
def parse_hosts(inventory_data)
|
132
|
+
hosts_data = inventory_data['hosts'] || inventory_data[:hosts] || {}
|
133
|
+
|
134
|
+
hosts_data.each do |hostname, host_config|
|
135
|
+
process_host(hostname, host_config || {})
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def process_host(hostname, host_config)
|
140
|
+
host_groups = find_host_groups(hostname)
|
141
|
+
host_roles = Array(host_config['roles'] || host_config[:roles] || host_groups)
|
142
|
+
|
143
|
+
@hosts[hostname] = create_host(hostname, host_config, host_roles)
|
144
|
+
end
|
145
|
+
|
146
|
+
def create_host(hostname, config, roles)
|
147
|
+
Host.new(
|
148
|
+
hostname,
|
149
|
+
user: config['user'] || config[:user],
|
150
|
+
port: config['port'] || config[:port],
|
151
|
+
ssh_options: parse_ssh_options(config),
|
152
|
+
roles: roles,
|
153
|
+
vars: config['vars'] || config[:vars] || {}
|
154
|
+
)
|
155
|
+
end
|
156
|
+
|
157
|
+
def parse_ssh_options(host_config)
|
158
|
+
ssh_config = host_config['ssh'] || host_config[:ssh] || {}
|
159
|
+
options = {}
|
160
|
+
|
161
|
+
process_ssh_key_options(ssh_config, options)
|
162
|
+
process_ssh_auth_options(ssh_config, options)
|
163
|
+
process_ssh_verification_options(ssh_config, options)
|
164
|
+
process_ssh_timeout_option(ssh_config, options)
|
165
|
+
|
166
|
+
options
|
167
|
+
end
|
168
|
+
|
169
|
+
def process_ssh_key_options(ssh_config, options)
|
170
|
+
if ssh_config['key_file'] || ssh_config[:key_file]
|
171
|
+
key_file = ssh_config['key_file'] || ssh_config[:key_file]
|
172
|
+
options[:keys] = [File.expand_path(key_file)]
|
173
|
+
end
|
174
|
+
|
175
|
+
return unless ssh_config['key_data'] || ssh_config[:key_data]
|
176
|
+
|
177
|
+
options[:key_data] = Array(ssh_config['key_data'] || ssh_config[:key_data])
|
178
|
+
end
|
179
|
+
|
180
|
+
def process_ssh_auth_options(ssh_config, options)
|
181
|
+
options[:password] = ssh_config['password'] || ssh_config[:password] if ssh_config['password'] || ssh_config[:password]
|
182
|
+
return unless ssh_config['passphrase'] || ssh_config[:passphrase]
|
183
|
+
|
184
|
+
options[:passphrase] = ssh_config['passphrase'] || ssh_config[:passphrase]
|
185
|
+
end
|
186
|
+
|
187
|
+
def process_ssh_verification_options(ssh_config, options)
|
188
|
+
return unless ssh_config.key?('verify_host_key') || ssh_config.key?(:verify_host_key)
|
189
|
+
|
190
|
+
verify_host_key = ssh_config['verify_host_key'] || ssh_config[:verify_host_key]
|
191
|
+
options[:verify_host_key] = verify_host_key ? :always : :never
|
192
|
+
end
|
193
|
+
|
194
|
+
def process_ssh_timeout_option(ssh_config, options)
|
195
|
+
options[:timeout] = ssh_config['timeout'] || ssh_config[:timeout] if ssh_config['timeout'] || ssh_config[:timeout]
|
196
|
+
end
|
197
|
+
|
198
|
+
def find_host_groups(hostname)
|
199
|
+
@groups.each_with_object([]) do |(group_name, group_config), groups|
|
200
|
+
groups << group_name if group_config[:hosts].include?(hostname)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def resolve_group_children
|
205
|
+
@groups.each do |group_name, group_config|
|
206
|
+
process_group_children(group_name, group_config)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def process_group_children(group_name, group_config)
|
211
|
+
group_config[:children].each do |child_group|
|
212
|
+
next unless @groups[child_group]
|
213
|
+
|
214
|
+
@groups[group_name][:hosts].concat(@groups[child_group][:hosts])
|
215
|
+
end
|
216
|
+
|
217
|
+
@groups[group_name][:hosts].uniq!
|
218
|
+
end
|
219
|
+
|
220
|
+
def apply_group_variables
|
221
|
+
@hosts.each do |hostname, host|
|
222
|
+
host_groups = find_host_groups(hostname)
|
223
|
+
apply_group_vars_to_host(host, host_groups)
|
224
|
+
apply_global_vars_to_host(host)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def apply_group_vars_to_host(host, host_groups)
|
229
|
+
host_groups.each do |group_name|
|
230
|
+
group_vars = @groups[group_name][:vars] || {}
|
231
|
+
group_vars.each do |key, value|
|
232
|
+
host.set_var(key, value) unless host.var(key)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def apply_global_vars_to_host(host)
|
238
|
+
@vars.each do |key, value|
|
239
|
+
host.set_var(key, value) unless host.var(key)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kdeploy
|
4
|
+
# Custom logger class for Kdeploy with colorized output
|
5
|
+
class KdeployLogger
|
6
|
+
class << self
|
7
|
+
attr_accessor :instance
|
8
|
+
|
9
|
+
# Set up logger instance with specified level and output file
|
10
|
+
# @param level [Symbol] Log level (:debug, :info, :warn, :error, :fatal)
|
11
|
+
# @param file [String, IO] Output file or IO stream
|
12
|
+
# @return [KdeployLogger] Logger instance
|
13
|
+
def setup(level: :info, file: nil)
|
14
|
+
@instance = new(level: level, file: file)
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_missing(method_name, ...)
|
18
|
+
return super unless respond_to_missing?(method_name, false)
|
19
|
+
|
20
|
+
@instance ||= new
|
21
|
+
@instance.send(method_name, ...)
|
22
|
+
end
|
23
|
+
|
24
|
+
def respond_to_missing?(method_name, include_private = false)
|
25
|
+
return true if %i[debug info warn error fatal].include?(method_name)
|
26
|
+
|
27
|
+
super
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Initialize logger with specified level and output file
|
32
|
+
# @param level [Symbol] Log level (:debug, :info, :warn, :error, :fatal)
|
33
|
+
# @param file [String, IO] Output file or IO stream
|
34
|
+
def initialize(level: :info, file: nil)
|
35
|
+
@logger = Logger.new(file || $stdout)
|
36
|
+
@logger.level = logger_level(level)
|
37
|
+
@logger.formatter = method(:format_message)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Log debug message
|
41
|
+
# @param message [String] Message to log
|
42
|
+
def debug(message)
|
43
|
+
@logger.debug(message)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Log info message
|
47
|
+
# @param message [String] Message to log
|
48
|
+
def info(message)
|
49
|
+
@logger.info(message)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Log warning message
|
53
|
+
# @param message [String] Message to log
|
54
|
+
def warn(message)
|
55
|
+
@logger.warn(message)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Log error message
|
59
|
+
# @param message [String] Message to log
|
60
|
+
def error(message)
|
61
|
+
@logger.error(message)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Log fatal message
|
65
|
+
# @param message [String] Message to log
|
66
|
+
def fatal(message)
|
67
|
+
@logger.fatal(message)
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def format_message(severity, datetime, _progname, msg)
|
73
|
+
timestamp = datetime.strftime('%Y-%m-%d %H:%M:%S')
|
74
|
+
colored_msg = colorize_message(severity, msg)
|
75
|
+
"[#{timestamp}] #{severity}: #{colored_msg}\n"
|
76
|
+
end
|
77
|
+
|
78
|
+
def logger_level(level)
|
79
|
+
case level.to_sym
|
80
|
+
when :debug then Logger::DEBUG
|
81
|
+
when :info then Logger::INFO
|
82
|
+
when :warn then Logger::WARN
|
83
|
+
when :error then Logger::ERROR
|
84
|
+
when :fatal then Logger::FATAL
|
85
|
+
else Logger::INFO
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def colorize_message(severity, message)
|
90
|
+
case severity
|
91
|
+
when 'DEBUG' then message.colorize(:light_black)
|
92
|
+
when 'INFO' then message.colorize(:green)
|
93
|
+
when 'WARN' then message.colorize(:yellow)
|
94
|
+
when 'ERROR' then message.colorize(:red)
|
95
|
+
when 'FATAL' then message.colorize(:light_red)
|
96
|
+
else message
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,249 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kdeploy
|
4
|
+
# Pipeline class for managing deployment tasks and hosts
|
5
|
+
class Pipeline
|
6
|
+
attr_reader :name, :hosts, :tasks, :variables
|
7
|
+
|
8
|
+
def initialize(name = 'default')
|
9
|
+
@name = name
|
10
|
+
@hosts = []
|
11
|
+
@tasks = []
|
12
|
+
@variables = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
# Add host to pipeline
|
16
|
+
# @param hostname [String] Hostname or IP address
|
17
|
+
# @param user [String] SSH user
|
18
|
+
# @param port [Integer] SSH port
|
19
|
+
# @param ssh_options [Hash] SSH options
|
20
|
+
# @param roles [Array] Host roles
|
21
|
+
# @param vars [Hash] Host variables
|
22
|
+
# @return [Host] Created host
|
23
|
+
def add_host(hostname, user: nil, port: nil, ssh_options: {}, roles: [], vars: {})
|
24
|
+
host = Host.new(
|
25
|
+
hostname,
|
26
|
+
user: user,
|
27
|
+
port: port,
|
28
|
+
ssh_options: ssh_options,
|
29
|
+
roles: roles,
|
30
|
+
vars: vars
|
31
|
+
)
|
32
|
+
@hosts << host unless @hosts.include?(host)
|
33
|
+
host
|
34
|
+
end
|
35
|
+
|
36
|
+
# Add multiple hosts from hash
|
37
|
+
# @param hosts_config [Hash] Hosts configuration
|
38
|
+
def add_hosts(hosts_config)
|
39
|
+
hosts_config.each do |hostname, config|
|
40
|
+
config ||= {}
|
41
|
+
add_host_from_config(hostname, config)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Get hosts by role
|
46
|
+
# @param role [String, Symbol] Role to filter by
|
47
|
+
# @return [Array<Host>] Hosts with specified role
|
48
|
+
def hosts_with_role(role)
|
49
|
+
@hosts.select { |host| host.has_role?(role) }
|
50
|
+
end
|
51
|
+
|
52
|
+
# Add task to pipeline
|
53
|
+
# @param name [String] Task name
|
54
|
+
# @param hosts [Array<Host>] Target hosts (default: all hosts)
|
55
|
+
# @param options [Hash] Task options
|
56
|
+
# @return [Task] Created task
|
57
|
+
def add_task(name, hosts: nil, **options)
|
58
|
+
target_hosts = hosts || @hosts
|
59
|
+
task = create_task(name, target_hosts, options)
|
60
|
+
@tasks << task
|
61
|
+
task
|
62
|
+
end
|
63
|
+
|
64
|
+
# Set global variable
|
65
|
+
# @param key [String, Symbol] Variable key
|
66
|
+
# @param value [Object] Variable value
|
67
|
+
def set_variable(key, value)
|
68
|
+
@variables[key.to_s] = value
|
69
|
+
end
|
70
|
+
|
71
|
+
# Get global variable
|
72
|
+
# @param key [String, Symbol] Variable key
|
73
|
+
# @return [Object] Variable value
|
74
|
+
def get_variable(key)
|
75
|
+
@variables[key.to_s] || @variables[key.to_sym]
|
76
|
+
end
|
77
|
+
|
78
|
+
# Execute all tasks in pipeline
|
79
|
+
# @return [Hash] Execution results
|
80
|
+
def execute
|
81
|
+
return empty_execution_result if @tasks.empty?
|
82
|
+
|
83
|
+
log_pipeline_start
|
84
|
+
start_time = Time.now
|
85
|
+
results = execute_tasks
|
86
|
+
duration = Time.now - start_time
|
87
|
+
success_count = count_successful_tasks(results)
|
88
|
+
|
89
|
+
log_pipeline_completion(duration, success_count)
|
90
|
+
build_execution_result(results, duration, success_count)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Get pipeline summary
|
94
|
+
# @return [Hash] Pipeline summary
|
95
|
+
def summary
|
96
|
+
{
|
97
|
+
name: @name,
|
98
|
+
hosts_count: @hosts.size,
|
99
|
+
tasks_count: @tasks.size,
|
100
|
+
hosts: @hosts.map(&:hostname),
|
101
|
+
tasks: @tasks.map(&:name)
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
# Validate pipeline configuration
|
106
|
+
# @return [Array<String>] Validation errors
|
107
|
+
def validate
|
108
|
+
errors = []
|
109
|
+
errors.concat(validate_pipeline_structure)
|
110
|
+
errors.concat(validate_hosts)
|
111
|
+
errors.concat(validate_tasks)
|
112
|
+
errors
|
113
|
+
end
|
114
|
+
|
115
|
+
# Check if pipeline is valid
|
116
|
+
# @return [Boolean] True if pipeline is valid
|
117
|
+
def valid?
|
118
|
+
validate.empty?
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def add_host_from_config(hostname, config)
|
124
|
+
add_host(
|
125
|
+
hostname,
|
126
|
+
user: config['user'] || config[:user],
|
127
|
+
port: config['port'] || config[:port],
|
128
|
+
ssh_options: config['ssh_options'] || config[:ssh_options] || {},
|
129
|
+
roles: config['roles'] || config[:roles] || [],
|
130
|
+
vars: config['vars'] || config[:vars] || {}
|
131
|
+
)
|
132
|
+
end
|
133
|
+
|
134
|
+
def create_task(name, target_hosts, options)
|
135
|
+
task = Task.new(name, target_hosts, options)
|
136
|
+
task.global_variables = @variables
|
137
|
+
task
|
138
|
+
end
|
139
|
+
|
140
|
+
def empty_execution_result
|
141
|
+
{ success: true, results: [], duration: 0 }
|
142
|
+
end
|
143
|
+
|
144
|
+
def log_pipeline_start
|
145
|
+
KdeployLogger.info(
|
146
|
+
"Starting pipeline '#{@name}' with #{@tasks.size} task(s) on #{@hosts.size} host(s)"
|
147
|
+
)
|
148
|
+
end
|
149
|
+
|
150
|
+
def execute_tasks
|
151
|
+
results = []
|
152
|
+
overall_success = true
|
153
|
+
|
154
|
+
@tasks.each_with_index do |task, index|
|
155
|
+
log_task_execution(task, index)
|
156
|
+
result = execute_task(task)
|
157
|
+
results << result
|
158
|
+
overall_success = false unless result[:success]
|
159
|
+
end
|
160
|
+
|
161
|
+
results
|
162
|
+
end
|
163
|
+
|
164
|
+
def log_task_execution(task, index)
|
165
|
+
KdeployLogger.info("Executing task #{index + 1}/#{@tasks.size}: '#{task.name}'")
|
166
|
+
end
|
167
|
+
|
168
|
+
def execute_task(task)
|
169
|
+
result = task.execute
|
170
|
+
log_task_failure(task) unless result[:success]
|
171
|
+
|
172
|
+
{
|
173
|
+
task_name: task.name,
|
174
|
+
**result
|
175
|
+
}
|
176
|
+
end
|
177
|
+
|
178
|
+
def log_task_failure(task)
|
179
|
+
KdeployLogger.error("Task '#{task.name}' failed, pipeline execution continuing...")
|
180
|
+
end
|
181
|
+
|
182
|
+
def count_successful_tasks(results)
|
183
|
+
results.count { |r| r[:success] }
|
184
|
+
end
|
185
|
+
|
186
|
+
def log_pipeline_completion(duration, success_count)
|
187
|
+
KdeployLogger.info(
|
188
|
+
"Pipeline '#{@name}' completed in #{duration.round(2)}s: " \
|
189
|
+
"#{success_count}/#{@tasks.size} tasks successful"
|
190
|
+
)
|
191
|
+
end
|
192
|
+
|
193
|
+
def build_execution_result(results, duration, success_count)
|
194
|
+
{
|
195
|
+
success: results.all? { |r| r[:success] },
|
196
|
+
results: results,
|
197
|
+
duration: duration,
|
198
|
+
tasks_count: @tasks.size,
|
199
|
+
success_count: success_count
|
200
|
+
}
|
201
|
+
end
|
202
|
+
|
203
|
+
def validate_pipeline_structure
|
204
|
+
errors = []
|
205
|
+
errors << 'No hosts defined' if @hosts.empty?
|
206
|
+
errors << 'No tasks defined' if @tasks.empty?
|
207
|
+
errors
|
208
|
+
end
|
209
|
+
|
210
|
+
def validate_hosts
|
211
|
+
@hosts.each_with_object([]) do |host, errors|
|
212
|
+
errors.concat(validate_host(host))
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def validate_host(host)
|
217
|
+
errors = []
|
218
|
+
errors << "Invalid hostname: #{host.hostname}" if invalid_hostname?(host)
|
219
|
+
errors << "Invalid user: #{host.user}" if invalid_user?(host)
|
220
|
+
errors << "Invalid port: #{host.port}" if invalid_port?(host)
|
221
|
+
errors
|
222
|
+
end
|
223
|
+
|
224
|
+
def invalid_hostname?(host)
|
225
|
+
host.hostname.nil? || host.hostname.empty?
|
226
|
+
end
|
227
|
+
|
228
|
+
def invalid_user?(host)
|
229
|
+
host.user.nil? || host.user.empty?
|
230
|
+
end
|
231
|
+
|
232
|
+
def invalid_port?(host)
|
233
|
+
!host.port.is_a?(Integer) || !host.port.positive?
|
234
|
+
end
|
235
|
+
|
236
|
+
def validate_tasks
|
237
|
+
@tasks.each_with_object([]) do |task, errors|
|
238
|
+
errors.concat(validate_task(task))
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def validate_task(task)
|
243
|
+
errors = []
|
244
|
+
errors << "Task '#{task.name}' has no commands" if task.commands.empty?
|
245
|
+
errors << "Task '#{task.name}' has no hosts" if task.hosts.empty?
|
246
|
+
errors
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|