cem_acpt 0.10.3 → 0.10.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2efdc497a91addf155de008bebfc7062fa1bac0336be8c932dfe660a6f9a2322
4
- data.tar.gz: 7bcb94758bd21aa8f826405a6620d42529b621f4194bb2ed1ca067c159dfeefa
3
+ metadata.gz: dc299de4494abd1da48700f76b43223cb05aae1cb8c26235757e31e909bd6c2f
4
+ data.tar.gz: 32948e52808bd436695d69cf0c0bf2c5ccd31ada90ed401341c9c73e00653c6b
5
5
  SHA512:
6
- metadata.gz: 7c4c6c1f60167444e9f7b2735c6f12fb4258fa2c37b4d2552d5e4d2a3a1d25d555aa38a15592c22a9784fa7430789bb18796f50185e60248bdd7c48227be49d2
7
- data.tar.gz: '09a2205577b3f8542dac2f849f769512978b64b044a97c2ec8a731e46171be0116f86e3b4857bc78f6e30f381a7fe7a2256991856b33d225f121e6bf95f09bb8'
6
+ metadata.gz: 2a916fec7be6263243deeb60eac717ac097e0c477359b8abe71c374ef68880ab1608ff48859339074d97e8ae4592b9da491b5ff76b61f842a60712a9efcd1f75
7
+ data.tar.gz: e4e3b465b224f8f1431867ae19b6bee5161a91248168fd8e0c8c221c6ffa2c6caf3e2bf45a2a54e139e02069e45bc1dc7beacbddbe7fa11c15bf269811c98c54
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cem_acpt (0.10.3)
4
+ cem_acpt (0.10.5)
5
5
  async-http (>= 0.60, < 0.70)
6
6
  bcrypt_pbkdf (>= 1.0, < 2.0)
7
7
  deep_merge (>= 1.2, < 2.0)
@@ -3,6 +3,7 @@
3
3
  require 'async'
4
4
  require 'async/barrier'
5
5
  require 'async/http/internet'
6
+ require_relative 'logging'
6
7
 
7
8
  module CemAcpt
8
9
  # Provides a way to register actions to be executed if the registered
@@ -12,7 +13,7 @@ module CemAcpt
12
13
  class Action
13
14
  attr_reader :name, :order
14
15
 
15
- def initialize(name, order = 0, &block)
16
+ def initialize(name, order: 0, &block)
16
17
  @name = name.to_sym
17
18
  @order = order
18
19
  @block = block
@@ -42,7 +43,7 @@ module CemAcpt
42
43
  # order of the associated action groups' execution order. Defaults to 0.
43
44
  # @param block [Proc] the block to be executed
44
45
  def register_action(name, order: 0, &block)
45
- new_action = Action.new(name, order, &block)
46
+ new_action = Action.new(name, order: order, &block)
46
47
  @actions << new_action
47
48
  sort!
48
49
  self # return self to allow chaining
@@ -119,6 +120,8 @@ module CemAcpt
119
120
  class << self
120
121
  attr_reader :config
121
122
 
123
+ include CemAcpt::Logging
124
+
122
125
  # Configures the Actions module.
123
126
  # @param world_config [CemAcpt::Config::Base] the current config for the "world"
124
127
  # @yield [CemAcpt::Actions::ActionConfig] the config object for the Actions module
@@ -134,12 +137,14 @@ module CemAcpt
134
137
  # @option opts [Hash] :context the context to be passed to the actions
135
138
  # @return [Array] the results of the actions
136
139
  def execute(**opts)
140
+ logger.debug('CemAcpt::Actions') { "Executing registered actions with opts: #{opts}" }
137
141
  context = opts[:context] || {}
138
142
  ordered_groups = config.groups.values.sort_by(&:order)
139
143
  ordered_groups.each_with_object([]) do |group, results|
140
144
  actions = group.filter_actions
141
145
  next if actions.empty?
142
146
 
147
+ logger.debug('CemAcpt::Actions') { "Executing action group: #{group.name}" }
143
148
  context[:group] = group.name
144
149
  context[:actions] = actions.map(&:name)
145
150
  if group.async
@@ -127,6 +127,7 @@ module CemAcpt
127
127
  end
