simp-rake-helpers 5.25.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -0
- data/Gemfile +13 -6
- data/lib/simp/command_utils.rb +6 -3
- data/lib/simp/componentinfo.rb +47 -33
- data/lib/simp/local_gpg_signing_key.rb +302 -303
- data/lib/simp/packer/iso_vars_json.rb +17 -15
- data/lib/simp/rake/build/auto.rb +415 -432
- data/lib/simp/rake/build/build.rb +119 -124
- data/lib/simp/rake/build/clean.rb +39 -39
- data/lib/simp/rake/build/code.rb +125 -128
- data/lib/simp/rake/build/constants.rb +7 -4
- data/lib/simp/rake/build/deps.rb +196 -207
- data/lib/simp/rake/build/helpers.rb +17 -13
- data/lib/simp/rake/build/iso.rb +404 -411
- data/lib/simp/rake/build/pkg.rb +752 -759
- data/lib/simp/rake/build/rpmdeps.rb +70 -70
- data/lib/simp/rake/build/spec.rb +44 -46
- data/lib/simp/rake/build/tar.rb +169 -173
- data/lib/simp/rake/build/unpack.rb +105 -107
- data/lib/simp/rake/build/upload.rb +93 -102
- data/lib/simp/rake/fixtures.rb +69 -66
- data/lib/simp/rake/helpers/rpm_spec.rb +10 -7
- data/lib/simp/rake/helpers/version.rb +3 -1
- data/lib/simp/rake/helpers.rb +12 -10
- data/lib/simp/rake/pkg.rb +417 -440
- data/lib/simp/rake/pupmod/helpers.rb +100 -87
- data/lib/simp/rake/pupmod/module_build.rb +39 -0
- data/lib/simp/rake/rubygem.rb +57 -56
- data/lib/simp/rake.rb +34 -29
- data/lib/simp/relchecks.rb +52 -43
- data/lib/simp/rpm.rb +123 -127
- data/lib/simp/rpm_signer.rb +57 -55
- data/lib/simp/yum.rb +54 -53
- data/spec/acceptance/nodesets/{default_ruby3_1.yml → almalinux10.yml} +4 -4
- data/spec/acceptance/nodesets/{default.yml → almalinux8.yml} +2 -2
- data/spec/acceptance/nodesets/almalinux9.yml +25 -0
- data/spec/acceptance/suites/default/00_pkg_rpm_custom_scriptlets_spec.rb +23 -28
- data/spec/acceptance/suites/default/10_pkg_rpm_spec.rb +54 -56
- data/spec/acceptance/suites/default/30_pkg_misc_spec.rb +17 -19
- data/spec/acceptance/suites/default/50_local_gpg_signing_key_spec.rb +5 -5
- data/spec/acceptance/suites/default/55_build_pkg_signing_spec.rb +109 -101
- data/spec/acceptance/suites/default/files/testpackage/spec/classes/init_spec.rb +1 -0
- data/spec/acceptance/suites/default/files/testpackage/spec/files/mock_something.rb +3 -1
- data/spec/acceptance/suites/default/files/testpackage/utils/convert_v1_to_v2.rb +2 -0
- data/spec/acceptance/suites/default/support/build_project_helpers.rb +20 -17
- data/spec/acceptance/suites/default/support/build_user_helpers.rb +4 -2
- data/spec/acceptance/suites/default/support/pkg_rpm_helpers.rb +30 -31
- data/spec/acceptance/support/simp_rake_helpers.rb +3 -1
- data/spec/lib/simp/command_utils_spec.rb +13 -10
- data/spec/lib/simp/componentinfo_changelog_regex_spec.rb +33 -33
- data/spec/lib/simp/componentinfo_spec.rb +99 -86
- data/spec/lib/simp/packer/iso_vars_json_spec.rb +16 -14
- data/spec/lib/simp/rake/build/helpers_spec.rb +7 -7
- data/spec/lib/simp/rake/build/rpmdeps_spec.rb +48 -46
- data/spec/lib/simp/rake/helpers_spec.rb +6 -5
- data/spec/lib/simp/rake/pkg_spec.rb +7 -5
- data/spec/lib/simp/rake/pupmod/fixtures/othermod/spec/classes/init_spec.rb +3 -1
- data/spec/lib/simp/rake/pupmod/fixtures/othermod/spec/spec_helper.rb +2 -0
- data/spec/lib/simp/rake/pupmod/fixtures/simpmod/spec/acceptance/suites/default/class_spec.rb +6 -5
- data/spec/lib/simp/rake/pupmod/fixtures/simpmod/spec/classes/init_spec.rb +51 -33
- data/spec/lib/simp/rake/pupmod/fixtures/simpmod/spec/spec_helper.rb +15 -16
- data/spec/lib/simp/rake/pupmod/fixtures/simpmod/spec/spec_helper_acceptance.rb +9 -9
- data/spec/lib/simp/rake/pupmod/helpers_spec.rb +20 -19
- data/spec/lib/simp/rake/pupmod/module_build_spec.rb +87 -0
- data/spec/lib/simp/rake_spec.rb +7 -6
- data/spec/lib/simp/relchecks_check_rpm_changelog_spec.rb +26 -31
- data/spec/lib/simp/relchecks_compare_latest_tag_spec.rb +32 -26
- data/spec/lib/simp/relchecks_create_tag_changelog_spec.rb +27 -19
- data/spec/lib/simp/rpm_signer_spec.rb +45 -39
- data/spec/lib/simp/rpm_spec.rb +190 -194
- data/spec/spec_helper.rb +4 -2
- data/spec/spec_helper_acceptance.rb +3 -5
- metadata +37 -72
- data/bin/simp_rake_helpers +0 -3
- data/lib/simp/ci/gitlab.rb +0 -226
- data/lib/simp/rake/ci.rb +0 -31
- data/spec/lib/simp/ci/files/global_nodesets_only/spec/acceptance/nodesets/default.yml +0 -41
- data/spec/lib/simp/ci/files/global_nodesets_only/spec/acceptance/nodesets/oel.yml +0 -41
- data/spec/lib/simp/ci/files/job_broken_link_nodeset/spec/acceptance/nodesets/centos.yml +0 -41
- data/spec/lib/simp/ci/files/job_broken_link_nodeset/spec/acceptance/nodesets/oel.yml +0 -41
- data/spec/lib/simp/ci/files/job_broken_link_nodeset/spec/acceptance/suites/default/class_spec.rb +0 -0
- data/spec/lib/simp/ci/files/job_invalid_nodeset/spec/acceptance/nodesets/default.yml +0 -41
- data/spec/lib/simp/ci/files/job_invalid_nodeset/spec/acceptance/nodesets/oel.yml +0 -41
- data/spec/lib/simp/ci/files/job_invalid_nodeset/spec/acceptance/suites/default/class_spec.rb +0 -0
- data/spec/lib/simp/ci/files/job_invalid_suite/spec/acceptance/nodesets/default.yml +0 -41
- data/spec/lib/simp/ci/files/job_invalid_suite/spec/acceptance/nodesets/oel.yml +0 -41
- data/spec/lib/simp/ci/files/job_invalid_suite/spec/acceptance/suites/default/class_spec.rb +0 -0
- data/spec/lib/simp/ci/files/job_missing_nodeset/spec/acceptance/nodesets/default.yml +0 -41
- data/spec/lib/simp/ci/files/job_missing_nodeset/spec/acceptance/nodesets/oel.yml +0 -41
- data/spec/lib/simp/ci/files/job_missing_nodeset/spec/acceptance/suites/default/class_spec.rb +0 -0
- data/spec/lib/simp/ci/files/job_missing_suite_and_nodeset/spec/acceptance/nodesets/default.yml +0 -41
- data/spec/lib/simp/ci/files/job_missing_suite_and_nodeset/spec/acceptance/nodesets/oel.yml +0 -41
- data/spec/lib/simp/ci/files/job_missing_suite_and_nodeset/spec/acceptance/suites/default/class_spec.rb +0 -0
- data/spec/lib/simp/ci/files/multiple_invalid_jobs/spec/acceptance/nodesets/default.yml +0 -41
- data/spec/lib/simp/ci/files/multiple_invalid_jobs/spec/acceptance/nodesets/oel.yml +0 -41
- data/spec/lib/simp/ci/files/multiple_invalid_jobs/spec/acceptance/suites/default/class_spec.rb +0 -0
- data/spec/lib/simp/ci/files/multiple_invalid_jobs/spec/acceptance/suites/feature-1/feature-1_spec.rb +0 -0
- data/spec/lib/simp/ci/files/multiple_invalid_jobs/spec/acceptance/suites/feature_2/feature_2_spec.rb +0 -0
- data/spec/lib/simp/ci/files/multiple_invalid_jobs/spec/acceptance/suites/feature_2/nodesets/default.yml +0 -35
- data/spec/lib/simp/ci/files/multiple_invalid_jobs/spec/acceptance/suites/feature_2/nodesets/oel.yml +0 -35
- data/spec/lib/simp/ci/files/multiple_valid_jobs/spec/acceptance/nodesets/default.yml +0 -41
- data/spec/lib/simp/ci/files/multiple_valid_jobs/spec/acceptance/nodesets/oel.yml +0 -41
- data/spec/lib/simp/ci/files/multiple_valid_jobs/spec/acceptance/suites/default/class_spec.rb +0 -0
- data/spec/lib/simp/ci/files/multiple_valid_jobs/spec/acceptance/suites/feature-1/feature-1_spec.rb +0 -0
- data/spec/lib/simp/ci/files/multiple_valid_jobs/spec/acceptance/suites/feature_2/feature_2_spec.rb +0 -0
- data/spec/lib/simp/ci/files/multiple_valid_jobs/spec/acceptance/suites/feature_2/nodesets/default.yml +0 -35
- data/spec/lib/simp/ci/files/multiple_valid_jobs/spec/acceptance/suites/feature_2/nodesets/oel.yml +0 -35
- data/spec/lib/simp/ci/files/no_acceptance_tests/spec/spec_helper.rb +0 -0
- data/spec/lib/simp/ci/files/no_gitlab_config_with_tests/spec/acceptance/nodesets/default.yml +0 -41
- data/spec/lib/simp/ci/files/no_gitlab_config_with_tests/spec/acceptance/nodesets/oel.yml +0 -41
- data/spec/lib/simp/ci/files/no_gitlab_config_with_tests/spec/acceptance/suites/default/class_spec.rb +0 -0
- data/spec/lib/simp/ci/files/no_gitlab_config_without_tests/spec/acceptance/nodesets/default.yml +0 -41
- data/spec/lib/simp/ci/files/no_gitlab_config_without_tests/spec/acceptance/nodesets/oel.yml +0 -41
- data/spec/lib/simp/ci/files/suite_skeleton_only/spec/acceptance/nodesets/centos.yml +0 -41
- data/spec/lib/simp/ci/files/suite_skeleton_only/spec/acceptance/nodesets/oel.yml +0 -41
- data/spec/lib/simp/ci/files/valid_job_global_nodeset/spec/acceptance/nodesets/default.yml +0 -41
- data/spec/lib/simp/ci/files/valid_job_global_nodeset/spec/acceptance/nodesets/oel.yml +0 -41
- data/spec/lib/simp/ci/files/valid_job_global_nodeset/spec/acceptance/suites/default/class_spec.rb +0 -0
- data/spec/lib/simp/ci/files/valid_job_nodeset_dir_link/spec/acceptance/nodesets/default.yml +0 -41
- data/spec/lib/simp/ci/files/valid_job_nodeset_dir_link/spec/acceptance/nodesets/oel.yml +0 -41
- data/spec/lib/simp/ci/files/valid_job_nodeset_dir_link/spec/acceptance/suites/default/class_spec.rb +0 -0
- data/spec/lib/simp/ci/files/valid_job_nodeset_link/spec/acceptance/suites/default/class_spec.rb +0 -0
- data/spec/lib/simp/ci/files/valid_job_nodeset_link/spec/acceptance/suites/default/nodesets/centos.yml +0 -41
- data/spec/lib/simp/ci/files/valid_job_nodeset_link/spec/acceptance/suites/default/nodesets/oel.yml +0 -41
- data/spec/lib/simp/ci/files/valid_job_suite_nodeset/spec/acceptance/suites/default/class_spec.rb +0 -0
- data/spec/lib/simp/ci/files/valid_job_suite_nodeset/spec/acceptance/suites/default/nodesets/default.yml +0 -41
- data/spec/lib/simp/ci/files/valid_job_suite_nodeset/spec/acceptance/suites/default/nodesets/oel.yml +0 -41
- 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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
#
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
#
|
|
36
|
-
#
|
|
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
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
90
|
+
# @return [Gem::Version]
|
|
91
|
+
def gpg_version
|
|
92
|
+
return @gpg_version if @gpg_version
|
|
59
93
|
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
155
|
+
break
|
|
159
156
|
end
|
|
160
157
|
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
188
|
-
|
|
175
|
+
# Ensure that the gpg-agent is running with a dev key
|
|
176
|
+
def ensure_key
|
|
177
|
+
ensure_gpg_directory
|
|
189
178
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
225
|
-
|
|
224
|
+
WARNING
|
|
225
|
+
end
|
|
226
226
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
247
|
-
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
292
|
-
|
|
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
|
-
|
|
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
|
-
|
|
299
|
+
agent_info
|
|
300
|
+
end
|
|
298
301
|
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
#
|
|
303
|
-
#
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
-
|
|
309
|
-
|
|
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
|
-
|
|
315
|
-
|
|
316
|
-
|
|
318
|
+
agent_info = gpg_agent_info
|
|
319
|
+
generate_key(agent_info[:info])
|
|
320
|
+
agent_info
|
|
321
|
+
end
|
|
317
322
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
-
#
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
352
|
-
|
|
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
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
-
|
|
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
|
-
|
|
370
|
+
AGENT_SCRIPT
|
|
371
371
|
|
|
372
|
-
|
|
373
|
-
|
|
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
|