simp-rake-helpers 5.11.6 → 5.12.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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +28 -0
  3. data/CONTRIBUTING.md +1 -1
  4. data/README.md +1 -1
  5. data/lib/simp/command_utils.rb +21 -0
  6. data/lib/simp/local_gpg_signing_key.rb +128 -79
  7. data/lib/simp/rake.rb +3 -17
  8. data/lib/simp/rake/build/pkg.rb +102 -40
  9. data/lib/simp/rake/helpers/version.rb +1 -1
  10. data/lib/simp/rake/pkg.rb +5 -1
  11. data/lib/simp/rake/pupmod/helpers.rb +2 -0
  12. data/lib/simp/rake/rubygem.rb +5 -1
  13. data/lib/simp/rpm.rb +10 -127
  14. data/lib/simp/rpm_signer.rb +321 -0
  15. data/spec/acceptance/00_pkg_rpm_custom_scriptlets_spec.rb +18 -19
  16. data/spec/acceptance/10_pkg_rpm_spec.rb +46 -48
  17. data/spec/acceptance/50_local_gpg_signing_key_spec.rb +7 -3
  18. data/spec/acceptance/55_build_pkg_signing_spec.rb +293 -42
  19. data/spec/acceptance/files/testpackage/README +8 -0
  20. data/spec/acceptance/files/testpackage/spec/classes/init_spec.rb +1 -0
  21. data/spec/acceptance/files/testpackage/spec/files/mock_something.rb +3 -0
  22. data/spec/acceptance/files/testpackage/utils/convert_v1_to_v2.rb +3 -0
  23. data/spec/acceptance/nodesets/default.yml +15 -2
  24. data/spec/acceptance/support/build_project_helpers.rb +32 -8
  25. data/spec/lib/simp/command_utils_spec.rb +29 -0
  26. data/spec/lib/simp/local_gpg_signing_key_spec.rb.beaker-only +115 -18
  27. data/spec/lib/simp/rake/pupmod/fixtures/simpmod/README.md +2 -2
  28. data/spec/lib/simp/rpm_signer_spec.rb +98 -0
  29. data/spec/lib/simp/rpm_spec.rb +0 -6
  30. metadata +12 -67
  31. data/.travis.yml +0 -41
  32. data/spec/acceptance/20_pkg_rpm_upgrade_spec.rb +0 -236
  33. data/spec/acceptance/files/custom_scriptlet_triggers/pupmod-new-package-2.1/CHANGELOG +0 -2
  34. data/spec/acceptance/files/custom_scriptlet_triggers/pupmod-new-package-2.1/Rakefile +0 -3
  35. data/spec/acceptance/files/custom_scriptlet_triggers/pupmod-new-package-2.1/build/rpm_metadata/custom/overrides +0 -14
  36. data/spec/acceptance/files/custom_scriptlet_triggers/pupmod-new-package-2.1/build/rpm_metadata/requires +0 -1
  37. data/spec/acceptance/files/custom_scriptlet_triggers/pupmod-new-package-2.1/metadata.json +0 -33
  38. data/spec/acceptance/files/custom_scriptlet_triggers/pupmod-new-package-3.0/CHANGELOG +0 -2
  39. data/spec/acceptance/files/custom_scriptlet_triggers/pupmod-new-package-3.0/Rakefile +0 -3
  40. data/spec/acceptance/files/custom_scriptlet_triggers/pupmod-new-package-3.0/build/rpm_metadata/custom/overrides +0 -14
  41. data/spec/acceptance/files/custom_scriptlet_triggers/pupmod-new-package-3.0/build/rpm_metadata/requires +0 -1
  42. data/spec/acceptance/files/custom_scriptlet_triggers/pupmod-new-package-3.0/metadata.json +0 -33
  43. data/spec/acceptance/files/custom_scriptlet_triggers/pupmod-old-package-1.0/CHANGELOG +0 -2
  44. data/spec/acceptance/files/custom_scriptlet_triggers/pupmod-old-package-1.0/Rakefile +0 -3
  45. data/spec/acceptance/files/custom_scriptlet_triggers/pupmod-old-package-1.0/build/rpm_metadata/requires +0 -1
  46. data/spec/acceptance/files/custom_scriptlet_triggers/pupmod-old-package-1.0/metadata.json +0 -33
  47. data/spec/acceptance/files/custom_scriptlet_triggers/pupmod-old-package-2.0/CHANGELOG +0 -2
  48. data/spec/acceptance/files/custom_scriptlet_triggers/pupmod-old-package-2.0/Rakefile +0 -3
  49. data/spec/acceptance/files/custom_scriptlet_triggers/pupmod-old-package-2.0/build/rpm_metadata/requires +0 -1
  50. data/spec/acceptance/files/custom_scriptlet_triggers/pupmod-old-package-2.0/metadata.json +0 -33
  51. data/spec/acceptance/files/custom_scriptlet_triggers/pupmod-old-package-2.2/CHANGELOG +0 -2
  52. data/spec/acceptance/files/custom_scriptlet_triggers/pupmod-old-package-2.2/Rakefile +0 -3
  53. data/spec/acceptance/files/custom_scriptlet_triggers/pupmod-old-package-2.2/build/rpm_metadata/custom/overrides +0 -14
  54. data/spec/acceptance/files/custom_scriptlet_triggers/pupmod-old-package-2.2/build/rpm_metadata/requires +0 -1
  55. data/spec/acceptance/files/custom_scriptlet_triggers/pupmod-old-package-2.2/metadata.json +0 -33
  56. data/spec/acceptance/files/mock_packages/pupmod-puppetlabs-stdlib.spec +0 -32
  57. data/spec/acceptance/files/mock_packages/pupmod-simp-foo.spec +0 -32
  58. data/spec/acceptance/files/mock_packages/pupmod-simp-simplib.spec +0 -32
  59. data/spec/acceptance/files/mock_packages/rpmbuild.sh +0 -25
  60. data/spec/acceptance/files/mock_packages/simp-adapter.spec +0 -43
  61. data/spec/acceptance/files/mock_packages/simp-adapter/etc/simp/adapter_config.yaml +0 -3
  62. data/spec/acceptance/files/mock_packages/simp-adapter/usr/local/sbin/simp_rpm_helper +0 -495
  63. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-1.0/CHANGELOG +0 -2
  64. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-1.0/Rakefile +0 -3
  65. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-1.0/build/rpm_metadata/requires +0 -2
  66. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-1.0/data/os/CentOS.yaml +0 -2
  67. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-1.0/data/os/RedHat.yaml +0 -2
  68. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-1.0/hiera.yaml +0 -14
  69. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-1.0/manifests/init.pp +0 -2
  70. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-1.0/metadata.json +0 -37
  71. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-2.0/CHANGELOG +0 -5
  72. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-2.0/Rakefile +0 -3
  73. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-2.0/build/rpm_metadata/requires +0 -2
  74. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-2.0/data/os/CentOS.yaml +0 -2
  75. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-2.0/data/os/RedHat.yaml +0 -2
  76. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-2.0/hiera.yaml +0 -14
  77. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-2.0/manifests/init.pp +0 -3
  78. data/spec/acceptance/files/package_upgrades/pupmod-simp-testpackage-2.0/metadata.json +0 -37
  79. data/spec/lib/simp/ci/files/job_broken_link_nodeset/spec/acceptance/suites/default/nodesets +0 -1
  80. data/spec/lib/simp/ci/files/job_invalid_nodeset/spec/acceptance/suites/default/nodesets +0 -1
  81. data/spec/lib/simp/ci/files/job_invalid_suite/spec/acceptance/suites/default/nodesets +0 -1
  82. data/spec/lib/simp/ci/files/job_missing_nodeset/spec/acceptance/suites/default/nodesets +0 -1
  83. data/spec/lib/simp/ci/files/job_missing_suite_and_nodeset/spec/acceptance/suites/default/nodesets +0 -1
  84. data/spec/lib/simp/ci/files/multiple_invalid_jobs/spec/acceptance/suites/default/nodesets +0 -1
  85. data/spec/lib/simp/ci/files/multiple_valid_jobs/spec/acceptance/suites/default/nodesets +0 -1
  86. data/spec/lib/simp/ci/files/no_gitlab_config_with_tests/spec/acceptance/suites/default/nodesets +0 -1
  87. data/spec/lib/simp/ci/files/no_gitlab_config_without_tests/spec/acceptance/suites/default/nodesets +0 -1
  88. data/spec/lib/simp/ci/files/suite_skeleton_only/spec/acceptance/nodesets/default.yml +0 -1
  89. data/spec/lib/simp/ci/files/suite_skeleton_only/spec/acceptance/suites/default/nodesets +0 -1
  90. data/spec/lib/simp/ci/files/valid_job_nodeset_dir_link/spec/acceptance/suites/default/nodesets +0 -1
  91. data/spec/lib/simp/ci/files/valid_job_nodeset_link/spec/acceptance/suites/default/nodesets/default.yml +0 -1
  92. data/spec/lib/simp/files/build/testpackage.spec +0 -1
  93. data/spec/lib/simp/rake/pupmod/fixtures/simpmod/spec/acceptance/nodesets/default.yml +0 -1
  94. data/spec/lib/simp/rake/pupmod/fixtures/simpmod/spec/acceptance/suites/default/nodesets +0 -1
