beaker-answers 0.4.3 → 0.5.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.
@@ -1,6 +1,11 @@
1
1
  module BeakerAnswers
2
2
  require 'stringify-hash'
3
3
  require 'require_all'
4
+ require 'beaker-answers/helpers'
4
5
  require 'beaker-answers/answers'
5
6
  require 'beaker-answers/version'
7
+ require 'json'
8
+ require 'hocon'
9
+ require 'hocon/config_value_factory'
10
+ require 'hocon/parser/config_document_factory'
6
11
  end
@@ -3,13 +3,18 @@ module BeakerAnswers
3
3
  # information.
4
4
  class Answers
5
5
 
6
+ # This is a temporary default for deciding which configuration file format
7
+ # to fall back to in 2016.2.0. Once we have cutover to MEEP, it should be
8
+ # removed.
9
+ DEFAULT_FORMAT = :bash
6
10
  DEFAULT_ANSWERS = StringifyHash.new.merge({
11
+ :q_install => 'y',
7
12
  :q_puppet_enterpriseconsole_auth_user_email => 'admin@example.com',
8
13
  :q_puppet_enterpriseconsole_auth_password => '~!@#$%^*-/ aZ',
9
14
  :q_puppet_enterpriseconsole_smtp_port => 25,
10
15
  :q_puppet_enterpriseconsole_smtp_use_tls => 'n',
11
16
  :q_verify_packages => 'y',
12
- :q_puppetdb_password => '~!@#$%^*-/ aZ',
17
+ :q_puppetdb_database_password => '~!@#$%^*-/ aZ',
13
18
  :q_puppetmaster_enterpriseconsole_port => 443,
14
19
  :q_puppet_enterpriseconsole_auth_database_name => 'console_auth',
15
20
  :q_puppet_enterpriseconsole_auth_database_user => 'mYu7hu3r',
@@ -25,8 +30,8 @@ module BeakerAnswers
25
30
  :q_puppetdb_database_user => 'mYpdBu3r',
26
31
  :q_database_port => 5432,
27
32
  :q_puppetdb_port => 8081,
33
+ :q_classifier_database_name => 'pe-classifier',
28
34
  :q_classifier_database_user => 'DFGhjlkj',
29
- :q_database_name => 'pe-classifier',
30
35
  :q_classifier_database_password => '~!@#$%^*-/ aZ',
31
36
  :q_activity_database_user => 'adsfglkj',
32
37
  :q_activity_database_name => 'pe-activity',
@@ -40,6 +45,27 @@ module BeakerAnswers
40
45
  :q_pe_check_for_updates => 'n',
41
46
  })
42
47
 
48
+ DEFAULT_HIERA_ANSWERS = StringifyHash.new.merge(flatten_keys_to_joined_string({
49
+ 'console_admin_password' => DEFAULT_ANSWERS[:q_puppet_enterpriseconsole_auth_password],
50
+ 'puppet_enterprise' => {
51
+ 'puppetdb_database_name' => DEFAULT_ANSWERS[:q_puppetdb_database_name],
52
+ 'puppetdb_database_user' => DEFAULT_ANSWERS[:q_puppetdb_database_user],
53
+ 'puppetdb_database_password' => DEFAULT_ANSWERS[:q_puppetdb_database_password],
54
+ 'classifier_database_name' => DEFAULT_ANSWERS[:q_classifier_database_name],
55
+ 'classifier_database_user' => DEFAULT_ANSWERS[:q_classifier_database_user],
56
+ 'classifier_database_password' => DEFAULT_ANSWERS[:q_classifier_database_password],
57
+ 'activity_database_name' => DEFAULT_ANSWERS[:q_activity_database_name],
58
+ 'activity_database_user' => DEFAULT_ANSWERS[:q_activity_database_user],
59
+ 'activity_database_password' => DEFAULT_ANSWERS[:q_activity_database_password],
60
+ 'rbac_database_name' => DEFAULT_ANSWERS[:q_rbac_database_name],
61
+ 'rbac_database_user' => DEFAULT_ANSWERS[:q_rbac_database_user],
62
+ 'rbac_database_password' => DEFAULT_ANSWERS[:q_rbac_database_password],
63
+ 'orchestrator_database_name' => DEFAULT_ANSWERS[:q_orchestrator_database_name],
64
+ 'orchestrator_database_user' => DEFAULT_ANSWERS[:q_orchestrator_database_user],
65
+ 'orchestrator_database_password' => DEFAULT_ANSWERS[:q_orchestrator_database_password],
66
+ 'use_application_services' => true,
67
+ }
68
+ }))
43
69
 
