simp-rake-helpers 5.24.0 → 6.0.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 (130) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +20 -1
  3. data/CHANGELOG.md +33 -0
  4. data/Gemfile +14 -6
  5. data/lib/simp/command_utils.rb +6 -3
  6. data/lib/simp/componentinfo.rb +47 -33
  7. data/lib/simp/local_gpg_signing_key.rb +302 -303
  8. data/lib/simp/packer/iso_vars_json.rb +17 -15
  9. data/lib/simp/rake/build/auto.rb +415 -432
  10. data/lib/simp/rake/build/build.rb +119 -124
  11. data/lib/simp/rake/build/clean.rb +39 -39
  12. data/lib/simp/rake/build/code.rb +125 -128
  13. data/lib/simp/rake/build/constants.rb +7 -4
  14. data/lib/simp/rake/build/deps.rb +196 -207
  15. data/lib/simp/rake/build/helpers.rb +17 -13
  16. data/lib/simp/rake/build/iso.rb +404 -411
  17. data/lib/simp/rake/build/pkg.rb +752 -759
  18. data/lib/simp/rake/build/rpmdeps.rb +70 -70
  19. data/lib/simp/rake/build/spec.rb +44 -46
  20. data/lib/simp/rake/build/tar.rb +169 -173
  21. data/lib/simp/rake/build/unpack.rb +105 -107
  22. data/lib/simp/rake/build/upload.rb +93 -102
  23. data/lib/simp/rake/fixtures.rb +69 -66
  24. data/lib/simp/rake/helpers/rpm_spec.rb +10 -7
  25. data/lib/simp/rake/helpers/version.rb +3 -1
  26. data/lib/simp/rake/helpers.rb +12 -10
  27. data/lib/simp/rake/pkg.rb +417 -440
  28. data/lib/simp/rake/pupmod/helpers.rb +100 -87
  29. data/lib/simp/rake/pupmod/module_build.rb +39 -0
  30. data/lib/simp/rake/rubygem.rb +57 -56
  31. data/lib/simp/rake.rb +34 -29
  32. data/lib/simp/relchecks.rb +52 -43
  33. data/lib/simp/rpm.rb +123 -127
  34. data/lib/simp/rpm_signer.rb +57 -55
  35. data/lib/simp/yum.rb +54 -53
  36. data/spec/acceptance/nodesets/{default_ruby3_1.yml → almalinux10.yml} +4 -4
  37. data/spec/acceptance/nodesets/{default.yml → almalinux8.yml} +2 -2
  38. data/spec/acceptance/nodesets/almalinux9.yml +25 -0
  39. data/spec/acceptance/suites/default/00_pkg_rpm_custom_scriptlets_spec.rb +23 -28
  40. data/spec/acceptance/suites/default/10_pkg_rpm_spec.rb +54 -56
  41. data/spec/acceptance/suites/default/30_pkg_misc_spec.rb +17 -19
  42. data/spec/acceptance/suites/default/50_local_gpg_signing_key_spec.rb +5 -5
  43. data/spec/acceptance/suites/default/55_build_pkg_signing_spec.rb +109 -101
  44. data/spec/acceptance/suites/default/files/testpackage/spec/classes/init_spec.rb +1 -0
  45. data/spec/acceptance/suites/default/files/testpackage/spec/files/mock_something.rb +3 -1
  46. data/spec/acceptance/suites/default/files/testpackage/utils/convert_v1_to_v2.rb +2 -0
  47. data/spec/acceptance/suites/default/support/build_project_helpers.rb +20 -17
  48. data/spec/acceptance/suites/default/support/build_user_helpers.rb +4 -2
  49. data/spec/acceptance/suites/default/support/pkg_rpm_helpers.rb +30 -31
  50. data/spec/acceptance/support/simp_rake_helpers.rb +3 -1
  51. data/spec/lib/simp/command_utils_spec.rb +13 -10
  52. data/spec/lib/simp/componentinfo_changelog_regex_spec.rb +33 -33
  53. data/spec/lib/simp/componentinfo_spec.rb +99 -86
  54. data/spec/lib/simp/packer/iso_vars_json_spec.rb +16 -14
  55. data/spec/lib/simp/rake/build/helpers_spec.rb +7 -7
  56. data/spec/lib/simp/rake/build/rpmdeps_spec.rb +48 -46
  57. data/spec/lib/simp/rake/helpers_spec.rb +6 -5
  58. data/spec/lib/simp/rake/pkg_spec.rb +7 -5
  59. data/spec/lib/simp/rake/pupmod/fixtures/othermod/spec/classes/init_spec.rb +3 -1
  60. data/spec/lib/simp/rake/pupmod/fixtures/othermod/spec/spec_helper.rb +2 -0
  61. data/spec/lib/simp/rake/pupmod/fixtures/simpmod/spec/acceptance/suites/default/class_spec.rb +6 -5
  62. data/spec/lib/simp/rake/pupmod/fixtures/simpmod/spec/classes/init_spec.rb +51 -33
  63. data/spec/lib/simp/rake/pupmod/fixtures/simpmod/spec/spec_helper.rb +15 -16
  64. data/spec/lib/simp/rake/pupmod/fixtures/simpmod/spec/spec_helper_acceptance.rb +9 -9
  65. data/spec/lib/simp/rake/pupmod/helpers_spec.rb +20 -19
  66. data/spec/lib/simp/rake/pupmod/module_build_spec.rb +87 -0
  67. data/spec/lib/simp/rake_spec.rb +7 -6
  68. data/spec/lib/simp/relchecks_check_rpm_changelog_spec.rb +26 -31
  69. data/spec/lib/simp/relchecks_compare_latest_tag_spec.rb +32 -26
  70. data/spec/lib/simp/relchecks_create_tag_changelog_spec.rb +27 -19
  71. data/spec/lib/simp/rpm_signer_spec.rb +45 -39
  72. data/spec/lib/simp/rpm_spec.rb +190 -194
  73. data/spec/spec_helper.rb +4 -2
  74. data/spec/spec_helper_acceptance.rb +3 -5
  75. metadata +47 -76
  76. data/bin/simp_rake_helpers +0 -3
  77. data/lib/simp/ci/gitlab.rb +0 -226
  78. data/lib/simp/rake/ci.rb +0 -31
  79. data/spec/lib/simp/ci/files/global_nodesets_only/spec/acceptance/nodesets/default.yml +0 -41
  80. data/spec/lib/simp/ci/files/global_nodesets_only/spec/acceptance/nodesets/oel.yml +0 -41
  81. data/spec/lib/simp/ci/files/job_broken_link_nodeset/spec/acceptance/nodesets/centos.yml +0 -41
  82. data/spec/lib/simp/ci/files/job_broken_link_nodeset/spec/acceptance/nodesets/oel.yml +0 -41
  83. data/spec/lib/simp/ci/files/job_broken_link_nodeset/spec/acceptance/suites/default/class_spec.rb +0 -0
  84. data/spec/lib/simp/ci/files/job_invalid_nodeset/spec/acceptance/nodesets/default.yml +0 -41
  85. data/spec/lib/simp/ci/files/job_invalid_nodeset/spec/acceptance/nodesets/oel.yml +0 -41
  86. data/spec/lib/simp/ci/files/job_invalid_nodeset/spec/acceptance/suites/default/class_spec.rb +0 -0
  87. data/spec/lib/simp/ci/files/job_invalid_suite/spec/acceptance/nodesets/default.yml +0 -41
  88. data/spec/lib/simp/ci/files/job_invalid_suite/spec/acceptance/nodesets/oel.yml +0 -41
  89. data/spec/lib/simp/ci/files/job_invalid_suite/spec/acceptance/suites/default/class_spec.rb +0 -0
  90. data/spec/lib/simp/ci/files/job_missing_nodeset/spec/acceptance/nodesets/default.yml +0 -41
  91. data/spec/lib/simp/ci/files/job_missing_nodeset/spec/acceptance/nodesets/oel.yml +0 -41
  92. data/spec/lib/simp/ci/files/job_missing_nodeset/spec/acceptance/suites/default/class_spec.rb +0 -0
  93. data/spec/lib/simp/ci/files/job_missing_suite_and_nodeset/spec/acceptance/nodesets/default.yml +0 -41
  94. data/spec/lib/simp/ci/files/job_missing_suite_and_nodeset/spec/acceptance/nodesets/oel.yml +0 -41
  95. data/spec/lib/simp/ci/files/job_missing_suite_and_nodeset/spec/acceptance/suites/default/class_spec.rb +0 -0
  96. data/spec/lib/simp/ci/files/multiple_invalid_jobs/spec/acceptance/nodesets/default.yml +0 -41
  97. data/spec/lib/simp/ci/files/multiple_invalid_jobs/spec/acceptance/nodesets/oel.yml +0 -41
  98. data/spec/lib/simp/ci/files/multiple_invalid_jobs/spec/acceptance/suites/default/class_spec.rb +0 -0
  99. data/spec/lib/simp/ci/files/multiple_invalid_jobs/spec/acceptance/suites/feature-1/feature-1_spec.rb +0 -0
  100. data/spec/lib/simp/ci/files/multiple_invalid_jobs/spec/acceptance/suites/feature_2/feature_2_spec.rb +0 -0
  101. data/spec/lib/simp/ci/files/multiple_invalid_jobs/spec/acceptance/suites/feature_2/nodesets/default.yml +0 -35
  102. data/spec/lib/simp/ci/files/multiple_invalid_jobs/spec/acceptance/suites/feature_2/nodesets/oel.yml +0 -35
  103. data/spec/lib/simp/ci/files/multiple_valid_jobs/spec/acceptance/nodesets/default.yml +0 -41
  104. data/spec/lib/simp/ci/files/multiple_valid_jobs/spec/acceptance/nodesets/oel.yml +0 -41
  105. data/spec/lib/simp/ci/files/multiple_valid_jobs/spec/acceptance/suites/default/class_spec.rb +0 -0
  106. data/spec/lib/simp/ci/files/multiple_valid_jobs/spec/acceptance/suites/feature-1/feature-1_spec.rb +0 -0
  107. data/spec/lib/simp/ci/files/multiple_valid_jobs/spec/acceptance/suites/feature_2/feature_2_spec.rb +0 -0
  108. data/spec/lib/simp/ci/files/multiple_valid_jobs/spec/acceptance/suites/feature_2/nodesets/default.yml +0 -35
  109. data/spec/lib/simp/ci/files/multiple_valid_jobs/spec/acceptance/suites/feature_2/nodesets/oel.yml +0 -35
  110. data/spec/lib/simp/ci/files/no_acceptance_tests/spec/spec_helper.rb +0 -0
  111. data/spec/lib/simp/ci/files/no_gitlab_config_with_tests/spec/acceptance/nodesets/default.yml +0 -41
  112. data/spec/lib/simp/ci/files/no_gitlab_config_with_tests/spec/acceptance/nodesets/oel.yml +0 -41
  113. data/spec/lib/simp/ci/files/no_gitlab_config_with_tests/spec/acceptance/suites/default/class_spec.rb +0 -0
  114. data/spec/lib/simp/ci/files/no_gitlab_config_without_tests/spec/acceptance/nodesets/default.yml +0 -41
  115. data/spec/lib/simp/ci/files/no_gitlab_config_without_tests/spec/acceptance/nodesets/oel.yml +0 -41
  116. data/spec/lib/simp/ci/files/suite_skeleton_only/spec/acceptance/nodesets/centos.yml +0 -41
  117. data/spec/lib/simp/ci/files/suite_skeleton_only/spec/acceptance/nodesets/oel.yml +0 -41
  118. data/spec/lib/simp/ci/files/valid_job_global_nodeset/spec/acceptance/nodesets/default.yml +0 -41
  119. data/spec/lib/simp/ci/files/valid_job_global_nodeset/spec/acceptance/nodesets/oel.yml +0 -41
  120. data/spec/lib/simp/ci/files/valid_job_global_nodeset/spec/acceptance/suites/default/class_spec.rb +0 -0
  121. data/spec/lib/simp/ci/files/valid_job_nodeset_dir_link/spec/acceptance/nodesets/default.yml +0 -41
  122. data/spec/lib/simp/ci/files/valid_job_nodeset_dir_link/spec/acceptance/nodesets/oel.yml +0 -41
  123. data/spec/lib/simp/ci/files/valid_job_nodeset_dir_link/spec/acceptance/suites/default/class_spec.rb +0 -0
  124. data/spec/lib/simp/ci/files/valid_job_nodeset_link/spec/acceptance/suites/default/class_spec.rb +0 -0
  125. data/spec/lib/simp/ci/files/valid_job_nodeset_link/spec/acceptance/suites/default/nodesets/centos.yml +0 -41
  126. data/spec/lib/simp/ci/files/valid_job_nodeset_link/spec/acceptance/suites/default/nodesets/oel.yml +0 -41
  127. data/spec/lib/simp/ci/files/valid_job_suite_nodeset/spec/acceptance/suites/default/class_spec.rb +0 -0
  128. data/spec/lib/simp/ci/files/valid_job_suite_nodeset/spec/acceptance/suites/default/nodesets/default.yml +0 -41
  129. data/spec/lib/simp/ci/files/valid_job_suite_nodeset/spec/acceptance/suites/default/nodesets/oel.yml +0 -41
  130. data/spec/lib/simp/ci/gitlab_spec.rb +0 -245
