chef_sous_vide 0.1.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 +7 -0
- data/.gitignore +14 -0
- data/.travis.yml +7 -0
- data/Berksfile +11 -0
- data/Berksfile.lock +45 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +297 -0
- data/LICENSE.txt +21 -0
- data/README.md +127 -0
- data/Rakefile +13 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/kitchen.yml +98 -0
- data/lib/sous_vide.rb +10 -0
- data/lib/sous_vide/event_methods.rb +176 -0
- data/lib/sous_vide/handler.rb +186 -0
- data/lib/sous_vide/outputs/json_file.rb +43 -0
- data/lib/sous_vide/outputs/json_http.rb +66 -0
- data/lib/sous_vide/outputs/logger.rb +48 -0
- data/lib/sous_vide/outputs/multi.rb +21 -0
- data/lib/sous_vide/tracked_resource.rb +75 -0
- data/lib/sous_vide/version.rb +3 -0
- data/sous_vide.gemspec +35 -0
- metadata +194 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require "chef/config"
|
|
2
|
+
require "chef/json_compat"
|
|
3
|
+
require "chef/log"
|
|
4
|
+
|
|
5
|
+
module SousVide
|
|
6
|
+
module Outputs
|
|
7
|
+
# Saves the report to a JSON file on a node. The file will be saved to chef cache directory.
|
|
8
|
+
#
|
|
9
|
+
# Outputs::JsonFile.new
|
|
10
|
+
#
|
|
11
|
+
# By the report will be saved to "<chef-cache-path>/sous-vide-report.json".
|
|
12
|
+
class JsonFile
|
|
13
|
+
def initialize(logger: logger)
|
|
14
|
+
@logger = logger
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def call(run_data:, node_data:, resources_data:)
|
|
18
|
+
log "=============== #{self.class.name} ==============="
|
|
19
|
+
log ""
|
|
20
|
+
log "Processing #{resources_data.size} resources."
|
|
21
|
+
|
|
22
|
+
json_data = resources_data.map do |tracked|
|
|
23
|
+
tracked.to_h.merge(node_data).merge(run_data)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
::Chef::FileCache.store("sous-vide-report.json",
|
|
27
|
+
::Chef::JSONCompat.to_json_pretty(json_data))
|
|
28
|
+
|
|
29
|
+
log "The report is in #{Chef::Config[:file_cache_path]}/sous-vide-report.json file."
|
|
30
|
+
log ""
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def log(*args)
|
|
34
|
+
message = args.compact.join(" ")
|
|
35
|
+
logger.info(message)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def logger
|
|
39
|
+
@logger ||= ::Chef::Log
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require "net/http"
|
|
2
|
+
require "time"
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module SousVide
|
|
6
|
+
module Outputs
|
|
7
|
+
# Makes a POST request to a configured endpoint. Logstash & Elasticsearch friendly format.
|
|
8
|
+
#
|
|
9
|
+
# JsonHTTP.new(url: "http://localhost:9200/endpoint", max_retries: 10)
|
|
10
|
+
class JsonHTTP
|
|
11
|
+
def initialize(url:, max_retries: 0, logger: nil)
|
|
12
|
+
@endpoint = URI(url)
|
|
13
|
+
@logger = logger
|
|
14
|
+
|
|
15
|
+
@max_retries = max_retries || 2
|
|
16
|
+
@http_client = Net::HTTP.new(@endpoint.host, @endpoint.port)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def call(run_data:, node_data:, resources_data:)
|
|
20
|
+
log "=============== #{self.class.name} ==============="
|
|
21
|
+
log ""
|
|
22
|
+
log "Processing #{resources_data.size} resources."
|
|
23
|
+
log "Target: #{@endpoint.to_s}"
|
|
24
|
+
|
|
25
|
+
resources_data.each do |tracked|
|
|
26
|
+
_path = @endpoint.path == "" ? "/" : @endpoint.path
|
|
27
|
+
post_request = Net::HTTP::Post.new(_path, "Content-Type" => "application/json")
|
|
28
|
+
|
|
29
|
+
payload = tracked.to_h.merge(node_data).merge(run_data)
|
|
30
|
+
payload["@timestamp"] = Time.parse(payload[:chef_resource_started_at]).iso8601(3)
|
|
31
|
+
|
|
32
|
+
post_request.body = payload.to_json
|
|
33
|
+
|
|
34
|
+
call_with_retries(post_request)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
log "All resources processed."
|
|
38
|
+
log ""
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def call_with_retries(nethttp_request)
|
|
42
|
+
_retry = 0
|
|
43
|
+
begin
|
|
44
|
+
@http_client.request(nethttp_request)
|
|
45
|
+
rescue
|
|
46
|
+
if _retry < @max_retries
|
|
47
|
+
_retry += 1
|
|
48
|
+
sleep 2
|
|
49
|
+
retry
|
|
50
|
+
else
|
|
51
|
+
raise
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def log(*args)
|
|
57
|
+
message = args.compact.join(" ")
|
|
58
|
+
logger.info(message)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def logger
|
|
62
|
+
@logger ||= ::Chef::Log
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module SousVide
|
|
2
|
+
module Outputs
|
|
3
|
+
# Prints the report to the logger (usually Chef logger).
|
|
4
|
+
#
|
|
5
|
+
# Outputs::Logger.new
|
|
6
|
+
class Logger
|
|
7
|
+
def initialize(logger: nil)
|
|
8
|
+
@logger = logger
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call(run_data:, node_data:, resources_data:)
|
|
12
|
+
log "=============== #{self.class.name} ==============="
|
|
13
|
+
log ""
|
|
14
|
+
log "Processing #{resources_data.size} resources."
|
|
15
|
+
log ""
|
|
16
|
+
|
|
17
|
+
resources_data.each do |tracked|
|
|
18
|
+
log("#{tracked.execution_order}.", tracked.to_s, tracked.status,
|
|
19
|
+
"(#{tracked.duration_ms.to_i} ms)", tracked.execution_phase)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
log ""
|
|
23
|
+
log "Node info:"
|
|
24
|
+
log ""
|
|
25
|
+
log "Name:", node_data[:chef_node_instance_id]
|
|
26
|
+
log "IP Address:", node_data[:chef_node_ipv4]
|
|
27
|
+
log "Role:", node_data[:chef_node_role]
|
|
28
|
+
log ""
|
|
29
|
+
log "Run info:"
|
|
30
|
+
log ""
|
|
31
|
+
log "ID:", run_data[:chef_run_id]
|
|
32
|
+
log "Started at:", run_data[:chef_run_started_at]
|
|
33
|
+
log "Completed at:", run_data[:chef_run_completed_at]
|
|
34
|
+
log "Success:", run_data[:chef_run_success]
|
|
35
|
+
log ""
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def log(*args)
|
|
39
|
+
message = args.compact.join(' ')
|
|
40
|
+
logger.info(message)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def logger
|
|
44
|
+
@logger ||= ::Chef::Log
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module SousVide
|
|
2
|
+
module Outputs
|
|
3
|
+
# Combines multiple outputs
|
|
4
|
+
#
|
|
5
|
+
# es = Outputs::ES.new ...
|
|
6
|
+
# log = Outputs::Logger.new ...
|
|
7
|
+
# multi = Outputs::Multi.new(es, log)
|
|
8
|
+
class Multi
|
|
9
|
+
def initialize(*outputs)
|
|
10
|
+
@outputs = outputs
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call(run_data:, node_data:, resources_data:)
|
|
14
|
+
@outputs.each do |output|
|
|
15
|
+
output.call(run_data: run_data, node_data: node_data,
|
|
16
|
+
resources_data: resources_data)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
module SousVide
|
|
2
|
+
# == SousVide::TrackedResource
|
|
3
|
+
#
|
|
4
|
+
# This is a very simple data structure SousVide uses to capture interesting
|
|
5
|
+
# information.
|
|
6
|
+
class TrackedResource
|
|
7
|
+
attr_accessor :type,
|
|
8
|
+
:name,
|
|
9
|
+
:action,
|
|
10
|
+
:status,
|
|
11
|
+
:duration_ms,
|
|
12
|
+
:guard_description,
|
|
13
|
+
:execution_phase,
|
|
14
|
+
:execution_order,
|
|
15
|
+
:notifying_resource,
|
|
16
|
+
:notification_type,
|
|
17
|
+
:before_notifications,
|
|
18
|
+
:immediate_notifications,
|
|
19
|
+
:delayed_notifications,
|
|
20
|
+
:retries,
|
|
21
|
+
:error_output,
|
|
22
|
+
:error_source,
|
|
23
|
+
:cookbook_name,
|
|
24
|
+
:cookbook_recipe,
|
|
25
|
+
:source_line,
|
|
26
|
+
:started_at,
|
|
27
|
+
:completed_at
|
|
28
|
+
|
|
29
|
+
attr_accessor :chef_resource_handle
|
|
30
|
+
|
|
31
|
+
def initialize(name:, action:, type:)
|
|
32
|
+
@name = name
|
|
33
|
+
@action = action
|
|
34
|
+
@type = type
|
|
35
|
+
|
|
36
|
+
@status = "unprocessed"
|
|
37
|
+
@duration_ms = nil
|
|
38
|
+
@guard_description = nil
|
|
39
|
+
|
|
40
|
+
@retries = 0
|
|
41
|
+
@error_output = nil
|
|
42
|
+
@error_source = nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def to_s
|
|
46
|
+
"#{@type}[#{@name}]##{@action}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def to_h
|
|
50
|
+
{
|
|
51
|
+
chef_resource: "#{@type}[#{@name}]##{@action}",
|
|
52
|
+
chef_resource_name: @name,
|
|
53
|
+
chef_resource_type: @type,
|
|
54
|
+
chef_resource_cookbook: @cookbook_name,
|
|
55
|
+
chef_resource_recipe: @cookbook_recipe,
|
|
56
|
+
chef_resource_action: @action,
|
|
57
|
+
chef_resource_guard: @guard_description,
|
|
58
|
+
chef_resource_duration_ms: @duration_ms,
|
|
59
|
+
chef_resource_error_output: @error_output,
|
|
60
|
+
chef_resource_error_source: @error_source,
|
|
61
|
+
chef_resource_retries: @retries,
|
|
62
|
+
chef_resource_notified_by: @notifying_resource,
|
|
63
|
+
chef_resource_notified_via: @notification_type,
|
|
64
|
+
chef_resource_before_notifications: @before_notifications,
|
|
65
|
+
chef_resource_immediate_notifications: @immediate_notifications,
|
|
66
|
+
chef_resource_delayed_notifications: @delayed_notifications,
|
|
67
|
+
chef_resource_order: @execution_order,
|
|
68
|
+
chef_resource_execution_phase: @execution_phase,
|
|
69
|
+
chef_resource_started_at: @started_at,
|
|
70
|
+
chef_resource_completed_at: @completed_at,
|
|
71
|
+
chef_resource_status: @status
|
|
72
|
+
}
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
data/sous_vide.gemspec
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
lib = File.expand_path("lib", __dir__)
|
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
|
+
require "sous_vide/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "chef_sous_vide"
|
|
7
|
+
spec.version = SousVide::VERSION
|
|
8
|
+
spec.authors = ["robuye"]
|
|
9
|
+
spec.email = ["rulejczyk@gmail.com"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "Sous vide cooking utilizes precise temperature control with circulation to produce results that you can’t achieve through any other cooking technique."
|
|
12
|
+
spec.description = "SousVide is a Chef Handler who will precisely track a converge process of your recipes and help you become a better cook."
|
|
13
|
+
spec.homepage = "https://github.com/lonelyplanet/sous_vide"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
|
|
16
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
17
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
18
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(cookbooks|kitchen|features)/}) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
spec.bindir = "exe"
|
|
22
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
23
|
+
spec.require_paths = ["lib"]
|
|
24
|
+
|
|
25
|
+
spec.add_dependency "chef", "~> 12.17.44"
|
|
26
|
+
|
|
27
|
+
spec.add_development_dependency "bundler", "~> 1.17"
|
|
28
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
|
29
|
+
spec.add_development_dependency "cucumber", "~> 3.1.2"
|
|
30
|
+
spec.add_development_dependency "pry"
|
|
31
|
+
spec.add_development_dependency "test-kitchen"
|
|
32
|
+
spec.add_development_dependency "kitchen-docker"
|
|
33
|
+
spec.add_development_dependency "kitchen-transport-rsync"
|
|
34
|
+
spec.add_development_dependency "berkshelf"
|
|
35
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: chef_sous_vide
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- robuye
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2019-04-01 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: chef
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 12.17.44
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 12.17.44
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: bundler
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '1.17'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '1.17'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rake
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '10.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '10.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: cucumber
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: 3.1.2
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: 3.1.2
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: pry
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: test-kitchen
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: kitchen-docker
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: kitchen-transport-rsync
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - ">="
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '0'
|
|
118
|
+
type: :development
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - ">="
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '0'
|
|
125
|
+
- !ruby/object:Gem::Dependency
|
|
126
|
+
name: berkshelf
|
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - ">="
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '0'
|
|
132
|
+
type: :development
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - ">="
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '0'
|
|
139
|
+
description: SousVide is a Chef Handler who will precisely track a converge process
|
|
140
|
+
of your recipes and help you become a better cook.
|
|
141
|
+
email:
|
|
142
|
+
- rulejczyk@gmail.com
|
|
143
|
+
executables: []
|
|
144
|
+
extensions: []
|
|
145
|
+
extra_rdoc_files: []
|
|
146
|
+
files:
|
|
147
|
+
- ".gitignore"
|
|
148
|
+
- ".travis.yml"
|
|
149
|
+
- Berksfile
|
|
150
|
+
- Berksfile.lock
|
|
151
|
+
- Gemfile
|
|
152
|
+
- Gemfile.lock
|
|
153
|
+
- LICENSE.txt
|
|
154
|
+
- README.md
|
|
155
|
+
- Rakefile
|
|
156
|
+
- bin/console
|
|
157
|
+
- bin/setup
|
|
158
|
+
- kitchen.yml
|
|
159
|
+
- lib/sous_vide.rb
|
|
160
|
+
- lib/sous_vide/event_methods.rb
|
|
161
|
+
- lib/sous_vide/handler.rb
|
|
162
|
+
- lib/sous_vide/outputs/json_file.rb
|
|
163
|
+
- lib/sous_vide/outputs/json_http.rb
|
|
164
|
+
- lib/sous_vide/outputs/logger.rb
|
|
165
|
+
- lib/sous_vide/outputs/multi.rb
|
|
166
|
+
- lib/sous_vide/tracked_resource.rb
|
|
167
|
+
- lib/sous_vide/version.rb
|
|
168
|
+
- sous_vide.gemspec
|
|
169
|
+
homepage: https://github.com/lonelyplanet/sous_vide
|
|
170
|
+
licenses:
|
|
171
|
+
- MIT
|
|
172
|
+
metadata: {}
|
|
173
|
+
post_install_message:
|
|
174
|
+
rdoc_options: []
|
|
175
|
+
require_paths:
|
|
176
|
+
- lib
|
|
177
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
178
|
+
requirements:
|
|
179
|
+
- - ">="
|
|
180
|
+
- !ruby/object:Gem::Version
|
|
181
|
+
version: '0'
|
|
182
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
183
|
+
requirements:
|
|
184
|
+
- - ">="
|
|
185
|
+
- !ruby/object:Gem::Version
|
|
186
|
+
version: '0'
|
|
187
|
+
requirements: []
|
|
188
|
+
rubyforge_project:
|
|
189
|
+
rubygems_version: 2.5.2
|
|
190
|
+
signing_key:
|
|
191
|
+
specification_version: 4
|
|
192
|
+
summary: Sous vide cooking utilizes precise temperature control with circulation to
|
|
193
|
+
produce results that you can’t achieve through any other cooking technique.
|
|
194
|
+
test_files: []
|