44
70
  # Determine the list of supported PE versions, return as an array
45
71
  # @return [Array<String>] An array of the supported versions
@@ -47,6 +73,12 @@ module BeakerAnswers
47
73
  BeakerAnswers.constants.select {|c| BeakerAnswers.const_get(c).is_a?(Class) && BeakerAnswers.const_get(c).respond_to?(:pe_version_matcher)}
48
74
  end
49
75
 
76
+ # Determine the list of supported upgrade PE versions, return as an array
77
+ # @return [Array<String>] An array of the supported versions
78
+ def self.supported_upgrade_versions
79
+ BeakerAnswers.constants.select {|c| BeakerAnswers.const_get(c).is_a?(Class) && BeakerAnswers.const_get(c).respond_to?(:upgrade_version_matcher)}
80
+ end
81
+
50
82
  # When given a Puppet Enterprise version, a list of hosts and other
51
83
  # qualifying data this method will return the appropriate object that can be used
52
84
  # to generate answer file data.
@@ -58,6 +90,15 @@ module BeakerAnswers
58
90
  # @return [Hash] A hash (keyed from hosts) containing hashes of answer file
59
91
  # data.
60
92
  def self.create version, hosts, options
93
+ # if :upgrade is detected, then we return the simpler upgrade answers
94
+ if options[:type] == :upgrade
95
+ self.supported_upgrade_versions.each do |upgrade_version_class|
96
+ if BeakerAnswers.const_get(upgrade_version_class).send(:upgrade_version_matcher) =~ version
97
+ return BeakerAnswers.const_get(upgrade_version_class).send(:new, version, hosts, options)
98
+ end
99
+ end
100
+ warn 'Only upgrades to version 3.8.x generate specific upgrade answers. Defaulting to full answers.'
101
+ end
61
102
 
62
103
  # finds all potential version classes
63
104
  # discovers new version classes as they are added, no more crazy case statement
@@ -71,16 +112,30 @@ module BeakerAnswers
71
112
  raise NotImplementedError, "Don't know how to generate answers for #{version}"
72
113
  end
73
114
 
74
- # The answer value for a provided question. Use the user answer when available, otherwise return the default
115
+ # The answer value for a provided question. Use the user answer when
116
+ # available, otherwise return the default.
117
+ #
75
118
  # @param [Hash] options options for answer file
76
- # @option options [Symbol] :answer Contains a hash of user provided question name and answer value pairs.
77
- # @param [String] default Should there be no user value for the provided question name return this default
119
+ # @option options [Symbol] :answers Contains a hash of user provided
120
+ # question name and answer value pairs.
121
+ # @param [String] default Should there be no user value for the provided
122
+ # question name return this default
78
123
  # @return [String] The answer value
79
124
  def answer_for(options, q, default = nil)
80
- answer = DEFAULT_ANSWERS[q]
125
+ case @format
126
+ when :bash
127
+ answer = DEFAULT_ANSWERS[q]
128
+ answers = options[:answers]
129
+ when :hiera
130
+ answer = DEFAULT_HIERA_ANSWERS[q]
131
+ answers = flatten_keys_to_joined_string(options[:answers]) if options[:answers]
132
+ else
133
+ raise NotImplementedError, "Don't know how to determine answers for #{@format}"
134
+ end
135
+
81
136
  # check to see if there is a value for this in the provided options