@@ -1,213 +1,213 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
1
4
  require 'securerandom'
2
5
  require 'rake'
3
6
  require 'simp/command_utils'
4
7
 
5
- module Simp
6
- # Ensure that a valid GPG signing key exists in a local directory
7
- #
8
- # This is typically used to sign packages during development.
9
- #
10
- # * The signing key will be generated if it is missing or expired.
11
- # * Generated keys are short-lived (default: 14 days).
12
- # * The signing key and its related assets are completely isolated from
13
- # the user's own GPG keys, keyrings, and agent.
14
- # - All files are kept under a local directory tree.
15
- # - New keys are generated using a temporary GPG agent with its own
16
- # settings and socket.
17
- #
18
- # The local signing key's directory includes the following:
19
- # gpg < 2.1.0 (EL7):
20
- #
21
- # ```
22
- # #{key_name}/ # key directory
23
- # +-- RPM-GPG-KEY-SIMP-#{key_name} # key file
24
- # +-- gengpgkey # --gen-key params file **
25
- # +-- gpg-agent-info.env # Lists location of gpg-agent socket + pid
26
- # +-- run_gpg_agnet # Script used to start gpg-agent
27
- # +-- pubring.gpg
28
- # +-- secring.gpg
29
- # +-- trustdb.gpg
30
- # ```
8
+ # Ensure that a valid GPG signing key exists in a local directory
9
+ #
10
+ # This is typically used to sign packages during development.
11
+ #
12
+ # * The signing key will be generated if it is missing or expired.
13
+ # * Generated keys are short-lived (default: 14 days).
14
+ # * The signing key and its related assets are completely isolated from
15
+ # the user's own GPG keys, keyrings, and agent.
16
+ # - All files are kept under a local directory tree.
17
+ # - New keys are generated using a temporary GPG agent with its own
18
+ # settings and socket.
19
+ #
20
+ # The local signing key's directory includes the following:
21
+ # gpg < 2.1.0 (EL7):
22
+ #
23
+ # ```
24
+ # #{key_name}/ # key directory
25
+ # +-- RPM-GPG-KEY-SIMP-#{key_name} # key file
26
+ # +-- gengpgkey # --gen-key params file **
27
+ # +-- gpg-agent-info.env # Lists location of gpg-agent socket + pid
28
+ # +-- run_gpg_agnet # Script used to start gpg-agent
29
+ # +-- pubring.gpg
30
+ # +-- secring.gpg
31
+ # +-- trustdb.gpg
32
+ # ```
33
+ #
34
+ # gpg >= 2.1.0 (EL8):
35
+ # ```
36
+ # #{key_name}/ # key directory
37
+ # +-- RPM-GPG-KEY-SIMP-#{key_name} # key file
38
+ # +-- gengpgkey # --gen-key params file **
39
+ # +-- openpgp-revocs.d/<fingerprint id>.rev
40
+ # +-- private-keys-v1.d/<user id>.key
41
+ # +-- pubring.kbx
42
+ # +-- trustdb.gpg
43
+ # ```
44
+ #
45
+ # `**` = `SIMP::RpmSigner.sign_rpms` will use the values in the `gengpgkey` file
46
+ # for the GPG signing key's email and passphrase
47
+ #
48
+ # If a new key is required, a project-only `gpg-agent` daemon is momentarily
49
+ # created to generate it, and destroyed after this is done. The daemon does
50
+ # not interact with any other `gpg-agent` daemons on the system. It is
51
+ # launched on random socket(s) whose socket file(s) can be found as follows:
52
+ #
53
+ # Location Environment
54
+ # #{key_name} dir Docker container for EL8
55
+ # temp dir in /run/user/<uid>/gnupg EL8
56
+ # temp dir in /tmp EL7
57
+ #
58
+ class Simp::LocalGpgSigningKey
59
+ include FileUtils
60
+ include Simp::CommandUtils
61
+
62
+ # `SIMP::RpmSigner.sign_rpms` will look for a 'gengpgkey' file to
63
+ # non-interactively sign packages.
31
64
  #