128
128
  ensure
129
129
  @items = (@cmd_output['items'] || []).map { |item| OutputItem.new(item) }
130
+ @error_obj ||= @items.find(&:error?)&.error
130
131
  if @items.empty? && @error_obj.nil? && @strict
131
132
  err = RuntimeError.new("Cannot set results, no error or items found for cmd_output:\n#{cmd_output}")
132
133
  @error_obj = err
@@ -158,11 +158,11 @@ module CemAcpt
158
158
  end
159
159
 
160
160
  def error?
161
- @command_result.is_a?(CemAcpt::Bolt::BoltActionError)
161
+ @command_result.error?
162
162
  end
163
163
 
164
164
  def error
165
- @command_result if error?
165
+ @command_result.error if error?
166
166
  end
167
167
 
168
168
  def to_h
@@ -378,7 +378,7 @@ module CemAcpt
378
378
  def load_test_data(run_data)
379
379
  run_data[:test_data].each_with_object({}) do |tdata, h|
380
380
  test_name = tdata[:test_name]
381
- next if h.key?(test_name) || tdata[:bolt_test].nil?
381
+ next if h.key?(test_name)
382
382
 
383
383
  logger.debug('CemAcpt::Bolt::Tests') { "Loading test data for test #{test_name}" }
384
384
  begin
@@ -53,6 +53,8 @@ module CemAcpt
53
53
  return false unless File.exist?(path)
54
54
 
55
55
  disk_contents = CemAcpt::Utils::Files.read(path, log_prefix: self.class.to_s)
56
+ return false unless disk_contents.is_a?(Hash)
57
+
56
58
  @hash.sort.to_h == disk_contents.sort.to_h
57
59
  end
58
60
 
@@ -60,6 +62,8 @@ module CemAcpt
60
62
  return false unless File.exist?(path)
61
63
 
62
64
  disk_contents = CemAcpt::Utils::Files.read(path, log_prefix: self.class.to_s)
65
+ return false unless disk_contents.is_a?(Hash)
66
+
63
67
  @hash.sort.to_h >= disk_contents.sort.to_h
64
68
  end
65
69
 
@@ -67,6 +71,8 @@ module CemAcpt
67
71
  return false unless File.exist?(path)
68
72
 
69
73
  disk_contents = CemAcpt::Utils::Files.read(path, log_prefix: self.class.to_s)
74
+ return false unless disk_contents.is_a?(Hash)
75
+
70
76
  @hash.sort.to_h <= disk_contents.sort.to_h
71
77
  end
72
78
  end
@@ -4,13 +4,14 @@ module CemAcpt
4
4
  module Goss
5
5
  module Api
6
6
  class ActionResponse
7
- attr_reader :host, :action, :body
7
+ attr_reader :host, :action, :body, :metadata
8
8
 
9
9
  def initialize(host, action, status, body)
10
10
  @host = host
11
11
  @action = action
12
12
  @status = status
13
13
  @body = body
14
+ @metadata = {}
14
15
  end
15
16
 
16
17
  def to_s
@@ -27,6 +28,7 @@ module CemAcpt
27
28
  action: action,
28
29
  status: @status,
29
30
  body: @body,
31
+ metadata: @metadata,
30
32
  }
31
33
  end
32
34
 
@@ -40,11 +42,11 @@ module CemAcpt
40
42
  end
41
43
 
42
44
  def error
43
- results.find(&:error) || StandardError.new('Unknown error')
45
+ results.find(&:error)
44
46
  end
45
47
 
46
48
  def error?
47
- !success?
49
+ !success? && !error.nil?
48
50
  end
49
51
 
50
52
  def results
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'async'
4
- require 'async/barrier'
5
- require 'async/http/internet'
6
3
  require 'json'
7
4
  require_relative 'api/action_response'
8
5
  require_relative '../logging'
@@ -31,64 +28,37 @@ module CemAcpt
31
28
  URI("http://#{host}:#{ACTIONS[action.to_sym]}")
32
29
  end
33
30
 
