simp-rake-helpers 5.6.2 → 5.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/lib/simp/packer/iso_vars_json.rb +87 -0
  4. data/lib/simp/rake/build/auto.rb +6 -31
  5. data/lib/simp/rake/helpers/assets/rpm_spec/simp6.spec +36 -8
  6. data/lib/simp/rake/helpers/assets/rpm_spec/simpdefault.spec +36 -8
  7. data/lib/simp/rake/helpers/version.rb +1 -1
  8. data/spec/acceptance/00_pkg_rpm_custom_scriptlets_spec.rb +19 -3
  9. data/spec/acceptance/10_pkg_rpm_spec.rb +52 -8
  10. data/spec/acceptance/20_pkg_rpm_upgrade_spec.rb +223 -0
  11. data/spec/acceptance/files/mock_packages/simp-adapter/usr/local/sbin/simp_rpm_helper +334 -245
  12. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-1.0/CHANGELOG +2 -0
  13. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-1.0/Rakefile +3 -0
  14. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-1.0/build/rpm_metadata/requires +2 -0
  15. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-1.0/data/os/CentOS.yaml +2 -0
  16. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-1.0/data/os/RedHat.yaml +2 -0
  17. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-1.0/hiera.yaml +14 -0
  18. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-1.0/manifests/init.pp +2 -0
  19. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-1.0/metadata.json +37 -0
  20. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-2.0/CHANGELOG +5 -0
  21. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-2.0/Rakefile +3 -0
  22. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-2.0/build/rpm_metadata/requires +2 -0
  23. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-2.0/data/os/CentOS.yaml +2 -0
  24. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-2.0/data/os/RedHat.yaml +2 -0
  25. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-2.0/hiera.yaml +14 -0
  26. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-2.0/manifests/init.pp +3 -0
  27. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-2.0/metadata.json +37 -0
  28. data/spec/acceptance/nodesets/default.yml +7 -8
  29. data/spec/acceptance/support/pkg_rpm_helpers.rb +2 -2
  30. data/spec/lib/simp/packer/iso_vars_json_spec.rb +65 -0
  31. metadata +29 -5
  32. data/spec/acceptance/20_pkg_rpm_safely_upgrading_obsolete_modules_spec.rb +0 -175