82
- if options[:answers] && options[:answers][q]
83
- answer = options[:answers][q]
137
+ if answers && answers[q]
138
+ answer = answers[q]
84
139
  end
85
140
  # use the default if we don't have anything
86
141
  if not answer
@@ -89,6 +144,16 @@ module BeakerAnswers
89
144
  answer
90
145
  end
91
146
 
147
+ def get_defaults_or_answers(defaults_to_set)
148
+ config = {}
149
+
150
+ defaults_to_set.each do |key|
151
+ config[key] = answer_for(@options, key)
152
+ end
153
+
154
+ return config
155
+ end
156
+
92
157
  # When given a Puppet Enterprise version, a list of hosts and other
93
158
  # qualifying data this method will return a hash (keyed from the hosts)
94
159
  # of default Puppet Enterprise answer file data hashes.
@@ -97,12 +162,17 @@ module BeakerAnswers
97
162
  # @param [Array<Beaker::Host>] hosts An array of host objects.
98
163
  # @param [Hash] options options for answer files
99
164
  # @option options [Symbol] :type Should be one of :upgrade or :install.
165
+ # @option options [Symbol] :format Should be one of :bash or :hiera. This
166
+ # is a temporary setting which only has an impact on version201620 answers.
167
+ # Setting :bash will result in the "classic" PE answer file being generated
168
+ # Setting :hiera will generate the new PE hiera config file format
100
169
  # @return [Hash] A hash (keyed from hosts) containing hashes of answer file
101
170
  # data.
102
171
  def initialize(version, hosts, options)
103
172
  @version = version
104
173
  @hosts = hosts
105
174
  @options = options
175
+ @format = (options[:format] || DEFAULT_FORMAT).to_sym
106
176
  end
107
177
 
108
178
  # Generate the answers hash based upon version, host and option information
@@ -132,6 +202,14 @@ module BeakerAnswers
132
202
  answers[host.name].map { |k,v| "#{k}=#{v}" }.join("\n")
133
203
  end
134
204
 
205
+ def answer_hiera
206
+ raise(NotImplementedError, "Hiera configuration is not available in this version of PE (#{self.class})")
207
+ end
208
+
209
+ def installer_configuration_string(host)
210
+ answer_string(host)
211
+ end
212
+
135
213
  #Find a single host with the role provided. Raise an error if more than one host is found to have the
136
214
  #provided role.
137
215
  #
@@ -147,7 +225,6 @@ module BeakerAnswers
147
225
  end
148
226
  found_hosts.first
149
227
  end
150
-
151
228
  end
152
229
 
153
230
  # pull in all the available answer versions
@@ -0,0 +1,14 @@
1
+ def flatten_keys_to_joined_string(h, key_delim='::')
2
+ flat = {}
3
+ h.each_pair do |k, v|
4
+ if v.respond_to?(:keys)
5
+ flatten_keys_to_joined_string(v, key_delim).each_pair do |k2, v2|
6
+ flat.merge!({[k.to_s, k2.to_s].join(key_delim) => v2})
7
+ end
8
+ else
9
+ flat.merge!({ k.to_s => v })
10
+ end
11
+ end
12
+
13
+ flat
14
+ end
@@ -1,5 +1,5 @@
1
1
  module BeakerAnswers
2
2
  module Version
3
- STRING = '0.4.3'
3
+ STRING = '0.5.0'
4
4
  end
5
5
  end