@@ -2,5 +2,5 @@ module Simp; end
2
2
  module Simp::Rake; end
3
3
 
4
4
  class Simp::Rake::Helpers
5
- VERSION = '5.11.6'
5
+ VERSION = '5.12.0'
6
6
  end
data/lib/simp/rake/pkg.rb CHANGED
@@ -235,7 +235,11 @@ module Simp::Rake
235
235
  %(-D '_sourcedir #{@rpm_srcdir}'),
236
236
  %(-D '_rpmdir #{@pkg_dir}'),
237
237
  %(-D '_srcrpmdir #{@pkg_dir}'),
238
- %(-D '_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm')
238
+ %(-D '_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm'),
239
+
240
+ # needed on EL8 to disable the aggressive brp_mangle_shebangs script
241
+ # that results in invalid script shebangs; does nothing in EL7
242
+ %(-D '__brp_mangle_shebangs /usr/bin/true')
239
243
  ]
240
244
  rpm_opts << '-v' if @verbose
241
245
  rpm_opts << "-D 'lua_debug 1'" if (ENV.fetch('SIMP_RAKE_PKG_LUA_verbose','no') =='yes')
@@ -290,6 +290,7 @@ class Simp::Rake::Pupmod::Helpers < ::Rake::TaskLib
290
290
  # That will give Travis a way of warning us if the changelog