@@ -0,0 +1,223 @@
1
+ require 'spec_helper_acceptance'
2
+ require_relative 'support/pkg_rpm_helpers'
3
+
4
+ require 'beaker/puppet_install_helper'
5
+ require 'json'
6
+
7
+ RSpec.configure do |c|
8
+ c.include Simp::BeakerHelpers::SimpRakeHelpers::PkgRpmHelpers
9
+ c.extend Simp::BeakerHelpers::SimpRakeHelpers::PkgRpmHelpers
10
+ end
11
+
12
+ # This tests RPM upgrade scenarios for components that use
13
+ # simp-adapter's simp_rpm_helper to copy files from the RPM install
14
+ # directory to a second destination directory
15
+
16
+
17
+ shared_examples_for 'RPM generator' do
18
+ it 'should create RPMs' do
19
+ testpackages.each do |package|
20
+ on host, %Q(#{run_cmd} "cd #{pkg_root_dir}/#{package}; ) +
21
+ %Q(rvm use default; bundle update --local || bundle update")
22
+ rpm_name = package.sub(/-[^-]+$/,'')
23
+ # In case previous tests haven't been clean
24
+ on host, "rpm -q #{rpm_name} && rpm -e #{rpm_name}; :"
25
+
26
+ on host, %(#{run_cmd} "cd #{pkg_root_dir}/#{package}; #{rake_cmd} pkg:rpm")
27
+ end
28
+ end
29
+ end
30
+
31
+ shared_examples_for 'an upgrade path that works safely with simp_rpm_helper' do |first_package_file, second_package_file|
32
+ let( :rpm_regex ) do
33
+ /^(?<name>pupmod-[a-z0-9_]+-[a-z0-9_]+)-(?<version>\d+\.\d+\.\d+)-(?<release>\d+)\..*\.rpm$/
34
+ end
35
+
36
+ let( :first_package_version ){ first_package_file.match(rpm_regex)['version'] }
37
+ let( :first_package_name ){ first_package_file.match(rpm_regex)['name'] }
38
+ let( :first_package_forge_name ){ first_package_name.sub(/^[^-]+-/,'') }
39
+ let( :first_package_module_name ){ first_package_forge_name.sub(/^[^-]+-/,'') }
40
+ let( :first_package_dir_name ){ first_package_name + '-' + first_package_version.sub(/\.\d+-\d+$/,'') }
41
+
42
+ let( :second_package_name ){ second_package_file.match(rpm_regex)['name'] }
43
+ let( :second_package_forge_name ){ second_package_name.sub(/^[^-]+-/,'') }
44
+ let( :second_package_module_name ){ second_package_forge_name.sub(/^[^-]+-/,'') }
45
+ let( :second_package_version ){ second_package_file.match(rpm_regex)['version'] }
46
+ let( :second_package_dir_name ){ second_package_name + '-' + second_package_version.sub(/\.\d+-\d+$/,'') }
47
+
48
+ context "When upgrading from #{first_package_file} to #{second_package_file}" do
49
+ it "should clean out any old installs" do
50
+ on host, "rpm -e #{first_package_name} &> /dev/null; " +
51
+ "rpm -e #{second_package_name} &> /dev/null ",
52
+ accept_all_exit_codes: true
53
+ end
54
+
55
+ it "should install #{first_package_file}" do
56
+ on host, "cd #{pkg_root_dir}/#{first_package_dir_name.gsub(/\.\d+$/,'')}; "+
57
+ "rpm -Uvh dist/#{first_package_file}"
58
+ end
59
+
60
+ it "should transfer contents of #{first_package_file} into the code directory" do
61
+ result = on host, "cat /opt/test/puppet/code/#{first_package_module_name}/metadata.json"
62
+ metadata = JSON.parse(result.stdout)
63
+ expect(metadata['name']).to eq first_package_forge_name
64
+ expect(metadata['version']).to eq first_package_version
65
+
66
+ # This verifies all files/dirs from the first package are copied
67
+ on host, "diff -r /usr/share/simp/modules/#{first_package_module_name} /opt/test/puppet/code/#{first_package_module_name}"
68
+ end
69
+
70
+ it "should upgrade to #{second_package_file}" do
71
+ on host, "cd #{pkg_root_dir}/#{second_package_dir_name.gsub(/\.\d+$/,'')}; rpm -Uvh dist/#{second_package_file}"
72
+ end
73
+
74
+ it "should transfer contents of #{second_package_file} into the code directory" do
75
+ result = on host, "cat /opt/test/puppet/code/#{second_package_module_name}/metadata.json"
76
+ metadata = JSON.parse(result.stdout)
77
+ expect(metadata['name']).to eq second_package_forge_name
78
+ expect(metadata['version']).to eq second_package_version
79
+
80
+ # This verifies all files/dirs from the second package are copied and
81
+ # no files/dirs onyn in the old package remain
82
+ on host, "diff -r /usr/share/simp/modules/#{second_package_module_name} /opt/test/puppet/code/#{second_package_module_name}"
83
+ end
84
+
85
+ end
86
+ end
87
+
88
+ describe 'rake pkg:rpm + component upgrade scenarios' do
89
+
90
+ before :all do
91
+ copy_host_files_into_build_user_homedir(hosts)
92
+
93
+ comment 'ensure the Puppet AIO is installed'
94
+ #FIXME Should install Puppet 5
95
+ ENV['PUPPET_INSTALL_TYPE'] ||= 'agent'
96
+ ENV['PUPPET_INSTALL_VERSION'] ||= '1.10.6'
97
+ run_puppet_install_helper_on(hosts)
98
+
99
+ comment 'configure puppet agent to look like a Puppet server for simp_rpm_helper'
100
+ on hosts, '/opt/puppetlabs/bin/puppet config --section master set user root; ' +
101
+ '/opt/puppetlabs/bin/puppet config --section master set group root; ' +
102
+ '/opt/puppetlabs/bin/puppet config --section master set codedir /opt/test/puppet/code; ' +
103
+ '/opt/puppetlabs/bin/puppet config --section master set confdir /opt/test/puppet/code'
104
+
105
+
106
+ comment 'build and install mock RPMs'
107
+ mock_pkg_dir = '/home/build_user/host_files/spec/acceptance/files/mock_packages'
108
+ on hosts, %Q[#{run_cmd} "cd #{mock_pkg_dir}; rm -rf pkg"]
109
+ on hosts, %Q[#{run_cmd} "cd #{mock_pkg_dir}; bash rpmbuild.sh simp-adapter.spec"]
110
+ on hosts, %Q[#{run_cmd} "cd #{mock_pkg_dir}; bash rpmbuild.sh pupmod-puppetlabs-stdlib.spec"]
111
+ on hosts, %Q[#{run_cmd} "cd #{mock_pkg_dir}; bash rpmbuild.sh pupmod-simp-simplib.spec"]
112
+ on hosts, %Q[#{run_cmd} "cd #{mock_pkg_dir}; bash rpmbuild.sh pupmod-simp-foo.spec"]
113
+
114
+ on hosts, %Q[rpm -Uvh "#{mock_pkg_dir}/pkg/dist/*.noarch.rpm"], acceptable_exit_codes: [0,1]
115
+ end
116
+
117
+ hosts.each do |_host|
118
+ context "on #{_host}" do
119
+ let!(:host){ _host }
120
+
121
+ # This tests standard upgrades, which should
122
+ context 'with normal module RPM upgrades' do
123
+ let(:pkg_root_dir) do
124
+ '/home/build_user/host_files/spec/acceptance/files/package_upgrades'
125
+ end
126
+
127
+ let(:testpackages) do
128
+ [
129
+ 'pupmod-simp-testpackage-1.0',
130
+ 'pupmod-simp-testpackage-2.0',
131
+ ]
132
+ end
133
+
134
+ context 'RPM build' do
135
+ it_should_behave_like('RPM generator')
136
+ end
137
+
138
+ context 'RPM upgrades' do
139
+ it_should_behave_like('an upgrade path that works safely with simp_rpm_helper',
140
+ 'pupmod-simp-testpackage-1.0.0-0.noarch.rpm',
141
+ 'pupmod-simp-testpackage-2.0.0-0.noarch.rpm')
142
+ end
143
+
144
+ context 'RPM erase' do
145
+ it 'should remove copied files on an erase' do
146
+ on host, 'rpm -e pupmod-simp-testpackage'
147
+ on host, 'ls /opt/test/puppet/code/testpackage', acceptable_exit_codes: [2]
148
+ end
149
+ end
150
+ end
151
+
152
+ # These tests demonstrate custom RPM triggers that work around the obsolete
153
+ # module RPM upgrate + simp_rpm_helper problem described in SIMP-3895:
154
+ #
155
+ # https://simp-project.atlassian.net/browse/SIMP-3988
156
+ #
157
+ # The expected outcome is that simp_rpm_helper always ensures the correct
158
+ # content is installed after an upgrade, even during after a package has been
159
+ # obsoleted. This is accomplished via %triggerpostun -- <name of old package>
160
+ #
161
+ # old 1.0 -> old 2.0 = no need for a trigger
162
+ # old 1.0 -> new 2.0 = must re-run simp_rpm_helper
163
+ # old 1.0 -> new 3.0 = must re-run simp_rpm_helper
164
+ # old 2.0 -> new 2.0 = must re-run simp_rpm_helper
165
+ # old 2.0 -> new 3.0 = must re-run simp_rpm_helper
166
+ # new 2.0 -> new 3.0 = no need for a trigger
167
+ #
168
+ context 'with module RPMs that are susceptible to SIMP-3895' do
169
+ let(:pkg_root_dir) do
170
+ '/home/build_user/host_files/spec/acceptance/files/custom_scriptlet_triggers'
171
+ end
172
+
173
+ let(:testpackages) do
174
+ [
175
+ 'pupmod-old-package-1.0',
176
+ 'pupmod-old-package-2.0',
177
+ 'pupmod-old-package-2.2',
178
+ 'pupmod-new-package-2.1',
179
+ 'pupmod-new-package-3.0',
180
+ ]
181
+ end
182
+
183
+ context 'RPM build' do
184
+ it_should_behave_like('RPM generator')
185
+ end
186
+
187
+ context 'RPM upgrades' do
188
+ it_should_behave_like('an upgrade path that works safely with simp_rpm_helper',
189
+ 'pupmod-old-package-1.0.0-0.noarch.rpm',
190
+ 'pupmod-old-package-2.0.0-0.noarch.rpm')
191
+
192
+ it_should_behave_like('an upgrade path that works safely with simp_rpm_helper',
193
+ 'pupmod-old-package-1.0.0-0.noarch.rpm',
194
+ 'pupmod-new-package-2.1.0-0.noarch.rpm')
195
+
196
+ it_should_behave_like('an upgrade path that works safely with simp_rpm_helper',
197
+ 'pupmod-new-package-2.1.0-0.noarch.rpm',
198
+ 'pupmod-old-package-2.2.0-0.noarch.rpm')
199
+
200
+ it_should_behave_like('an upgrade path that works safely with simp_rpm_helper',
201
+ 'pupmod-old-package-1.0.0-0.noarch.rpm',
202
+ 'pupmod-new-package-3.0.0-0.noarch.rpm')
203
+
204
+ it_should_behave_like('an upgrade path that works safely with simp_rpm_helper',
205
+ 'pupmod-old-package-2.0.0-0.noarch.rpm',
206
+ 'pupmod-new-package-2.1.0-0.noarch.rpm')
207
+
208
+ it_should_behave_like('an upgrade path that works safely with simp_rpm_helper',
209
+ 'pupmod-old-package-2.0.0-0.noarch.rpm',
210
+ 'pupmod-new-package-3.0.0-0.noarch.rpm')
211
+
212
+ it_should_behave_like('an upgrade path that works safely with simp_rpm_helper',
213
+ 'pupmod-old-package-2.2.0-0.noarch.rpm',
214
+ 'pupmod-new-package-3.0.0-0.noarch.rpm')
215
+
216
+ it_should_behave_like('an upgrade path that works safely with simp_rpm_helper',
217
+ 'pupmod-new-package-2.1.0-0.noarch.rpm',
218
+ 'pupmod-new-package-3.0.0-0.noarch.rpm')
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end
@@ -3,8 +3,8 @@
3
3
  # Purpose
4
4
  # -------
5
5
  #
6
- # This script is meant to be called by the %preun and %post sections of the
7
- # various SIMP Puppet module RPMs.
6
+ # This script is meant to be called by the %preun and %postttrans sections of
7
+ # the various SIMP Puppet module RPMs.
8
8
  #
9
9
  # The purpose of the script is to provide helper methods that correctly
10
10
  # scaffold the system in such a way that all SIMP Puppet Modules can be
@@ -56,279 +56,307 @@ require 'find'
56
56
  # Make sure we can find the Puppet executables
57
57
  ENV['PATH'] += ':/opt/puppetlabs/bin'
58
58
 
59
- # Get the Puppet configuration parameters currently in use
60
- def get_puppet_config
61
- system_config = %x{puppet config --section master print}
59
+ class SimpRpmHelper
60
+ def initialize
61
+ @program_name = File.basename(__FILE__)
62
62
 
63
- config_hash = Hash.new
64
-
65
- system_config.each_line do |line|
66
- k,v = line.split('=')
67
- config_hash[k.strip] = v.strip
63
+ # A list of modules that should never be touched once installed
64
+ @safe_modules = ['site']
68
65
  end
69
66
 
70
- return config_hash
71
- end
72
-
73
- # A more friendly failure message
74
- def fail(msg)
75
- $stderr.puts(msg)
76
- exit(1)
77
- end
67
+ def debug(msg)
68
+ # SIMP RPMs do not enable debug when they call this script. So, if
69
+ # you want to debug an RPM problem with this script, comment out
70
+ # the line below.
71
+ return unless @options.debug
72
+ msg.split("\n").each do |line|
73
+ puts ">>>#{@program_name} DEBUG: #{line}"
74
+ end
75
+ end
78
76
 
79
- # Determine whether the passed path is under management by git or svn
80
- def is_managed?(path)
81
- # Short circuit if the directory is not present
82
- return false unless File.directory?(path)
77
+ def info(msg)
78
+ # When these messages get written out in an RPM upgrade, name of program
79
+ # is helpful to end user
80
+ puts "#{@program_name}: #{msg}"
81
+ end
83
82
 
84
- git = Facter::Core::Execution.which('git')
85
- svn = Facter::Core::Execution.which('svn')
83
+ # Get the Puppet configuration parameters currently in use
84
+ def get_puppet_config
85
+ system_config = %x{puppet config --section master print}
86
86
 
87
- Dir.chdir(path) do
88
- if git
89
- %x{#{git} ls-files . --error-unmatch &> /dev/null}
87
+ config_hash = Hash.new
90
88
 
91
- return true if $?.success?
89
+ system_config.each_line do |line|
90
+ k,v = line.split('=')
91
+ config_hash[k.strip] = v.strip
92
92
  end
93
93
 
94
- if svn
95
- %x{#{svn} info &> /dev/null}
96
-
97
- return true if $?.success?
98
- end
94
+ return config_hash
99
95
  end
100
96
 
101
- return false
102
- end
97
+ # Determine whether the passed path is under management by git or svn
98
+ def is_managed?(path)
99
+ # Short circuit if the directory is not present
100
+ return false unless File.directory?(path)
103
101
 
104
- def parse_options
105
- options = OpenStruct.new
106
- options.config_file = '/etc/simp/adapter_config.yaml'
107
- options.preserve = false
108
- # These are not settable, but it's nice to have all this material in one place
109
- options.puppet_user = @puppet_config['user']
110
- options.puppet_group = @puppet_config['group']
111
-
112
- all_opts = OptionParser.new do |opts|
113
- opts.banner = "Usage: #{$0} [options]"
114
-
115
- opts.separator ""
116
-
117
- opts.on(
118
- "--rpm_dir PATH",
119
- "The directory into which the RPM source material is installed"
120
- ) do |arg|
121
- options.rpm_dir = arg.strip
122
- options.module_name = File.basename(options.rpm_dir)
123
- end
102
+ git = Facter::Core::Execution.which('git')
103
+ svn = Facter::Core::Execution.which('svn')
124
104
 
125
- opts.on(
126
- "--rpm_section SECTION",
127
- "The section of the RPM from which the script is being called.",
128
- " Must be one of 'pre', 'post', 'preun', 'postun'"
129
- ) do |arg|
130
- options.rpm_section = arg.strip
131
- end
105
+ Dir.chdir(path) do
106
+ if git
107
+ %x{#{git} ls-files . --error-unmatch &> /dev/null}
132
108
 
133
- opts.on(
134
- "--rpm_status STATUS",
135
- "The status code passed to the RPM section"
136
- ) do |arg|
137
- options.rpm_status = arg.strip
138
- end
109
+ return true if $?.success?
110
+ end
139
111
 
140
- opts.on(
141
- "-f CONFIG_FILE",
142
- "--config CONFIG_FILE",
143
- "The configuration file to use",
144
- " Default: #{options.config_file}"
145
- ) do |arg|
146
- options.config_file = arg.strip
147
- end
112
+ if svn
113
+ %x{#{svn} info &> /dev/null}
148
114
 
149
- opts.on(
150
- "-p",
151
- "--preserve",
152
- "Preserve material in 'target_dir' that is not in 'rpm_dir'"
153
- ) do |arg|
154
- options.preserve = true
115
+ return true if $?.success?
116
+ end
155
117
  end
156
118
 
157
- opts.on(
158
- "-e",
159
- "--enforce",
160
- "If set, enforce the copy, regardless of the setting in the config file",
161
- " Default: false"
162
- ) do |arg|
163
- options.copy_rpm_data = true
164
- end
119
+ return false
120
+ end
165
121
 
166
- opts.on(
167
- "-t DIR",
168
- "--target_dir DIR",
169
- "The subdirectory of #{simp_target_dir('')}",
170
- "into which to copy the materials.",
171
- " Default: #{simp_target_dir.gsub(/#{simp_target_dir('')}/,'')}"
172
- ) do |arg|
173
- options.target_dir = simp_target_dir(arg.strip)
174
- end
122
+ def parse_options(args)
175
123
 
176
- opts.on(
177
- "-h",
178
- "--help",
179
- "Help Message"
180
- ) do
181
- puts opts
182
- exit(0)
183
- end
184
- end
124
+ @options = OpenStruct.new
125
+ @options.config_file = '/etc/simp/adapter_config.yaml'
126
+ @options.preserve = false
185
127
 
186
- begin
187
- all_opts.parse!(ARGV)
188
- rescue OptionParser::ParseError => e
189
- puts e
190
- puts all_opts
191
- exit 1
192
- end
128
+ all_opts = OptionParser.new do |opts|
129
+ opts.banner = "Usage: #{@program_name} [options]"
193
130
 
194
- validate_options(options, all_opts.to_s)
131
+ opts.separator ''
195
132
 
196
- return options
197
- end
133
+ opts.on(
134
+ '--rpm_dir PATH',
135
+ 'The directory into which the RPM source material is installed'
136
+ ) do |arg|
137
+ @options.rpm_dir = arg.strip
138
+ @options.module_name = File.basename(@options.rpm_dir)
139
+ end
198
140
 
199
- # Process the config, validate the entries and do some munging
200
- # Return an options hash
201
- def process_config(config_file, options=OpenStruct.new)
202
- # Defaults
203
- config = {
204
- 'target_directory' => 'auto',
205
- 'copy_rpm_data' => false
206
- }
141
+ opts.on(
142
+ '--rpm_section SECTION',
143
+ 'The section of the RPM from which the script is being called.',
144
+ " Must be one of 'pre', 'preun', 'postun', 'posttrans'"
145
+ ) do |arg|
146
+ @options.rpm_section = arg.strip
147
+ end
207
148
 
208
- if File.exist?(config_file)
209
- begin
210
- system_config = YAML.load_file(config_file)
211
- if system_config
212
- config.merge!(system_config)
149
+ opts.on(
150
+ '--rpm_status STATUS',
151
+ 'The status code passed to the RPM section'
152
+ ) do |arg|
153
+ @options.rpm_status = arg.strip
213
154
  end
214
- rescue
215
- fail("Error: Config file '#{config_file}' could not be processed")
216
- end
217
- end
218
155
 
219
- if options.copy_rpm_data.nil?
220
- options.copy_rpm_data = (config['copy_rpm_data'].to_s == 'true')
221
- end
156
+ opts.on(
157
+ '-f CONFIG_FILE',
158
+ '--config CONFIG_FILE',
159
+ 'The configuration file to use.',
160
+ " Default: #{@options.config_file}"
161
+ ) do |arg|
162
+ @options.config_file = arg.strip
163
+ end
222
164
 
223
- if options.target_dir.nil? && config['target_directory']
224
- if config['target_directory'] == 'auto'
225
- options.target_dir = simp_target_dir
226
- else
227
- unless config['target_directory'][0].chr == '/'
228
- fail("Error: 'target_directory' in '#{config_file}' must be an absolute path")
165
+ opts.on(
166
+ '-p',
167
+ '--preserve',
168
+ "Preserve material in 'target_dir' that is not in 'rpm_dir'"
169
+ ) do |arg|
170
+ @options.preserve = true
229
171
  end
230
172
 
231
- options.target_dir = config['target_directory'].strip
232
- end
233
- end
173
+ opts.on(
174
+ '-e',
175
+ '--enforce',
176
+ 'If set, enforce the copy, regardless of the setting in the config file',
177
+ ' Default: false'
178
+ ) do |arg|
179
+ @options.copy_rpm_data = true
180
+ end
234
181
 
235
- return options
236
- end
182
+ opts.on(
183
+ '-t DIR',
184
+ '--target_dir DIR',
185
+ "The subdirectory of #{simp_target_dir('')}",
186
+ 'into which to copy the materials.',
187
+ " Default: #{simp_target_dir.gsub(/#{simp_target_dir('')}/,'')}"
188
+ ) do |arg|
189
+ @options.target_dir = simp_target_dir(arg.strip)
190
+ end
237
191
 
238
- def puppet_codedir
239
- # Figure out where the Puppet code should go
240
- # Puppet 4+
241
- code_dir = @puppet_config['codedir']
242
- if !code_dir || code_dir.empty?
243
- code_dir = @puppet_config['confdir']
244
- end
192
+ opts.on(
193
+ '-v',
194
+ '--verbose',
195
+ 'Print out debug info when processing.'
196
+ ) do
197
+ @options.debug = true
198
+ end
245
199
 
246
- return code_dir
247
- end
200
+ opts.on(
201
+ '-h',
202
+ '--help',
203
+ 'Help Message'
204
+ ) do
205
+ puts opts
206
+ @options.help_requested = true
207
+ end
208
+ end
248
209
 
249
- # Return the target installation directory
250
- def simp_target_dir(subdir=File.join('simp','modules'))
251
- install_target = puppet_codedir
210
+ begin
211
+ all_opts.parse!(args)
212
+ rescue OptionParser::ParseError => e
213
+ msg = "Error: #{e}\n\n#{all_opts}"
214
+ raise(msg)
215
+ end
252
216
 
253
- if install_target.empty?
254
- fail('Error: Could not find a Puppet code directory for installation')
217
+ validate_options(all_opts.to_s)
255
218
  end
256
219
 
257
- install_target = File.join(install_target,'environments', subdir)
220
+ # Process the config, validate the entries and do some munging
221
+ # Sets @options hash.
222
+ def process_config
223
+ # Defaults
224
+ config = {
225
+ 'target_directory' => 'auto',
226
+ 'copy_rpm_data' => false
227
+ }
228
+
229
+ if File.exist?(@options.config_file)
230
+ begin
231
+ system_config = YAML.load_file(@options.config_file)
232
+ if system_config
233
+ config.merge!(system_config)
234
+ end
235
+ rescue
236
+ raise("Error: Config file '#{@options.config_file}' could not be processed")
237
+ end
238
+ end
258
239
 
259
- return install_target
260
- end
240
+ if @options.copy_rpm_data.nil?
241
+ @options.copy_rpm_data = (config['copy_rpm_data'].to_s == 'true')
242
+ end
261
243
 
262
- # Input Validation
263
- def validate_options(options, usage)
244
+ if @options.target_dir.nil? && config['target_directory']
245
+ if config['target_directory'] == 'auto'
246
+ @options.target_dir = simp_target_dir
247
+ else
248
+ unless config['target_directory'][0].chr == '/'
249
+ raise("Error: 'target_directory' in '#{@options.config_file}' must be an absolute path")
250
+ end
264
251
 
265
- unless options.rpm_dir
266
- fail("Error: 'rpm_dir' is required\n#{usage}")
252
+ @options.target_dir = config['target_directory'].strip
253
+ end
254
+ end
267
255
  end
268
256
 
269
- unless options.rpm_status
270
- fail("Error: 'rpm_status' is required\n#{usage}")
257
+ def puppet_codedir
258
+ # Figure out where the Puppet code should go
259
+ # Puppet 4+
260
+ code_dir = puppet_config['codedir']
261
+ if !code_dir || code_dir.empty?
262
+ code_dir = puppet_config['confdir']
263
+ end
264
+
265
+ return code_dir
271
266
  end
272
267
 
273
- unless options.rpm_section
274
- fail("Error: 'rpm_section' is required\n#{usage}")
268
+ def puppet_config
269
+ unless @puppet_config
270
+ @puppet_config = get_puppet_config
271
+ end
272
+ @puppet_config
275
273
  end
276
274
 
277
- valid_rpm_sections = ['pre','post','preun','postun']
278
275
 
279
- unless valid_rpm_sections.include?(options.rpm_section)
280
- fail("Error: 'rpm_section' must be one of '#{valid_rpm_sections.join("', '")}'\n#{usage}")
276
+ def puppet_group
277
+ puppet_config['group']
281
278
  end
282
279
 
283
- if (options.rpm_section == 'post') || (options.rpm_section == 'preun')
284
- unless File.directory?(options.rpm_dir)
285
- fail("Error: Could not find 'rpm_dir': '#{options.rpm_dir}'")
280
+ # Return the target installation directory
281
+ def simp_target_dir(subdir=File.join('simp','modules'))
282
+ install_target = puppet_codedir
283
+
284
+ if install_target.empty?
285
+ raise('Error: Could not find a Puppet code directory for installation')
286
286
  end
287
- end
288
287
 
289
- unless options.rpm_status =~ /^\d+$/
290
- fail("Error: 'rpm_status' must be an integer\n#{usage}")
288
+ install_target = File.join(install_target,'environments', subdir)
289
+
290
+ return install_target
291
291
  end
292
292
 
293
- fail('Error: Could not determine puppet user') if options.puppet_user.empty?
294
- fail('Error: Could not determine puppet group') if options.puppet_group.empty?
295
- end
293
+ # Input Validation
294
+ def validate_options(usage)
295
+ return if @options.help_requested
296
296
 
297
- ### Main Code
297
+ unless @options.rpm_dir
298
+ raise("Error: 'rpm_dir' is required\n#{usage}")
299
+ end
298
300
 
299
- @puppet_config = get_puppet_config
300
- options = parse_options
301
- options = process_config(options.config_file, options)
301
+ unless @options.rpm_status
302
+ raise("Error: 'rpm_status' is required\n#{usage}")
303
+ end
302
304
 
303
- # A list of modules that should never be touched once installed
304
- safe_modules = ['site']
305
+ unless @options.rpm_section
306
+ raise("Error: 'rpm_section' is required\n#{usage}")
307
+ end
305
308
 
306
- # If the target directory is managed, we're done
307
- unless is_managed?(File.join(options.target_dir, options.module_name)) || !options.copy_rpm_data
309
+ # We allow 'post' for backward compatibility with SIMP RPMs that use
310
+ # this, but copying over files in the 'post' during an upgrade is
311
+ # problematic. If the old package has files that are not in the new
312
+ # package, these files will not be removed in the destination directory.
313
+ # This is because during %post, the old package files have not yet
314
+ # been removed from the source directory by RPM. So, the 'rsync'
315
+ # operation copies over the OBE files from the old package.
316
+ valid_rpm_sections = ['pre','post','preun','postun', 'posttrans']
317
+
318
+ unless valid_rpm_sections.include?(@options.rpm_section)
319
+ raise("Error: 'rpm_section' must be one of '#{valid_rpm_sections.join("', '")}'\n#{usage}")
320
+ end
308
321
 
309
- rsync = Facter::Core::Execution.which('rsync')
310
- fail("Error: Could not find 'rsync' command!") unless rsync
322
+ if (@options.rpm_section == 'posttrans') || (@options.rpm_section == 'preun') || (@options.rpm_section == 'post')
323
+ unless File.directory?(@options.rpm_dir)
324
+ raise("Error: Could not find 'rpm_dir': '#{@options.rpm_dir}'")
325
+ end
326
+ end
311
327
 
312
- # A regular installation or upgrade
313
- if options.rpm_section == 'post'
314
- if safe_modules.include?(options.module_name)
328
+ unless @options.rpm_status =~ /^\d+$/
329
+ raise("Error: 'rpm_status' must be an integer\n#{usage}")
330
+ end
331
+
332
+ end
333
+
334
+ def handle_install
335
+ debug("Processing install, upgrade, or downgrade of #{@options.module_name}")
336
+ if @safe_modules.include?(@options.module_name)
315
337
  # Make sure that we preserve anything in the safe modules on installation
316
- options.preserve = true
338
+ @options.preserve = true
317
339
 
318
- if options.rpm_status == '2'
340
+ if @options.rpm_status == '2'
319
341
  # Short circuit on upgrading safe modules, just don't touch them!
320
- if File.directory?(File.join(options.target_dir, options.module_name))
321
- exit(0)
342
+ target_module_dir = File.join(@options.target_dir, @options.module_name)
343
+ if File.directory?(target_module_dir)
344
+ debug("Skipping upgrade of 'safe' module directory #{target_module_dir}")
345
+ return
322
346
  end
323
347
  end
324
348
  end
325
349
 
350
+ raise('Error: Could not determine puppet group') if puppet_group.empty?
351
+ rsync = Facter::Core::Execution.which('rsync')
352
+ raise("Error: Could not find 'rsync' command!") unless rsync
353
+
326
354
  # Create the directories, with the proper mode, all the way down
327
- dir_paths = options.target_dir.split(File::SEPARATOR).reject(&:empty?)
355
+ dir_paths = @options.target_dir.split(File::SEPARATOR).reject(&:empty?)
328
356
  top_dir = File::SEPARATOR + dir_paths.shift
329
357
  unless File.directory?(top_dir)
330
358
  FileUtils.mkdir(top_dir, :mode => 0750)
331
- FileUtils.chown('root', options.puppet_group, top_dir)
359
+ FileUtils.chown('root', puppet_group, top_dir)
332
360
  end
333
361
 
334
362
  orig_dir = Dir.pwd
@@ -336,7 +364,7 @@ unless is_managed?(File.join(options.target_dir, options.module_name)) || !optio
336
364
  dir_paths.each do |dir|
337
365
  unless File.directory?(dir)
338
366
  FileUtils.mkdir(dir, :mode => 0750)
339
- FileUtils.chown('root', options.puppet_group, dir)
367
+ FileUtils.chown('root', puppet_group, dir)
340
368
  end
341
369
 
342
370
  Dir.chdir(dir)
@@ -345,62 +373,123 @@ unless is_managed?(File.join(options.target_dir, options.module_name)) || !optio
345
373
 
346
374
  cmd = %(#{rsync} -a --force)
347
375
 
348
- if options.preserve
376
+ if @options.preserve
349
377
  cmd += %( --ignore-existing)
350
378
  else
351
- cmd += %( --delete)
379
+ cmd += %( --delete)
352
380
  end
353
381
 
354
- cmd += %( #{options.rpm_dir} #{options.target_dir})
382
+ cmd += %( --verbose) if @options.debug
383
+
384
+ cmd += %( #{@options.rpm_dir} #{@options.target_dir})
355
385
  cmd += %( 2>&1)
356
386
 
387
+ info("Copying '#{@options.module_name}' files into '#{@options.target_dir}'")
388
+ debug("Executing: #{cmd}")
357
389
  output = %x{#{cmd}}
390
+ debug("Output:\n#{output}")
358
391
  unless $?.success?
359
- fail(%(Error: Copy of '#{options.module_name}' into '#{options.target_dir}' using '#{cmd}' failed with the following error:\n #{output.gsub("\n","\n ")}))
392
+ raise(%(Error: Copy of '#{@options.module_name}' into '#{@options.target_dir}' using '#{cmd}' failed with the following error:\n #{output.gsub("\n","\n ")}))
360
393
  end
361
394
 
362
- FileUtils.chown_R(nil, "#{options.puppet_group}", options.target_dir)
395
+ FileUtils.chown_R(nil, "#{puppet_group}", @options.target_dir)
363
396
  end
364
397
 
365
- # A regular uninstall or downgrade
366
- # This needs to happen *before* we uninstall since we need to compare with what's on disk
367
- if options.rpm_section == 'preun' && options.rpm_status == '0'
398
+ def handle_uninstall
399
+ debug("Processing uninstall of #{@options.module_name}")
368
400
  # Play it safe, this needs to have at least 'environments/simp' in it!
369
- if options.target_dir.split(File::SEPARATOR).reject(&:empty?).size < 3
370
- fail("Error: Not removing directory '#{options.target_dir}' for safety")
371
- else
372
- unless safe_modules.include?(options.module_name)
373
- # Find out what we have
374
- ref_list = []
375
- Dir.chdir(options.rpm_dir) do
376
- Find.find('.').each do |file|
377
- if File.symlink?(file)
378
- ref_list << file
379
- Find.prune
380
- end
381
-
382
- ref_list << file
383
- end
401
+ if @options.target_dir.split(File::SEPARATOR).reject(&:empty?).size < 3
402
+ raise("Error: Not removing directory '#{@options.target_dir}' for safety")
403
+ end
404
+
405
+ if @safe_modules.include?(@options.module_name)
406
+ target_module_dir = File.join(@options.target_dir, @options.module_name)
407
+ debug("Skipping removal of 'safe' module directory #{target_module_dir}")
408
+ return
409
+ end
410
+
411
+ info("Removing '#{@options.module_name}' files from '#{@options.target_dir}'")
412
+
413
+ # Find out what we have
414
+ ref_list = []
415
+ Dir.chdir(@options.rpm_dir) do
416
+ Find.find('.').each do |file|
417
+ if File.symlink?(file)
418
+ ref_list << file
419
+ Find.prune
384
420
  end
385
421
 
386
- # Delete from the bottom up to clear out the directories first
387
- ref_list.reverse!
388
- ref_list.map{|x| x.sub!(/^./, options.module_name)}
389
-
390
- # Only delete items that are in the reference repo
391
- Dir.chdir(options.target_dir) do
392
- ref_list.each do |to_rm|
393
- if File.symlink?(to_rm)
394
- FileUtils.rm_f(to_rm)
395
- elsif File.directory?(to_rm) && (Dir.entries(to_rm).delete_if {|dir|
396
- dir == '.' || dir == '..'}.size == 0)
397
- FileUtils.rmdir(to_rm)
398
- elsif File.exist?(to_rm)
399
- FileUtils.rm_f(to_rm)
400
- end
401
- end
422
+ ref_list << file
423
+ end
424
+ end
425
+
426
+ # Delete from the bottom up to clear out the directories first
427
+ # before removing them
428
+ ref_list.reverse!
429
+ ref_list.map{|x| x.sub!(/^./, @options.module_name)}
430
+
431
+ # Only delete items that are in the reference repo
432
+ Dir.chdir(@options.target_dir) do
433
+ ref_list.each do |to_rm|
434
+ if File.symlink?(to_rm)
435
+ debug("Removing symlink #{to_rm}")
436
+ FileUtils.rm_f(to_rm)
437
+ elsif File.directory?(to_rm) && (Dir.entries(to_rm).delete_if {|dir|
438
+ dir == '.' || dir == '..'}.size == 0)
439
+ debug("Removing directory #{to_rm}")
440
+ FileUtils.rmdir(to_rm)
441
+ elsif File.exist?(to_rm)
442
+ debug("Removing file #{to_rm}")
443
+ FileUtils.rm_f(to_rm)
402
444
  end
403
445
  end
404
446
  end
405
447
  end
448
+
449
+ def run(args)
450
+ parse_options(args)
451
+ return 0 if @options.help_requested
452
+
453
+ process_config
454
+ debug("Running with config=#{@options.to_s}")
455
+
456
+ # If the target directory is managed, we're done
457
+ target_module_dir = File.join(@options.target_dir, @options.module_name)
458
+ unless is_managed?(target_module_dir) || !@options.copy_rpm_data
459
+
460
+ debug("Processing unmanaged target directory #{target_module_dir}")
461
+
462
+ if (@options.rpm_section == 'posttrans') || (@options.rpm_section == 'post')
463
+ # A regular installation, upgrade or downgrade
464
+ # This *should* happen in the RPM %posttrans, but we allow this to
465
+ # occur in the %post for backward compatibility with SIMP RPMs that
466
+ # erroneously try to affect a copy in the %post. (Copying over the
467
+ # files in the RPM %post during an upgrade/downgrade is problematic.
468
+ # If the old package has files that are not in the new package,
469
+ # these files will not yet have been removed in the source
470
+ # directory, and thus end up in the target directory.)
471
+ handle_install
472
+ elsif @options.rpm_section == 'preun' && @options.rpm_status == '0'
473
+ # A regular uninstall
474
+ # This needs to happen *before* RPM removes the files (%preun with
475
+ # status 0), since we need to compare with what's on disk to undo
476
+ # the copy done during the RPM install via handle_install()
477
+ handle_uninstall
478
+ end
479
+ end
480
+ return 0
481
+ rescue RuntimeError => e
482
+ $stderr.puts(e)
483
+ return 1
484
+ rescue Exception => e
485
+ $stderr.puts(e)
486
+ e.backtrace.first(10).each{|l| $stderr.puts l }
487
+ return 1
488
+ end
406
489
  end
490
+
491
+ if __FILE__ == $0
492
+ helper = SimpRpmHelper.new
493
+ exit helper.run(ARGV)
494
+ end
495
+