34
- # Run the specified actions against the specified hosts asynchronously.
35
- # @param context [Hash] The context for the test run.
36
- # @option context [Array<String>] :hosts The hosts to run the actions against. Each
37
- # host should be a public IP address or a DNS-resolvable name.
38
- # @option context [Queue] :results The queue to push the results to.
39
- # @option [Array<String>] :actions The actions to run.
40
- # @return [Queue] The queue of results.
41
- def run_actions_async(context = {})
42
- actions = context[:actions] || []
43
- hosts = context[:hosts] || []
44
- results = context[:results] || Queue.new
45
- raise ArgumentError, 'hosts must be an Array' unless hosts.is_a?(Array)
46
- raise ArgumentError, 'results must be a Queue-like object implementing #<<' unless results.respond_to?(:<<)
47
- raise ArgumentError, 'actions must be an Array' unless actions.is_a?(Array)
48
-
49
- actions.map!(&:to_sym)
50
- actions.select! { |action| ACTIONS.key?(action) }
51
- logger.info('CemAcpt::Goss::Api') do
52
- "Running test actions #{actions.join(', ')} against #{hosts.size} host(s)..."
53
- end
54
- task = Async do
55
- internet = Async::HTTP::Internet.new
56
- barrier = Async::Barrier.new
57
- barrier.async do
58
- hosts.each do |host|
59
- actions.each do |action|
60
- results << run_action(internet, host, action)
61
- end
62
- end
63
- end
64
- barrier.wait
65
- ensure
66
- internet&.close
31
+ # Get the JSON response from the specified URI.
32
+ # @param uri [URI] The URI to get the JSON response from.
33
+ # @param internet [nil, Async::HTTP::Internet] The object to use for the request. If nil,
34
+ # Net::HTTP will be used.
35
+ # @return [Array] The status code and the parsed JSON response body.
36
+ def get(uri, internet = nil)
37
+ if internet.nil?
38
+ require 'net/http'
39
+ response = Net::HTTP.get_response(uri)
40
+ [response.code, JSON.parse(response.body)]
41
+ else
42
+ response = internet.get(uri.to_s)
43
+ [response.status, JSON.parse(response.read)]
67
44
  end
68
- task.wait
69
- logger.info('CemAcpt::Goss::Api') { 'Finished running test actions, returning results...' }
70
- results
71
- end
72
-
73
- def run_action_async(context = {})
74
- context[:results] ||= Queue.new
75
- context[:hosts].each do |host|
76
- context[:results] << run_action(context[:internet], host, context[:action])
77
- end
78
- context[:results]
79
45
  end
80
46
 
81
47
  # Run the specified action against the specified host.
82
- # @param internet [Async::HTTP::Internet] The Async::HTTP::Internet object to use for the request.
83
48
  # @param host [String] The host to run the action against. This should be
84
49
  # a public IP address or a DNS-resolvable name.
85
50
  # @param action [Symbol] The action to run.
51
+ # @param internet [nil, Async::HTTP::Internet] The object to use for the request.
86
52
  # @return [ActionResponse] The response from the action.
87
- def run_action(internet, host, action)
53
+ def run_action(host, action, internet = nil)
88
54
  uri = action_uri(host, action)
89
- response = internet.get(uri.to_s)
90
- body = JSON.parse(response.read)
91
- ActionResponse.new(host, action, response.status, body)
55
+ status_code, body = get(uri, internet)
56
+ ActionResponse.new(host, action, status_code, body)
57
+ end
58
+
59
+ def get_run_logs(host, internet = nil)
60
+ status_code, body = get(URI("http://#{host}:8083/run-logs"), internet)
61
+ ActionResponse.new(host, :run_logs, status_code, body)
92
62
  end
93
63
  end
94
64
  end
@@ -198,7 +198,7 @@ module CemAcpt
198
198
  else
199
199
  severity
200
200
  end
201
- block_message = block_given? ? block.call : nil
201
+ block_message = block ? yield : nil
202
202
  base = [progname, block_message].compact.join(': ')
203
203
  return if base.empty?
204
204
 
@@ -272,7 +272,7 @@ module CemAcpt
272
272
 
