hybrid_platforms_conductor 33.3.0 → 33.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.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/README.md +31 -2
  4. data/docs/config_dsl.md +43 -0
  5. data/lib/hybrid_platforms_conductor/bitbucket.rb +134 -90
  6. data/lib/hybrid_platforms_conductor/common_config_dsl/bitbucket.rb +12 -44
  7. data/lib/hybrid_platforms_conductor/common_config_dsl/github.rb +9 -31
  8. data/lib/hybrid_platforms_conductor/confluence.rb +93 -88
  9. data/lib/hybrid_platforms_conductor/credentials.rb +112 -95
  10. data/lib/hybrid_platforms_conductor/deployer.rb +2 -2
  11. data/lib/hybrid_platforms_conductor/github.rb +39 -0
  12. data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/proxmox.rb +4 -2
  13. data/lib/hybrid_platforms_conductor/hpc_plugins/report/confluence.rb +3 -1
  14. data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/keepass.rb +2 -1
  15. data/lib/hybrid_platforms_conductor/hpc_plugins/secrets_reader/thycotic.rb +3 -1
  16. data/lib/hybrid_platforms_conductor/hpc_plugins/test/bitbucket_conf.rb +4 -1
  17. data/lib/hybrid_platforms_conductor/hpc_plugins/test/github_ci.rb +4 -1
  18. data/lib/hybrid_platforms_conductor/hpc_plugins/test/jenkins_ci_conf.rb +6 -2
  19. data/lib/hybrid_platforms_conductor/hpc_plugins/test/jenkins_ci_masters_ok.rb +6 -2
  20. data/lib/hybrid_platforms_conductor/hpc_plugins/test_report/confluence.rb +3 -1
  21. data/lib/hybrid_platforms_conductor/logger_helpers.rb +7 -1
  22. data/lib/hybrid_platforms_conductor/thycotic.rb +80 -75
  23. data/lib/hybrid_platforms_conductor/version.rb +1 -1
  24. data/spec/hybrid_platforms_conductor_test.rb +6 -0
  25. data/spec/hybrid_platforms_conductor_test/api/credentials_spec.rb +247 -0
  26. data/spec/hybrid_platforms_conductor_test/api/deployer/secrets_reader_plugins/keepass_spec.rb +280 -319
  27. data/spec/hybrid_platforms_conductor_test/api/deployer/secrets_reader_plugins/thycotic_spec.rb +2 -2
  28. data/spec/hybrid_platforms_conductor_test/api/tests_runner/test_plugins/bitbucket_conf_spec.rb +49 -69
  29. data/spec/hybrid_platforms_conductor_test/api/tests_runner/test_plugins/github_ci_spec.rb +29 -39
  30. metadata +18 -2
@@ -106,10 +106,10 @@ module HybridPlatformsConductor
106
106
 
107
107
  end
108
108
 
109
- include LoggerHelpers
110
-
111
109
  Config.extend_config_dsl_with ConfigDSLExtension, :init_deployer_config
112
110
 
111
+ include LoggerHelpers
112
+
113
113
  # Do we use why-run mode while deploying? [default = false]
114
114
  # Boolean
115
115
  attr_accessor :use_why_run
@@ -0,0 +1,39 @@
1
+ require 'octokit'
2
+ require 'hybrid_platforms_conductor/credentials'
3
+
4
+ module HybridPlatformsConductor
5
+
6
+ # Mixin used to access Github API
7
+ module Github
8
+
9
+ include Credentials
10
+
11
+ # Iterate over each Github repository
12
+ #
13
+ # Parameters::
14
+ # * Proc: Code called for each Github repository:
15
+ # * Parameters::
16
+ # * *github* (Octokit::Client): The client instance accessing the Github API
17
+ # * *repo_info* (Hash<Symbol, Object>): The repository info:
18
+ # * *name* (String): Repository name.
19
+ # * *slug* (String): Repository slug.
20
+ def for_each_github_repo
21
+ @config.known_github_repos.each do |repo_info|
22
+ Octokit.configure do |c|
23
+ c.api_endpoint = repo_info[:url]
24
+ end
25
+ with_credentials_for(:github, resource: repo_info[:url]) do |_github_user, github_token|
26
+ client = Octokit::Client.new(access_token: github_token)
27
+ (repo_info[:repos] == :all ? client.repositories(repo_info[:user]).map { |repo| repo[:name] } : repo_info[:repos]).each do |name|
28
+ yield client, {
29
+ name: name,
30
+ slug: "#{repo_info[:user]}/#{name}"
31
+ }
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ end
38
+
39
+ end
@@ -263,6 +263,8 @@ module HybridPlatformsConductor
263
263
 