32
- # gpg >= 2.1.0 (EL8):
33
- # ```
34
- # #{key_name}/ # key directory
35
- # +-- RPM-GPG-KEY-SIMP-#{key_name} # key file
36
- # +-- gengpgkey # --gen-key params file **
37
- # +-- openpgp-revocs.d/<fingerprint id>.rev
38
- # +-- private-keys-v1.d/<user id>.key
39
- # +-- pubring.kbx
40
- # +-- trustdb.gpg
41
- # ```
42
- #
43
- # `**` = `SIMP::RpmSigner.sign_rpms` will use the values in the `gengpgkey` file
44
- # for the GPG signing key's email and passphrase
65
+ # @see SIMP::RpmSigner.sign_rpms
66
+ GPG_GENKEY_PARAMS_FILENAME = 'gengpgkey'
67
+
68
+ # @param dir [String] path to gpg-agent / key directory
69
+ # @param opts [Hash] optional configurations
45
70
  #
46
- # If a new key is required, a project-only `gpg-agent` daemon is momentarily
47
- # created to generate it, and destroyed after this is done. The daemon does
48
- # not interact with any other `gpg-agent` daemons on the system. It is
49
- # launched on random socket(s) whose socket file(s) can be found as follows:
71
+ # @option opts [String] :label Defaults to the basename of `dir` (dev)
72
+ # @option opts [String] :email (gatekeeper@simp.development.key)
73
+ # @option opts [String] :file Default based on label (RPM-GPG-KEY-SIMP-Dev)
74
+ # @option opts [Boolean] :verbose (false)
50
75
  #