@@ -0,0 +1,18 @@
1
+ module BeakerAnswers
2
+ # In the case of upgrades, we only start with the answer for installation
3
+ class Upgrade < Answers
4
+
5
+ def default_upgrade_answers
6
+ {:q_install => answer_for(@options, :q_install)}
7
+ end
8
+
9
+ def generate_answers
10
+ the_answers = {}
11
+ @hosts.each do |host|
12
+ the_answers[host.name] = default_upgrade_answers
13
+ end
14
+ the_answers
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,79 @@
1
+ module BeakerAnswers
2
+ # In the case of upgrades, we lay down only necessary answers
3
+ class Upgrade38 < Upgrade
4
+
5
+ def self.upgrade_version_matcher
6
+ /\A3\.8/
7
+ end
8
+
9
+ def generate_answers
10
+ the_answers = super
11
+ dashboard = only_host_with_role(@hosts, 'dashboard')
12
+ master = only_host_with_role(@hosts, 'master')
13
+ database = only_host_with_role(@hosts, 'database')
14
+ @hosts.each do |host|
15
+ # Both the dashboard and database need shared answers about the new console services
16
+ if host == dashboard || host == database
17
+ the_answers[host.name][:q_rbac_database_name] = answer_for(@options, :q_rbac_database_name)
18
+ the_answers[host.name][:q_rbac_database_user] = answer_for(@options, :q_rbac_database_user)
19
+ the_answers[host.name][:q_rbac_database_password] = "'#{answer_for(@options, :q_rbac_database_password)}'"
20
+ the_answers[host.name][:q_activity_database_name] = answer_for(@options, :q_activity_database_name)
21
+ the_answers[host.name][:q_activity_database_user] = answer_for(@options, :q_activity_database_user)
22
+ the_answers[host.name][:q_activity_database_password] = "'#{answer_for(@options, :q_activity_database_password)}'"
23
+ the_answers[host.name][:q_classifier_database_name] = answer_for(@options, :q_classifier_database_name)
24
+ the_answers[host.name][:q_classifier_database_user] = answer_for(@options, :q_classifier_database_user)
25
+ the_answers[host.name][:q_classifier_database_password] = "'#{answer_for(@options, :q_classifier_database_password)}'"
26
+ the_answers[host.name][:q_puppetmaster_certname] = answer_for(@options, :q_puppetmaster_certname)
27
+ # The dashboard also needs additional answers about puppetdb on the remote host
28
+ if host == dashboard
29
+ the_answers[host.name][:q_puppet_enterpriseconsole_auth_password] = "'#{answer_for(@options, :q_puppet_enterpriseconsole_auth_password)}'"
30
+ the_answers[host.name][:q_puppetdb_hostname] = answer_for(@options, :q_puppetdb_hostname, database.reachable_name)
31
+ the_answers[host.name][:q_puppetdb_database_password] = "'#{answer_for(@options, :q_puppetdb_database_password)}'"
32
+ the_answers[host.name][:q_puppetdb_database_name] = answer_for(@options, :q_puppetdb_database_name)
33
+ the_answers[host.name][:q_puppetdb_database_user] = answer_for(@options, :q_puppetdb_database_user)
34
+ the_answers[host.name][:q_puppetdb_port] = answer_for(@options, :q_puppetdb_port)
35
+ end
36
+ end
37
+ # merge custom host answers if available
38
+ the_answers[host.name] = the_answers[host.name].merge(host[:custom_answers]) if host[:custom_answers]
39
+ end
40
+
41
+ the_answers.map do |hostname, answers|
42
+ # First check to see if there is a host option for this setting
43
+ # and skip to the next object if it is already defined.
44
+ if the_answers[hostname][:q_enable_future_parser]
45
+ next
46
+ # Check now if it was set in the global options.
47
+ elsif @options[:answers] && @options[:answers][:q_enable_future_parser]
48
+ the_answers[hostname][:q_enable_future_parser] = @options[:answers][:q_enable_future_parser]
49
+ next
50
+ # If we didn't set it on a per host or global option basis, set it to
51
+ # 'y' here. We could have possibly set it in the DEFAULT_ANSWERS, but it
52
+ # is unclear what kind of effect that might have on all the other answers
53
+ # that rely on it defaulting to 'n'.
54
+ else
55
+ the_answers[hostname][:q_enable_future_parser] = 'y'
56
+ end
57
+ end
58
+
59
+ the_answers.map do |hostname, answers|
60
+ # First check to see if there is a host option for this setting
61
+ # and skip to the next object if it is already defined.
62
+ if the_answers[hostname][:q_exit_for_nc_migrate]
63
+ next
64
+ # Check now if it was set in the global options.
65
+ elsif @options[:answers] && @options[:answers][:q_exit_for_nc_migrate]
66
+ the_answers[hostname][:q_exit_for_nc_migrate] = @options[:answers][:q_exit_for_nc_migrate]
67
+ next
68
+ # If we didn't set it on a per host or global option basis, set it to
69
+ # 'n' here. We could have possibly set it in the DEFAULT_ANSWERS, but it
70
+ # is unclear what kind of effect that might have on all the other answers
71
+ # that rely on it defaulting to 'n'.
72
+ else
73
+ the_answers[hostname][:q_exit_for_nc_migrate] = 'n'
74
+ end
75
+ end
76
+ the_answers
77
+ end
78
+ end
79
+ end
@@ -14,6 +14,18 @@ module BeakerAnswers
14
14
  the_answers = super