264
264
  private
265
265
 
266
+ include Credentials
267
+
266
268
  # Connect to the Proxmox API
267
269
  #
268
270
  # Parameters::
@@ -273,7 +275,7 @@ module HybridPlatformsConductor
273
275
  url = proxmox_test_info[:api_url]
274
276
  raise 'No Proxmox server defined' if url.nil?
275
277
 
276
- Credentials.with_credentials_for(:proxmox, @logger, @logger_stderr, url: url) do |user, password|
278
+ with_credentials_for(:proxmox, resource: url) do |user, password|
277
279
  log_debug "[ #{@node}/#{@environment} ] - Connect to Proxmox #{url}"
278
280
  proxmox_logs = StringIO.new
279
281
  proxmox = ::Proxmox::Proxmox.new(
@@ -415,7 +417,7 @@ module HybridPlatformsConductor
415
417
  extra_files[config_file] = './proxmox/config'
416
418
  cmd << " --config ./proxmox/config/#{File.basename(config_file)}"
417
419
  stdout = nil
418
- Credentials.with_credentials_for(:proxmox, @logger, @logger_stderr, url: proxmox_test_info[:api_url]) do |user, password|
420
+ with_credentials_for(:proxmox, resource: proxmox_test_info[:api_url]) do |user, password|
419
421
  # To avoid too fine concurrent accesses on the sync node file system, make sure all threads of our process wait for their turn to upload their files.
420
422
  # Otherwise there is a small probability that a directory scp makes previously copied files inaccessible for a short period of time.
421
423
  self.class.proxmox_waiter_files_mutex.synchronize do
@@ -14,6 +14,8 @@ module HybridPlatformsConductor
14
14
 
15
15
  extend_config_dsl_with CommonConfigDsl::Confluence, :init_confluence
16
16
 
17
+ include HybridPlatformsConductor::Confluence
18
+
17
19
  # Give the list of supported locales by this report generator
18
20
  # [API] - This method is mandatory.
19
21
  #
@@ -34,7 +36,7 @@ module HybridPlatformsConductor
34
36
  if confluence_info
35
37
  if confluence_info[:inventory_report_page_id]
36
38
  @nodes = nodes
37
- HybridPlatformsConductor::Confluence.with_confluence(confluence_info[:url], @logger, @logger_stderr) do |confluence|
39
+ with_confluence(confluence_info[:url]) do |confluence|
38
40
  confluence.update_page(confluence_info[:inventory_report_page_id], render('confluence_inventory'))
39
41
  end
40
42
  out "Inventory report Confluence page updated. Please visit #{confluence_info[:url]}/pages/viewpage.action?pageId=#{confluence_info[:inventory_report_page_id]}"
@@ -17,6 +17,7 @@ module HybridPlatformsConductor
17
17
  class Keepass < HybridPlatformsConductor::SecretsReader
18
18
 
19
19
  include SafeMerge
20
+ include Credentials
20
21
 
21
22
  # Extend the Config DSL
22
23
  module ConfigDSLExtension
@@ -84,7 +85,7 @@ module HybridPlatformsConductor
84
85
  unless @secrets.key?(secret_id)
85
86
  raise 'Missing KPScript configuration. Please use use_kpscript_from to set it.' if @config.kpscript.nil?
86
87
 
87
- Credentials.with_credentials_for(:keepass, @logger, @logger_stderr) do |_user, password|
88
+ with_credentials_for(:keepass, resource: keepass_secrets_info[:database]) do |_user, password|
88
89
  Tempfile.create('hpc_keepass') do |xml_file|
89
90
  key_file = ENV['hpc_key_file_for_keepass']
90
91
  password_enc = ENV['hpc_password_enc_for_keepass']
@@ -42,6 +42,8 @@ module HybridPlatformsConductor
42
42
 
43
43
  Config.extend_config_dsl_with ConfigDSLExtension, :init_thycotic_config
44
44
 
45
+ include HybridPlatformsConductor::Thycotic
46
+
45
47
  # Return secrets for a given service to be deployed on a node.
46
48
  # [API] - This method is mandatory
47
49
  # [API] - The following API components are accessible:
@@ -62,7 +64,7 @@ module HybridPlatformsConductor
62
64
  @nodes_handler.select_confs_for_node(node, @config.thycotic_secrets).each do |thycotic_secrets_info|
63
65
  server_id = "#{thycotic_secrets_info[:thycotic_url]}:#{thycotic_secrets_info[:secret_id]}"
64
66
  unless @secrets.key?(server_id)
65
- HybridPlatformsConductor::Thycotic.with_thycotic(thycotic_secrets_info[:thycotic_url], @logger, @logger_stderr) do |thycotic|
67
+ with_thycotic(thycotic_secrets_info[:thycotic_url]) do |thycotic|
66
68
  secret_file_item_id = thycotic.get_secret(thycotic_secrets_info[:secret_id]).dig(:secret, :items, :secret_item, :id)
67
69
  raise "Unable to fetch secret file ID #{thycotic_secrets_info[:secret_id]} from #{thycotic_secrets_info[:thycotic_url]}" if secret_file_item_id.nil?
68
70
 
@@ -1,4 +1,5 @@
1
1
  require 'git'
2
+ require 'hybrid_platforms_conductor/bitbucket'
2
3
  require 'hybrid_platforms_conductor/common_config_dsl/bitbucket'
3
4
 
4
5
  module HybridPlatformsConductor
@@ -12,9 +13,11 @@ module HybridPlatformsConductor
12
13
 
13
14
  extend_config_dsl_with CommonConfigDsl::Bitbucket, :init_bitbucket
14
15
 
16
+ include HybridPlatformsConductor::Bitbucket
17
+
15
18
  # Check my_test_plugin.rb.sample documentation for signature details.
16
19
  def test
17
- @config.for_each_bitbucket_repo do |bitbucket, repo_info|
20
+ for_each_bitbucket_repo do |bitbucket, repo_info|
18
21
  # Test repo_info
19
22
  repo_id = "#{repo_info[:project]}/#{repo_info[:name]}"
20
23
  settings_pr = bitbucket.settings_pr(repo_info[:project], repo_info[:name])
@@ -1,3 +1,4 @@
1
+ require 'hybrid_platforms_conductor/github'
1
2
  require 'hybrid_platforms_conductor/common_config_dsl/github'
2
3
 
3
4
  module HybridPlatformsConductor
@@ -11,9 +12,11 @@ module HybridPlatformsConductor
11
12
 
12
13
  extend_config_dsl_with CommonConfigDsl::Github, :init_github
13
14
 
15
+ include HybridPlatformsConductor::Github
16
+
14
17
  # Check my_test_plugin.rb.sample documentation for signature details.
15
18
  def test
16
- @config.for_each_github_repo do |client, repo_info|
19
+ for_each_github_repo do |client, repo_info|
17
20
  log_debug "Checking CI for Github repository #{repo_info[:slug]}"
18
21
  last_status = client.repository_workflow_runs(repo_info[:slug])[:workflow_runs].
19
22
  select { |run| run[:head_branch] == 'master' }.
@@ -1,6 +1,7 @@
1
1
  require 'open-uri'
2
2
  require 'nokogiri'
3
3
  require 'hybrid_platforms_conductor/credentials'
4
+ require 'hybrid_platforms_conductor/bitbucket'
4
5
  require 'hybrid_platforms_conductor/common_config_dsl/bitbucket'
5
6
 
6
7
  module HybridPlatformsConductor
@@ -14,13 +15,16 @@ module HybridPlatformsConductor
14
15
 
15
16
  extend_config_dsl_with CommonConfigDsl::Bitbucket, :init_bitbucket
16
17
 
18
+ include Credentials
19
+ include HybridPlatformsConductor::Bitbucket
20
+
17
21
  # Check my_test_plugin.rb.sample documentation for signature details.
18
22
  def test
19
- @config.for_each_bitbucket_repo do |bitbucket, repo_info|
23
+ for_each_bitbucket_repo do |bitbucket, repo_info|
20
24
  if repo_info[:jenkins_ci_url].nil?
21
25
  error "Repository #{repo_info[:name]} does not have any Jenkins CI URL configured."
22
26
  else
23
- Credentials.with_credentials_for(:jenkins_ci, @logger, @logger_stderr, url: repo_info[:jenkins_ci_url]) do |jenkins_user, jenkins_password|
27
+ with_credentials_for(:jenkins_ci, resource: repo_info[:jenkins_ci_url]) do |jenkins_user, jenkins_password|
24
28
  # Get its config
25
29
  doc = Nokogiri::XML(URI.parse("#{repo_info[:jenkins_ci_url]}/config.xml").open(http_basic_authentication: [jenkins_user, jenkins_password]).read)
26
30
  # Check that this job builds the correct Bitbucket repository
@@ -1,5 +1,6 @@
1
1
  require 'json'
2
2
  require 'hybrid_platforms_conductor/credentials'
3
+ require 'hybrid_platforms_conductor/bitbucket'
3
4
  require 'hybrid_platforms_conductor/common_config_dsl/bitbucket'
4
5
 
5
6
  module HybridPlatformsConductor
@@ -13,6 +14,9 @@ module HybridPlatformsConductor
13
14
 
14
15
  extend_config_dsl_with CommonConfigDsl::Bitbucket, :init_bitbucket
15
16
 
17
+ include Credentials
18
+ include HybridPlatformsConductor::Bitbucket
19
+
16
20
  SUCCESS_STATUSES = [
17
21
  # Add nil as the status of a currently running job (which is always the case for hybrid-platforms) is null
18
22
  nil,
@@ -23,12 +27,12 @@ module HybridPlatformsConductor
23
27
 
24
28
  # Check my_test_plugin.rb.sample documentation for signature details.
25
29
  def test
26
- @config.for_each_bitbucket_repo do |_bitbucket, repo_info|
30
+ for_each_bitbucket_repo do |_bitbucket, repo_info|
27
31
  if repo_info[:jenkins_ci_url].nil?
28
32
  error "Repository #{repo_info[:name]} does not have any Jenkins CI URL configured."
29
33
  else
30
34
  master_info_url = "#{repo_info[:jenkins_ci_url]}/job/master/api/json"
31
- Credentials.with_credentials_for(:jenkins_ci, @logger, @logger_stderr, url: master_info_url) do |jenkins_user, jenkins_password|
35
+ with_credentials_for(:jenkins_ci, resource: master_info_url) do |jenkins_user, jenkins_password|
32
36
  # Get the master branch info from the API
33
37
  master_info = JSON.parse(URI.parse(master_info_url).open(http_basic_authentication: [jenkins_user, jenkins_password]).read)
34
38
  # Get the last build's URL
@@ -14,6 +14,8 @@ module HybridPlatformsConductor
14
14
 
15
15
  extend_config_dsl_with CommonConfigDsl::Confluence, :init_confluence
16
16
 
17
+ include HybridPlatformsConductor::Confluence
18
+
17
19
  # Maximum errors to be reported by item
18
20
  MAX_ERROR_ITEMS_DISPLAYED = 10
19
21
 
@@ -28,7 +30,7 @@ module HybridPlatformsConductor
28
30
  confluence_info = @config.confluence_info
29
31
  if confluence_info
30
32
  if confluence_info[:tests_report_page_id]
31
- HybridPlatformsConductor::Confluence.with_confluence(confluence_info[:url], @logger, @logger_stderr) do |confluence|
33
+ with_confluence(confluence_info[:url]) do |confluence|
32
34
  # Get previous percentages for the evolution
33
35
  @previous_success_percentages = confluence.page_storage_format(confluence_info[:tests_report_page_id]).
34
36
  at('h1:contains("Evolution")').
@@ -88,7 +88,13 @@ module HybridPlatformsConductor
88
88
  define_method("log_#{level}") do |message|
89
89
  (LEVELS_TO_STDERR.include?(level) ? @logger_stderr : @logger).send(
90
90
  level,
91
- defined?(@log_component) ? @log_component : self.class.name.split('::').last
91
+ if defined?(@log_component)
92
+ @log_component
93
+ else
94
+ # Handle the case when the class is unnamed
95
+ class_name = self.class.name
96
+ class_name.nil? ? '<Unnamed class>' : class_name.split('::').last
97
+ end
92
98
  ) { message }
93
99
  end
94
100
  end
@@ -5,95 +5,100 @@ require 'hybrid_platforms_conductor/logger_helpers'
5
5
 
6
6
  module HybridPlatformsConductor
7
7
 
8
- # Gives ways to query the Thycotic SOAP API at a given URL
9
- class Thycotic
8
+ # Mixin giving ways to query the Thycotic SOAP API at a given URL
9
+ module Thycotic
10
10
 
11
- include LoggerHelpers
11
+ include Credentials
12
12
 
13
13
  # Provide a Thycotic connector, and make sure the password is being cleaned when exiting.
14
14
  #
15
15
  # Parameters::
16
16
  # * *thycotic_url* (String): The Thycotic URL
17
- # * *logger* (Logger): Logger to be used
18
- # * *logger_stderr* (Logger): Logger to be used for stderr
19
17
  # * *domain* (String): Domain to use for authentication to Thycotic [default: ENV['hpc_domain_for_thycotic']]
20
18
  # * Proc: Code called with the Thyctotic instance.
21
- # * *thycotic* (Thyctotic): The Thyctotic instance to use.
22
- def self.with_thycotic(thycotic_url, logger, logger_stderr, domain: ENV['hpc_domain_for_thycotic'])
23
- Credentials.with_credentials_for(:thycotic, logger, logger_stderr, url: thycotic_url) do |thycotic_user, thycotic_password|
24
- yield Thycotic.new(thycotic_url, thycotic_user, thycotic_password, domain: domain, logger: logger, logger_stderr: logger_stderr)
19
+ # * *thycotic* (ThyctoticApi): The Thycotic instance to use.
20
+ def with_thycotic(thycotic_url, domain: ENV['hpc_domain_for_thycotic'])
21
+ with_credentials_for(:thycotic, resource: thycotic_url) do |thycotic_user, thycotic_password|
22
+ yield ThycoticApi.new(thycotic_url, thycotic_user, thycotic_password, domain: domain, logger: @logger, logger_stderr: @logger_stderr)
25
23
  end
26
24
  end
27
25
 
28
- # Constructor
29
- #
30
- # Parameters::
31
- # * *url* (String): URL of the Thycotic Secret Server
32
- # * *user* (String): User name to be used to connect to Thycotic
33
- # * *password* (String): Password to be used to connect to Thycotic
34
- # * *domain* (String): Domain to use for authentication to Thycotic [default: ENV['hpc_domain_for_thycotic']]
35
- # * *logger* (Logger): Logger to be used [default: Logger.new(STDOUT)]
36
- # * *logger_stderr* (Logger): Logger to be used for stderr [default: Logger.new(STDERR)]
37
- def initialize(
38
- url,
39
- user,
40
- password,
41
- domain: ENV['hpc_domain_for_thycotic'],
42
- logger: Logger.new($stdout),
43
- logger_stderr: Logger.new($stderr)
44
- )
45
- init_loggers(logger, logger_stderr)
46
- # Get a token to this SOAP API
47
- @client = Savon.client(
48
- wsdl: "#{url}/webservices/SSWebservice.asmx?wsdl",
49
- ssl_verify_mode: :none,
50
- logger: @logger,
51
- log: log_debug?
26
+ # Access to the Thycotic API
27
+ class ThycoticApi
28
+
29
+ include LoggerHelpers
30
+
31
+ # Constructor
32
+ #
33
+ # Parameters::
34
+ # * *url* (String): URL of the Thycotic Secret Server
35
+ # * *user* (String): User name to be used to connect to Thycotic
36
+ # * *password* (String): Password to be used to connect to Thycotic
37
+ # * *domain* (String): Domain to use for authentication to Thycotic [default: ENV['hpc_domain_for_thycotic']]
38
+ # * *logger* (Logger): Logger to be used [default: Logger.new(STDOUT)]
39
+ # * *logger_stderr* (Logger): Logger to be used for stderr [default: Logger.new(STDERR)]
40
+ def initialize(
41
+ url,
42
+ user,
43
+ password,
44
+ domain: ENV['hpc_domain_for_thycotic'],
45
+ logger: Logger.new($stdout),
46
+ logger_stderr: Logger.new($stderr)
52
47
  )
53
- @token = @client.call(
54
- :authenticate,
55
- message: {
56
- username: user,
57
- password: password,
58
- domain: domain
59
- }
60
- ).to_hash.dig(:authenticate_response, :authenticate_result, :token)
61
- raise "Unable to get token from SOAP authentication to #{url}" if @token.nil?
62
- end
48
+ init_loggers(logger, logger_stderr)
49
+ # Get a token to this SOAP API
50
+ @client = Savon.client(
51
+ wsdl: "#{url}/webservices/SSWebservice.asmx?wsdl",
52
+ ssl_verify_mode: :none,
53
+ logger: @logger,
54
+ log: log_debug?
55
+ )
56
+ @token = @client.call(
57
+ :authenticate,
58
+ message: {
59
+ username: user,
60
+ password: password,
61
+ domain: domain
62
+ }
63
+ ).to_hash.dig(:authenticate_response, :authenticate_result, :token)
64
+ raise "Unable to get token from SOAP authentication to #{url}" if @token.nil?
65
+ end
63
66
 
64
- # Return secret corresponding to a given secret ID
65
- #
66
- # Parameters::
67
- # * *secret_id* (Object): The secret ID
68
- # Result::
69
- # * Hash: The corresponding API result
70
- def get_secret(secret_id)
71
- @client.call(
72
- :get_secret,
73
- message: {
74
- token: @token,
75
- secretId: secret_id
76
- }
77
- ).to_hash.dig(:get_secret_response, :get_secret_result)
78
- end
67
+ # Return secret corresponding to a given secret ID
68
+ #
69
+ # Parameters::
70
+ # * *secret_id* (Object): The secret ID
71
+ # Result::
72
+ # * Hash: The corresponding API result
73
+ def get_secret(secret_id)
74
+ @client.call(
75
+ :get_secret,
76
+ message: {
77
+ token: @token,
78
+ secretId: secret_id
79
+ }
80
+ ).to_hash.dig(:get_secret_response, :get_secret_result)
81
+ end
82
+
83
+ # Get a file attached to a given secret
84
+ #
85
+ # Parameters::
86
+ # * *secret_id* (Object): The secret ID
87
+ # * *secret_item_id* (Object): The secret item id
88
+ # Result::
89
+ # * String or nil: The file content, or nil if none
90
+ def download_file_attachment_by_item_id(secret_id, secret_item_id)
91
+ encoded_file = @client.call(
92
+ :download_file_attachment_by_item_id,
93
+ message: {
94
+ token: @token,
95
+ secretId: secret_id,
96
+ secretItemId: secret_item_id
97
+ }
98
+ ).to_hash.dig(:download_file_attachment_by_item_id_response, :download_file_attachment_by_item_id_result, :file_attachment)
99
+ encoded_file.nil? ? nil : Base64.decode64(encoded_file)
100
+ end
79
101
 
80
- # Get a file attached to a given secret
81
- #
82
- # Parameters::
83
- # * *secret_id* (Object): The secret ID
84
- # * *secret_item_id* (Object): The secret item id
85
- # Result::
86
- # * String or nil: The file content, or nil if none
87
- def download_file_attachment_by_item_id(secret_id, secret_item_id)
88
- encoded_file = @client.call(
89
- :download_file_attachment_by_item_id,
90
- message: {
91
- token: @token,
92
- secretId: secret_id,
93
- secretItemId: secret_item_id
94
- }
95
- ).to_hash.dig(:download_file_attachment_by_item_id_response, :download_file_attachment_by_item_id_result, :file_attachment)
96
- encoded_file.nil? ? nil : Base64.decode64(encoded_file)
97
102
  end
98
103
 
99
104
  end
@@ -1,5 +1,5 @@
1
1
  module HybridPlatformsConductor
2
2
 
3
- VERSION = '33.3.0'
3
+ VERSION = '33.4.0'
4
4
 
5
5
  end