51
- # Location Environment
52
- # #{key_name} dir Docker container for EL8
53
- # temp dir in /run/user/<uid>/gnupg EL8
54
- # temp dir in /tmp EL7
76
+ def initialize(dir = 'dev', opts = {})
77
+ @dir = File.expand_path(dir)
78
+ @label = opts[:label] || File.basename(dir.downcase)
79
+ @key_email = opts[:email] || 'gatekeeper@simp.development.key'
80
+ @key_file = opts[:file] || "RPM-GPG-KEY-SIMP-#{@label.capitalize}"
81
+ @verbose = opts[:verbose] || false
82
+
83
+ # for EL7 only
84
+ @gpg_agent_env_file = 'gpg-agent-info.env'
85
+ @gpg_agent_script = 'run_gpg_agent'
86
+ end
87
+
88
+ # Return the version of GPG installed on the system
55
89
  #
56
- class LocalGpgSigningKey
57
- include FileUtils
58
- include Simp::CommandUtils
90
+ # @return [Gem::Version]
91
+ def gpg_version
92
+ return @gpg_version if @gpg_version
59
93
 
60
- # `SIMP::RpmSigner.sign_rpms` will look for a 'gengpgkey' file to
61
- # non-interactively sign packages.
62
- #
63
- # @see SIMP::RpmSigner.sign_rpms
64
- GPG_GENKEY_PARAMS_FILENAME = 'gengpgkey'.freeze
94
+ which('gpg', true)
95
+ @gpg_version = `gpg --version`.lines.first.split(%r{\s+}).last
65
96
 
66
- # @param dir [String] path to gpg-agent / key directory
67
- # @param opts [Hash] optional configurations
68
- #
69
- # @option opts [String] :label Defaults to the basename of `dir` (dev)
70
- # @option opts [String] :email (gatekeeper@simp.development.key)
71
- # @option opts [String] :file Default based on label (RPM-GPG-KEY-SIMP-Dev)
72
- # @option opts [Boolean] :verbose (false)
73
- #
74
- def initialize(dir = 'dev', opts = {})
75
- @dir = File.expand_path(dir)
76
- @label = opts[:label] || File.basename(dir.downcase)
77
- @key_email = opts[:email] || 'gatekeeper@simp.development.key'
78
- @key_file = opts[:file] || "RPM-GPG-KEY-SIMP-#{@label.capitalize}"
79
- @verbose = opts[:verbose] || false
80
-
81
- # for EL7 only
82
- @gpg_agent_env_file = 'gpg-agent-info.env'
83
- @gpg_agent_script = 'run_gpg_agent'
97
+ unless @gpg_version.nil? || @gpg_version.empty?
98
+ @gpg_version = Gem::Version.new(@gpg_version)
84
99
  end
85
100
 