273
273
  @log_config = {
274
274
  logdev: $stdout,
275
- shift_age: 'o',
275
+ shift_age: 0,
276
276
  shift_size: 1_048_576,
277
277
  level: ::Logger::INFO,
278
278
  progname: 'CemAcpt',
@@ -30,6 +30,9 @@ module CemAcpt
30
30
  commands = [
31
31
  "sudo /opt/puppetlabs/puppet/bin/puppet module install #{destination_provision_directory}/#{remote_module_package_name}",
32
32
  'curl -fsSL https://goss.rocks/install | sudo sh',
33
+ 'sudo /opt/puppetlabs/puppet/bin/gem install webrick',
34
+ 'sudo chmod +x /opt/cem_acpt/log_service/log_service.rb',
35
+ 'sudo /opt/cem_acpt/log_service/log_service.rb',
33
36
  ]
34
37
  unless systemd_files.empty?
35
38
  systemd_files.each do |file|
@@ -54,6 +57,10 @@ module CemAcpt
54
57
  ]
55
58
  commands = (commands << provision_commands).flatten
56
59
  commands
60
+ elsif image_name.include?('ubuntu')
61
+ commands = ['sudo apt purge -y unattended-upgrades', 'sudo apt-get update -y']
62
+ commands = (commands << provision_commands).flatten
63
+ commands
57
64
  else
58
65
  provision_commands
59
66
  end
@@ -26,25 +26,7 @@ module CemAcpt
26
26
  super(response)
27
27
  log_subject.each_with_object([]) do |res, ary|
28
28
  res.results.each do |r|
29
- header = [
30
- "#{success_str(r.success?).capitalize}: #{r.name}",
31
- "action: #{r.action}",
32
- "target: #{name_from_ip(r.target)}",
33
- "object: #{r.object}",
34
- "status: #{r.status}",
35
- ]
36
- parts = [
37
- header.join(', '),
38
- "validation results:\n#{JSON.pretty_generate(r.failed_validation_results)}",
39
- ]
40
- if CemAcpt::Logging.verbose?
41
- parts << "command result:\n#{JSON.pretty_generate(r.command_result.to_h)}"
42
- end
43
- parts << r.error if r.error?
44
- if r.respond_to?(:details) && !r.details&.empty?
45
- parts << "details:\n#{JSON.pretty_generate(r.details)}\n"
46
- end
47
- ary << parts.join("\n")
29
+ ary << (r.error? ? format_error_result(r) : format_result(r))
48
30
  end
49
31
  end
50
32
  end
@@ -58,6 +40,41 @@ module CemAcpt
58
40
  super(response)
59
41
  'Bolt tests'
60
42
  end
43
+
44
+ private
45
+
46
+ def result_header(r)
47
+ [
48
+ "#{success_str(r.success?).capitalize}: #{r.name}",
49
+ "action: #{r.action}",
50
+ "target: #{name_from_ip(r.target)}",
51
+ "object: #{r.object}",
52
+ "status: #{r.status}",
53
+ ].join(', ')
54
+ end
55
+
56
+ def format_result(r)
57
+ parts = [
58
+ result_header(r),
59
+ "validation results:\n#{JSON.pretty_generate(r.failed_validation_results)}",
60
+ ]
61
+ if CemAcpt::Logging.verbose?
62
+ parts << "command result:\n#{JSON.pretty_generate(r.command_result.to_h)}"
63
+ end
64
+ if r.respond_to?(:details) && !r.details&.empty?
65
+ parts << "details:\n#{JSON.pretty_generate(r.details)}\n"
66
+ end
67
+ parts.join("\n")
68
+ end
69
+
70
+ def format_error_result(r)
71
+ [
72
+ result_header(r),
73
+ "error: #{r.error.kind}",
74
+ "issue code: #{r.error.issue_code}",
75
+ r.error.msg,
76
+ ].join("\n")
77
+ end
61
78
  end
62
79
  end
63
80
  end
@@ -28,14 +28,14 @@ module CemAcpt
28
28
  end
29
29
 
30
30
  def <<(result)
