Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/continuous_integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ jobs:
uses: actions/checkout@v2

- name: Install Ruby
uses: actions/setup-ruby@v1
uses: ruby/setup-ruby@v1
with:
ruby-version: '2.7'
ruby-version: '3.4'

- name: Install sshpass as it is needed to test the SSH connector
run: sudo apt install sshpass
Expand Down Expand Up @@ -52,9 +52,9 @@ jobs:
uses: actions/checkout@v2

- name: Install Ruby
uses: actions/setup-ruby@v1
uses: ruby/setup-ruby@v1
with:
ruby-version: '2.7'
ruby-version: '3.4'

- name: Install Node
uses: actions/setup-node@v2
Expand All @@ -63,7 +63,7 @@ jobs:
run: npm install @mermaid-js/mermaid-cli

- name: Install semantic-release
run: npm install semantic-release@17.4.7 @semantic-release/git@9.0.1 @semantic-release/changelog@5.0.1 @semantic-release/exec@5.0.0 semantic-release-rubygem -D
run: npm install semantic-release@17.4.7 @semantic-release/git@9.0.1 @semantic-release/changelog@5.0.1 @semantic-release/exec@5.0.0 @webhippie/semantic-release-rubygem -D

- name: Install dependencies
run: bundle install
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ node_modules/
vendor/
package-lock.json
hybrid_platforms_conductor-*.gem