86
- # Return the version of GPG installed on the system
87
- #
88
- # @return [Gem::Version]
89
- def gpg_version
90
- return @gpg_version if @gpg_version
91
-
92
- which('gpg', true)
93
- @gpg_version = %x{gpg --version}.lines.first.split(/\s+/).last
94
-
95
- unless @gpg_version.nil? || @gpg_version.empty?
96
- @gpg_version = Gem::Version.new(@gpg_version)
97
- end
98
-
99
- @gpg_version
100
- end
101
+ @gpg_version
102
+ end
101
103
 
102
- # Returns a gpg-agent's env string, if it can be detected from the
103
- # gpg-agent-info file
104
- #
105
- # @return [String] if the env string was detected
106
- # @return [nil] if the env string was not detected
107
- #
108
- def gpg_agent_info
109
- if File.exist?(@gpg_agent_env_file)
110
- puts "Reading gpg_agent_info from `#{@gpg_agent_env_file}`..." if @verbose
111
- info = parse_gpg_agent_info_env(File.read(@gpg_agent_env_file))
112
- else
113
- puts "Couldn't find a valid source to read gpg_agent_info..." if @verbose
114
- info = nil
115
- end
116
- info
104
+ # Returns a gpg-agent's env string, if it can be detected from the
105
+ # gpg-agent-info file
106
+ #
107
+ # @return [String] if the env string was detected
108
+ # @return [nil] if the env string was not detected
109
+ #
110
+ def gpg_agent_info
111
+ if File.exist?(@gpg_agent_env_file)
112
+ puts "Reading gpg_agent_info from `#{@gpg_agent_env_file}`..." if @verbose
113
+ info = parse_gpg_agent_info_env(File.read(@gpg_agent_env_file))
114
+ else
115
+ puts "Couldn't find a valid source to read gpg_agent_info..." if @verbose
116
+ info = nil
117
117
  end
118
+ info
119
+ end
118
120
 