31
- case result
32
- when CemAcpt::Goss::Api::ActionResponse, CemAcpt::Bolt::SummaryResults
33
- @results_queue << TestActionResult.new(result, new_formatter(result))
34
- when StandardError
35
- @results_queue << TestErrorActionResult.new(result, new_formatter(result))
36
- else
37
- raise ArgumentError, "result must be a CemAcpt::Goss::Api::ActionResponse, CemAcpt::Bolt::SummaryResults, or StandardError, got #{result.class}"
38
- end
31
+ @results_queue << case result
32
+ when TestActionResult, TestErrorActionResult
33
+ result
34
+ when StandardError
35
+ TestErrorActionResult.new(result, new_formatter(result))
36
+ else
37
+ TestActionResult.new(result, new_formatter(result))
38
+ end
39
39
  end
40
40
 
41
41
  def to_a
@@ -28,6 +28,7 @@ module CemAcpt
28
28
 
29
29
  def initialize(config)
30
30
  @config = config
31
+ @module_builder = CemAcpt::Utils::Puppet::ModulePackageBuilder.new(config.get('module_dir'))
31
32
  @run_data = {}
32
33
  @duration = 0
33
34
  @exit_code = 0
@@ -101,8 +102,7 @@ module CemAcpt
101
102
  destroy_test_nodes
102
103
  end
103
104
  rescue StandardError => e
104
- logger.verbose('CemAcpt::TestRunner') { "Error cleaning up: #{e}" }
105
- logger.verbose('CemAcpt::TestRunner') { e.backtrace.join("\n") }
105
+ logger.verbose('CemAcpt::TestRunner') { "Error cleaning up: #{e}\n#{e.backtrace.join("\n")}" }
106
106
  ensure
107
107
  logger.end_ci_group
108
108
  end
@@ -118,16 +118,20 @@ module CemAcpt
118
118
  c.register_group(:goss, order: 0, async: true)
119
119
  CemAcpt::Goss::Api::ACTIONS.each_key do |a|
120
120
  c[:goss].register_action(a) do |context|
121
- run_goss_test(context)
121
+ context[:hosts] = @hosts
122
+ context[:results] = @results
123
+ context[:hosts].each do |host|
124
+ action_res = CemAcpt::Goss::Api.run_action(host, context[:action], context[:internet])
125
+ action_res.metadata[:run_logs] = CemAcpt::Goss::Api.get_run_logs(host, context[:internet]).body
126
+ context[:results] << action_res
127
+ end
128
+ context[:results]
122
129
  end
123
130
  end
124
131
  c.register_group(:bolt, order: 1).register_action(:bolt) do |context|
125
132
  run_bolt_tests(context)
126
133
  end
127
134
  end
128
- logger.debug('CemAcpt::TestRunner') { "All actions #{CemAcpt::Actions.config.action_names}" }
129
- logger.debug('CemAcpt::TestRunner') { "Only actions: #{CemAcpt::Actions.config.only}" }
130
- logger.debug('CemAcpt::TestRunner') { "Except actions: #{CemAcpt::Actions.config.except}" }
131
135
  logger.info('CemAcpt::TestRunner') do
132
136
  "Configured and registered actions, will run actions: #{CemAcpt::Actions.config.action_names.join(', ')}"
133
137
  end
@@ -135,13 +139,7 @@ module CemAcpt
135
139
 
136
140
  # @return [String] The path to the module package
137
141
  def build_module_package
138
- pkg_path = if config.get('tests').first.include? 'windows'
139
- CemAcpt::Utils.package_win_module(config.get('module_dir'))
140
- else
141
- CemAcpt::Utils::Puppet.build_module_package(config.get('module_dir'))
142
- end
143
- logger.info('CemAcpt::TestRunner') { "Created module package: #{pkg_path}..." }
144
- pkg_path
142
+ @module_builder.build
145
143
  end
146
144
 
147
145
  # @return [Array<String>] The paths to the ssh private key, public key, and known hosts file
@@ -188,7 +186,7 @@ module CemAcpt
188
186
  @run_data[:nodes] = new_node_data
189
187
  logger.verbose('CemAcpt::TestRunner') { "Initial run data:\n#{@run_data}" }
190
188
  logger.info('CemAcpt::TestRunner') { 'Created initial run data...' }
191
- setup_bolt if CemAcpt::Actions.config.action_names.include?('bolt')
189
+ setup_bolt if CemAcpt::Actions.config.action_names.include?(:bolt)
192
190
  end
193
191
 
194
192
  def setup_bolt