15
15
 
16
16
  return the_answers if @options[:masterless]
17
+
18
+ case @format
19
+ when :bash
20
+ return generate_bash_answers(the_answers)
21
+ when :hiera
22
+ return generate_hiera_config
23
+ else
24
+ raise NotImplementedError, "Don't know how to generate answers for #{@format}"
25
+ end
26
+ end
27
+
28
+ def generate_bash_answers(answers)
17
29
  console = only_host_with_role(@hosts, 'dashboard')
18
30
 
19
31
  # To allow SSL cert based auth in the new installer while maintaining the legacy
@@ -24,9 +36,101 @@ module BeakerAnswers
24
36
  :q_orchestrator_database_user => answer_for(@options, :q_orchestrator_database_user),
25
37
  }
26
38
 
27
- the_answers[console.name].merge!(orchestrator_db)
39
+ answers[console.name].merge!(orchestrator_db)
40
+
41
+ return answers
42
+ end
43
+
44
+ def generate_hiera_config
45
+ # The hiera answer file format will get all answers, regardless of role
46
+ # it is being installed on
47
+ hiera_hash = {}
48
+
49
+ # Add the correct host values for this beaker configuration
50
+ hiera_hash.merge!(hiera_host_config)
51
+
52
+ hiera_hash.merge!(get_defaults_or_answers([
53
+ "console_admin_password",
54
+ "puppet_enterprise::use_application_services",
55
+ ]))
56
+
57
+ hiera_hash.merge!(hiera_db_config)
58
+
59
+ # Override with any values provided in the :answers key hash
60
+ if @options[:answers]
61
+ if @options[:answers].keys.any? { |k| k.to_s.start_with?('q_') }
62
+ raise(TypeError, "q_ answers are not supported when using the hiera answers format")
63
+ else
64
+ hiera_hash.merge!(flatten_keys_to_joined_string(@options[:answers]))
65
+ end
66
+ end
67
+
68
+ return hiera_hash
69
+ end
70
+
71
+ def hiera_host_config
72
+ config = {}
73
+ ns = "puppet_enterprise"
74
+
75
+ master = only_host_with_role(@hosts, 'master')
76
+ puppetdb = only_host_with_role(@hosts, 'database')
77
+ console = only_host_with_role(@hosts, 'dashboard')
78
+
79
+ config["#{ns}::certificate_authority_host"] = answer_for(@options, "#{ns}::certificate_authority_host", master.hostname)
80
+ config["#{ns}::puppet_master_host"] = answer_for(@options, "#{ns}::puppet_master_host", master.hostname)
81
+ config["#{ns}::console_host"] = answer_for(@options, "#{ns}::console_host", console.hostname)
82
+ config["#{ns}::puppetdb_host"] = answer_for(@options, "#{ns}::puppetdb_host", puppetdb.hostname)
83
+ config["#{ns}::database_host"] = answer_for(@options, "#{ns}::database_host", puppetdb.hostname)
84
+ config["#{ns}::pcp_broker_host"] = answer_for(@options, "#{ns}::pcp_broker_host", master.hostname)
85
+ config["#{ns}::mcollective_middleware_hosts"] = [answer_for(@options, "#{ns}::mcollective_middleware_hosts", master.hostname)]
86
+
87
+ return config
88
+ end
89
+
90
+ def hiera_db_config
91
+ ns = "puppet_enterprise"
92
+ defaults_to_set = []
93
+
94
+ # Set database users only if we are upgrading from < 2016.2.0; necessary
95
+ # because BeakerAnswers sets database user to non-default values in
96
+ # earlier versions.
97
+ if @options[:include_legacy_database_defaults]
98
+ # Database names/users. Required for password and cert-based auth
99
+ defaults_to_set += [
100
+ "#{ns}::puppetdb_database_user",
101
+ "#{ns}::classifier_database_user",
102
+ "#{ns}::activity_database_user",
103
+ "#{ns}::rbac_database_user",
104
+ "#{ns}::orchestrator_database_user",
105
+ ]
106
+ end
107
+
108
+ get_defaults_or_answers(defaults_to_set)
109
+ end
110
+
111
+ # This converts a data hash provided by answers, and returns a Puppet
112
+ # Enterprise compatible hiera config file ready for use.
113
+ #
114
+ # @return [String] a string of parseable hocon
115
+ # @example Generating an answer file for a series of hosts
116
+ # hosts.each do |host|
117
+ # answers = Beaker::Answers.new("2.0", hosts, "master")
118
+ # create_remote_file host, "/mypath/answer", answers.answer_hiera
119
+ # end
120
+ def answer_hiera
121
+ # Render pretty JSON, because it is a subset of HOCON
122
+ json = JSON.pretty_generate(answers)
123
+ hocon = Hocon::Parser::ConfigDocumentFactory.parse_string(json)
124
+ hocon.render
125
+ end
28
126
 