119
- # Return the number of days left before the GPG signing key expires or
120
- # 0 if the key does not exist or the key is missing an expiration date.
121
- def dev_key_days_left
122
- which('gpg', true)
123
- ensure_gpg_directory
124
-
125
- days_left = 0
126
- cmd = "gpg --with-colons --homedir=#{@dir} --list-keys '<#{@key_email}>' 2>&1"
127
- puts "Executing: #{cmd}" if @verbose
128
- %x(#{cmd}).each_line do |line|
129
- # See https://github.com/CSNW/gnupg/blob/master/doc/DETAILS
130
- # Index Content
131
- # 0 record type
132
- # 6 expiration date
133
- #
134
- # If expiration date contains a 'T', it is in an ISO 8601 format
135
- # (e.g., 20210223T091500). Otherwise it is seconds since the epoch.
136
- #
137
- fields = line.split(':')
138
- if fields[0] && (fields[0] == 'pub')
139
- raw_exp_date = fields[6]
140
- unless raw_exp_date.nil? || raw_exp_date.strip.empty?
141
- require 'date'
142
-
143
- exp_date = nil
144
- if raw_exp_date.include?('T')
145
- exp_date = DateTime.parse(raw_exp_date).to_date
146
- else
147
- exp_date = Time.at(raw_exp_date.to_i).to_date
148
- end
149
-
150
- days_left = (exp_date - Date.today).to_i
151
- days_left = 0 if days_left < 0
152
- end
153
-
154
- break
155
- end
121
+ # Return the number of days left before the GPG signing key expires or
122
+ # 0 if the key does not exist or the key is missing an expiration date.
123
+ def dev_key_days_left
124
+ which('gpg', true)
125
+ ensure_gpg_directory
126
+
127
+ days_left = 0
128
+ cmd = "gpg --with-colons --homedir=#{@dir} --list-keys '<#{@key_email}>' 2>&1"
129
+ puts "Executing: #{cmd}" if @verbose
130
+ `#{cmd}`.each_line do |line|
131
+ # See https://github.com/CSNW/gnupg/blob/master/doc/DETAILS
132
+ # Index Content
133
+ # 0 record type
134
+ # 6 expiration date
135
+ #
136
+ # If expiration date contains a 'T', it is in an ISO 8601 format
137
+ # (e.g., 20210223T091500). Otherwise it is seconds since the epoch.
138
+ #
139
+ fields = line.split(':')
140
+ next unless fields[0] && (fields[0] == 'pub')
141
+
142
+ raw_exp_date = fields[6]
143
+ unless raw_exp_date.nil? || raw_exp_date.strip.empty?
144
+ require 'date'
145
+ exp_date = if raw_exp_date.include?('T')
146
+ DateTime.parse(raw_exp_date).to_date
147
+ else
148
+ Time.at(raw_exp_date.to_i).to_date
149
+ end
150
+
151
+ days_left = (exp_date - Date.today).to_i
152
+ days_left = 0 if days_left.negative?
156
153
  end
157
154
 
158
- days_left
155
+ break
159
156
  end
160
157
 
161
- # Remove all files under the key directory
162
- def clean_gpg_agent_directory
163
- puts " Removing all files under '#{@dir}'" if @verbose
164
- Dir.glob(File.join(@dir, '*')).each do |todel|
165
- rm_rf(todel, :verbose => @verbose)
166
- end
167
- end
158
+ days_left
159
+ end
168
160
 
169
- # Make sure the local key's directory exists and has correct permissions
170
- def ensure_gpg_directory
171
- mkdir_p(@dir, :verbose => @verbose)
172
- chmod(0o700, @dir, :verbose => @verbose)
161
+ # Remove all files under the key directory
162
+ def clean_gpg_agent_directory
163
+ puts " Removing all files under '#{@dir}'" if @verbose
164
+ Dir.glob(File.join(@dir, '*')).each do |todel|
165
+ rm_rf(todel, :verbose => @verbose)
173
166
  end
167
+ end
174
168
 
175
- # Ensure that the gpg-agent is running with a dev key
176
- def ensure_key
177
- ensure_gpg_directory
178
-
179
- if (days_left = dev_key_days_left) > 0
180
- puts "GPG key (#{@key_email}) will expire in #{days_left} days."
181
- return
182
- end
183
-
184
- Dir.chdir @dir do |_dir|
185
- puts 'Creating a new dev GPG agent...'
169
+ # Make sure the local key's directory exists and has correct permissions
170
+ def ensure_gpg_directory
171
+ mkdir_p(@dir, :verbose => @verbose)
172
+ chmod(0o700, @dir, :verbose => @verbose)
173
+ end
186
174
 
187
- clean_gpg_agent_directory
188
- write_genkey_parameter_file
175
+ # Ensure that the gpg-agent is running with a dev key
176
+ def ensure_key
177
+ ensure_gpg_directory
189
178
 
190
- agent_info = nil
191
- begin
192
- if gpg_version < Gem::Version.new('2.1')
193
- agent_info = start_gpg_agent_old
194
- else
195
- agent_info = start_gpg_agent
196
- end
197
- ensure
198
- kill_agent(agent_info[:pid]) if agent_info
199
- end
179
+ if (days_left = dev_key_days_left).positive?
180
+ puts "GPG key (#{@key_email}) will expire in #{days_left} days."
181
+ return
182
+ end
200
183
 
201
- agent_info
184
+ Dir.chdir @dir do |_dir|
185
+ puts 'Creating a new dev GPG agent...'
186
+
187
+ clean_gpg_agent_directory
188
+ write_genkey_parameter_file
189
+
190
+ agent_info = nil
191
+ begin
192
+ agent_info = if gpg_version < Gem::Version.new('2.1')
193
+ start_gpg_agent_old
194
+ else
195
+ start_gpg_agent
196
+ end
197
+ ensure
198
+ kill_agent(agent_info[:pid]) if agent_info
202
199
  end
200
+
201
+ agent_info
203
202
  end
203
+ end
204
204
 
205
- # Provides an informative warning message to display in the unlikely event
206
- # that a new `gpg-agent` daemon returns empty output when it is started.
207
- #
208
- # @return [String] Warning message
209
- def empty_gpg_agent_message
210
- <<~WARNING
205
+ # Provides an informative warning message to display in the unlikely event
206
+ # that a new `gpg-agent` daemon returns empty output when it is started.
207
+ #
208
+ # @return [String] Warning message
209
+ def empty_gpg_agent_message
210
+ <<~WARNING
211
211
  WARNING: Tried to start an project-only gpg-agent daemon on a random socket by
212
212
  running the script:
213
213
 
@@ -221,156 +221,155 @@ module Simp
221
221
  message appears, please report this issue to the SIMP project,
222
222
  including the OS you were were running from and its versions of the
223
223
  `gpg-agent` and `gpg`/`gpg2` commands.
224
- WARNING
225
- end
224
+ WARNING
225
+ end
226
226
 
227
- # Kills the GPG agent by pid, if it is running
228
- #
229
- # @param pid [String] The GPG Agent PID to kill
230
- def kill_agent(pid)
231
- if pid
232
- Process.kill(0, pid)
233
- Process.kill(15, pid)
234
- end
235
- rescue Errno::ESRCH
236
- # Not Running, Nothing to do!
227
+ # Kills the GPG agent by pid, if it is running
228
+ #
229
+ # @param pid [String] The GPG Agent PID to kill
230
+ def kill_agent(pid)
231
+ if pid
232
+ Process.kill(0, pid)
233
+ Process.kill(15, pid)
237
234
  end
235
+ rescue Errno::ESRCH
236
+ # Not Running, Nothing to do!
237
+ end
238
238
 
239
- # Generate a RPM GPG signing key for local development
240
- #
241
- # @param gpg_agent_info_str [String] value to set the GPG_AGENT_INFO
242
- # environment variable to use in order to use the correct `gpg-agent`.
243
- def generate_key(gpg_agent_info_str)
244
- which('gpg', true)
239
+ # Generate a RPM GPG signing key for local development
240
+ #
241
+ # @param gpg_agent_info_str [String] value to set the GPG_AGENT_INFO
242
+ # environment variable to use in order to use the correct `gpg-agent`.
243
+ def generate_key(gpg_agent_info_str)
244
+ which('gpg', true)
245
245
 
246
- puts "Generating new GPG key#{@verbose ? " under '#{@dir}'" : ''}..."
247
- gpg_cmd = %(GPG_AGENT_INFO=#{gpg_agent_info_str} gpg --homedir="#{@dir}")
246
+ puts "Generating new GPG key#{" under '#{@dir}'" if @verbose}..."
247
+ gpg_cmd = %(GPG_AGENT_INFO=#{gpg_agent_info_str} gpg --homedir="#{@dir}")
248
248
 
249
- pipe = @verbose ? '| tee' : '>'
250
- %x(#{gpg_cmd} --batch --gen-key #{GPG_GENKEY_PARAMS_FILENAME})
251
- %x(#{gpg_cmd} --armor --export '<#{@key_email}>' #{pipe} "#{@key_file}")
249
+ pipe = @verbose ? '| tee' : '>'
250
+ `#{gpg_cmd} --batch --gen-key #{GPG_GENKEY_PARAMS_FILENAME}`
251
+ `#{gpg_cmd} --armor --export '<#{@key_email}>' #{pipe} "#{@key_file}"`
252
252
 
253
- if File.stat(@key_file).size == 0
254
- fail "Error: Something went wrong generating #{@key_file}"
255
- end
256
- end
253
+ return unless File.stat(@key_file).size.zero?
257
254
 
258
- # Return a data structure from a gpg-agent env-file formatted string.
259
- #
260
- # @param str [String] path to gpg-agent / key directory
261
- def parse_gpg_agent_info_env(str)
262
- info = %r{^(GPG_AGENT_INFO=)?(?<info>[^;]+)}.match(str)[:info]
263
- matches = %r{^(?<socket>[^:]+):(?<pid>[^:]+)}.match(info)
264
- { info: info.strip, socket: matches[:socket], pid: matches[:pid].to_i }
255
+ raise "Error: Something went wrong generating #{@key_file}"
256
+ end
257
+
258
+ # Return a data structure from a gpg-agent env-file formatted string.
259
+ #
260
+ # @param str [String] path to gpg-agent / key directory
261
+ def parse_gpg_agent_info_env(str)
262
+ info = %r{^(GPG_AGENT_INFO=)?(?<info>[^;]+)}.match(str)[:info]
263
+ matches = %r{^(?<socket>[^:]+):(?<pid>[^:]+)}.match(info)
264
+ { info: info.strip, socket: matches[:socket], pid: matches[:pid].to_i }
265
+ end
266
+
267
+ # Start the gpg-agent
268
+ # @return Hash of agent info
269
+ # @raise if gpg-agent fails to start
270
+ def start_gpg_agent
271
+ which('gpg', true)
272
+ which('gpg-agent', true)
273
+ which('gpg-connect-agent', true)
274
+
275
+ # Start the GPG agent, if it is not already running
276
+ check_agent = "gpg-agent -q --homedir=#{Dir.pwd} >&/dev/null"
277
+ start_agent = "gpg-agent --homedir=#{Dir.pwd} --daemon >&/dev/null"
278
+ cmd = "#{check_agent} || #{start_agent}"
279
+ puts "Executing: #{cmd}" if @verbose
280
+ `#{cmd}`
281
+ if $CHILD_STATUS && ($CHILD_STATUS.exitstatus != 0)
282
+ err_msg = [
283
+ 'Failed to start gpg-agent during key creation.',
284
+ " Execute '#{start_agent.gsub(' >&/dev/null', '')}' to debug.",
285
+ ].join("\n")
286
+ raise(err_msg)
265
287
  end
266
288
 
267
- # Start the gpg-agent
268
- # @return Hash of agent info
269
- # @raise if gpg-agent fails to start
270
- def start_gpg_agent
271
- which('gpg', true)
272
- which('gpg-agent', true)
273
- which('gpg-connect-agent', true)
274
-
275
- # Start the GPG agent, if it is not already running
276
- check_agent = "gpg-agent -q --homedir=#{Dir.pwd} >&/dev/null"
277
- start_agent = "gpg-agent --homedir=#{Dir.pwd} --daemon >&/dev/null"
278
- cmd = "#{check_agent} || #{start_agent}"
279
- puts "Executing: #{cmd}" if @verbose
280
- %x(#{cmd})
281
- if $? && ($?.exitstatus != 0)
282
- err_msg = [
283
- 'Failed to start gpg-agent during key creation.',
284
- " Execute '#{start_agent.gsub(' >&/dev/null','')}' to debug."
285
- ].join("\n")
286
- raise(err_msg)
287
- end
289
+ agent_info = {}
288
290
 
289
- agent_info = {}
291
+ # Provide a local socket (needed by the `gpg` command when
292
+ agent_info[:socket] = `echo 'GETINFO socket_name' | gpg-connect-agent --homedir=#{Dir.pwd}`.lines.first[1..].strip
290
293
 
291
- # Provide a local socket (needed by the `gpg` command when
292
- agent_info[:socket] = %x{echo 'GETINFO socket_name' | gpg-connect-agent --homedir=#{Dir.pwd}}.lines.first[1..-1].strip
294
+ # Get the pid
295
+ agent_info[:pid] = `echo 'GETINFO pid' | gpg-connect-agent --homedir=#{Dir.pwd}`.lines.first[1..].strip.to_i
293
296
 
294
- # Get the pid
295
- agent_info[:pid] = %x{echo 'GETINFO pid' | gpg-connect-agent --homedir=#{Dir.pwd}}.lines.first[1..-1].strip.to_i
297
+ generate_key(%(#{agent_info[:socket]}:#{agent_info[:pid]}:1))
296
298
 
297
- generate_key(%{#{agent_info[:socket]}:#{agent_info[:pid]}:1})
299
+ agent_info
300
+ end
298
301
 
299
- agent_info
300
- end
302
+ # Start the gpg-agent with options suitable for gpg version < 2.1
303
+ # @return Hash of agent info
304
+ def start_gpg_agent_old
305
+ write_gpg_agent_startup_script
306
+ gpg_agent_output = `./#{@gpg_agent_script}`.strip
301
307
 
302
- # Start the gpg-agent with options suitable for gpg version < 2.1
303
- # @return Hash of agent info
304
- def start_gpg_agent_old
305
- write_gpg_agent_startup_script
306
- gpg_agent_output = %x(./#{@gpg_agent_script}).strip
308
+ # By the time we get here, we can be assured we will be starting a
309
+ # new agent, because the directory is cleaned out.
310
+ #
311
+ # Follow-on gpg actions will read the agent's information from
312
+ # the env-file the agent writes at startup.
307
313
 
308
- # By the time we get here, we can be assured we will be starting a
309
- # new agent, because the directory is cleaned out.
310
- #
311
- # Follow-on gpg actions will read the agent's information from
312
- # the env-file the agent writes at startup.
314
+ # We're using the --sh option which will spew out the agent config
315
+ # when the agent starts. If it is empty, this is a problem.
316
+ warn(empty_gpg_agent_message) if gpg_agent_output.empty?
313
317
 
314
- # We're using the --sh option which will spew out the agent config
315
- # when the agent starts. If it is empty, this is a problem.
316
- warn(empty_gpg_agent_message) if gpg_agent_output.empty?
318
+ agent_info = gpg_agent_info
319
+ generate_key(agent_info[:info])
320
+ agent_info
321
+ end
317
322
 
318
- agent_info = gpg_agent_info
319
- generate_key(agent_info[:info])
320
- agent_info
323
+ # Write the `gpg --genkey --batch` control parameter file
324
+ #
325
+ # @see "Unattended key generation" in /usr/share/doc/gnupg2-*/DETAILS for
326
+ # documentation on the command parameters format
327
+ def write_genkey_parameter_file
328
+ now = Time.now.to_i.to_s
329
+ expire_date = Date.today + 14
330
+ passphrase = SecureRandom.base64(100)
331
+ genkey_parameters = [
332
+ '%echo Generating Development GPG Key',
333
+ '%echo',
334
+ "%echo This key will expire on #{expire_date}",
335
+ '%echo',
336
+ 'Key-Type: RSA',
337
+ 'Key-Length: 4096',
338
+ 'Key-Usage: sign',
339
+ 'Name-Real: SIMP Development',
340
+ "Name-Comment: Development key #{now}",
341
+ "Name-Email: #{@key_email}",
342
+ 'Expire-Date: 2w',
343
+ "Passphrase: #{passphrase}",
344
+ ]
345
+
346
+ if gpg_version < Gem::Version.new('2.1')
347
+ genkey_parameters << '%pubring pubring.gpg'
348
+ genkey_parameters << '%secring secring.gpg'
321
349
  end
322
350
 
323
- # Write the `gpg --genkey --batch` control parameter file
324
- #
325
- # @see "Unattended key generation" in /usr/share/doc/gnupg2-*/DETAILS for
326
- # documentation on the command parameters format
327
- def write_genkey_parameter_file
328
- now = Time.now.to_i.to_s
329
- expire_date = Date.today + 14
330
- passphrase = SecureRandom.base64(100)
331
- genkey_parameters = [
332
- '%echo Generating Development GPG Key',
333
- '%echo',
334
- "%echo This key will expire on #{expire_date}",
335
- '%echo',
336
- 'Key-Type: RSA',
337
- 'Key-Length: 4096',
338
- 'Key-Usage: sign',
339
- 'Name-Real: SIMP Development',
340
- "Name-Comment: Development key #{now}",
341
- "Name-Email: #{@key_email}",
342
- 'Expire-Date: 2w',
343
- "Passphrase: #{passphrase}",
344
- ]
345
-
346
- if gpg_version < Gem::Version.new('2.1')
347
- genkey_parameters << '%pubring pubring.gpg'
348
- genkey_parameters << '%secring secring.gpg'
349
- end
351
+ genkey_parameters << '# The following creates the key, so we can print "Done!" afterwards'
352
+ genkey_parameters << '%commit'
353
+ genkey_parameters << '%echo New GPG Development Key Created'
350
354
 
351
- genkey_parameters << '# The following creates the key, so we can print "Done!" afterwards'
352
- genkey_parameters << '%commit'
353
- genkey_parameters << '%echo New GPG Development Key Created'
354
-
355
- File.open(GPG_GENKEY_PARAMS_FILENAME, 'w') { |fh| fh.puts(genkey_parameters.join("\n")) }
356
- end
355
+ File.open(GPG_GENKEY_PARAMS_FILENAME, 'w') { |fh| fh.puts(genkey_parameters.join("\n")) }
356
+ end
357
357
 
358
- # Write a local gpg-agent daemon script file
359
- def write_gpg_agent_startup_script
360
- which('gpg-agent', true)
361
- pinentry_cmd = which('pinentry-curses', true)
358
+ # Write a local gpg-agent daemon script file
359
+ def write_gpg_agent_startup_script
360
+ which('gpg-agent', true)
361
+ pinentry_cmd = which('pinentry-curses', true)
362
362
 
363
- gpg_agent_script = <<~AGENT_SCRIPT
363
+ gpg_agent_script = <<~AGENT_SCRIPT
364
364
  #!/bin/sh
365
365
 
366
366
  gpg-agent --homedir=#{Dir.pwd} --daemon \
367
367
  --no-use-standard-socket --sh --batch \
368
368
  --write-env-file "#{@gpg_agent_env_file}" \
369
369
  --pinentry-program #{pinentry_cmd} < /dev/null &
370
- AGENT_SCRIPT
370
+ AGENT_SCRIPT
371
371
 
372
- File.open(@gpg_agent_script, 'w') { |fh| fh.puts(gpg_agent_script) }
373
- chmod(0o755, @gpg_agent_script)
374
- end
372
+ File.open(@gpg_agent_script, 'w') { |fh| fh.puts(gpg_agent_script) }
373
+ chmod(0o755, @gpg_agent_script)
375
374
  end
376
375
  end