291
291
  # will prevent the rpm from building.
292
292
  task :changelog_annotation, [:quiet] do |t,args|
293
+ warning('DEPRECATED: use pkg:create_tag_changelog')
293
294
  quiet = true if args[:quiet].to_s == 'true'
294
295
  puts changelog_annotation( quiet )
295
296
  end
@@ -332,6 +333,7 @@ class Simp::Rake::Pupmod::Helpers < ::Rake::TaskLib
332
333
  - doc directory
333
334
  EOM
334
335
  task :compare_latest_tag, [:tags_source, :ignore_owner, :verbose] do |t,args|
336
+ warning('DEPRECATED: use pkg:compare_latest_tag')
335
337
  require 'json'
336
338
 
337
339
  tags_source = args[:tags_source].nil? ? 'origin' : args[:tags_source]
@@ -49,7 +49,11 @@ module Simp::Rake
49
49
  task :install_gem => [:clean, :gem] do
50
50
  Dir.chdir @rakefile_dir
51
51
  Dir.glob("dist/#{@package}*.gem") do |pkg|
52
- sh %Q{bundle exec gem install --no-ri --no-rdoc #{pkg}}
52
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.6')
53
+ sh %Q{bundle exec gem install --no-document #{pkg}}
54
+ else
55
+ sh %Q{bundle exec gem install --no-ri --no-rdoc #{pkg}}
56
+ end
53
57
  end
54
58
  end
55
59
  end
data/lib/simp/rpm.rb CHANGED
@@ -14,7 +14,6 @@ module Simp
14
14
  require 'pty'
15
15
  require 'rake'
16
16
 
17
- @@gpg_keys = Hash.new
18
17
  attr_reader :verbose, :lua_debug, :packages
19
18
 
20
19
  if Gem.loaded_specs['rake'].version >= Gem::Version.new('0.9')
@@ -402,20 +401,20 @@ module Simp
402
401
  end
403
402
 
404
403
  if version_results[:exit_status] != 0
405
- raise <<-EOE
406
- #{indent('Error getting RPM info:', 2)}
407
- #{indent(version_results[:stderr].strip, 5)}
408
- #{indent("Run '#{rpm_version_query.gsub("\n",'\\n')} #{query_source}' to recreate the issue.", 2)}
409
- EOE
404
+ raise <<~EOE
405
+ #{indent('Error getting RPM info for #{query_source}:', 2)}
406
+ #{indent(version_results[:stderr].strip, 5)}
407
+ #{indent("Run '#{rpm_version_query.gsub("\n",'\\n')} #{query_source}' to recreate the issue.", 2)}
408
+ EOE
410
409
  end
411
410
 
412
411
  unless signature_results.nil?
413
412
  if signature_results[:exit_status] != 0
414
- raise <<-EOE
415
- #{indent('Error getting RPM signature:', 2)}
416
- #{indent(signature_results[:stderr].strip, 5)}
417
- #{indent("Run '#{rpm_signature_query.gsub("\n",'\\n')} #{query_source}' to recreate the issue.", 2)}
418
- EOE
413
+ raise <<~EOE
414
+ #{indent('Error getting RPM signature for #{query_source}:', 2)}
415
+ #{indent(signature_results[:stderr].strip, 5)}
416
+ #{indent("Run '#{rpm_signature_query.gsub("\n",'\\n')} #{query_source}' to recreate the issue.", 2)}
417
+ EOE
419
418
  else
420
419
  signature = signature_results[:stdout].strip
421
420
  end
@@ -498,122 +497,6 @@ EOE
498
497
  end
499
498
  end
500
499
 
501
- # Loads metadata for a GPG key. The GPG key is to be used to sign RPMs. The
502
- # value of gpg_key should be the full path of the directory where the key
503
- # resides. If the metadata cannot be found, then the user will be prompted
504
- # for it.
505
- def self.load_key(gpg_key)
506
- keydir = gpg_key
507
- File.directory?(keydir) || fail("Error: Could not find '#{keydir}'")
508
-
509
- gpg_key = File.basename(gpg_key)
510
-
511
- if @@gpg_keys[gpg_key]
512
- return @@gpg_keys[gpg_key]
513
- end
514
-
515
- gpg_name = nil
516
- gpg_password = nil
517
- begin
518
- File.read("#{keydir}/gengpgkey").each_line do |ln|
519
- name_line = ln.split(/^\s*Name-Email:/)
520
- if name_line.length > 1
521
- gpg_name = name_line.last.strip
522
- end
523
-
524
- passwd_line = ln.split(/^\s*Passphrase:/)
525
- if passwd_line.length > 1
526
- gpg_password = passwd_line.last.strip
527
- end
528
- end
529
- rescue Errno::ENOENT
530
- end
531
-
532
- if gpg_name.nil?
533
- puts "Warning: Could not find valid e-mail address for use with GPG."
534
- puts "Please enter e-mail address to use:"
535
- gpg_name = $stdin.gets.strip
536
- end
537
-
538
- if gpg_password.nil?
539
- if File.exist?(%(#{keydir}/password))
540
- gpg_password = File.read(%(#{keydir}/password)).chomp
541
- end
542
-
543
- if gpg_password.nil?
544
- puts "Warning: Could not find a password in '#{keydir}/password'!"
545
- puts "Please enter your GPG key password:"
546
- system 'stty -echo'
547
- gpg_password = $stdin.gets.strip
548
- system 'stty echo'
549
- end
550
- end
551
-
552
- gpg_key_size = nil
553
- gpg_key_id = nil
554
- %x(gpg --homedir=#{keydir} --list-keys #{gpg_name} 2>&1).each_line do |line|
555
- head,data = line.split(/\s+/)
556
- if head == 'pub'
557
- gpg_key_size,gpg_key_id = data.split('/')
558
- break
559
- end
560
- end
561
-
562
- if !gpg_key_size || !gpg_key_id
563
- fail("Error getting GPG Key metadata")
564
- end
565
-
566
- @@gpg_keys[gpg_key] = {
567
- :dir => keydir,
568
- :name => gpg_name,
569
- :key_id => gpg_key_id,
570
- :key_size => gpg_key_size,
571
- :password => gpg_password
572
- }
573
- end
574
-
575
- # Signs the given RPM with the given gpg_key (see Simp::RPM.load_key for
576
- # details on the value of this parameter).
577
- def self.signrpm(rpm, gpg_key)
578
- gpgkey = load_key(gpg_key)
579
-
580
- gpg_sig = nil
581
- %x(rpm -Kv #{rpm}).each_line do |line|
582
- if line =~ /key\sID\s(.*):/
583
- gpg_sig = $1.strip
584
- end
585
- end
586
-
587
- unless gpg_sig == gpgkey[:key_id]
588
- signcommand = "rpm " +
589
- "--define '%_signature gpg' " +
590
- "--define '%__gpg %{_bindir}/gpg' " +
591
- "--define '%_gpg_name #{gpgkey[:name]}' " +
592
- "--define '%_gpg_path #{gpgkey[:dir]}' " +
593
- "--resign #{rpm}"
594
- begin
595
- PTY.spawn(signcommand) do |read, write, pid|
596
- begin
597
- while !read.eof? do
598
- read.expect(/pass\s?phrase:.*/) do |text|
599
- write.puts(gpgkey[:password])
600
- write.flush
601
- end
602
- end
603
- rescue Errno::EIO
604
- # This ALWAYS happens in Ruby 1.8.
605
- end
606
- Process.wait(pid)
607
- end
608
-
609
- raise "Failure running #{signcommand}" unless $?.success?
610
- rescue Exception => e
611
- puts "Error occured while attempting to sign #{rpm}, skipping."
612
- puts e
613
- end
614
- end
615
- end
616
-
617
500
  # Returns the version of RPM installed on the system
618
501
  def self.version
619
502
  %x{rpm --version}.strip.split.last
@@ -0,0 +1,321 @@
1
+ require 'find'
2
+ require 'parallel'
3
+ require 'simp/rpm'
4
+ require 'simp/command_utils'
5
+
6
+ module Simp; end
7
+
8
+ # Class to sign RPMs. Uses 'gpg' and 'rpm' executables.
9
+ class Simp::RpmSigner
10
+ require 'expect'
11
+ require 'pty'
12
+
13
+ extend Simp::CommandUtils
14
+
15
+ @@gpg_keys = Hash.new
16
+
17
+ # Kill the GPG agent operating with the specified key dir, if
18
+ # rpm version 4.13.0 or later.
19
+ #
20
+ # Beginning with version 4.13.0, rpm stands up a gpg-agent when
21
+ # a signing operation is requested.
22
+ def self.kill_gpg_agent(gpg_keydir)
23
+ return if Gem::Version.new(Simp::RPM.version) < Gem::Version.new('4.13.0')
24
+
25
+ %x(gpg-agent --homedir #{gpg_keydir} -q >& /dev/null)
26
+ if $? && ($?.exitstatus == 0)
27
+ # gpg-agent is running for specified keydir, so query it for its pid
28
+ output = %x{echo 'GETINFO pid' | gpg-connect-agent --homedir=#{gpg_keydir}}
29
+ if $? && ($?.exitstatus == 0)
30
+ pid = output.lines.first[1..-1].strip.to_i
31
+ begin
32
+ Process.kill(0, pid)
33
+ Process.kill(15, pid)
34
+ rescue Errno::ESRCH
35
+ # No longer running, so nothing to do!
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ # Loads metadata for a GPG key found in gpg_keydir.
42
+ #
43
+ # The GPG key is to be used to sign RPMs. If the required metadata cannot be
44
+ # retrieved from files found in the gpg_keydir, the user will be prompted
45
+ # for it.
46
+ #
47
+ # @param gpg_keydir The full path of the directory where the key resides
48
+ # @param verbose Whether to log debug information.
49
+ #
50
+ # @raise If the 'gpg' executable cannot be found, the GPG key directory
51
+ # does not exist or GPG key metadata cannot be determined via 'gpg'
52
+ #
53
+ def self.load_key(gpg_keydir, verbose = false)
54
+ which('gpg') || raise("ERROR: Cannot sign RPMs without 'gpg'")
55
+ File.directory?(gpg_keydir) || raise("ERROR: Could not find GPG keydir '#{gpg_keydir}'")
56
+
57
+ gpg_key = File.basename(gpg_keydir)
58
+
59
+ if @@gpg_keys[gpg_key]
60
+ return @@gpg_keys[gpg_key]
61
+ end
62
+
63
+ gpg_name = nil
64
+ gpg_password = nil
65
+ begin
66
+ File.read("#{gpg_keydir}/gengpgkey").each_line do |ln|
67
+ name_line = ln.split(/^\s*Name-Email:/)
68
+ if name_line.length > 1
69
+ gpg_name = name_line.last.strip
70
+ end
71
+
72
+ passwd_line = ln.split(/^\s*Passphrase:/)
73
+ if passwd_line.length > 1
74
+ gpg_password = passwd_line.last.strip
75
+ end
76
+ end
77
+ rescue Errno::ENOENT
78
+ end
79
+
80
+ if gpg_name.nil?
81
+ puts "Warning: Could not find valid e-mail address for use with GPG."
82
+ puts "Please enter e-mail address to use:"
83
+ gpg_name = $stdin.gets.strip
84
+ end
85
+
86
+ if gpg_password.nil?
87
+ if File.exist?(%(#{gpg_keydir}/password))
88
+ gpg_password = File.read(%(#{gpg_keydir}/password)).chomp
89
+ end
90
+
91
+ if gpg_password.nil?
92
+ puts "Warning: Could not find a password in '#{gpg_keydir}/password'!"
93
+ puts "Please enter your GPG key password:"
94
+ system 'stty -echo'
95
+ gpg_password = $stdin.gets.strip
96
+ system 'stty echo'
97
+ end
98
+ end
99
+
100
+ gpg_key_size = nil
101
+ gpg_key_id = nil
102
+ cmd = "gpg --with-colons --homedir=#{gpg_keydir} --list-keys '<#{gpg_name}>' 2>&1"
103
+ puts "Executing: #{cmd}" if verbose
104
+ %x(#{cmd}).each_line do |line|
105
+ # See https://github.com/CSNW/gnupg/blob/master/doc/DETAILS
106
+ # Index Content
107
+ # 0 record type
108
+ # 2 key length
109
+ # 4 keyID
110
+ fields = line.split(':')
111
+ if fields[0] && (fields[0] == 'pub')
112
+ gpg_key_size = fields[2].to_i
113
+ gpg_key_id = fields[4]
114
+ break
115
+ end
116
+ end
117
+
118
+ if !gpg_key_size || !gpg_key_id
119
+ raise("Error getting GPG key ID or Key size metadata for #{gpg_name}")
120
+ end
121
+
122
+ @@gpg_keys[gpg_key] = {
123
+ :dir => gpg_keydir,
124
+ :name => gpg_name,
125
+ :key_id => gpg_key_id,
126
+ :key_size => gpg_key_size,
127
+ :password => gpg_password
128
+ }
129
+ end
130
+
131
+ # Signs the given RPM with the GPG key found in gpg_keydir
132
+ #
133
+ # @param rpm Fully qualified path to an RPM to be signed.
134
+ # @param gpg_keydir The full path of the directory where the key resides.
135
+ # @param options Options Hash
136
+ #
137
+ # @options options :digest_algo Digest algorithm to use in RPM
138
+ # signing operation; defaults to 'sha256'
139
+ # @options options :timeout_seconds Timeout in seconds for an individual
140
+ # RPM signing operation; defaults to 60.
141
+ # @options options :verbose Whether to log debug information;
142
+ # defaults to false.
143
+ #
144
+ # @return Whether package signing operation succeeded
145
+ # @raise RuntimeError if 'rpmsign' executable cannot be found, the 'gpg
146
+ # 'executable cannot be found, the GPG key directory does not exist or
147
+ # the GPG key metadata cannot be determined via 'gpg'
148
+ def self.sign_rpm(rpm, gpg_keydir, options={})
149
+ # This may be a little confusing...Although we're using 'rpm --resign'
150
+ # in lieu of 'rpmsign --addsign', they are equivalent and the presence
151
+ # of 'rpmsign' is a legitimate check that the 'rpm --resign' capability
152
+ # is available (i.e., rpm-sign package has been installed).
153
+ which('rpmsign') || raise("ERROR: Cannot sign RPMs without 'rpmsign'.")
154
+
155
+ digest_algo = options.key?(:digest_algo) ? options[:digest_algo] : 'sha256'
156
+ timeout_seconds = options.key?(:timeout_seconds) ? options[:timeout_seconds] : 60
157
+ verbose = options.key?(:verbose) ? options[:verbose] : false
158
+
159
+ gpgkey = load_key(gpg_keydir, verbose)
160
+
161
+ gpg_sign_cmd_extra_args = nil
162
+ if Gem::Version.new(Simp::RPM.version) >= Gem::Version.new('4.13.0')
163
+ gpg_sign_cmd_extra_args = "--define '%_gpg_sign_cmd_extra_args --pinentry-mode loopback --verbose'"
164
+ end
165
+
166
+ signcommand = [
167
+ 'rpm',
168
+ "--define '%_signature gpg'",
169
+ "--define '%__gpg %{_bindir}/gpg'",
170
+ "--define '%_gpg_name #{gpgkey[:name]}'",
171
+ "--define '%_gpg_path #{gpgkey[:dir]}'",
172
+ "--define '%_gpg_digest_algo #{digest_algo}'",
173
+ gpg_sign_cmd_extra_args,
174
+ "--resign #{rpm}"
175
+ ].compact.join(' ')
176
+
177
+ success = false
178
+ begin
179
+ if verbose
180
+ puts "Signing #{rpm} with #{gpgkey[:name]} from #{gpgkey[:dir]}:\n #{signcommand}"
181
+ end
182
+
183
+ require 'timeout'
184
+ # With rpm-sign-4.14.2-11.el8_0 (EL 8.0), if rpm cannot start the
185
+ # gpg-agent daemon, it will just hang. We need to be able to detect
186
+ # the problem and report the failure.
187
+ Timeout::timeout(timeout_seconds) do
188
+
189
+ status = nil
190
+ PTY.spawn(signcommand) do |read, write, pid|
191
+ begin
192
+ while !read.eof? do
193
+ # rpm version >= 4.13.0 will stand up a gpg-agent and so the
194
+ # prompt for the passphrase will only actually happen if this is
195
+ # the first RPM to be signed with the key after the gpg-agent is
196
+ # started and the key's passphrase has not been cleared from the
197
+ # agent's cache.
198
+ read.expect(/pass\s?phrase:.*/) do |text|
199
+ write.puts(gpgkey[:password])
200
+ write.flush
201
+ end
202
+ end
203
+ rescue Errno::EIO
204
+ # Will get here once input is no longer needed, which can be
205
+ # immediately, if a gpg-agent is already running and the
206
+ # passphrase for the key is loaded in its cache.
207
+ end
208
+
209
+ Process.wait(pid)
210
+ status = $?
211
+ end
212
+
213
+ if status && !status.success?
214
+ raise "Failure running <#{signcommand}>"
215
+ end
216
+ end
217
+
218
+ puts "Successfully signed #{rpm}" if verbose
219
+ success = true
220
+
221
+ rescue Timeout::Error
222
+ $stderr.puts "Failed to sign #{rpm} in #{timeout_seconds} seconds."
223
+ rescue Exception => e
224
+ $stderr.puts "Error occurred while attempting to sign #{rpm}:"
225
+ $stderr.puts e
226
+ end
227
+
228
+ success
229
+ end
230
+
231
+ # Signs any RPMs found within the entire rpm_dir directory tree with
232
+ # the GPG key found in gpg_keydir
233
+ #
234
+ # @param rpm_dir A directory or directory glob pattern specifying 1 or
235
+ # more directories containing RPM files to sign.
236
+ # @param gpg_keydir The full path of the directory where the key resides
237
+ # @param options Options Hash
238
+ #
239
+ # @options options :digest_algo Digest algorithm to use in RPM
240
+ # signing operation; defaults to
241
+ # 'sha256'
242
+ # @options options :force Force RPMs that are already signed
243
+ # to be resigned; defaults to false.
244
+ # @options options :max_concurrent Maximum number of concurrent RPM
245
+ # signing operations; defaults to 1.
246
+ # @options options :progress_bar_title Title for the progress bar logged to
247
+ # the console during the signing process;
248
+ # defaults to 'sign_rpms'.
249
+ # @options options :timeout_seconds Timeout in seconds for an individual
250
+ # RPM signing operation; defauls to 60.
251
+ # @options options :verbose Whether to log debug information;
252
+ # defaults to false.
253
+ #
254
+ # @return Hash of RPM signing results or nil if no RPMs found in rpm_dir
255
+ # * Each Hash key is the path to a RPM
256
+ # * Each Hash value is the status of the signing operation: :signed,
257
+ # :unsigned, :skipped_already_signed
258
+ #
259
+ # @raise RuntimeError if 'rpmsign' executable cannot be found, the 'gpg'
260
+ # executable cannot be found, the GPG key directory does not exist,
261
+ # the GPG key metadata cannot be determined via 'gpg' or any RPM signing
262
+ # operation failed
263
+ #
264
+ def self.sign_rpms(rpm_dir, gpg_keydir, options = {})
265
+ opts = {
266
+ :digest_algo => 'sha256',
267
+ :force => false,
268
+ :max_concurrent => 1,
269
+ :progress_bar_title => 'sign_rpms',
270
+ :timeout_seconds => 60,
271
+ :verbose => false
272
+ }.merge(options)
273
+
274
+ rpm_dirs = Dir.glob(rpm_dir)
275
+ to_sign = []
276
+
277
+ rpm_dirs.each do |rpm_dir|
278
+ Find.find(rpm_dir) do |rpm|
279
+ next unless File.readable?(rpm)
280
+ to_sign << rpm if rpm =~ /\.rpm$/
281
+ end
282
+ end
283
+
284
+ return nil if to_sign.empty?
285
+
286
+ results = []
287
+ begin
288
+ results = Parallel.map(
289
+ to_sign,
290
+ :in_processes => opts[:max_concurrent],
291
+ :progress => opts[:progress_bar_title]
292
+ ) do |rpm|
293
+ _result = nil
294
+
295
+ begin
296
+ if opts[:force] || !Simp::RPM.new(rpm).signature
297
+ _result = [ rpm, sign_rpm(rpm, gpg_keydir, opts) ]
298
+ _result[1] = _result[1] ? :signed : :unsigned
299
+ else
300
+ puts "Skipping signed package #{rpm}" if opts[:verbose]
301
+ _result = [ rpm, :skipped_already_signed ]
302
+ end
303
+ rescue Exception => e
304
+ # can get here if rpm is malformed and Simp::RPM.new fails
305
+ $stderr.puts "Failed to sign #{rpm}:\n#{e.message}"
306
+ _result = [ rpm, :unsigned ]
307
+ end
308
+
309
+ _result
310
+ end
311
+ ensure
312
+ kill_gpg_agent(gpg_keydir)
313
+ end
314
+
315
+ results.to_h
316
+ end
317
+
318
+ def self.clear_gpg_keys_cache
319
+ @@gpg_keys.clear
320
+ end
321
+ end