@@ -199,12 +197,12 @@ module CemAcpt
199
197
  rescue CemAcpt::ShellCommandNotFoundError => e
200
198
  logger.warning('CemAcpt::TestRunner') { e.message }
201
199
  logger.warning('CemAcpt::TestRunner') { 'Adding Bolt action to ignore list...' }
202
- CemAcpt::Actions.config.ignore << 'bolt'
200
+ CemAcpt::Actions.config.ignore << :bolt
203
201
  return
204
202
  end
205
203
  return unless @bolt_test_runner.tests.to_a.empty?
206
204
 
207
- if !CemAcpt::Actions.config.only.empty? && CemAcpt::Actions.config.only.include?('bolt')
205
+ if !CemAcpt::Actions.config.only.empty? && CemAcpt::Actions.config.only.include?(:bolt)
208
206
  raise 'No Bolt tests to run and only bolt action was specified'
209
207
  end
210
208
 
@@ -261,20 +259,6 @@ module CemAcpt
261
259
  CemAcpt::Actions.execute
262
260
  end
263
261
 
264
- def run_goss_tests(context = {})
265
- logger.info('CemAcpt::TestRunner') { 'Running goss tests...' }
266
- context[:hosts] = @hosts
267
- context[:results] = @results
268
- CemAcpt::Goss::Api.run_actions_async(context)
269
- end
270
-
271
- def run_goss_test(context = {})
272
- logger.info('CemAcpt::TestRunner') { "Running Goss test for action #{context[:action]}..." }
273
- context[:hosts] = @hosts
274
- context[:results] = @results
275
- CemAcpt::Goss::Api.run_action_async(context)
276
- end
277
-
278
262
  def run_bolt_tests(_context = {})
279
263
  logger.info('CemAcpt::TestRunner') { 'Running Bolt tests...' }
280
264
  # If the Bolt config has tests:only or tests:ignore lists, we need to filter the hosts
@@ -364,6 +348,20 @@ module CemAcpt
364
348
  logger.info { r }
365
349
  else
366
350
  logger.error { r }
351
+ next unless result.respond_to?(:metadata) && result.metadata.key?(:run_logs)
352
+
353
+ logs = if config.debug? && !config.get('puppet.no_debug')
354
+ result.metadata[:run_logs]&.dup
355
+ else
356
+ filtered = result.metadata[:run_logs]&.dup
357
+ filtered&.each { |_, v| v.reject! { |l| l.include?('(debug)') } }
358
+ filtered
359
+ end
360
+ unless logs.nil? || logs.empty?
361
+ logger.debug { "Logs for provision apply:\n#{logs['provision'].join}" } if logs['provision']
362
+ logger.error { "Logs for idempotent apply:\n#{logs['idempotent'].join}" } if logs['idempotent']
363
+ logger.error { "Logs for noop apply:\n#{logs['noop'].join}" } if logs['noop']
364
+ end
367
365
  end
368
366
  end
369
367
  end
@@ -2,27 +2,61 @@
2
2
 
3
3
  require 'puppet/modulebuilder'
4
4
  require 'fileutils'
5
+ require_relative '../logging'
5
6
 
6
7
  module CemAcpt
7
8
  module Utils
8
9
  # Puppet-related utilities
9
10
  module Puppet
