kdeploy 0.2.0 → 0.4.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 +4 -4
- data/README.md +122 -318
- data/exe/kdeploy +6 -0
- data/k.md +149 -0
- data/lib/kdeploy/banner.rb +44 -14
- data/lib/kdeploy/cli.rb +179 -1395
- data/lib/kdeploy/dsl.rb +62 -530
- data/lib/kdeploy/executor.rb +73 -0
- data/lib/kdeploy/initializer.rb +229 -0
- data/lib/kdeploy/runner.rb +40 -180
- data/lib/kdeploy/template.rb +19 -161
- data/lib/kdeploy/version.rb +1 -2
- data/lib/kdeploy.rb +9 -100
- metadata +74 -52
- data/.editorconfig +0 -12
- data/.rspec +0 -3
- data/.rubocop.yml +0 -100
- data/LICENSE +0 -21
- data/README_CN.md +0 -1030
- data/Rakefile +0 -46
- data/bin/kdeploy +0 -7
- data/kdeploy.gemspec +0 -49
- data/lib/kdeploy/command.rb +0 -182
- data/lib/kdeploy/configuration.rb +0 -83
- data/lib/kdeploy/host.rb +0 -85
- data/lib/kdeploy/inventory.rb +0 -243
- data/lib/kdeploy/logger.rb +0 -100
- data/lib/kdeploy/pipeline.rb +0 -249
- data/lib/kdeploy/ssh_connection.rb +0 -187
- data/lib/kdeploy/statistics.rb +0 -439
- data/lib/kdeploy/task.rb +0 -240
- data/scripts/common_tasks.rb +0 -218
- data/scripts/deploy.rb +0 -50
data/Rakefile
DELETED
@@ -1,46 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'bundler/gem_tasks'
|
4
|
-
require 'rspec/core/rake_task'
|
5
|
-
require 'time'
|
6
|
-
require_relative 'lib/kdeploy/version'
|
7
|
-
|
8
|
-
# Default task runs tests and RuboCop, then pushes code
|
9
|
-
task default: %w[push]
|
10
|
-
|
11
|
-
# Run RSpec tests
|
12
|
-
RSpec::Core::RakeTask.new(:test) do |spec|
|
13
|
-
spec.pattern = 'spec/**{,/*/**}/*_spec.rb'
|
14
|
-
end
|
15
|
-
|
16
|
-
# Run RuboCop
|
17
|
-
task :rubocop do
|
18
|
-
system 'bundle exec rubocop'
|
19
|
-
end
|
20
|
-
|
21
|
-
# Auto-commit and push changes
|
22
|
-
task :push do
|
23
|
-
system 'bundle exec rubocop -A'
|
24
|
-
system 'git add .'
|
25
|
-
system "git commit -m 'Update #{Time.now}'"
|
26
|
-
system 'git pull'
|
27
|
-
system 'git push'
|
28
|
-
end
|
29
|
-
|
30
|
-
# Documentation task
|
31
|
-
task :doc do
|
32
|
-
system 'bundle exec yard doc'
|
33
|
-
end
|
34
|
-
|
35
|
-
# Clean build artifacts
|
36
|
-
task :clean do
|
37
|
-
system 'rm -f *.gem'
|
38
|
-
system 'rm -rf doc/'
|
39
|
-
system 'rm -rf coverage/'
|
40
|
-
end
|
41
|
-
|
42
|
-
# Install gem locally
|
43
|
-
task :install do
|
44
|
-
system 'gem build kdeploy.gemspec'
|
45
|
-
system "gem install kdeploy-#{Kdeploy::VERSION}.gem"
|
46
|
-
end
|
data/bin/kdeploy
DELETED
data/kdeploy.gemspec
DELETED
@@ -1,49 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'lib/kdeploy/version'
|
4
|
-
|
5
|
-
Gem::Specification.new do |spec|
|
6
|
-
spec.name = 'kdeploy'
|
7
|
-
spec.version = Kdeploy::VERSION
|
8
|
-
spec.authors = ['Kdeploy Team']
|
9
|
-
spec.email = ['kevin197011@outlook.com']
|
10
|
-
|
11
|
-
spec.summary = 'Lightweight agentless deployment tool with DSL, heredoc, and ERB template support'
|
12
|
-
spec.description = <<~DESC
|
13
|
-
Kdeploy is a lightweight, agentless deployment tool similar to Chef, Puppet, and Ansible.
|
14
|
-
It uses Ruby DSL for defining deployment pipelines with support for inventory management,
|
15
|
-
parallel execution, SSH-based remote operations, heredoc syntax for multi-line scripts,
|
16
|
-
and ERB templates for dynamic configuration generation.
|
17
|
-
DESC
|
18
|
-
spec.homepage = 'https://github.com/kevin197011/kdeploy'
|
19
|
-
spec.license = 'MIT'
|
20
|
-
spec.required_ruby_version = '>= 3.0.0'
|
21
|
-
|
22
|
-
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
23
|
-
spec.metadata['homepage_uri'] = spec.homepage
|
24
|
-
spec.metadata['source_code_uri'] = 'https://github.com/kevin197011/kdeploy'
|
25
|
-
spec.metadata['changelog_uri'] = 'https://github.com/kevin197011/kdeploy/blob/main/CHANGELOG.md'
|
26
|
-
spec.metadata['rubygems_mfa_required'] = 'true'
|
27
|
-
|
28
|
-
# Specify which files should be added to the gem when it is released.
|
29
|
-
spec.files = Dir.chdir(__dir__) do
|
30
|
-
`git ls-files -z`.split("\x0").reject do |f|
|
31
|
-
(File.expand_path(f) == __FILE__) ||
|
32
|
-
f.start_with?(*%w[exe/ test/ spec/ features/ .git .circleci appveyor Gemfile])
|
33
|
-
end
|
34
|
-
end
|
35
|
-
spec.bindir = 'bin'
|
36
|
-
spec.executables = spec.files.grep(%r{\Abin/}) { |f| File.basename(f) }
|
37
|
-
spec.require_paths = ['lib']
|
38
|
-
|
39
|
-
# Dependencies
|
40
|
-
spec.add_dependency 'colorize', '~> 0.8'
|
41
|
-
spec.add_dependency 'concurrent-ruby', '~> 1.2'
|
42
|
-
spec.add_dependency 'net-scp', '~> 4.0'
|
43
|
-
spec.add_dependency 'net-ssh', '~> 7.0'
|
44
|
-
spec.add_dependency 'thor', '~> 1.3'
|
45
|
-
spec.add_dependency 'tty-prompt', '~> 0.23'
|
46
|
-
spec.add_dependency 'yaml', '~> 0.2'
|
47
|
-
|
48
|
-
# NOTE: Development dependencies are managed in Gemfile
|
49
|
-
end
|
data/lib/kdeploy/command.rb
DELETED
@@ -1,182 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Kdeploy
|
4
|
-
# Command class for executing commands on remote hosts
|
5
|
-
class Command
|
6
|
-
attr_reader :name, :command, :options, :result
|
7
|
-
|
8
|
-
def initialize(name, command, options = {})
|
9
|
-
@name = name
|
10
|
-
@command = command
|
11
|
-
@options = default_options.merge(options)
|
12
|
-
@global_variables = options.delete(:global_variables) || {}
|
13
|
-
@result = nil
|
14
|
-
end
|
15
|
-
|
16
|
-
# Execute command on specified host
|
17
|
-
# @param host [Host] Target host
|
18
|
-
# @param connection [SSHConnection] SSH connection
|
19
|
-
# @return [Boolean] True if command succeeded
|
20
|
-
def execute(host, connection)
|
21
|
-
start_time = Time.now
|
22
|
-
processed_command = process_command_template(host)
|
23
|
-
|
24
|
-
log_command_start(host, processed_command)
|
25
|
-
@result = execute_with_retry(connection, processed_command)
|
26
|
-
duration = Time.now - start_time
|
27
|
-
|
28
|
-
log_result(host, duration)
|
29
|
-
record_statistics(host.hostname, duration, @result[:success])
|
30
|
-
|
31
|
-
@result[:success]
|
32
|
-
rescue StandardError => e
|
33
|
-
handle_execution_error(host, e, start_time)
|
34
|
-
end
|
35
|
-
|
36
|
-
# Check if command should run on host
|
37
|
-
# @param host [Host] Target host
|
38
|
-
# @return [Boolean] True if command should run
|
39
|
-
def should_run_on?(host)
|
40
|
-
return true unless @options[:only] || @options[:except]
|
41
|
-
|
42
|
-
if @options[:only]
|
43
|
-
roles = Array(@options[:only])
|
44
|
-
return roles.any? { |role| host.has_role?(role) }
|
45
|
-
end
|
46
|
-
|
47
|
-
if @options[:except]
|
48
|
-
roles = Array(@options[:except])
|
49
|
-
return roles.none? { |role| host.has_role?(role) }
|
50
|
-
end
|
51
|
-
|
52
|
-
true
|
53
|
-
end
|
54
|
-
|
55
|
-
private
|
56
|
-
|
57
|
-
def default_options
|
58
|
-
{
|
59
|
-
timeout: nil,
|
60
|
-
retry_count: nil,
|
61
|
-
retry_delay: nil,
|
62
|
-
ignore_errors: false,
|
63
|
-
only: nil,
|
64
|
-
except: nil
|
65
|
-
}
|
66
|
-
end
|
67
|
-
|
68
|
-
def process_command_template(host)
|
69
|
-
processed = @command.dup
|
70
|
-
process_global_variables(processed)
|
71
|
-
process_host_variables(processed, host)
|
72
|
-
process_host_info(processed, host)
|
73
|
-
end
|
74
|
-
|
75
|
-
def process_global_variables(command)
|
76
|
-
@global_variables.each_with_object(command) do |(key, value), cmd|
|
77
|
-
cmd.gsub!("{{#{key}}}", value.to_s)
|
78
|
-
cmd.gsub!("${#{key}}", value.to_s)
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
def process_host_variables(command, host)
|
83
|
-
host.vars.each_with_object(command) do |(key, value), cmd|
|
84
|
-
cmd.gsub!("{{#{key}}}", value.to_s)
|
85
|
-
cmd.gsub!("${#{key}}", value.to_s)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
def process_host_info(command, host)
|
90
|
-
command.gsub('{{hostname}}', host.hostname)
|
91
|
-
.gsub('{{user}}', host.user)
|
92
|
-
.gsub('{{port}}', host.port.to_s)
|
93
|
-
end
|
94
|
-
|
95
|
-
def execute_with_retry(connection, command)
|
96
|
-
retry_count = @options[:retry_count] || Kdeploy.configuration&.retry_count || 0
|
97
|
-
retry_delay = @options[:retry_delay] || Kdeploy.configuration&.retry_delay || 1
|
98
|
-
|
99
|
-
result = nil
|
100
|
-
attempts = 0
|
101
|
-
|
102
|
-
loop do
|
103
|
-
attempts += 1
|
104
|
-
result = connection.execute(command, timeout: @options[:timeout])
|
105
|
-
|
106
|
-
break if result[:success] || attempts > retry_count
|
107
|
-
|
108
|
-
if attempts <= retry_count
|
109
|
-
log_retry_attempt(attempts, retry_count, retry_delay)
|
110
|
-
sleep(retry_delay)
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
result
|
115
|
-
end
|
116
|
-
|
117
|
-
def log_command_start(host, command)
|
118
|
-
KdeployLogger.info("🚀 Executing '#{@name}' on #{host}")
|
119
|
-
KdeployLogger.debug(" Command: #{command}")
|
120
|
-
end
|
121
|
-
|
122
|
-
def log_retry_attempt(attempts, retry_count, retry_delay)
|
123
|
-
KdeployLogger.warn(
|
124
|
-
"Command '#{@name}' failed (attempt #{attempts}/#{retry_count + 1}), " \
|
125
|
-
"retrying in #{retry_delay}s..."
|
126
|
-
)
|
127
|
-
end
|
128
|
-
|
129
|
-
def log_result(host, duration)
|
130
|
-
if @result[:success]
|
131
|
-
log_success(host, duration)
|
132
|
-
else
|
133
|
-
log_failure(host, duration)
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
def log_success(host, duration)
|
138
|
-
KdeployLogger.info("✅ Command '#{@name}' completed on #{host} in #{duration.round(2)}s")
|
139
|
-
return if @result[:stdout].strip.empty?
|
140
|
-
|
141
|
-
KdeployLogger.info('📤 Output:')
|
142
|
-
@result[:stdout].strip.split("\n").each do |line|
|
143
|
-
KdeployLogger.info(" #{line}")
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
def log_failure(host, duration)
|
148
|
-
level = @options[:ignore_errors] ? :warn : :error
|
149
|
-
icon = @options[:ignore_errors] ? '⚠️' : '❌'
|
150
|
-
|
151
|
-
KdeployLogger.send(
|
152
|
-
level,
|
153
|
-
"#{icon} Command '#{@name}' failed on #{host} in #{duration.round(2)}s " \
|
154
|
-
"(exit code: #{@result[:exit_code]})"
|
155
|
-
)
|
156
|
-
|
157
|
-
KdeployLogger.send(level, "📤 STDERR: #{@result[:stderr]}") unless @result[:stderr].empty?
|
158
|
-
KdeployLogger.send(level, "📤 STDOUT: #{@result[:stdout]}") unless @result[:stdout].strip.empty?
|
159
|
-
end
|
160
|
-
|
161
|
-
def handle_execution_error(host, error, start_time)
|
162
|
-
duration = Time.now - start_time
|
163
|
-
KdeployLogger.error(
|
164
|
-
"Command '#{@name}' failed on #{host} after #{duration.round(2)}s: #{error.message}"
|
165
|
-
)
|
166
|
-
|
167
|
-
@result = {
|
168
|
-
stdout: '',
|
169
|
-
stderr: error.message,
|
170
|
-
exit_code: 1,
|
171
|
-
success: false
|
172
|
-
}
|
173
|
-
|
174
|
-
record_statistics(host.hostname, duration, false)
|
175
|
-
false
|
176
|
-
end
|
177
|
-
|
178
|
-
def record_statistics(hostname, duration, success)
|
179
|
-
Kdeploy.statistics.record_command(@name, hostname, success, duration)
|
180
|
-
end
|
181
|
-
end
|
182
|
-
end
|
@@ -1,83 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Kdeploy
|
4
|
-
# Configuration class for managing global settings
|
5
|
-
class Configuration
|
6
|
-
attr_accessor :max_concurrent_tasks, :ssh_timeout, :command_timeout,
|
7
|
-
:retry_count, :retry_delay, :log_level, :log_file,
|
8
|
-
:default_user, :default_port, :ssh_options, :inventory_file,
|
9
|
-
:template_dir
|
10
|
-
|
11
|
-
def initialize
|
12
|
-
set_default_values
|
13
|
-
end
|
14
|
-
|
15
|
-
# Load configuration from YAML file
|
16
|
-
# @param config_file [String] Path to configuration file
|
17
|
-
# @return [void]
|
18
|
-
def load_from_file(config_file)
|
19
|
-
return unless File.exist?(config_file)
|
20
|
-
|
21
|
-
config = YAML.load_file(config_file)
|
22
|
-
return unless config.is_a?(Hash)
|
23
|
-
|
24
|
-
apply_configuration(config)
|
25
|
-
end
|
26
|
-
|
27
|
-
# Merge SSH options with defaults
|
28
|
-
# @param options [Hash] SSH options to merge
|
29
|
-
# @return [Hash] Merged SSH options
|
30
|
-
def merged_ssh_options(options = {})
|
31
|
-
ssh_options.merge(options)
|
32
|
-
end
|
33
|
-
|
34
|
-
private
|
35
|
-
|
36
|
-
def set_default_values
|
37
|
-
set_default_timeouts
|
38
|
-
set_default_retry_settings
|
39
|
-
set_default_logging
|
40
|
-
set_default_ssh_settings
|
41
|
-
set_default_paths
|
42
|
-
end
|
43
|
-
|
44
|
-
def set_default_timeouts
|
45
|
-
@max_concurrent_tasks = 10
|
46
|
-
@ssh_timeout = 30
|
47
|
-
@command_timeout = 300
|
48
|
-
end
|
49
|
-
|
50
|
-
def set_default_retry_settings
|
51
|
-
@retry_count = 3
|
52
|
-
@retry_delay = 1
|
53
|
-
end
|
54
|
-
|
55
|
-
def set_default_logging
|
56
|
-
@log_level = :info
|
57
|
-
@log_file = nil
|
58
|
-
end
|
59
|
-
|
60
|
-
def set_default_ssh_settings
|
61
|
-
@default_user = ENV.fetch('USER', 'root')
|
62
|
-
@default_port = 22
|
63
|
-
@ssh_options = {
|
64
|
-
verify_host_key: :never,
|
65
|
-
non_interactive: true,
|
66
|
-
use_agent: true,
|
67
|
-
forward_agent: false
|
68
|
-
}
|
69
|
-
end
|
70
|
-
|
71
|
-
def set_default_paths
|
72
|
-
@inventory_file = 'inventory.yml'
|
73
|
-
@template_dir = 'templates'
|
74
|
-
end
|
75
|
-
|
76
|
-
def apply_configuration(config)
|
77
|
-
config.each do |key, value|
|
78
|
-
method_name = "#{key}="
|
79
|
-
send(method_name, value) if respond_to?(method_name)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
data/lib/kdeploy/host.rb
DELETED
@@ -1,85 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Kdeploy
|
4
|
-
# Host class for managing remote host configuration and connection details
|
5
|
-
class Host
|
6
|
-
attr_reader :hostname, :user, :port, :ssh_options, :roles, :vars
|
7
|
-
|
8
|
-
def initialize(hostname, user: nil, port: nil, ssh_options: {}, roles: [], vars: {})
|
9
|
-
@hostname = hostname
|
10
|
-
@user = user || Kdeploy.configuration&.default_user || ENV.fetch('USER', nil)
|
11
|
-
@port = port || Kdeploy.configuration&.default_port || 22
|
12
|
-
@ssh_options = ssh_options
|
13
|
-
@roles = Array(roles)
|
14
|
-
@vars = vars || {}
|
15
|
-
end
|
16
|
-
|
17
|
-
# Check if host has specific role
|
18
|
-
# @param role [String, Symbol] Role to check
|
19
|
-
# @return [Boolean] True if host has role
|
20
|
-
def has_role?(role)
|
21
|
-
@roles.include?(role.to_s) || @roles.include?(role.to_sym)
|
22
|
-
end
|
23
|
-
|
24
|
-
# Get variable value
|
25
|
-
# @param key [String, Symbol] Variable key
|
26
|
-
# @return [Object] Variable value
|
27
|
-
def var(key)
|
28
|
-
@vars[key.to_s] || @vars[key.to_sym]
|
29
|
-
end
|
30
|
-
|
31
|
-
# Set variable value
|
32
|
-
# @param key [String, Symbol] Variable key
|
33
|
-
# @param value [Object] Variable value
|
34
|
-
# @return [Object] Set value
|
35
|
-
def set_var(key, value)
|
36
|
-
@vars[key.to_s] = value
|
37
|
-
end
|
38
|
-
|
39
|
-
# Get connection string for display
|
40
|
-
# @return [String] Connection string
|
41
|
-
def connection_string
|
42
|
-
"#{@user}@#{@hostname}:#{@port}"
|
43
|
-
end
|
44
|
-
|
45
|
-
# Get SSH connection options
|
46
|
-
# @return [Hash] SSH options
|
47
|
-
def connection_options
|
48
|
-
base_options = Kdeploy.configuration&.merged_ssh_options(@ssh_options) || @ssh_options
|
49
|
-
base_options.merge(
|
50
|
-
timeout: Kdeploy.configuration&.ssh_timeout || 30
|
51
|
-
)
|
52
|
-
end
|
53
|
-
|
54
|
-
# String representation of the host
|
55
|
-
# @return [String] Connection string
|
56
|
-
def to_s
|
57
|
-
connection_string
|
58
|
-
end
|
59
|
-
|
60
|
-
# Detailed string representation of the host
|
61
|
-
# @return [String] Host details
|
62
|
-
def inspect
|
63
|
-
"#<Kdeploy::Host #{connection_string} roles=#{@roles} vars=#{@vars.keys}>"
|
64
|
-
end
|
65
|
-
|
66
|
-
# Compare hosts for equality
|
67
|
-
# @param other [Host] Host to compare with
|
68
|
-
# @return [Boolean] True if hosts are equal
|
69
|
-
def ==(other)
|
70
|
-
return false unless other.is_a?(Host)
|
71
|
-
|
72
|
-
hostname == other.hostname &&
|
73
|
-
user == other.user &&
|
74
|
-
port == other.port
|
75
|
-
end
|
76
|
-
|
77
|
-
alias eql? ==
|
78
|
-
|
79
|
-
# Generate hash code for host
|
80
|
-
# @return [Integer] Hash code
|
81
|
-
def hash
|
82
|
-
[hostname, user, port].hash
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
data/lib/kdeploy/inventory.rb
DELETED
@@ -1,243 +0,0 @@
|
|
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
|