chef-utils 16.18.0 → 17.0.242

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: c4524e07ecea48ece3b12acd259f84d62ba88623e6666ecae04098498e668ec6
4
- data.tar.gz: '083c6e20e1c4ba0c338ad70a96cf76e1676fc2b9f1918f582647f518b077644b'
3
+ metadata.gz: 8b28b0dd9a34f1242fdeca73f39924515befdec0a2990e4bd56d4d94f071c2aa
4
+ data.tar.gz: 49cb4691092f6b0db0567a8975e72c3905682208ff0c089fe3149ef8ee8da4d0
5
5
  SHA512:
6
- metadata.gz: c4ff54b542ac1b5c6c587d5b2c359dc316aa0ede83dfc4f548de87eba28945b50d54742c2ca489764789ae0ddb2b7b552182f852ae72ab9a358b515de833712d
7
- data.tar.gz: 5f7d7dc5298a8df5e0c3f68a9a374da04a25815e42e0055795a874ae225b84dca4c5d6646cb4170b40de4f321282c45ad42517f90489297d8d074771220604b1
6
+ metadata.gz: 16f7f528de5d5fb631552951cef5f0e763bffce0c52bb2fb563f8d1cebd16269027dc3a2daf1f151bc58d5901adfb2309d93ecc3b6868e29f75be14b5538511b
7
+ data.tar.gz: 705ddfa785ecb1a7448a95f342a3718e5f90996131fe2678c6cd15875f3eee5d5cc7d5b7d38fabfa1500c07df26eac804261360cd13b205a7a24435a8a4ee922
data/chef-utils.gemspec CHANGED
@@ -13,7 +13,7 @@ Gem::Specification.new do |spec|
13
13
  spec.homepage = "https://github.com/chef/chef/tree/master/chef-utils"
14
14
  spec.license = "Apache-2.0"
15
15
 
16
- spec.required_ruby_version = ">= 2.6.0"
16
+ spec.required_ruby_version = ">= 2.6"
17
17
 