10
- # Builds a Puppet module package.
11
- # @param module_dir [String] Path to the module directory. If target_dir
12
- # is specified as a relative path, it will be relative to the module dir.
13
- # @param target_dir [String] Path to the target directory where the package
14
- # will be built. This defaults to the relative path 'pkg/'.
15
- # @param should_log [Boolean] Whether or not to log the build process.
16
- # @return [String] Path to the built package.
17
- def self.build_module_package(module_dir, target_dir = nil, should_log: false)
18
- builder_logger = should_log ? logger : nil
19
- builder = ::Puppet::Modulebuilder::Builder.new(::File.expand_path(module_dir), target_dir, builder_logger)
20
-
21
- # Validates module metadata by raising exception if invalid
22
- _metadata = builder.metadata
23
-
24
- # Builds the module package
25
- builder.build
11
+ class ModulePackageBuilder
12
+ include CemAcpt::Logging
13
+
14
+ attr_reader :module_dir, :target_dir, :should_log, :metadata, :package_path
15
+
16
+ def initialize(module_dir, target_dir: nil, should_log: false, builder: ::Puppet::Modulebuilder::Builder)
17
+ @module_dir = ::File.expand_path(module_dir)
18
+ @target_dir = target_dir
19
+ @should_log = should_log
20
+ @builder = new_builder(builder)
21
+ @metadata = @builder.metadata # Validates metadata
22
+ @package_built = false
23
+ @package_path = nil
24
+ end
25
+
26
+ # Builds a Puppet module package.
27
+ # @return [String] Path to the built package.
28
+ def build
29
+ @package_path = @metadata['name'].include?('windows') ? build_windows_package : @builder.build
30
+ @package_built = true
31
+ logger.info('CemAcpt::Utils::Puppet::ModulePackageBuilder') { "Built module package: #{@package_path}" }
32
+ @package_path
33
+ end
34
+
35
+ # Determines if a package has been built.
36
+ def package_built?
37
+ @package_built
38
+ end
39
+
40
+ private
41
+
42
+ # This method is used to create a new builder instance.
43
+ # It is used to allow for dependency injection in tests.
44
+ def new_builder(builder)
45
+ builder.new(@module_dir, @target_dir, @should_log ? logger : nil)
46
+ end
47
+
48
+ # This method is used to build a Windows module package.
49
+ # It's unknown exactly why we need to package the Windows module differently from the Linux module.
50
+ # When we find out, we should add an explanation here.
51
+ def build_windows_module
52
+ package_name = "#{@metadata['name']}.tar.gz"
53
+ package_file = ::File.join(@module_dir, package_name)
54
+ ::FileUtils.rm_f(package_file) # Remove the old package file if it exists
55
+ Dir.chdir(@module_dir) do
56
+ `touch #{package_name} && tar -czf #{package_name} --exclude=#{package_name} *`
57
+ end
58
+ package_file
59
+ end
26
60
  end
27
61
  end
28
62
  end
@@ -13,6 +13,7 @@ module CemAcpt
13
13
  class << self
14
14
  include CemAcpt::Logging
15
15
 
16
+ # This is method currently unused, see lib/cem_acpt/utils/puppet.rb for details.
16
17
  def package_win_module(module_dir)
17
18
  # Path to the package file
18
19
  package_file = File.join(module_dir, 'puppetlabs-cem_windows.tar.gz')
@@ -53,7 +54,7 @@ module CemAcpt
53
54
  def get_windows_login_info(instance_name, hash_of_instance)
54
55
  password_and_username = {}
55
56
  password_and_username[instance_name] = {}
56
- info = reset_password_readiness_polling(instance_name).split(/\r?\n/)[1..2]
57
+ info = reset_password_readiness_polling(instance_name).split(%r{\r?\n})[1..2]
57
58
  info.each do |line|
58
59
  key_val = line.split(' ')
59
60
  password_and_username[instance_name][key_val[0].strip.delete(':')] = key_val[1].strip
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CemAcpt
4
- VERSION = '0.10.3'
4
+ VERSION = '0.10.5'
5
5
  end
