cem_acpt 0.10.3 → 0.10.5

Sign up to get free protection for your applications and to get access to all the features.
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