# Don't commit files that could be generated by tests
test_output
2 changes: 1 addition & 1 deletion .releaserc
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"generateNotesCmd": "bundle exec sem_ver_git --from \"${lastRelease.gitTag}\" --output semantic_release_generate_notes"
}],
"@semantic-release/changelog",
"semantic-release-rubygem",
"@webhippie/semantic-release-rubygem",
["@semantic-release/git", {
"assets": [
'CHANGELOG.md',
Expand Down
4 changes: 2 additions & 2 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
require: rubocop-rspec
plugins: rubocop-rspec

AllCops:
TargetRubyVersion: 2.6
TargetRubyVersion: 3.0
NewCops: enable
Exclude:
# Auto-generated files in examples
Expand Down
11 changes: 11 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
source 'https://rubygems.org'

gemspec

# Test framework
gem 'rspec', '~> 3.13'
# Automatic semantic releasing
gem 'sem_ver_components', '~> 0.4'
# Lint checker
gem 'rubocop', '~> 1.75'
# Lint checker for rspec
gem 'rubocop-rspec', '~> 3.6'
# Mock web responses when needed
gem 'webmock', '~> 3.25'
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ Here are the various plugin categories:
# Installation

Installing Hybrid Platforms Conductor requires 2 steps:
1. Have **Ruby >= 2.5 and < 3.0** installed.
1. Have **Ruby >= 3.0** installed.
2. Install the `hybrid_platforms_conductor` Rubygem.

See [installation details](docs/install.md) for more details on how to install those.
Expand Down Expand Up @@ -429,6 +429,8 @@ A subset of tests (or even a single test) can be run by using a part of their na

To enable debugging logs during tests run, set the environment variable `TEST_DEBUG` to `1`: `TEST_DEBUG=1 bundle exec rspec -e "HybridPlatformsConductor::Deployer checking the docker images provisioning"`

Some tests depend on Docker and sshpass which should be installed separately if those tests are required to pass.

## License

BSD license (details in the [LICENSE.md file](LICENSE.md)).
2 changes: 1 addition & 1 deletion bin/free_veids
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ executable.parse_options!
nodes_handler.prefetch_metadata_of nodes_handler.known_nodes, :veid
veids = nodes_handler.
known_nodes.
map { |node| nodes_handler.get_veid_of(node) ? nodes_handler.get_veid_of(node).to_i : nil }.
map { |node| nodes_handler.get_veid_of(node)&.to_i }.
compact

executable.out "Free VEIDs: #{([10_000] + veids).missing.rangify}"
2 changes: 1 addition & 1 deletion bin/last_deploys
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ info_displayed = {
error: 'Error'
}
executable.out(Terminal::Table.new(headings: info_displayed.values) do |table|
sorted_deploy_info.each do |_node, deploy_info|
sorted_deploy_info.each do |(_node, deploy_info)|
table << info_displayed.keys.map { |key| deploy_info[key] }
end
end)
6 changes: 3 additions & 3 deletions bin/nodes_to_deploy
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ unless ignore_deploy_info
node_impacted = false
# Loop over all possible repositories concerned by this deployment
repo_idx = 0
while node_deploy_info[:deployment_info].key?("repo_name_#{repo_idx}".to_sym)
repo_name = node_deploy_info[:deployment_info]["repo_name_#{repo_idx}".to_sym]
commit_id = node_deploy_info[:deployment_info]["commit_id_#{repo_idx}".to_sym]
while node_deploy_info[:deployment_info].key?(:"repo_name_#{repo_idx}")
repo_name = node_deploy_info[:deployment_info][:"repo_name_#{repo_idx}"]
commit_id = node_deploy_info[:deployment_info][:"commit_id_#{repo_idx}"]
impacted_nodes = cache_impacted_nodes.dig(repo_name, commit_id)
if impacted_nodes.nil?
if platforms_handler.platform(repo_name)
Expand Down
54 changes: 22 additions & 32 deletions hybrid_platforms_conductor.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -8,65 +8,55 @@ Gem::Specification.new do |s|
s.license = 'BSD-3-Clause'
s.summary = 'Hybrid Platforms Conductor'
s.description = 'Provides a complete toolset to help DevOps maintain, deploy, monitor and test multiple platforms using various technologies'
s.required_ruby_version = '~> 2.6'
s.required_ruby_version = '~> 3.0'

s.files = Dir['*.md'] + Dir['{bin,docs,examples,lib,spec,tools}/**/*']
s.executables = Dir['bin/**/*'].map { |exec_name| File.basename(exec_name) }
s.extra_rdoc_files = Dir['*.md'] + Dir['{docs,examples}/**/*']

# Dependencies
# To display IP ranges correctly
s.add_runtime_dependency 'range_operators', '~> 0.1'
s.add_dependency 'range_operators', '~> 0.1'
# To display reports in tables
s.add_runtime_dependency 'terminal-table', '~> 1.8'
s.add_dependency 'terminal-table', '~> 4.0'
# To perform operations on IP addresses
s.add_runtime_dependency 'ipaddress', '~> 0.8'
s.add_dependency 'ipaddress', '~> 0.8'
# To display nicely formatted progress bars
s.add_runtime_dependency 'ruby-progressbar', '~> 1.10'
s.add_dependency 'ruby-progressbar', '~> 1.13'
# To clone platform repositories if needed
s.add_runtime_dependency 'git', '~> 1.5'
s.add_dependency 'git', '~> 3.0'
# To generate some erb templates
s.add_runtime_dependency 'erubis', '~> 2.7'
s.add_dependency 'erubis', '~> 2.7'
# To use Docker images
s.add_runtime_dependency 'docker-api', '~> 1.34'
s.add_dependency 'docker-api', '~> 2.4'
# To test SSH access
s.add_runtime_dependency 'net-ssh', '~> 5.2'
s.add_dependency 'net-ssh', '~> 7.3'
# To have colored output
s.add_runtime_dependency 'colorize', '~> 0.8'
s.add_dependency 'colorize', '~> 1.1'
# To run commands in an efficient way and colored output
s.add_runtime_dependency 'tty-command', '~> 0.8'
s.add_dependency 'tty-command', '~> 0.10'
# To have HTML parsing capabilities
s.add_runtime_dependency 'nokogiri', '~> 1.10'
s.add_dependency 'nokogiri', '~> 1.18'
# To read netrc files
s.add_runtime_dependency 'netrc', '~> 0.11'
s.add_dependency 'netrc', '~> 0.11'
# To get file-based mutexes
s.add_runtime_dependency 'futex', '~> 0.8'
s.add_dependency 'futex', '~> 0.8'
# To query SOAP APIs
s.add_runtime_dependency 'savon', '~> 2.12'
s.add_dependency 'savon', '~> 2.15'
# To query Proxmox API
s.add_runtime_dependency 'proxmox', '~> 0.0'
s.add_dependency 'proxmox', '~> 0.0'
# To evaluate DSLs in a safe way
s.add_runtime_dependency 'cleanroom', '~> 1.0'
s.add_dependency 'cleanroom', '~> 1.0'
# To define schedules in a simple way
s.add_runtime_dependency 'ice_cube', '~> 0.16'
s.add_dependency 'ice_cube', '~> 0.17'
# To access Github API
s.add_runtime_dependency 'octokit', '~> 4.21'
s.add_dependency 'octokit', '~> 9.2'
# To read KeePass databases
s.add_runtime_dependency 'keepass_kpscript', '~> 1.0'
s.add_dependency 'keepass_kpscript', '~> 1.1'
# To protect passwords and secrets in memory
s.add_runtime_dependency 'secret_string', '~> 1.1'
s.add_dependency 'secret_string', '~> 1.1'
# To work-around a bug of IceCube dependency: https://stackoverflow.com/questions/27109766/undefined-method-delegate-for-capybaradslmodule
s.add_runtime_dependency 'activesupport', '~> 7.0'
s.add_dependency 'activesupport', '~> 8.0'

# Test framework
s.add_development_dependency 'rspec', '~> 3.8'
# Automatic semantic releasing
s.add_development_dependency 'sem_ver_components', '~> 0.0'
# Lint checker
s.add_development_dependency 'rubocop', '~> 1.16'
# Lint checker for rspec
s.add_development_dependency 'rubocop-rspec', '~> 2.4'
# Mock web responses when needed
s.add_development_dependency 'webmock', '~> 3.11'
s.metadata['rubygems_mfa_required'] = 'true'
end
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def without_bundled_env(&block)

# @return [Hash] Environment with all bundler-related variables removed
def current_unbundled_env
env = ENV.clone.to_hash
env = ENV.to_h
%w[
PATH
RUBYLIB
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def expose(name)
# []
# {:string=>"With to_hash"}
KWARGS_TYPES = %i[key keyreq]
private_constant :KWARGS_TYPES

#
# The list of exposed methods with kwargs.
Expand Down
2 changes: 1 addition & 1 deletion lib/hybrid_platforms_conductor/credentials.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def with_credentials_for(id, resource: nil)

provider ||= proc do |requested_resource, requester|
# Check environment variables
user = ENV["hpc_user_for_#{id}"]
user = ENV.fetch("hpc_user_for_#{id}", nil)
# Clone the password as we are going to treat it as a secret string that will be wiped out
password = ENV["hpc_password_for_#{id}"].dup
if user.nil? || user.empty? || password.nil? || password.empty?
Expand Down
6 changes: 3 additions & 3 deletions lib/hybrid_platforms_conductor/current_dir_monitor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ class << self
# * *module_to_decorate* (Module): The module including the method to decorate
# * *method_name* (Symbol): The method name to be decorated
def self.decorate_method(module_to_decorate, method_name)
original_method_name = "__hpc__#{method_name}__undecorated__".to_sym
original_method_name = :"__hpc__#{method_name}__undecorated__"
module_to_decorate.alias_method original_method_name, method_name
module_to_decorate.define_method(method_name) do |*args, &block|
module_to_decorate.define_method(method_name) do |*args, **kwargs, &block|
result = nil
CurrentDirMonitor.monitor.synchronize do
# puts "TID #{Thread.current.object_id} from #{caller[2]} - Current dir monitor taken from #{Dir.pwd}"
result = send(original_method_name, *args, &block)
result = send(original_method_name, *args, **kwargs, &block)
# puts "TID #{Thread.current.object_id} from #{caller[2]} - Current dir monitor released back to #{Dir.pwd}"
end
result
Expand Down
2 changes: 1 addition & 1 deletion lib/hybrid_platforms_conductor/deployer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ def deploy_on(*nodes_selectors)

# Check that we are allowed to deploy
unless @use_why_run
reason_for_interdiction = @services_handler.deploy_allowed?(
reason_for_interdiction = @services_handler.barrier_to_deploy(
services: services_to_deploy,
local_environment: @local_environment
)
Expand Down
2 changes: 1 addition & 1 deletion lib/hybrid_platforms_conductor/executable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def initialize(
#
# Result::
# * Boolean: Has a singleton been instantiated for this component?
define_method("#{component}_instantiated?".to_sym) do
define_method(:"#{component}_instantiated?") do
@instantiated_components.key?(component)
end

Expand Down
2 changes: 2 additions & 0 deletions lib/hybrid_platforms_conductor/hpc_plugins/cmdb/host_ip.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ def get_host_ip(_nodes, metadata)
# Number of threads max to use for getent calls
MAX_THREADS_GETENT = 32

private_constant :TIMEOUT_GETENT, :MAX_THREADS_GETENT

# Discover the real IPs associated to a list of hosts.
#
# Parameters::
Expand Down
2 changes: 2 additions & 0 deletions lib/hybrid_platforms_conductor/hpc_plugins/cmdb/host_keys.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ def get_host_keys(_nodes, metadata)
# Number of threads max to use for ssh-keyscan calls
MAX_THREADS_SSH_KEY_SCAN = 32

private_constant :TIMEOUT_SSH_KEYSCAN, :MAX_THREADS_SSH_KEY_SCAN

# Discover the host keys associated to a list of hosts.
#
# Parameters::
Expand Down
16 changes: 9 additions & 7 deletions lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def erb_context.private_binding
binding
end
variables.each do |var_name, var_value|
erb_context.instance_variable_set("@#{var_name}".to_sym, var_value)
erb_context.instance_variable_set(:"@#{var_name}", var_value)
end
ERB.new(@gateways[gateway_conf]).result(erb_context.private_binding)
end
Expand Down Expand Up @@ -131,8 +131,8 @@ def erb_context.private_binding
# [API] - @nodes_handler can be used
def init
# Default values
@ssh_user = ENV['hpc_ssh_user']
@ssh_user = ENV['USER'] if @ssh_user.nil? || @ssh_user.empty?
@ssh_user = ENV.fetch('hpc_ssh_user', nil)
@ssh_user = ENV.fetch('USER', nil) if @ssh_user.nil? || @ssh_user.empty?
if @ssh_user.nil? || @ssh_user.empty?
_exit_status, stdout = @cmd_runner.run_cmd 'whoami', log_to_stdout: log_debug?
@ssh_user = stdout.strip
Expand All @@ -141,7 +141,7 @@ def init
@ssh_strict_host_key_checking = true
@passwords = {}
@auth_password = false
@ssh_gateways_conf = ENV['hpc_ssh_gateways_conf'].nil? ? nil : ENV['hpc_ssh_gateways_conf'].to_sym
@ssh_gateways_conf = ENV['hpc_ssh_gateways_conf']&.to_sym
@ssh_gateway_user = ENV['hpc_ssh_gateway_user'].nil? ? 'ubradm' : ENV['hpc_ssh_gateway_user']
# The map of existing ssh directories that have been created, per node that can access them
# Array< String, Array<String> >
Expand Down Expand Up @@ -333,13 +333,13 @@ def remote_copy(from, to, sudo: false, owner: nil, group: nil)
#{File.basename(from)} | \
#{ssh_exec} \
#{ssh_url} \
\"#{need_sudo ? @actions_executor.sudo_prefix(@node) : ''}tar \
"#{need_sudo ? @actions_executor.sudo_prefix(@node) : ''}tar \
--extract \
--gunzip \
--file - \
--directory #{to} \
--owner root \
\"
"
EO_BASH
end
end
Expand Down Expand Up @@ -415,7 +415,7 @@ def ssh_config(ssh_exec: 'ssh', known_hosts_file: nil, nodes: @nodes_handler.kno
User #{@ssh_user}
# Default control socket path to be used when multiplexing SSH connections
ControlPath #{control_master_file('%h', '%p', '%r')}
#{open_ssh_major_version >= 7 ? 'PubkeyAcceptedKeyTypes +ssh-dss' : ''}
#{open_ssh_major_version >= 7 && open_ssh_major_version < 9 ? 'PubkeyAcceptedKeyTypes +ssh-dss' : ''}
#{known_hosts_file.nil? ? '' : "UserKnownHostsFile #{known_hosts_file}"}
#{@ssh_strict_host_key_checking ? '' : 'StrictHostKeyChecking no'}

Expand Down Expand Up @@ -492,6 +492,8 @@ def ssh_exec_for(node)
# Time in seconds to wait between different retries because system is booting up
WAIT_TIME_FOR_BOOT = 10

private_constant :MAX_THREADS_CONTROL_MASTER, :MAX_RETRIES_FOR_BOOT, :WAIT_TIME_FOR_BOOT

# Open an SSH control master to multiplex connections to a given list of nodes.
# This method is re-entrant and reuses the same control masters.
# It is multi-processes:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ def recipe_usage(cookbook_dir, cookbook, recipe)
flatten(1)
# Check for some helpers we know include some recipes
@config.known_helpers_including_recipes.each do |helper_name, used_recipes_by_helper|
if recipe_content =~ Regexp.new(/(\W|^)#{Regexp.escape(helper_name)}(\W|$)/)
if recipe_content =~ /(\W|^)#{Regexp.escape(helper_name)}(\W|$)/
used_recipes.concat(used_recipes_by_helper.map { |recipe_def| @platform.decode_recipe(recipe_def) })
used_recipes.uniq!
end
Expand Down Expand Up @@ -214,6 +214,7 @@ def known_resources
INVALID_LIBRARY_METHODS = [
'initialize'
]
private_constant :INVALID_LIBRARY_METHODS

# Get the user defined library methods, per cookbook.
# Keep a memory cache of it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class YamlInventory < HybridPlatformsConductor::PlatformHandler
def init
# This method is called when initializing a new instance of this platform handler, for a given repository.
inv_file = "#{@repository_path}/inventory.yaml"
@inventory = File.exist?(inv_file) ? YAML.safe_load(File.read(inv_file)) : {}
@inventory = File.exist?(inv_file) ? YAML.safe_load_file(inv_file) : {}
end

# Get the list of known nodes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,7 @@ def release_lxc_container(vm_id)
# Consider that it is to be used on the following patterns: (config|create|destroy)_<ID>.json
# So remaining length is 255 - 13 = 242 characters.
MAX_FILE_ID_SIZE = 242
private_constant :MAX_FILE_ID_SIZE

# Get an ID unique for this node/environment and that can be used in file names.
#
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,7 @@ def vm_state(pve_node, vm_id)

# Timeout in seconds before giving up on a lock
LOCK_TIMEOUT = 30
private_constant :LOCK_TIMEOUT

# Get the IP address of a given LXC container
#
Expand Down
Loading