beaker-answers 0.4.3 → 0.5.0

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