18
18
  spec.metadata = {
19
19
  "bug_tracker_uri" => "https://github.com/chef/chef/issues",
@@ -41,6 +41,10 @@ Gem::Specification.new do |spec|
41
41
  # ABSOLUTELY NO EXCEPTIONS
42
42
  #
43
43
 
44
+ # concurrent-ruby is: 1. lightweight, 2. has zero deps, 3. is external to chef
45
+ # this is used for the parallel_map enumerable extension for lightweight threading
46
+ spec.add_dependency "concurrent-ruby"
47
+
44
48
  spec.files = %w{Rakefile LICENSE} + Dir.glob("*.gemspec") +
45
49
  Dir.glob("{lib,spec}/**/*", File::FNM_DOTMATCH).reject { |f| File.directory?(f) }
46
50
  end
@@ -77,12 +77,6 @@ module ChefUtils
77
77
 
78
78
  # The server's configuration utility
79
79
  SERVER_CTL = "chef-server-ctl"
80
-
81
- # OS user for server
82
- SYSTEM_USER = "opscode"
83
-
84
- # The server`s docs URL
85
- SERVER_DOCS = "https://docs.chef.io/server/"
86
80
  end
87
81
 
88
82
  class Solo
@@ -23,7 +23,7 @@ module ChefUtils
23
23
  module Cloud
24
24
  include Internal
25
25
 
26
- # Determine if the current node is "in the cloud".
26
+ # Determine if the current node is running in a known cloud.
27
27
  #
28
28
  # @param [Chef::Node] node the node to check
29
29
  # @since 15.8
@@ -35,7 +35,18 @@ module ChefUtils
35
35
  !node["cloud"].nil?
36
36
  end
37
37
 
38
- # Return true if the current current node is in EC2.
38
+ # Determine if the current node is running in Alibaba Cloud
39
+ #
40
+ # @param [Chef::Node] node the node to check
41
+ # @since 17.0
42
+ #
43
+ # @return [Boolean]
44
+ #
45
+ def alibaba?(node = __getnode)
46
+ node.key?("alibaba")
47
+ end
48
+
49
+ # Determine if the current node is running in AWS EC2.
39
50
  #
40
51
  # @param [Chef::Node] node the node to check
41
52
  # @since 15.8
@@ -46,7 +57,7 @@ module ChefUtils
46
57
  node.key?("ec2")
47
58
  end
48
59
 
49
- # Return true if the current current node is in GCE.
60
+ # Determine if the current node running in Google Compute Engine (GCE).
50
61
  #
51
62
  # @param [Chef::Node] node the node to check
52
63
  # @since 15.8
@@ -57,7 +68,7 @@ module ChefUtils
57
68
  node.key?("gce")
58
69
  end
59
70
 
60
- # Return true if the current current node is in Rackspace.
71
+ # Determine if the current node is running in Rackspace.
61
72
  #
62
73
  # @param [Chef::Node] node the node to check
63
74
  # @since 15.8
@@ -68,7 +79,7 @@ module ChefUtils
68
79
  node.key?("rackspace")
69
80
  end
70
81
 
71
- # Return true if the current current node is in Eucalyptus.
82
+ # Determine if the current node is running in Eucalyptus.
72
83
  #
73
84
  # @param [Chef::Node] node the node to check
74
85
  # @since 15.8
@@ -81,7 +92,7 @@ module ChefUtils
81
92
  # chef-sugar backcompat method
82
93
  alias_method :euca?, :eucalyptus?
83
94
 
84
- # Return true if the current current node is in Linode.
95
+ # Determine if the current node is running in Linode.
85
96
  #
86
97
  # @param [Chef::Node] node the node to check
87
98
  # @since 15.8
@@ -92,7 +103,7 @@ module ChefUtils
92
103
  node.key?("linode")
93
104
  end
94
105
 
95
- # Return true if the current current node is in OpenStack.
106
+ # Determine if the current node is running in OpenStack.
96
107
  #
97
108
  # @param [Chef::Node] node the node to check
98
109
  # @since 15.8
@@ -103,7 +114,7 @@ module ChefUtils
103
114
  node.key?("openstack")
104
115
  end
105
116
 
106
- # Return true if the current current node is in Azure.
117
+ # Determine if the current node is running in Microsoft Azure.
107
118
  #
108
119
  # @param [Chef::Node] node the node to check
109
120
  # @since 15.8
@@ -114,7 +125,7 @@ module ChefUtils
114
125
  node.key?("azure")
115
126
  end
116
127
 
117
- # Return true if the current current node is in DigitalOcean.
128
+ # Determine if the current node is running in DigitalOcean.
118
129
  #
119
130
  # @param [Chef::Node] node the node to check
120
131
  # @since 15.8
@@ -127,7 +138,7 @@ module ChefUtils
127
138
  # chef-sugar backcompat method
128
139
  alias_method :digitalocean?, :digital_ocean?
129
140
 
130
- # Return true if the current current node is in SoftLayer.
141
+ # Determine if the current node is running in SoftLayer (IBM Cloud).
131
142
  #
132
143
  # @param [Chef::Node] node the node to check
133
144
  # @since 15.8
@@ -29,6 +29,17 @@ module ChefUtils
29
29
  module Introspection
30
30
  include TrainHelpers
31
31
 
32
+ # Determine if the node is using the Chef Effortless pattern in which the Chef Infra Client is packaged using Chef Habitat
33
+ #
34
+ # @param [Chef::Node] node the node to check
35
+ # @since 17.0
36
+ #
37
+ # @return [Boolean]
38
+ #
39
+ def effortless?(node = __getnode)
40
+ !!(node && node.read("chef_packages", "chef", "chef_effortless"))
41
+ end
42
+
32
43
  # Determine if the node is a docker container.
33
44
  #
34
45
  # @param [Chef::Node] node the node to check
@@ -126,7 +126,7 @@ module ChefUtils
126
126
  # Determine if the current node is CentOS Stream.
127
127
  #
128
128
  # @param [Chef::Node] node the node to check
129
- # @since 16.13
129
+ # @since 17.0
130
130
  #
131
131
  # @return [Boolean]
132
132
  #
@@ -58,7 +58,7 @@ module ChefUtils
58
58
  node["kernel"]["product_type"] == "Server"
59
59
  end
60
60
 
61
- # Determine the current Windows NT version. The NT version often differs from the marketing version, but offers a good way to find desktop and server releases that are based on the same codebase. IE: NT 6.3 is Windows 8.1 and Windows 2012 R2.
61
+ # Determine the current Windows NT version. The NT version often differs from the marketing version, but offers a good way to find desktop and server releases that are based on the same codebase. For example NT 6.3 corresponds to Windows 8.1 and Windows 2012 R2.
62
62
  #
63
63
  # @param [Chef::Node] node the node to check
64
64
  # @since 15.8
@@ -94,6 +94,10 @@ module ChefUtils
94
94
  end
95
95
  end
96
96
 
97
+ unless method_defined?(:regular_reader)
98
+ alias_method :regular_reader, :[]
99
+ end
100
+
97
101
  unless method_defined?(:regular_writer)
98
102
  alias_method :regular_writer, :[]=
99
103
  end
@@ -102,12 +106,9 @@ module ChefUtils
102
106
  alias_method :regular_update, :update
103
107
  end
104
108
 
105
- unless method_defined?(:regular_clear)
106
- alias_method :regular_clear, :clear
107
- end
108
-
109
- unless method_defined?(:regular_delete)
110
- alias_method :regular_delete, :delete
109
+ # @param key<Object> The key to get.
110
+ def [](key)
111
+ regular_reader(key)
111
112
  end
112
113
 
113
114
  # @param key<Object> The key to set.
@@ -120,6 +121,12 @@ module ChefUtils
120
121
  regular_writer(convert_key(key), convert_value(value))
121
122
  end
122
123
 
124
+ # internal API for use by Chef's deep merge cache
125
+ # @api private
126
+ def internal_get(key)
127
+ regular_reader(key)
128
+ end
129
+
123
130
  # internal API for use by Chef's deep merge cache
124
131
  # @api private
125
132
  def internal_set(key, value)
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Copyright:: Copyright (c) Chef Software Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require "concurrent/executors"
20
+ require "concurrent/future"
21
+ require "singleton" unless defined?(Singleton)
22
+
23
+ module ChefUtils
24
+ #
25
+ # This module contains ruby refinements that adds several methods to the Enumerable
26
+ # class which are useful for parallel processing.
27
+ #
28
+ module ParallelMap
29
+ refine Enumerable do
30
+
31
+ # Enumerates through the collection in parallel using the thread pool provided
32
+ # or the default thread pool. By using the default thread pool this supports
33
+ # recursively calling the method without deadlocking while using a globally
34
+ # fixed number of workers. This method supports lazy collections. It returns
35
+ # synchronously, waiting until all the work is done. Failures are only reported
36
+ # after the collection has executed and only the first exception is raised.
37
+ #
38
+ # (0..).lazy.parallel_map { |i| i*i }.first(5)
39
+ #
40
+ # @return [Array] output results
41
+ #
42
+ def parallel_map(pool: nil)
43
+ return self unless block_given?
44
+
45
+ pool ||= ChefUtils::DefaultThreadPool.instance.pool
46
+
47
+ futures = map do |item|
48
+ future = Concurrent::Future.execute(executor: pool) do
49
+ yield item
50
+ end
51
+ end
52
+
53
+ futures.map(&:value!)
54
+ end
55
+
56
+ # This has the same behavior as parallel_map but returns the enumerator instead of
57
+ # the return values.
58
+ #
59
+ # @return [Enumerable] the enumerable for method chaining
60
+ #
61
+ def parallel_each(pool: nil, &block)
62
+ return self unless block_given?
63
+
64
+ parallel_map(pool: pool, &block)
65
+
66
+ self
67
+ end
68
+
69
+ # The flat_each method is tightly coupled to the usage of parallel_map within the
70
+ # ChefFS implementation. It is not itself a parallel method, but it is used to
71
+ # iterate through the 2nd level of nested structure, which is tied to the nested
72
+ # structures that ChefFS returns.
73
+ #
74
+ # This is different from Enumerable#flat_map because that behaves like map.flatten(1) while
75
+ # this behaves more like flatten(1).each. We need this on an Enumerable, so we have no
76
+ # Enumerable#flatten method to call.
77
+ #
78
+ # [ [ 1, 2 ], [ 3, 4 ] ].flat_each(&block) calls block four times with 1, 2, 3, 4
79
+ #
80
+ # [ [ 1, 2 ], [ 3, 4 ] ].flat_map(&block) calls block twice with [1, 2] and [3,4]
81
+ #
82
+ def flat_each(&block)
83
+ map do |value|
84
+ if value.is_a?(Enumerable)
85
+ value.each(&block)
86
+ else
87
+ yield value
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ # The DefaultThreadPool has a fixed thread size and has no
95
+ # queue of work and the behavior on failure to find a thread is for the
96
+ # caller to run the work. This contract means that the thread pool can
97
+ # be called recursively without deadlocking and while keeping the fixed
98
+ # number of threads (and not exponentially growing the thread pool with
99
+ # the depth of recursion).
100
+ #
101
+ class DefaultThreadPool
102
+ include Singleton
103
+
104
+ DEFAULT_THREAD_SIZE = 10
105
+
106
+ # Size of the thread pool, must be set before getting the thread pool or
107
+ # calling parallel_map/parallel_each. Does not (but could be modified to)
108
+ # support dynamic resizing. To get fully synchronous behavior set this equal to
109
+ # zero rather than one since the caller will get work if the threads are
110
+ # busy.
111
+ #
112
+ # @return [Integer] number of threads
113
+ attr_accessor :threads
114
+
115
+ # Memoizing accessor for the thread pool
116
+ #
117
+ # @return [Concurrent::ThreadPoolExecutor] the thread pool
118
+ def pool
119
+ @pool ||= Concurrent::ThreadPoolExecutor.new(
120
+ min_threads: threads || DEFAULT_THREAD_SIZE,
121
+ max_threads: threads || DEFAULT_THREAD_SIZE,
122
+ max_queue: 0,
123
+ # "synchronous" redefines the 0 in max_queue to mean 'no queue' instead of 'infinite queue'
124
+ # it does not mean synchronous execution (no threads) but synchronous offload to the threads.
125
+ synchronous: true,
126
+ # this prevents deadlocks on recursive parallel usage
127
+ fallback_policy: :caller_runs
128
+ )
129
+ end
130
+ end
131
+ end
@@ -16,5 +16,5 @@
16
16
 
17
17
  module ChefUtils
18
18
  CHEFUTILS_ROOT = File.expand_path("..", __dir__)
19
- VERSION = "16.18.0"
19
+ VERSION = "17.0.242"
20
20
  end
@@ -45,6 +45,10 @@ RSpec.describe ChefUtils::DSL::Cloud do
45
45
  end
46
46
  end
47
47
 
48
+ context "on alibaba" do
49
+ cloud_reports_true_for(:cloud?, :alibaba?, node: { "alibaba" => {}, "cloud" => {} })
50
+ end
51
+
48
52
  context "on ec2" do
49
53
  cloud_reports_true_for(:cloud?, :ec2?, node: { "ec2" => {}, "cloud" => {} })
50
54
  end
@@ -32,6 +32,18 @@ RSpec.describe ChefUtils::DSL::Introspection do
32
32
 
33
33
  let(:test_instance) { IntrospectionTestClass.new(node) }
34
34
 
35
+ context "#effortless?" do
36
+ # FIXME: use a real VividMash for these tests instead of stubbing
37
+ it "is false by default" do
38
+ expect(node).to receive(:read).with("chef_packages", "chef", "chef_effortless").and_return(nil)
39
+ expect(ChefUtils.effortless?(node)).to be false
40
+ end
41
+ it "is true when ohai reports a effortless" do
42
+ expect(node).to receive(:read).with("chef_packages", "chef", "chef_effortless").and_return(true)
43
+ expect(ChefUtils.effortless?(node)).to be true
44
+ end
45
+ end
46
+
35
47
  context "#docker?" do
36
48
  # FIXME: use a real VividMash for these tests instead of stubbing
37
49
  it "is false by default" do
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Copyright:: Copyright (c) Chef Software Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require "chef-utils/parallel_map"
20
+
21
+ using ChefUtils::ParallelMap
22
+
23
+ RSpec.describe ChefUtils::ParallelMap do
24
+
25
+ shared_examples_for "common parallel API tests" do
26
+
27
+ before(:each) do
28
+ ChefUtils::DefaultThreadPool.instance.instance_variable_set(:@pool, nil)
29
+ ChefUtils::DefaultThreadPool.instance.threads = threads
30
+ end
31
+
32
+ after(:each) do
33
+ ChefUtils::DefaultThreadPool.instance.instance_variable_set(:@pool, nil)
34
+ end
35
+
36
+ it "parallel_map runs in parallel" do
37
+ # this is implicitly also testing that we run in the caller when we exhaust threads by running threads+1
38
+ val = threads + 1
39
+ ret = []
40
+ start = Time.now
41
+ (1..val).parallel_map do |i|
42
+ loop do
43
+ if val == i
44
+ ret << i
45
+ val -= 1
46
+ break
47
+ end
48
+ # we spin for quite awhile to wait for very slow testers if we have to
49
+ if Time.now - start > 30
50
+ raise "timed out; deadlocked due to lack of parallelization?"
51
+ end
52
+
53
+ # need to sleep a tiny bit to let other threads schedule
54
+ sleep 0.000001
55
+ end
56
+ end
57
+ expected = (1..threads + 1).to_a.reverse
58
+ expect(ret).to eql(expected)
59
+ end
60
+
61
+ it "parallel_each runs in parallel" do
62
+ # this is implicitly also testing that we run in the caller when we exhaust threads by running threads+1
63
+ val = threads + 1
64
+ ret = []
65
+ start = Time.now
66
+ (1..val).parallel_each do |i|
67
+ loop do
68
+ if val == i
69
+ ret << i
70
+ val -= 1
71
+ break
72
+ end
73
+ # we spin for quite awhile to wait for very slow testers if we have to
74
+ if Time.now - start > 30
75
+ raise "timed out; deadlocked due to lack of parallelization?"
76
+ end
77
+
78
+ # need to sleep a tiny bit to let other threads schedule
79
+ sleep 0.000001
80
+ end
81
+ end
82
+ expected = (1..threads + 1).to_a.reverse
83
+ expect(ret).to eql(expected)
84
+ end
85
+
86
+ it "parallel_map throws exceptions" do
87
+ expect { (0..10).parallel_map { |i| raise "boom" } }.to raise_error(RuntimeError)
88
+ end
89
+
90
+ it "parallel_each throws exceptions" do
91
+ expect { (0..10).parallel_each { |i| raise "boom" } }.to raise_error(RuntimeError)
92
+ end
93
+
94
+ it "parallel_map runs" do
95
+ ans = Timeout.timeout(30) do
96
+ (1..10).parallel_map { |i| i }
97
+ end
98
+ expect(ans).to eql((1..10).to_a)
99
+ end
100
+
101
+ it "parallel_each runs" do
102
+ Timeout.timeout(30) do
103
+ (1..10).parallel_each { |i| i }
104
+ end
105
+ end
106
+
107
+ it "recursive parallel_map will not deadlock" do
108
+ ans = Timeout.timeout(30) do
109
+ (1..2).parallel_map { |i| (1..2).parallel_map { |i| i } }
110
+ end
111
+ expect(ans).to eql([[1, 2], [1, 2]])
112
+ end
113
+
114
+ it "recursive parallel_each will not deadlock" do
115
+ ans = Timeout.timeout(30) do
116
+ (1..2).parallel_each { |i| (1..2).parallel_each { |i| i } }
117
+ end
118
+ end
119
+
120
+ it "parallel_map is lazy" do
121
+ ans = Timeout.timeout(30) do
122
+ (1..).lazy.parallel_map { |i| i }.first(5)
123
+ end
124
+ expect(ans).to eql((1..5).to_a)
125
+ end
126
+
127
+ it "parallel_each is lazy" do
128
+ ans = Timeout.timeout(30) do
129
+ (1..).lazy.parallel_each { |i| i }.first(5)
130
+ end
131
+ end
132
+ end
133
+
134
+ context "with 10 threads" do
135
+ let(:threads) { 10 }
136
+ it_behaves_like "common parallel API tests"
137
+ end
138
+
139
+ context "with 0 threads" do
140
+ let(:threads) { 0 }
141
+ it_behaves_like "common parallel API tests"
142
+ end
143
+
144
+ context "with 1 threads" do
145
+ let(:threads) { 1 }
146
+ it_behaves_like "common parallel API tests"
147
+ end
148
+
149
+ context "flat_each" do
150
+ it "runs each over items which are nested one level" do
151
+ sum = 0
152
+ [ [ 1, 2 ], [3, 4]].flat_each { |i| sum += i }
153
+ expect(sum).to eql(10)
154
+ end
155
+ end
156
+ end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chef-utils
3
3
  version: !ruby/object:Gem::Version
4
- version: 16.18.0
4
+ version: 17.0.242
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chef Software, Inc
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-04-28 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2021-04-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: concurrent-ruby
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  description:
14
28
  email:
15
29
  - oss@chef.io
@@ -38,6 +52,7 @@ files:
38
52
  - lib/chef-utils/dsl/windows.rb
39
53
  - lib/chef-utils/internal.rb
40
54
  - lib/chef-utils/mash.rb
55
+ - lib/chef-utils/parallel_map.rb
41
56
  - lib/chef-utils/version.rb
42
57
  - lib/chef-utils/version_string.rb
43
58
  - spec/spec_helper.rb
@@ -54,6 +69,7 @@ files:
54
69
  - spec/unit/dsl/which_spec.rb
55
70
  - spec/unit/dsl/windows_spec.rb
56
71
  - spec/unit/mash_spec.rb
72
+ - spec/unit/parallel_map_spec.rb
57
73
  homepage: https://github.com/chef/chef/tree/master/chef-utils
58
74
  licenses:
59
75
  - Apache-2.0
@@ -71,14 +87,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
71
87
  requirements:
72
88
  - - ">="
73
89
  - !ruby/object:Gem::Version
74
- version: 2.6.0
90
+ version: '2.6'
75
91
  required_rubygems_version: !ruby/object:Gem::Requirement
76
92
  requirements:
77
93
  - - ">="
78
94
  - !ruby/object:Gem::Version
79
95
  version: '0'
80
96
  requirements: []
81
- rubygems_version: 3.1.6
97
+ rubygems_version: 3.2.15
82
98
  signing_key:
83
99
  specification_version: 4
84
100
  summary: Basic utility functions for Core Chef Infra development