@@ -0,0 +1,62 @@
1
+ #!/opt/puppetlabs/puppet/bin/ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'json'
5
+ require 'webrick'
6
+
7
+ # Implements a simple HTTP server that serves the various run logs
8
+ class RunLogs < WEBrick::HTTPServlet::AbstractServlet
9
+ # rubocop:disable Naming/MethodName
10
+ def do_GET(_request, response)
11
+ run_logs = {}
12
+ p_logs = read_log_file('/opt/cem_acpt/provision_apply.log')
13
+ i_logs = read_log_file('/opt/cem_acpt/idempotent_apply.log')
14
+ n_logs = read_log_file('/opt/cem_acpt/noop_apply.log')
15
+ run_logs['provision'] = p_logs unless p_logs.empty?
16
+ run_logs['idempotent'] = i_logs unless i_logs.empty?
17
+ run_logs['noop'] = n_logs unless n_logs.empty?
18
+ response.status = run_logs.empty? ? 404 : 200
19
+ response['Content-Type'] = 'application/json'
20
+ response.body = run_logs.to_json
21
+ end
22
+ # rubocop:enable Naming/MethodName
23
+
24
+ private
25
+
26
+ def read_log_file(file)
27
+ if File.exist?(file)
28
+ format_log(File.readlines(file))
29
+ else
30
+ []
31
+ end
32
+ rescue StandardError => e
33
+ ['Error reading log file', e.message, e.backtrace.join("\n")]
34
+ end
35
+
36
+ def format_log(log)
37
+ # Force UTF-8 on each line so they aren't mangled by the UTF to ASCII conversion
38
+ encoded = log.map { |l| l.force_encoding(Encoding::UTF_8) }
39
+ # Now we need to condense the lines into logical groups based on log entry. This
40
+ # allows us to filter out debug logs that span multiple lines.
41
+ memo = nil
42
+ encoded.each_with_object([]) do |line, formatted|
43
+ if memo.nil?
44
+ memo = line
45
+ elsif line.match?(%r{^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} \+\d{4}.*$})
46
+ formatted << memo
47
+ memo = line
48
+ else
49
+ memo += line
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ server = WEBrick::HTTPServer.new(Port: 8083)
56
+ server.mount '/run-logs', RunLogs
57
+
58
+ trap 'INT' do
59
+ server.shutdown
60
+ end
61
+ WEBrick::Daemon.start
62
+ server.start
@@ -2,7 +2,7 @@
2
2
  Description=unit file for goss server acpt endpoint service
3
3
 
4
4
  [Service]
5
- ExecStart=/usr/local/bin/goss -g /opt/cem_acpt/goss.yaml serve -f json --endpoint "/acpt" --cache "10m"
5
+ ExecStart=/usr/local/bin/goss -g /opt/cem_acpt/goss.yaml serve -f json -o pretty --endpoint "/acpt" --cache '0s'
6
6
 
7
7
  [Install]
8
8
  WantedBy=multi-user.target
@@ -2,7 +2,7 @@
2
2
  Description=unit file for goss server acpt endpoint service
3
3
 
4
4
  [Service]
5
- ExecStart=/usr/local/bin/goss -g /opt/cem_acpt/goss/puppet_idempotent.yaml serve -f json -l ":8081" --endpoint "/idempotent" --cache "10m"
5
+ ExecStart=/usr/local/bin/goss -g /opt/cem_acpt/goss/puppet_idempotent.yaml serve -f json -o pretty -l ":8081" --endpoint "/idempotent" --cache '0s'
6
6
 
7
7
  [Install]
8
8
  WantedBy=multi-user.target
@@ -2,7 +2,7 @@
2
2
  Description=unit file for goss server acpt endpoint service
3
3
 
4
4
  [Service]
5
- ExecStart=/usr/local/bin/goss -g /opt/cem_acpt/goss/puppet_noop.yaml serve -f json -l ":8082" --endpoint "/noop" --cache "10m"
5
+ ExecStart=/usr/local/bin/goss -g /opt/cem_acpt/goss/puppet_noop.yaml serve -f json -o pretty -l ":8082" --endpoint "/noop" --cache '0s'
6
6
 
7
7
  [Install]
8
8
  WantedBy=multi-user.target
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cem_acpt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.3
4
+ version: 0.10.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - puppetlabs
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-07-11 00:00:00.000000000 Z
11
+ date: 2024-10-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async-http
@@ -280,6 +280,7 @@ files:
280
280
  - lib/cem_acpt/version.rb
281
281
  - lib/terraform/gcp/linux/goss/puppet_idempotent.yaml
282
282
  - lib/terraform/gcp/linux/goss/puppet_noop.yaml
283
+ - lib/terraform/gcp/linux/log_service/log_service.rb
283
284
  - lib/terraform/gcp/linux/main.tf
284
285
  - lib/terraform/gcp/linux/systemd/goss-acpt.service
285
286
  - lib/terraform/gcp/linux/systemd/goss-idempotent.service
@@ -313,7 +314,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
313
314
  - !ruby/object:Gem::Version
314
315
  version: '0'
315
316
  requirements: []
316
- rubygems_version: 3.4.22
317
+ rubygems_version: 3.5.18
317
318
  signing_key:
318
319
  specification_version: 4
319
320
  summary: CEM Acceptance Tests