openvox 8.25.0 → 8.26.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ea7920147264533ca3a9d0999ab864433f49dc39921288ccd6208a97434ad7d5
4
- data.tar.gz: 37cc470528c245977e29d2312c2c2636a2ae500995d59789bc9e2c13232c48e6
3
+ metadata.gz: 1c5a3cca4402aa0cc4a7878c7b240f32cf76f6d7711f62eb335689b95d59f26b
4
+ data.tar.gz: 715d1cabc9ab1b3b9b0bf928066500e2c5ae7c07a13e89e57af9a3d962559567
5
5
  SHA512:
6
- metadata.gz: 2083c76d72fd039be07dc02110e33c2dafb663d48b9045d3c5a753c2c807585ecfdec21d75804b4239a279aa8292e42db85e0515182c665fe1fa794a46f69449
7
- data.tar.gz: dbc8342fd12a07243d1c5937585c2c61c1e1ce3e01e270fbd114ca0813ca7e993202e26443c095ce8cd85a25dbf83e8d92844baa753c6fcb26863509dee0f85d
6
+ metadata.gz: 1ba0c6d016eba599d49c419c24561e2cc54fc50b6591193992820a7dfa9a99362c919aa63f66d5e0cb241c36b2cc59f1b3b62007ed249e16d6cf5e3547441f39
7
+ data.tar.gz: b199727c5ee128e6ab7b22a93a5610efc6b626850223a53b8f4b858a76402ad9134d38dfb920450a5ded5f087cd26173f8bb4b88f211a955cf3dc75512e8ba29
data/CHANGELOG.md CHANGED
@@ -2,12 +2,46 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
- ## [8.25.0](https://github.com/openvoxproject/openvox/tree/8.25.0) (2026-02-16)
5
+ ## [8.26.0](https://github.com/openvoxproject/openvox/tree/8.26.0) (2026-04-14)
6
+
7
+ [Full Changelog](https://github.com/openvoxproject/openvox/compare/8.25.0...8.26.0)
8
+
9
+ **Implemented enhancements:**
10
+
11
+ - \[Feature request\]: file resource should support etag [\#328](https://github.com/OpenVoxProject/openvox/issues/328)
12
+ - \[Feature request\]: Provide SLES16 packages [\#246](https://github.com/OpenVoxProject/openvox/issues/246)
13
+ - Add a renew\_cert subcommand to puppet ssl [\#363](https://github.com/OpenVoxProject/openvox/pull/363) ([jay7x](https://github.com/jay7x))
14
+ - Make regsubst\(\) sensitive-aware [\#354](https://github.com/OpenVoxProject/openvox/pull/354) ([cocker-cc](https://github.com/cocker-cc))
15
+ - Feature: file etag support [\#329](https://github.com/OpenVoxProject/openvox/pull/329) ([C24-AK](https://github.com/C24-AK))
16
+ - Add environment parameter to the package resource type [\#321](https://github.com/OpenVoxProject/openvox/pull/321) ([C24-AK](https://github.com/C24-AK))
17
+
18
+ **Fixed bugs:**
19
+
20
+ - \[Bug\]: OpenVox Agent on SuSE 15.5 fails with `version 'GLIBC_2.34' not found` [\#226](https://github.com/OpenVoxProject/openvox/issues/226)
21
+ - fix: puppet module install should honor manage\_file\_permissions=false [\#362](https://github.com/OpenVoxProject/openvox/pull/362) ([dotconfig404](https://github.com/dotconfig404))
22
+ - unbreak OpenBSD package handling. [\#316](https://github.com/OpenVoxProject/openvox/pull/316) ([buzzdeee](https://github.com/buzzdeee))
23
+ - Use usermod\(8\) on OpenBSD to unbreak password management [\#294](https://github.com/OpenVoxProject/openvox/pull/294) ([klemensn](https://github.com/klemensn))
24
+
25
+ **Merged pull requests:**
26
+
27
+ - Promote openfact 5.6.0 [\#387](https://github.com/OpenVoxProject/openvox/pull/387) ([OpenVoxProjectBot](https://github.com/OpenVoxProjectBot))
28
+ - Promote puppet-runtime 2026.04.09.1 [\#386](https://github.com/OpenVoxProject/openvox/pull/386) ([OpenVoxProjectBot](https://github.com/OpenVoxProjectBot))
29
+ - fix: skip SHA1 CSR signing test when OpenSSL doesn't support it [\#378](https://github.com/OpenVoxProject/openvox/pull/378) ([silug](https://github.com/silug))
30
+ - fix: stub SRV DNS in ca\_server session spec [\#377](https://github.com/OpenVoxProject/openvox/pull/377) ([silug](https://github.com/silug))
31
+ - Fix acceptance tests for systemd PrivateTmp compatibility [\#376](https://github.com/OpenVoxProject/openvox/pull/376) ([nmburgan](https://github.com/nmburgan))
32
+ - Fix acceptance tests for systemd PrivateTmp compatibility [\#375](https://github.com/OpenVoxProject/openvox/pull/375) ([nmburgan](https://github.com/nmburgan))
33
+ - Add libffi-devel to Windows setup script [\#373](https://github.com/OpenVoxProject/openvox/pull/373) ([nmburgan](https://github.com/nmburgan))
34
+ - Promote puppet-runtime 2026.04.05.1 [\#372](https://github.com/OpenVoxProject/openvox/pull/372) ([OpenVoxProjectBot](https://github.com/OpenVoxProjectBot))
35
+ - Restore the legend of help help help help help [\#364](https://github.com/OpenVoxProject/openvox/pull/364) ([nmburgan](https://github.com/nmburgan))
36
+ - Fixes for Ruby 4.0 compatibility [\#311](https://github.com/OpenVoxProject/openvox/pull/311) ([silug](https://github.com/silug))
37
+
38
+ ## [8.25.0](https://github.com/openvoxproject/openvox/tree/8.25.0) (2026-02-17)
6
39
 
7
40
  [Full Changelog](https://github.com/openvoxproject/openvox/compare/8.24.2...8.25.0)
8
41
 
9
42
  **Implemented enhancements:**
10
43
 
44
+ - \[Feature request\]: environment parameter for package resource [\#298](https://github.com/OpenVoxProject/openvox/issues/298)
11
45
  - Add Ubuntu 26.04 support [\#315](https://github.com/OpenVoxProject/openvox/pull/315) ([bastelfreak](https://github.com/bastelfreak))
12
46
  - Reduce OpenSSL monkey patch to only calling set\_params [\#308](https://github.com/OpenVoxProject/openvox/pull/308) ([ekohl](https://github.com/ekohl))
13
47
  - Remove monkey patch to remove daemonize [\#307](https://github.com/OpenVoxProject/openvox/pull/307) ([ekohl](https://github.com/ekohl))
data/Gemfile CHANGED
@@ -19,11 +19,9 @@ end
19
19
  gem "openfact", *location_for(ENV['OPENFACT_LOCATION'] || ["~> 5.0"])
20
20
  gem "semantic_puppet", *location_for(ENV['SEMANTIC_PUPPET_LOCATION'] || ["~> 1.0"])
21
21
  gem "puppet-resource_api", *location_for(ENV['RESOURCE_API_LOCATION'] || ["~> 2.0"])
22
- # Need to update the openssl gem on MacOS to avoid SSL errors. Doesn't hurt to have the newest
23
- # for all platforms.
22
+ # Need to update the openssl gem on MacOS to avoid SSL errors.
24
23
  # https://www.rubyonmac.dev/certificate-verify-failed-unable-to-get-certificate-crl-openssl-ssl-sslerror
25
- # openssl 4 raises some errors that need to be investigated
26
- gem 'openssl', '~> 3' unless `uname -o`.chomp == 'Cygwin'
24
+ gem 'openssl', '~> 3' if RUBY_PLATFORM =~ /darwin/
27
25
 
28
26
  group(:features) do
29
27
  gem 'diff-lcs', '~> 1.3', require: false
@@ -58,7 +56,7 @@ group(:test) do
58
56
  gem 'webrick', '~> 1.7', require: false
59
57
  gem 'yard', require: false
60
58
 
61
- gem 'rubocop', '~> 1.81.6', require: false, platforms: [:ruby]
59
+ gem 'rubocop', '~> 1.86.1', require: false, platforms: [:ruby]
62
60
  gem 'rubocop-i18n', '~> 3.0', require: false, platforms: [:ruby]
63
61
  gem 'rubocop-performance', '~> 1.0', require: false, platforms: [:ruby]
64
62
  gem 'rubocop-rake', '~> 0.6', require: false, platforms: [:ruby]
@@ -86,13 +84,17 @@ end
86
84
  group(:documentation, optional: true) do
87
85
  gem 'gettext-setup', '~> 1.0', require: false, platforms: [:ruby]
88
86
  gem 'ronn-ng', '~> 0.10.1', require: false, platforms: [:ruby]
89
- gem 'puppet-strings', require: false, platforms: [:ruby]
87
+ gem 'openvox-strings', require: false, platforms: [:ruby]
90
88
  gem 'pandoc-ruby', require: false, platforms: [:ruby]
91
89
  end
92
90
 
93
- group :release, optional: true do
94
- gem 'faraday-retry', require: false
95
- gem 'github_changelog_generator', require: false, git: 'https://github.com/voxpupuli/github-changelog-generator', branch: 'avoid-processing-a-single-commit-multiple-time'
91
+ # exlude the windows platform, faraday doesn't install properly on it
92
+ # and we only generate the changelog in github linux runners
93
+ platforms :ruby do
94
+ group :release, optional: true do
95
+ gem 'faraday-retry', require: false
96
+ gem 'github_changelog_generator', require: false, git: 'https://github.com/voxpupuli/github-changelog-generator', branch: 'avoid-processing-a-single-commit-multiple-time'
97
+ end
96
98
  end
97
99
 
98
100
  if File.exist? "#{__FILE__}.local"
@@ -9,7 +9,7 @@
9
9
  # then running systemctl show puppet | grep LimitNOFILE
10
10
  #
11
11
  [Unit]
12
- Description=Puppet agent
12
+ Description=Puppet agent daemon provided by OpenVox
13
13
  Documentation=man:puppet-agent(8)
14
14
  Wants=basic.target
15
15
  After=basic.target network.target network-online.target
@@ -43,6 +43,11 @@ class Puppet::Application::Ssl < Puppet::Application
43
43
  * --target CERTNAME
44
44
  Clean the specified device certificate instead of this host's certificate.
45
45
 
46
+ * --if-expiring-in DURATION
47
+ When renewing a certificate only renew if the certificate is valid for
48
+ less than this amount of time. Duration can be specified as a time
49
+ interval, such as 30s, 5m, 1h.
50
+
46
51
  ACTIONS
47
52
  -------
48
53
 
@@ -71,6 +76,11 @@ class Puppet::Application::Ssl < Puppet::Application
71
76
  for subsequent requests. If there is already an existing certificate, it
72
77
  will be overwritten.
73
78
 
79
+ * renew_cert
80
+ Renew an existing and non-expired client certificate. When
81
+ `--if-expiring-in` option is specified, then renew the certificate only
82
+ if it's going to expire in the amount of time given.
83
+
74
84
  * verify:
75
85
  Verify the private key and certificate are present and match, verify the
76
86
  certificate is issued by a trusted CA, and check revocation status.
@@ -98,6 +108,28 @@ class Puppet::Application::Ssl < Puppet::Application
98
108
  option('--localca')
99
109
  option('--verbose', '-v')
100
110
  option('--debug', '-d')
111
+ option('--if-expiring-in DURATION') do |arg|
112
+ options[:expiring_in_sec] = parse_duration(arg)
113
+ end
114
+
115
+ def parse_duration(value)
116
+ unit_map = {
117
+ "y" => 365 * 24 * 60 * 60,
118
+ "d" => 24 * 60 * 60,
119
+ "h" => 60 * 60,
120
+ "m" => 60,
121
+ "s" => 1
122
+ }
123
+ format = /^(\d+)(y|d|h|m|s)?$/
124
+
125
+ v = (value.is_a?(Integer) ? "#{value}s" : value)
126
+
127
+ if v =~ format
128
+ Regexp.last_match(1).to_i * unit_map[::Regexp.last_match(2) || 's']
129
+ else
130
+ raise ArgumentError, "Invalid duration format: #{value}"
131
+ end
132
+ end
101
133
 
102
134
  def initialize(command_line = Puppet::Util::CommandLine.new)
103
135
  super(command_line)
@@ -148,6 +180,8 @@ class Puppet::Application::Ssl < Puppet::Application
148
180
  unless cert
149
181
  raise Puppet::Error, _("The certificate for '%{name}' has not yet been signed") % { name: certname }
150
182
  end
183
+ when 'renew_cert'
184
+ renew_cert(certname, options[:expiring_in_sec])
151
185
  when 'generate_request'
152
186
  generate_request(certname)
153
187
  when 'verify'
@@ -248,6 +282,37 @@ class Puppet::Application::Ssl < Puppet::Application
248
282
  raise Puppet::Error.new(_("Failed to download certificate: %{message}") % { message: e.message }, e)
249
283
  end
250
284
 
285
+ def renew_cert(certname, expiring_in_sec_maybe)
286
+ ssl_context = @ssl_provider.load_context(certname: certname)
287
+
288
+ if expiring_in_sec_maybe && (ssl_context[:client_cert].not_after - Time.now) > expiring_in_sec_maybe
289
+ Puppet.info _("Certificate '%{name}' is still valid until %{date}") % { name: certname, date: ssl_context[:client_cert].not_after }
290
+ return ssl_context[:client_cert]
291
+ end
292
+
293
+ Puppet.debug _("Renewing certificate '%{name}'") % { name: certname }
294
+ route = create_route(ssl_context)
295
+ _, x509 = route.post_certificate_renewal(ssl_context)
296
+ cert = OpenSSL::X509::Certificate.new(x509)
297
+ Puppet.notice _("Downloaded certificate '%{name}' with fingerprint %{fingerprint}") % { name: certname, fingerprint: fingerprint(cert) }
298
+
299
+ # verify client cert before saving
300
+ @ssl_provider.create_context(
301
+ cacerts: ssl_context.cacerts, crls: ssl_context.crls, private_key: ssl_context.private_key, client_cert: cert
302
+ )
303
+ @cert_provider.save_client_cert(certname, cert)
304
+ @cert_provider.delete_request(certname)
305
+ cert
306
+ rescue Puppet::HTTP::ResponseError => e
307
+ if e.response.code == 404
308
+ nil
309
+ else
310
+ raise Puppet::Error.new(_("Failed to download certificate: %{message}") % { message: e.message }, e)
311
+ end
312
+ rescue => e
313
+ raise Puppet::Error.new(_("Failed to download certificate: %{message}") % { message: e.message }, e)
314
+ end
315
+
251
316
  def verify(certname)
252
317
  password = @cert_provider.load_private_key_password
253
318
  ssl_context = @ssl_provider.load_context(certname: certname, password: password)
@@ -26,8 +26,8 @@ module Puppet
26
26
 
27
27
  def self.valid_file_checksum_types
28
28
  Puppet::Util::Platform.fips_enabled? ?
29
- %w[sha256 sha256lite sha384 sha512 sha224 sha1 sha1lite mtime ctime] :
30
- %w[sha256 sha256lite sha384 sha512 sha224 sha1 sha1lite md5 md5lite mtime ctime]
29
+ %w[sha256 sha256lite sha384 sha512 sha224 sha1 sha1lite mtime ctime etag] :
30
+ %w[sha256 sha256lite sha384 sha512 sha224 sha1 sha1lite md5 md5lite mtime ctime etag]
31
31
  end
32
32
 
33
33
  def self.default_cadir
@@ -42,6 +42,24 @@ Puppet::Face.define(:help, '0.0.1') do
42
42
  end
43
43
 
44
44
  if args.length > 2
45
+ # rubocop:disable all
46
+ if args.select { |x| x == 'help' }.length > 2 then
47
+ c = "\n %'(),-./=ADEFHILORSTUXY\\_`gnv|".split('')
48
+ i = <<-'EOT'.gsub(/\s*/, '').to_i(36)
49
+ 3he6737w1aghshs6nwrivl8mz5mu9nywg9tbtlt081uv6fq5kvxse1td3tj1wvccmte806nb
50
+ cy6de2ogw0fqjymbfwi6a304vd56vlq71atwmqsvz3gpu0hj42200otlycweufh0hylu79t3
51
+ gmrijm6pgn26ic575qkexyuoncbujv0vcscgzh5us2swklsp5cqnuanlrbnget7rt3956kam
52
+ j8adhdrzqqt9bor0cv2fqgkloref0ygk3dekiwfj1zxrt13moyhn217yy6w4shwyywik7w0l
53
+ xtuevmh0m7xp6eoswin70khm5nrggkui6z8vdjnrgdqeojq40fya5qexk97g4d8qgw0hvokr
54
+ pli1biaz503grqf2ycy0ppkhz1hwhl6ifbpet7xd6jjepq4oe0ofl575lxdzjeg25217zyl4
55
+ nokn6tj5pq7gcdsjre75rqylydh7iia7s3yrko4f5ud9v8hdtqhu60stcitirvfj6zphppmx
56
+ 7wfm7i9641d00bhs44n6vh6qvx39pg3urifgr6ihx3e0j1ychzypunyou7iplevitkyg6gbg
57
+ wm08oy1rvogcjakkqc1f7y1awdfvlb4ego8wrtgu9vzw4vmj59utwifn2ejcs569dh1oaavi
58
+ sc581n7jjg1dugzdu094fdobtx6rsvk3sfctvqnr36xctold
59
+ EOT
60
+ 353.times{i,x=i.divmod(1184);a,b=x.divmod(37);print(c[a]*b)}
61
+ end
62
+ # rubocop:enable all
45
63
  # TRANSLATORS 'puppet help' is a command line and should not be translated
46
64
  raise ArgumentError, _("The 'puppet help' command takes two (optional) arguments: a subcommand and an action")
47
65
  end
@@ -35,6 +35,19 @@ class Puppet::FileServing::HttpMetadata < Puppet::FileServing::Metadata
35
35
  end
36
36
  end
37
37
 
38
+ etag = http_response['etag']
39
+ if etag && !etag.start_with?('W/')
40
+ etag_value = etag.delete('"').strip
41
+ case etag_value
42
+ when /\A[0-9a-f]{64}\z/i
43
+ @checksums[:etag] = "{sha256}#{etag_value.downcase}"
44
+ when /\A[0-9a-f]{40}\z/i
45
+ @checksums[:etag] = "{sha1}#{etag_value.downcase}"
46
+ when /\A[0-9a-f]{32}\z/i
47
+ @checksums[:etag] = "{md5}#{etag_value.downcase}"
48
+ end
49
+ end
50
+
38
51
  last_modified = http_response['last-modified']
39
52
  if last_modified
40
53
  mtime = DateTime.httpdate(last_modified).to_time
@@ -51,6 +64,18 @@ class Puppet::FileServing::HttpMetadata < Puppet::FileServing::Metadata
51
64
  # Prefer the checksum_type from the indirector request options
52
65
  # but fall back to the alternative otherwise
53
66
  [@checksum_type, :sha256, :sha1, :md5, :mtime].each do |type|
67
+ if type == :etag
68
+ if @checksums[:etag]
69
+ @checksum = @checksums[:etag]
70
+ resolved = sumtype(@checksum).to_sym
71
+ next if resolved == :md5 && Puppet::Util::Platform.fips_enabled?
72
+
73
+ @checksum_type = resolved
74
+ break
75
+ end
76
+ next
77
+ end
78
+
54
79
  next if type == :md5 && Puppet::Util::Platform.fips_enabled?
55
80
 
56
81
  @checksum_type = type
@@ -29,7 +29,7 @@ Puppet::Functions.create_function(:regsubst) do
29
29
  # $i3 = regsubst($ipaddress,'^(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)$','\\3')
30
30
  # ```
31
31
  dispatch :regsubst_string do
32
- param 'Variant[Array[String],String]', :target
32
+ param 'Variant[Array[Variant[String,Sensitive[String]]],Sensitive[Array[Variant[String,Sensitive[String]]]],Variant[String,Sensitive[String]]]', :target
33
33
  param 'String', :pattern
34
34
  param 'Variant[String,Hash[String,String]]', :replacement
35
35
  optional_param 'Optional[Pattern[/^[GEIM]*$/]]', :flags
@@ -59,7 +59,7 @@ Puppet::Functions.create_function(:regsubst) do
59
59
  # $x = regsubst($ipaddress, /([0-9]+)/, '<\\1>', 'G')
60
60
  # ```
61
61
  dispatch :regsubst_regexp do
62
- param 'Variant[Array[String],String]', :target
62
+ param 'Variant[Array[Variant[String,Sensitive[String]]],Sensitive[Array[Variant[String,Sensitive[String]]]],Variant[String,Sensitive[String]]]', :target
63
63
  param 'Variant[Regexp,Type[Regexp]]', :pattern
64
64
  param 'Variant[String,Hash[String,String]]', :replacement
65
65
  optional_param 'Pattern[/^G?$/]', :flags
@@ -94,7 +94,26 @@ Puppet::Functions.create_function(:regsubst) do
94
94
  end
95
95
 
96
96
  def inner_regsubst(target, re, replacement, op)
97
- target.respond_to?(op) ? target.send(op, re, replacement) : target.collect { |e| e.send(op, re, replacement) }
97
+ if target.is_a?(Puppet::Pops::Types::PSensitiveType::Sensitive) && target.unwrap.is_a?(Array)
98
+ # this is a Sensitive Array
99
+ target = target.unwrap
100
+ target.map do |item|
101
+ inner_regsubst(item, re, replacement, op)
102
+ end
103
+ elsif target.is_a?(Array)
104
+ # this is an Array
105
+ target.map do |item|
106
+ inner_regsubst(item, re, replacement, op)
107
+ end
108
+ elsif target.is_a?(Puppet::Pops::Types::PSensitiveType::Sensitive)
109
+ # this is a Sensitive
110
+ target = target.unwrap
111
+ target = target.respond_to?(op) ? target.send(op, re, replacement) : target.map { |e| e.send(op, re, replacement) }
112
+ Puppet::Pops::Types::PSensitiveType::Sensitive.new(target)
113
+ else
114
+ # this should be a String
115
+ target.respond_to?(op) ? target.send(op, re, replacement) : target.map { |e| e.send(op, re, replacement) }
116
+ end
98
117
  end
99
118
  private :inner_regsubst
100
119
  end
@@ -16,6 +16,8 @@ module Puppet::ModuleTool
16
16
  end
17
17
 
18
18
  def self.harmonize_ownership(source, target)
19
+ return unless Puppet[:manage_internal_file_permissions]
20
+
19
21
  unless Puppet::Util::Platform.windows?
20
22
  source = Pathname.new(source) unless source.respond_to?(:stat)
21
23
  target = Pathname.new(target) unless target.respond_to?(:stat)
@@ -26,7 +26,7 @@ module Issues
26
26
  attr_writer :demotable
27
27
 
28
28
  # Configures the Issue with required arguments (bound by occurrence), and a block producing a message.
29
- def initialize issue_code, *args, &block
29
+ def initialize(issue_code, *args, &block)
30
30
  @issue_code = issue_code
31
31
  @message_block = block
32
32
  @arg_names = args
@@ -62,7 +62,7 @@ module Issues
62
62
  # @api private
63
63
  #
64
64
  class MessageData
65
- def initialize *argnames
65
+ def initialize(*argnames)
66
66
  singleton = class << self; self end
67
67
  argnames.each do |name|
68
68
  singleton.send(:define_method, name) do
@@ -165,7 +165,7 @@ Puppet::Type.type(:package).provide :apt, :parent => :dpkg, :source => :dpkg do
165
165
 
166
166
  unhold if properties[:mark] == :hold
167
167
  begin
168
- aptget(*cmd)
168
+ with_environment { aptget(*cmd) }
169
169
  ensure
170
170
  hold if @resource[:mark] == :hold
171
171
  end
@@ -213,7 +213,7 @@ Puppet::Type.type(:package).provide :apt, :parent => :dpkg, :source => :dpkg do
213
213
  args = ['-y', '-q']
214
214
  args << '--allow-change-held-packages' if properties[:mark] == :hold
215
215
  args << :remove << @resource[:name]
216
- aptget(*args)
216
+ with_environment { aptget(*args) }
217
217
  end
218
218
 
219
219
  def purge
@@ -221,7 +221,7 @@ Puppet::Type.type(:package).provide :apt, :parent => :dpkg, :source => :dpkg do
221
221
  args = ['-y', '-q']
222
222
  args << '--allow-change-held-packages' if properties[:mark] == :hold
223
223
  args << :remove << '--purge' << @resource[:name]
224
- aptget(*args)
224
+ with_environment { aptget(*args) }
225
225
  # workaround a "bug" in apt, that already removed packages are not purged
226
226
  super
227
227
  end
@@ -103,7 +103,7 @@ Puppet::Type.type(:package).provide :dpkg, :parent => Puppet::Provider::Package
103
103
 
104
104
  unhold if properties[:mark] == :hold
105
105
  begin
106
- dpkg(*args)
106
+ with_environment { dpkg(*args) }
107
107
  ensure
108
108
  hold if @resource[:mark] == :hold
109
109
  end
@@ -166,11 +166,11 @@ Puppet::Type.type(:package).provide :dpkg, :parent => Puppet::Provider::Package
166
166
  end
167
167
 
168
168
  def uninstall
169
- dpkg "-r", @resource[:name]
169
+ with_environment { dpkg "-r", @resource[:name] }
170
170
  end
171
171
 
172
172
  def purge
173
- dpkg "--purge", @resource[:name]
173
+ with_environment { dpkg "--purge", @resource[:name] }
174
174
  end
175
175
 
176
176
  def hold
@@ -6,6 +6,10 @@ require_relative '../../../puppet/provider/package'
6
6
  Puppet::Type.type(:package).provide :openbsd, :parent => Puppet::Provider::Package do
7
7
  desc "OpenBSD's form of `pkg_add` support.
8
8
 
9
+ OpenBSD has the concept of package branches, providing multiple versions of the
10
+ same package, i.e. `stable` vs. `snapshot`. To select a specific branch,
11
+ suffix the package name with % sign follwed by the branch name, i.e. `gimp%stable`.
12
+
9
13
  This provider supports the `install_options` and `uninstall_options`
10
14
  attributes, which allow command-line flags to be passed to pkg_add and pkg_delete.
11
15
  These options should be specified as an array where each element is either a
@@ -18,10 +22,8 @@ Puppet::Type.type(:package).provide :openbsd, :parent => Puppet::Provider::Packa
18
22
  defaultfor 'os.name' => :openbsd
19
23
  confine 'os.name' => :openbsd
20
24
 
21
- has_feature :versionable
22
25
  has_feature :install_options
23
26
  has_feature :uninstall_options
24
- has_feature :upgradeable
25
27
  has_feature :supports_flavors
26
28
 
27
29
  def self.instances
@@ -30,8 +32,8 @@ Puppet::Type.type(:package).provide :openbsd, :parent => Puppet::Provider::Packa
30
32
  begin
31
33
  execpipe(listcmd) do |process|
32
34
  # our regex for matching pkg_info output
33
- regex = /^(.*)-(\d[^-]*)-?([\w-]*)(.*)$/
34
- fields = [:name, :ensure, :flavor]
35
+ regex = /^(.*)--([\w-]+)?(%[^w]+)?$/
36
+ fields = [:name, :flavor, :branch]
35
37
  hash = {}
36
38
 
37
39
  # now turn each returned line into a package object
@@ -42,8 +44,9 @@ Puppet::Type.type(:package).provide :openbsd, :parent => Puppet::Provider::Packa
42
44
  hash[field] = value
43
45
  }
44
46
 
45
- hash[:provider] = name
47
+ hash[:name] = "#{hash[:name]}#{hash[:branch]}" if hash[:branch]
46
48
 
49
+ hash[:provider] = name
47
50
  packages << new(hash)
48
51
  hash = {}
49
52
  else
@@ -63,175 +66,55 @@ Puppet::Type.type(:package).provide :openbsd, :parent => Puppet::Provider::Packa
63
66
  end
64
67
 
65
68
  def self.listcmd
66
- [command(:pkginfo), "-a"]
67
- end
68
-
69
- def latest
70
- parse_pkgconf
71
-
72
- if @resource[:source][-1, 1] == ::File::SEPARATOR
73
- e_vars = { 'PKG_PATH' => @resource[:source] }
74
- else
75
- e_vars = {}
76
- end
77
-
78
- if @resource[:flavor]
79
- query = "#{@resource[:name]}--#{@resource[:flavor]}"
80
- else
81
- query = @resource[:name]
82
- end
83
-
84
- output = Puppet::Util.withenv(e_vars) { pkginfo "-Q", query }
85
- version = properties[:ensure]
86
-
87
- if output.nil? or output.size == 0 or output =~ /Error from /
88
- debug "Failed to query for #{resource[:name]}"
89
- return version
90
- else
91
- # Remove all fuzzy matches first.
92
- output = output.split.select { |p| p =~ /^#{resource[:name]}-(\d[^-]*)-?(\w*)/ }.join
93
- debug "pkg_info -Q for #{resource[:name]}: #{output}"
94
- end
95
-
96
- if output =~ /^#{resource[:name]}-(\d[^-]*)-?(\w*) \(installed\)$/
97
- debug "Package is already the latest available"
98
- version
99
- else
100
- match = /^(.*)-(\d[^-]*)-?(\w*)$/.match(output)
101
- debug "Latest available for #{resource[:name]}: #{match[2]}"
102
-
103
- if version.to_sym == :absent || version.to_sym == :purged
104
- return match[2]
105
- end
106
-
107
- vcmp = version.split('.').map(&:to_i) <=> match[2].split('.').map(&:to_i)
108
- if vcmp > 0
109
- # The locally installed package may actually be newer than what a mirror
110
- # has. Log it at debug, but ignore it otherwise.
111
- debug "Package #{resource[:name]} #{version} newer then available #{match[2]}"
112
- version
113
- else
114
- match[2]
115
- end
116
- end
69
+ [command(:pkginfo), "-a", "-z"]
117
70
  end
118
71
 
119
- def update
120
- install(true)
121
- end
122
-
123
- def parse_pkgconf
124
- unless @resource[:source]
125
- if Puppet::FileSystem.exist?("/etc/pkg.conf")
126
- File.open("/etc/pkg.conf", "rb").readlines.each do |line|
127
- matchdata = line.match(/^installpath\s*=\s*(.+)\s*$/i)
128
- if matchdata
129
- @resource[:source] = matchdata[1]
130
- else
131
- matchdata = line.match(/^installpath\s*\+=\s*(.+)\s*$/i)
132
- if matchdata
133
- if @resource[:source].nil?
134
- @resource[:source] = matchdata[1]
135
- else
136
- @resource[:source] += ":" + matchdata[1]
137
- end
138
- end
139
- end
140
- end
141
-
142
- unless @resource[:source]
143
- raise Puppet::Error,
144
- _("No valid installpath found in /etc/pkg.conf and no source was set")
145
- end
146
- else
147
- raise Puppet::Error,
148
- _("You must specify a package source or configure an installpath in /etc/pkg.conf")
149
- end
150
- end
151
- end
152
-
153
- def install(latest = false)
72
+ def install
154
73
  cmd = []
155
74
 
156
- parse_pkgconf
157
-
158
- if @resource[:source][-1, 1] == ::File::SEPARATOR
159
- e_vars = { 'PKG_PATH' => @resource[:source] }
160
- full_name = get_full_name(latest)
161
- else
162
- e_vars = {}
163
- full_name = @resource[:source]
164
- end
75
+ full_name = get_full_name
165
76
 
77
+ cmd << '-r'
166
78
  cmd << install_options
167
79
  cmd << full_name
168
80
 
169
- if latest
170
- cmd.unshift('-rz')
81
+ # pkg_add(1) doesn't set the return value upon failure so we have to peek
82
+ # at it's output to see if something went wrong.
83
+ output = Puppet::Util.withenv({}) { pkgadd cmd.flatten.compact }
84
+ if output =~ /Can't find /
85
+ self.fail "pkg_add returned: #{output.chomp}"
171
86
  end
172
-
173
- Puppet::Util.withenv(e_vars) { pkgadd cmd.flatten.compact }
174
87
  end
175
88
 
176
- def get_full_name(latest = false)
89
+ def get_full_name
177
90
  # In case of a real update (i.e., the package already exists) then
178
- # pkg_add(8) can handle the flavors. However, if we're actually
91
+ # pkg_add(1) can handle the flavors. However, if we're actually
179
92
  # installing with 'latest', we do need to handle the flavors. This is
180
- # done so we can feed pkg_add(8) the full package name to install to
93
+ # done so we can feed pkg_add(1) the full package name to install to
181
94
  # prevent ambiguity.
182
- if latest && resource[:flavor]
183
- "#{resource[:name]}--#{resource[:flavor]}"
184
- elsif latest
185
- # Don't depend on get_version for updates.
186
- @resource[:name]
187
- else
188
- # If :ensure contains a version, use that instead of looking it up.
189
- # This allows for installing packages with the same stem, but multiple
190
- # version such as openldap-server.
191
- if @resource[:ensure].to_s =~ /(\d[^-]*)$/
192
- use_version = @resource[:ensure]
193
- else
194
- use_version = get_version
195
- end
196
95
 
197
- [@resource[:name], use_version, @resource[:flavor]].join('-').gsub(/-+$/, '')
96
+ name_branch_regex = /^(\S*)(%\w*)$/
97
+ match = name_branch_regex.match(@resource[:name])
98
+ if match
99
+ use_name = match.captures[0]
100
+ use_branch = match.captures[1]
101
+ else
102
+ use_name = @resource[:name]
103
+ use_branch = ''
198
104
  end
199
- end
200
-
201
- def get_version
202
- execpipe([command(:pkginfo), "-I", @resource[:name]]) do |process|
203
- # our regex for matching pkg_info output
204
- regex = /^(.*)-(\d[^-]*)-?(\w*)(.*)$/
205
- master_version = 0
206
- version = -1
207
-
208
- process.each_line do |line|
209
- match = regex.match(line.split[0])
210
- next unless match
211
-
212
- # now we return the first version, unless ensure is latest
213
- version = match.captures[1]
214
- return version unless @resource[:ensure] == "latest"
215
-
216
- master_version = version unless master_version > version
217
- end
218
-
219
- return master_version unless master_version == 0
220
- return '' if version == -1
221
105
 
222
- raise Puppet::Error, _("%{version} is not available for this package") % { version: version }
106
+ if @resource[:flavor]
107
+ "#{use_name}--#{@resource[:flavor]}#{use_branch}"
108
+ else
109
+ "#{use_name}--#{use_branch}"
223
110
  end
224
- rescue Puppet::ExecutionFailure
225
- nil
226
111
  end
227
112
 
228
113
  def query
229
- # Search for the version info
230
- if pkginfo(@resource[:name]) =~ /Information for (inst:)?#{@resource[:name]}-(\S+)/
231
- { :ensure => Regexp.last_match(2) }
232
- else
233
- nil
114
+ pkg = self.class.instances.find do |package|
115
+ @resource[:name] == package.name
234
116
  end
117
+ pkg ? pkg.properties : nil
235
118
  end
236
119
 
237
120
  def install_options
@@ -239,15 +122,15 @@ Puppet::Type.type(:package).provide :openbsd, :parent => Puppet::Provider::Packa
239
122
  end
240
123
 
241
124
  def uninstall_options
242
- join_options(resource[:uninstall_options])
125
+ join_options(resource[:uninstall_options]) || []
243
126
  end
244
127
 
245
128
  def uninstall
246
- pkgdelete uninstall_options.flatten.compact, @resource[:name]
129
+ pkgdelete uninstall_options.flatten.compact, get_full_name
247
130
  end
248
131
 
249
132
  def purge
250
- pkgdelete "-c", "-q", @resource[:name]
133
+ pkgdelete "-c", "-qq", uninstall_options.flatten.compact, get_full_name
251
134
  end
252
135
 
253
136
  def flavor
@@ -256,7 +139,6 @@ Puppet::Type.type(:package).provide :openbsd, :parent => Puppet::Provider::Packa
256
139
 
257
140
  def flavor=(value)
258
141
  if flavor != @resource.should(:flavor)
259
- uninstall
260
142
  install
261
143
  end
262
144
  end
@@ -135,7 +135,7 @@ Puppet::Type.type(:package).provide :rpm, :source => :rpm, :parent => Puppet::Pr
135
135
  flag = ["-i"]
136
136
  flag = ["-U", "--oldpackage"] if version && (version != :absent && version != :purged)
137
137
  flag += install_options if resource[:install_options]
138
- rpm flag, source
138
+ with_environment { rpm flag, source }
139
139
  end
140
140
 
141
141
  def uninstall
@@ -171,7 +171,7 @@ Puppet::Type.type(:package).provide :rpm, :source => :rpm, :parent => Puppet::Pr
171
171
 
172
172
  flag = ['-e']
173
173
  flag += uninstall_options if resource[:uninstall_options]
174
- rpm flag, identifier
174
+ with_environment { rpm flag, identifier }
175
175
  end
176
176
 
177
177
  def update
@@ -142,7 +142,7 @@ Puppet::Type.type(:package).provide :zypper, :parent => :rpm, :source => :rpm do
142
142
  options << '--name' unless major < 1 || @resource.allow_virtual? || should
143
143
  options << wanted
144
144
 
145
- zypper(*options)
145
+ with_environment { zypper(*options) }
146
146
 
147
147
  unless query
148
148
  raise Puppet::ExecutionFailure, _("Could not find package %{name}") % { name: name }
@@ -176,7 +176,7 @@ Puppet::Type.type(:package).provide :zypper, :parent => :rpm, :source => :rpm do
176
176
 
177
177
  options << @resource[:name]
178
178
 
179
- zypper(*options)
179
+ with_environment { zypper(*options) }
180
180
  end
181
181
  end
182
182
 
@@ -35,6 +35,73 @@ class Puppet::Provider::Package < Puppet::Provider
35
35
  true
36
36
  end
37
37
 
38
+ # @return [Hash<String, String>] Environment variables parsed from the resource's
39
+ # `environment` parameter, or an empty hash if none are set.
40
+ # @api private
41
+ def package_environment
42
+ env = {}
43
+
44
+ return env unless resource.respond_to?(:parameters)
45
+
46
+ env_param = resource.parameters[:environment]
47
+ return env unless env_param
48
+
49
+ envlist = env_param.value
50
+ return env unless envlist
51
+
52
+ envlist = [envlist] unless envlist.is_a? Array
53
+ envlist.each do |setting|
54
+ unless (match = /\A(\w+)=((.|\n)*)\z/.match(setting))
55
+ warning _("Cannot understand environment setting %{setting}") % { setting: setting.inspect }
56
+ next
57
+ end
58
+ var = match[1]
59
+ value = match[2]
60
+
61
+ if env.include?(var)
62
+ warning _("Overriding environment setting '%{var}'") % { var: var }
63
+ end
64
+
65
+ if value.nil? || value.empty?
66
+ msg = _("Empty environment setting '%{var}'") % { var: var }
67
+ Puppet.warn_once('undefined_variables', "empty_env_var_#{var}", msg, resource.file, resource.line)
68
+ end
69
+
70
+ env[var] = value
71
+ end
72
+
73
+ env
74
+ end
75
+
76
+ # Injects the resource's environment variables into execute calls via
77
+ # `:custom_environment`. Providers using `execute()` directly get this
78
+ # automatically.
79
+ def execute(*args)
80
+ env = package_environment
81
+ unless env.empty?
82
+ if args.last.is_a?(Hash)
83
+ existing = args.last[:custom_environment] || {}
84
+ args.last[:custom_environment] = existing.merge(env)
85
+ else
86
+ args << { :custom_environment => env }
87
+ end
88
+ end
89
+ super(*args)
90
+ end
91
+
92
+ # Wraps a block with the resource's environment variables applied via
93
+ # `Puppet::Util.withenv`. Use this around named command methods (e.g.
94
+ # `aptget`, `dpkg`, `rpm`) that bypass instance-level `execute`.
95
+ # @api private
96
+ def with_environment(&block)
97
+ env = package_environment
98
+ if env.empty?
99
+ yield
100
+ else
101
+ Puppet::Util.withenv(env, &block)
102
+ end
103
+ end
104
+
38
105
  # Turns a array of options into flags to be passed to a command.
39
106
  # The options can be passed as a string or hash. Note that passing a hash
40
107
  # should only be used in case --foo=bar must be passed,
@@ -76,4 +76,19 @@ Puppet::Type.type(:user).provide :openbsd, :parent => :useradd do
76
76
  end
77
77
  cmd
78
78
  end
79
+
80
+ def password=(value)
81
+ user = @resource.name
82
+ begin
83
+ cmd = [command(:modify), '-p', value, user]
84
+ execute_options = {
85
+ :failonfail => true,
86
+ :combine => true,
87
+ :sensitive => has_sensitive_data?
88
+ }
89
+ execute(cmd, execute_options)
90
+ rescue => detail
91
+ raise Puppet::Error, "Could not set password on #{@resource.class.name}[#{@resource.name}]: #{detail}", detail.backtrace
92
+ end
93
+ end
79
94
  end
@@ -590,7 +590,7 @@ class Puppet::Resource
590
590
  argtype =~ /^([^\[\]]+)\[(.+)\]$/m then [::Regexp.last_match(1), ::Regexp.last_match(2)]
591
591
  elsif argtitle then [argtype, argtitle]
592
592
  elsif argtype.is_a?(Puppet::Type) then [argtype.class.name, argtype.title]
593
- else raise ArgumentError, _("No title provided and %{type} is not a valid resource reference") % { type: argtype.inspect } # rubocop:disable Lint/ElseLayout
593
+ else raise ArgumentError, _("No title provided and %{type} is not a valid resource reference") % { type: argtype.inspect }
594
594
  end
595
595
  end
596
596
  private_class_method :extract_type_and_title
@@ -13,7 +13,7 @@ Puppet::Type.type(:file).newparam(:checksum) do
13
13
  The default checksum type is sha256."
14
14
 
15
15
  # The values are defined in Puppet::Util::Checksums.known_checksum_types
16
- newvalues(:sha256, :sha256lite, :md5, :md5lite, :sha1, :sha1lite, :sha512, :sha384, :sha224, :mtime, :ctime, :none)
16
+ newvalues(:sha256, :sha256lite, :md5, :md5lite, :sha1, :sha1lite, :sha512, :sha384, :sha224, :mtime, :ctime, :none, :etag)
17
17
 
18
18
  defaultto do
19
19
  Puppet[:digest_algorithm].to_sym
@@ -47,8 +47,18 @@ Puppet::Type.type(:file).newparam(:checksum) do
47
47
  private
48
48
 
49
49
  # Return the appropriate digest algorithm with fallbacks in case puppet defaults have not
50
- # been initialized.
50
+ # been initialized. When the checksum type is :etag, resolve to the actual
51
+ # hash algorithm that the HTTP server's ETag represents.
51
52
  def digest_algorithm
52
- value || Puppet[:digest_algorithm].to_sym
53
+ type = value || Puppet[:digest_algorithm].to_sym
54
+ return type unless type == :etag
55
+
56
+ source = resource.parameter(:source)
57
+ resolved = source&.metadata&.checksum_type
58
+ if resolved && ![:etag, :mtime, :ctime, :none].include?(resolved)
59
+ return resolved
60
+ end
61
+
62
+ :md5
53
63
  end
54
64
  end
@@ -533,6 +533,30 @@ module Puppet
533
533
  defaultto false
534
534
  end
535
535
 
536
+ newparam(:environment) do
537
+ desc <<-EOT
538
+ An array of additional environment variables to set for package
539
+ commands, such as `[ 'HOME=/root', 'DISABLE_TELEMETRY=1']`.
540
+
541
+ package { 'opensearch':
542
+ ensure => installed,
543
+ environment => [ 'OPENSEARCH_INITIAL_ADMIN_PASSWORD=myStrongP@ss!' ],
544
+ }
545
+
546
+ These variables are applied to all package management commands
547
+ (install, update, uninstall, purge) executed by the provider.
548
+ EOT
549
+
550
+ validate do |values|
551
+ values = [values] unless values.is_a? Array
552
+ values.each do |value|
553
+ unless value =~ /\A\w+=/
554
+ raise ArgumentError, _("Invalid environment setting '%{value}'") % { value: value }
555
+ end
556
+ end
557
+ end
558
+ end
559
+
536
560
  newparam(:install_options, :parent => Puppet::Parameter::PackageOptions, :required_features => :install_options) do
537
561
  desc <<-EOT
538
562
  An array of additional options to pass when installing a package. These
@@ -17,7 +17,8 @@ module Puppet::Util::Checksums
17
17
  :sha512,
18
18
  :sha384,
19
19
  :sha224,
20
- :mtime, :ctime, :none
20
+ :mtime, :ctime, :none,
21
+ :etag
21
22
  ].freeze
22
23
 
23
24
  # It's not a good idea to use some of these in some contexts: for example, I
@@ -339,6 +340,25 @@ module Puppet::Util::Checksums
339
340
  ""
340
341
  end
341
342
 
343
+ # ETag-based checksum delegates to md5 for local file computation.
344
+ # The actual algorithm is determined at runtime by HttpMetadata#collect
345
+ # based on the ETag header length.
346
+ def etag(content)
347
+ md5(content)
348
+ end
349
+
350
+ def etag?(string)
351
+ string =~ /^\h{32,64}$/
352
+ end
353
+
354
+ def etag_file(filename, lite = false)
355
+ md5_file(filename, lite)
356
+ end
357
+
358
+ def etag_stream(lite = false, &block)
359
+ md5_stream(lite, &block)
360
+ end
361
+
342
362
  class DigestLite
343
363
  def initialize(digest, lite = false)
344
364
  @digest = digest
@@ -78,8 +78,8 @@ module Puppet::Util::Execution
78
78
  # a predictable output
79
79
  english_env = ENV.to_hash.merge({ 'LANG' => 'C', 'LC_ALL' => 'C' })
80
80
  output = Puppet::Util.withenv(english_env) do
81
- # We are intentionally using 'pipe' with open to launch a process
82
- open("| #{command_str} 2>&1") do |pipe| # rubocop:disable Security/Open
81
+ # Use IO.popen instead of open for Ruby 4 compatibility
82
+ IO.popen("#{command_str} 2>&1", "r") do |pipe|
83
83
  yield pipe
84
84
  end
85
85
  end
@@ -11,7 +11,8 @@ class Puppet::Util::TagSet < Set
11
11
  end
12
12
 
13
13
  def to_yaml
14
- @hash.keys.to_yaml
14
+ # Ruby 4 changed Set's internal structure, use to_a instead of @hash.keys
15
+ to_a.to_yaml
15
16
  end
16
17
 
17
18
  def self.from_data_hash(data)
@@ -8,7 +8,7 @@
8
8
  # Raketasks and such to set the version based on the output of `git describe`
9
9
 
10
10
  module Puppet
11
- PUPPETVERSION = '8.25.0'
11
+ PUPPETVERSION = '8.26.0'
12
12
  IMPLEMENTATION = 'openvox'
13
13
 
14
14
  ##
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openvox
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.25.0
4
+ version: 8.26.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OpenVox Project
@@ -97,6 +97,20 @@ dependencies:
97
97
  - - "<"
98
98
  - !ruby/object:Gem::Version
99
99
  version: '5'
100
+ - !ruby/object:Gem::Dependency
101
+ name: fiddle
102
+ requirement: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - "~>"
105
+ - !ruby/object:Gem::Version
106
+ version: '1.1'
107
+ type: :runtime
108
+ prerelease: false
109
+ version_requirements: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - "~>"
112
+ - !ruby/object:Gem::Version
113
+ version: '1.1'
100
114
  - !ruby/object:Gem::Dependency
101
115
  name: getoptlong
102
116
  requirement: !ruby/object:Gem::Requirement
@@ -1352,7 +1366,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
1352
1366
  - !ruby/object:Gem::Version
1353
1367
  version: 1.3.1
1354
1368
  requirements: []
1355
- rubygems_version: 4.0.3
1369
+ rubygems_version: 4.0.6
1356
1370
  specification_version: 4
1357
1371
  summary: OpenVox, a community implementation of Puppet -- an automated configuration
1358
1372
  management tool