29
- the_answers
127
+ def installer_configuration_string(host)
128
+ case @format
129
+ when :bash then answer_string(host)
130
+ when :hiera then answer_hiera
131
+ else
132
+ raise NotImplementedError, "Don't know how to generate for configuration #{@format}"
133
+ end
30
134
  end
31
135
  end
32
136
  end
@@ -73,7 +73,7 @@ module BeakerAnswers
73
73
  console_auth_password = "'#{answer_for(options, :q_puppet_enterpriseconsole_auth_password)}'"
74
74
  puppetdb_database_name = answer_for(options, :q_puppetdb_database_name, 'pe-puppetdb')
75
75
  puppetdb_database_user = answer_for(options, :q_puppetdb_database_user, 'mYpdBu3r')
76
- puppetdb_database_password = answer_for(options, :q_puppetdb_database_password, "'#{answer_for(options, :q_puppetdb_password)}'")
76
+ puppetdb_database_password = "'#{answer_for(options, :q_puppetdb_database_password)}'"
77
77
  console_auth_database_name = answer_for(options, :q_puppet_enterpriseconsole_auth_database_name, 'console_auth')
78
78
  console_auth_database_user = answer_for(options, :q_puppet_enterpriseconsole_auth_database_user, 'mYu7hu3r')
79
79
  console_auth_database_password = answer_for(options, :q_puppet_enterpriseconsole_auth_database_password, console_auth_password)
@@ -20,7 +20,7 @@ module BeakerAnswers
20
20
  the_answers = super
21
21
 
22
22
  classifier_database_user = answer_for(@options, :q_classifier_database_user, 'DFGhjlkj')
23
- classifier_database_name = answer_for(@options, :q_database_name, 'pe-classifier')
23
+ classifier_database_name = answer_for(@options, :q_classifier_database_name, 'pe-classifier')
24
24
  classifier_database_password = "'#{answer_for(@options, :q_classifier_database_password)}'"
25
25
  activity_database_user = answer_for(@options, :q_activity_database_user, 'adsfglkj')
26
26
  activity_database_name = answer_for(@options, :q_activity_database_name, 'pe-activity')