simp-rake-helpers 5.6.2 → 5.7.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.
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
+