chef-stove 7.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +202 -0
- data/bin/stove +5 -0
- data/lib/stove.rb +87 -0
- data/lib/stove/artifactory.rb +83 -0
- data/lib/stove/cli.rb +199 -0
- data/lib/stove/config.rb +76 -0
- data/lib/stove/cookbook.rb +120 -0
- data/lib/stove/cookbook/metadata.rb +245 -0
- data/lib/stove/error.rb +56 -0
- data/lib/stove/filter.rb +60 -0
- data/lib/stove/mash.rb +25 -0
- data/lib/stove/mixins/insideable.rb +13 -0
- data/lib/stove/mixins/instanceable.rb +24 -0
- data/lib/stove/mixins/optionable.rb +41 -0
- data/lib/stove/mixins/validatable.rb +11 -0
- data/lib/stove/packager.rb +156 -0
- data/lib/stove/plugins/artifactory.rb +14 -0
- data/lib/stove/plugins/base.rb +48 -0
- data/lib/stove/plugins/git.rb +70 -0
- data/lib/stove/plugins/supermarket.rb +18 -0
- data/lib/stove/rake_task.rb +22 -0
- data/lib/stove/runner.rb +39 -0
- data/lib/stove/supermarket.rb +79 -0
- data/lib/stove/util.rb +56 -0
- data/lib/stove/validator.rb +68 -0
- data/lib/stove/version.rb +3 -0
- data/templates/errors/abstract_method.erb +5 -0
- data/templates/errors/artifactory_key_validation_failed.erb +11 -0
- data/templates/errors/git_clean_validation_failed.erb +1 -0
- data/templates/errors/git_failed.erb +5 -0
- data/templates/errors/git_repository_validation_failed.erb +3 -0
- data/templates/errors/git_tagging_failed.erb +5 -0
- data/templates/errors/git_up_to_date_validation_failed.erb +7 -0
- data/templates/errors/metadata_not_found.erb +1 -0
- data/templates/errors/server_unavailable.erb +1 -0
- data/templates/errors/stove_error.erb +1 -0
- data/templates/errors/supermarket_already_exists.erb +5 -0
- data/templates/errors/supermarket_key_validation_failed.erb +3 -0
- data/templates/errors/supermarket_username_validation_failed.erb +3 -0
- metadata +212 -0
data/lib/stove/config.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Stove
|
5
|
+
class Config
|
6
|
+
include Logify
|
7
|
+
include Mixin::Instanceable
|
8
|
+
|
9
|
+
def method_missing(m, *args, &block)
|
10
|
+
if m.to_s.end_with?('=')
|
11
|
+
__set__(m.to_s.chomp('='), args.first)
|
12
|
+
else
|
13
|
+
__get__(m)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def respond_to_missing?(m, include_private = false)
|
18
|
+
__has__?(m) || super
|
19
|
+
end
|
20
|
+
|
21
|
+
def save
|
22
|
+
FileUtils.mkdir_p(File.dirname(__path__))
|
23
|
+
File.open(__path__, 'w') do |f|
|
24
|
+
f.write(JSON.fast_generate(__raw__))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
"#<#{self.class.name} #{__raw__.to_s}>"
|
30
|
+
end
|
31
|
+
|
32
|
+
def inspect
|
33
|
+
"#<#{self.class.name} #{__raw__.inspect}>"
|
34
|
+
end
|
35
|
+
|
36
|
+
def __get__(key)
|
37
|
+
__raw__[key.to_sym]
|
38
|
+
end
|
39
|
+
|
40
|
+
def __has__?(key)
|
41
|
+
__raw__.key?(key.to_sym)
|
42
|
+
end
|
43
|
+
|
44
|
+
def __set__(key, value)
|
45
|
+
__raw__[key.to_sym] = value
|
46
|
+
end
|
47
|
+
|
48
|
+
def __unset__(key)
|
49
|
+
__raw__.delete(key.to_sym)
|
50
|
+
end
|
51
|
+
|
52
|
+
def __path__
|
53
|
+
@path ||= File.expand_path(ENV['STOVE_CONFIG'] || '~/.stove')
|
54
|
+
end
|
55
|
+
|
56
|
+
def __raw__
|
57
|
+
return @__raw__ if @__raw__
|
58
|
+
|
59
|
+
@__raw__ = JSON.parse(File.read(__path__), symbolize_names: true)
|
60
|
+
|
61
|
+
if @__raw__.key?(:community)
|
62
|
+
$stderr.puts "Detected old Stove configuration file, converting..."
|
63
|
+
|
64
|
+
@__raw__ = {
|
65
|
+
:username => @__raw__[:community][:username],
|
66
|
+
:key => @__raw__[:community][:key],
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
@__raw__
|
71
|
+
rescue Errno::ENOENT => e
|
72
|
+
log.warn { "No config file found at `#{__path__}'!" }
|
73
|
+
@__raw__ = {}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'tempfile'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module Stove
|
6
|
+
class Cookbook
|
7
|
+
include Logify
|
8
|
+
|
9
|
+
require_relative 'cookbook/metadata'
|
10
|
+
|
11
|
+
#
|
12
|
+
# The path to this cookbook on disk.
|
13
|
+
#
|
14
|
+
# @return [Pathname]
|
15
|
+
#
|
16
|
+
attr_reader :path
|
17
|
+
|
18
|
+
#
|
19
|
+
# The name of the cookbook (must correspond to the name of the
|
20
|
+
# cookbook on the Supermarket).
|
21
|
+
#
|
22
|
+
# @return [String]
|
23
|
+
#
|
24
|
+
attr_reader :name
|
25
|
+
|
26
|
+
#
|
27
|
+
# The version of this cookbook (originally).
|
28
|
+
#
|
29
|
+
# @return [String]
|
30
|
+
#
|
31
|
+
attr_reader :version
|
32
|
+
|
33
|
+
#
|
34
|
+
# The metadata for this cookbook.
|
35
|
+
#
|
36
|
+
# @return [Stove::Cookbook::Metadata]
|
37
|
+
#
|
38
|
+
attr_reader :metadata
|
39
|
+
|
40
|
+
#
|
41
|
+
# The changeset for this cookbook. This is written by the changelog
|
42
|
+
# generator and read by various plugins.
|
43
|
+
#
|
44
|
+
# @return [String, nil]
|
45
|
+
# the changeset for this cookbook
|
46
|
+
#
|
47
|
+
attr_accessor :changeset
|
48
|
+
|
49
|
+
#
|
50
|
+
# Create a new wrapper around the cookbook object.
|
51
|
+
#
|
52
|
+
# @param [String] path
|
53
|
+
# the relative or absolute path to the cookbook on disk
|
54
|
+
#
|
55
|
+
def initialize(path)
|
56
|
+
@path = File.expand_path(path)
|
57
|
+
load_metadata!
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# The tag version. This is just the current version prefixed with the
|
62
|
+
# letter "v".
|
63
|
+
#
|
64
|
+
# @example Tag version for 1.0.0
|
65
|
+
# cookbook.tag_version #=> "v1.0.0"
|
66
|
+
#
|
67
|
+
# @return [String]
|
68
|
+
#
|
69
|
+
def tag_version
|
70
|
+
"v#{version}"
|
71
|
+
end
|
72
|
+
|
73
|
+
#
|
74
|
+
# Deterine if this cookbook version is released on the Supermarket
|
75
|
+
#
|
76
|
+
# @warn
|
77
|
+
# This is a fairly expensive operation and the result cannot be
|
78
|
+
# reliably cached!
|
79
|
+
#
|
80
|
+
# @return [Boolean]
|
81
|
+
# true if this cookbook at the current version exists on the community
|
82
|
+
# site, false otherwise
|
83
|
+
#
|
84
|
+
def released?
|
85
|
+
Supermarket.cookbook(name, version)
|
86
|
+
true
|
87
|
+
rescue ChefAPI::Error::HTTPNotFound
|
88
|
+
false
|
89
|
+
end
|
90
|
+
|
91
|
+
#
|
92
|
+
# So there's this really really crazy bug that the tmp directory could
|
93
|
+
# be deleted mid-request...
|
94
|
+
#
|
95
|
+
# @return [File]
|
96
|
+
#
|
97
|
+
def tarball(extended_metadata = false)
|
98
|
+
@tarball ||= Packager.new(self, extended_metadata).tarball
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
# Load the metadata and set the @metadata instance variable.
|
103
|
+
#
|
104
|
+
# @raise [ArgumentError]
|
105
|
+
# if there is no metadata.rb
|
106
|
+
#
|
107
|
+
# @return [String]
|
108
|
+
# the path to the metadata file
|
109
|
+
def load_metadata!
|
110
|
+
metadata_path = File.join(path, 'metadata.rb')
|
111
|
+
|
112
|
+
@metadata = Stove::Cookbook::Metadata.from_file(metadata_path)
|
113
|
+
@name = @metadata.name
|
114
|
+
@version = @metadata.version
|
115
|
+
|
116
|
+
metadata_path
|
117
|
+
end
|
118
|
+
alias_method :reload_metadata!, :load_metadata!
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,245 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Stove
|
4
|
+
class Cookbook
|
5
|
+
# Borrowed and modified from:
|
6
|
+
# {https://raw.github.com/opscode/chef/11.4.0/lib/chef/cookbook/metadata.rb}
|
7
|
+
#
|
8
|
+
# Copyright:: Copyright 2008-2017 Chef Software, Inc.
|
9
|
+
#
|
10
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
11
|
+
# you may not use this file except in compliance with the License.
|
12
|
+
# You may obtain a copy of the License at
|
13
|
+
#
|
14
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
15
|
+
#
|
16
|
+
# Unless required by applicable law or agreed to in writing, software
|
17
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
18
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
19
|
+
# See the License for the specific language governing permissions and
|
20
|
+
# limitations under the License.
|
21
|
+
#
|
22
|
+
# == Chef::Cookbook::Metadata
|
23
|
+
# Chef::Cookbook::Metadata provides a convenient DSL for declaring metadata
|
24
|
+
# about Chef Cookbooks.
|
25
|
+
class Metadata
|
26
|
+
class << self
|
27
|
+
def from_file(path)
|
28
|
+
new.from_file(path)
|
29
|
+
end
|
30
|
+
|
31
|
+
def def_attribute(field)
|
32
|
+
class_eval <<-EOM, __FILE__, __LINE__ + 1
|
33
|
+
def #{field}(arg = nil)
|
34
|
+
set_or_return(:#{field}, arg)
|
35
|
+
end
|
36
|
+
EOM
|
37
|
+
end
|
38
|
+
|
39
|
+
def def_meta_cookbook(field, instance_variable)
|
40
|
+
class_eval <<-EOM, __FILE__, __LINE__ + 1
|
41
|
+
def #{field}(thing, *args)
|
42
|
+
version = args.first
|
43
|
+
@#{instance_variable}[thing] = version || DEFAULT_VERSION
|
44
|
+
@#{instance_variable}[thing]
|
45
|
+
end
|
46
|
+
EOM
|
47
|
+
end
|
48
|
+
|
49
|
+
def def_meta_setter(field, instance_variable)
|
50
|
+
class_eval <<-EOM, __FILE__, __LINE__ + 1
|
51
|
+
def #{field}(name, description)
|
52
|
+
@#{instance_variable}[name] = description
|
53
|
+
@#{instance_variable}
|
54
|
+
end
|
55
|
+
EOM
|
56
|
+
end
|
57
|
+
|
58
|
+
def def_meta_gems(field, instance_variable)
|
59
|
+
class_eval <<-EOM, __FILE__, __LINE__ + 1
|
60
|
+
def #{field}(*args)
|
61
|
+
@#{instance_variable} << args unless args.empty?
|
62
|
+
@#{instance_variable}
|
63
|
+
end
|
64
|
+
EOM
|
65
|
+
end
|
66
|
+
|
67
|
+
def def_meta_version(field)
|
68
|
+
class_eval <<-EOM, __FILE__, __LINE__ + 1
|
69
|
+
def #{field}(*args)
|
70
|
+
@#{field} << args unless args.empty?
|
71
|
+
@#{field}
|
72
|
+
end
|
73
|
+
EOM
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
DEFAULT_VERSION = '>= 0.0.0'.freeze
|
78
|
+
|
79
|
+
COMPARISON_FIELDS = [
|
80
|
+
:name, :description, :long_description, :maintainer,
|
81
|
+
:maintainer_email, :license, :platforms, :dependencies,
|
82
|
+
:recommendations, :suggestions, :conflicting, :providing,
|
83
|
+
:replacing, :attributes, :groupings, :recipes, :version
|
84
|
+
]
|
85
|
+
|
86
|
+
def_attribute :name
|
87
|
+
def_attribute :maintainer
|
88
|
+
def_attribute :maintainer_email
|
89
|
+
def_attribute :license
|
90
|
+
def_attribute :description
|
91
|
+
def_attribute :long_description
|
92
|
+
|
93
|
+
# These attributes are available for reading, but are not written by
|
94
|
+
# default. In order to maintain backwards and forwards compatability,
|
95
|
+
# these attributes are here.
|
96
|
+
def_attribute :source_url
|
97
|
+
def_attribute :issues_url
|
98
|
+
def_meta_version :chef_version
|
99
|
+
def_meta_version :ohai_version
|
100
|
+
|
101
|
+
def_meta_cookbook :supports, :platforms
|
102
|
+
def_meta_cookbook :depends, :dependencies
|
103
|
+
def_meta_cookbook :recommends, :recommendations
|
104
|
+
def_meta_cookbook :suggests, :suggestions
|
105
|
+
def_meta_cookbook :conflicts, :conflicting
|
106
|
+
def_meta_cookbook :provides, :providing
|
107
|
+
def_meta_cookbook :replaces, :replacing
|
108
|
+
|
109
|
+
def_meta_setter :recipe, :recipes
|
110
|
+
def_meta_setter :grouping, :groupings
|
111
|
+
def_meta_setter :attribute, :attributes
|
112
|
+
def_meta_gems :gem, :gems
|
113
|
+
|
114
|
+
attr_reader :cookbook
|
115
|
+
attr_reader :platforms
|
116
|
+
attr_reader :dependencies
|
117
|
+
attr_reader :recommendations
|
118
|
+
attr_reader :gems
|
119
|
+
attr_reader :suggestions
|
120
|
+
attr_reader :conflicting
|
121
|
+
attr_reader :providing
|
122
|
+
attr_reader :replacing
|
123
|
+
attr_reader :attributes
|
124
|
+
attr_reader :groupings
|
125
|
+
attr_reader :recipes
|
126
|
+
attr_reader :version
|
127
|
+
|
128
|
+
def initialize(cookbook = nil, maintainer = 'YOUR_COMPANY_NAME', maintainer_email = 'YOUR_EMAIL', license = 'none')
|
129
|
+
@cookbook = cookbook
|
130
|
+
@name = cookbook ? cookbook.name : ''
|
131
|
+
@long_description = ''
|
132
|
+
@source_url = Stove::Mash.new
|
133
|
+
@issues_url = Stove::Mash.new
|
134
|
+
@gems = []
|
135
|
+
@chef_version = []
|
136
|
+
@ohai_version = []
|
137
|
+
@platforms = Stove::Mash.new
|
138
|
+
@dependencies = Stove::Mash.new
|
139
|
+
@recommendations = Stove::Mash.new
|
140
|
+
@suggestions = Stove::Mash.new
|
141
|
+
@conflicting = Stove::Mash.new
|
142
|
+
@providing = Stove::Mash.new
|
143
|
+
@replacing = Stove::Mash.new
|
144
|
+
@attributes = Stove::Mash.new
|
145
|
+
@groupings = Stove::Mash.new
|
146
|
+
@recipes = Stove::Mash.new
|
147
|
+
|
148
|
+
self.maintainer(maintainer)
|
149
|
+
self.maintainer_email(maintainer_email)
|
150
|
+
self.license(license)
|
151
|
+
self.description('A fabulous new cookbook')
|
152
|
+
self.version('0.0.0')
|
153
|
+
|
154
|
+
if cookbook
|
155
|
+
@recipes = cookbook.fully_qualified_recipe_names.inject({}) do |r, e|
|
156
|
+
e = self.name if e =~ /::default$/
|
157
|
+
r[e] = ""
|
158
|
+
self.provides e
|
159
|
+
r
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def from_file(path)
|
165
|
+
path = path.to_s
|
166
|
+
path_json = File.join(File.dirname(path), 'metadata.json')
|
167
|
+
|
168
|
+
if File.exist?(path) && File.readable?(path)
|
169
|
+
self.instance_eval(IO.read(path), path, 1)
|
170
|
+
self
|
171
|
+
elsif File.exist?(path_json) && File.readable?(path_json)
|
172
|
+
metadata_from_json(path_json)
|
173
|
+
else
|
174
|
+
raise Error::MetadataNotFound.new(path: path)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def ==(other)
|
179
|
+
COMPARISON_FIELDS.inject(true) do |equal_so_far, field|
|
180
|
+
equal_so_far && other.respond_to?(field) && (other.send(field) == send(field))
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def version(arg = UNSET_VALUE)
|
185
|
+
if arg == UNSET_VALUE
|
186
|
+
@version
|
187
|
+
else
|
188
|
+
@version = arg.to_s
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def to_hash(extended_metadata = false)
|
193
|
+
hash = {
|
194
|
+
'name' => self.name,
|
195
|
+
'version' => self.version,
|
196
|
+
'description' => self.description,
|
197
|
+
'long_description' => self.long_description,
|
198
|
+
'maintainer' => self.maintainer,
|
199
|
+
'maintainer_email' => self.maintainer_email,
|
200
|
+
'license' => self.license,
|
201
|
+
'platforms' => self.platforms,
|
202
|
+
'dependencies' => self.dependencies,
|
203
|
+
'recommendations' => self.recommendations,
|
204
|
+
'suggestions' => self.suggestions,
|
205
|
+
'conflicting' => self.conflicting,
|
206
|
+
'providing' => self.providing,
|
207
|
+
'replacing' => self.replacing,
|
208
|
+
'attributes' => self.attributes,
|
209
|
+
'groupings' => self.groupings,
|
210
|
+
'recipes' => self.recipes,
|
211
|
+
}
|
212
|
+
|
213
|
+
if extended_metadata
|
214
|
+
hash['source_url'] = self.source_url unless self.source_url.empty?
|
215
|
+
hash['issues_url'] = self.issues_url unless self.issues_url.empty?
|
216
|
+
hash['gems'] = self.gems unless self.gems.empty?
|
217
|
+
hash['chef_version'] = self.chef_version.map(&:sort)
|
218
|
+
hash['ohai_version'] = self.ohai_version.map(&:sort)
|
219
|
+
end
|
220
|
+
|
221
|
+
return hash
|
222
|
+
end
|
223
|
+
|
224
|
+
private
|
225
|
+
|
226
|
+
def metadata_from_json(path)
|
227
|
+
json = JSON.parse(IO.read(path))
|
228
|
+
json.keys.each do |key|
|
229
|
+
set_or_return(key.to_sym, json[key])
|
230
|
+
end
|
231
|
+
self
|
232
|
+
end
|
233
|
+
|
234
|
+
def set_or_return(symbol, arg)
|
235
|
+
iv_symbol = "@#{symbol.to_s}".to_sym
|
236
|
+
|
237
|
+
if arg.nil? && self.instance_variable_defined?(iv_symbol)
|
238
|
+
self.instance_variable_get(iv_symbol)
|
239
|
+
else
|
240
|
+
self.instance_variable_set(iv_symbol, arg)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
data/lib/stove/error.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module Stove
|
4
|
+
module Error
|
5
|
+
class ErrorBinding
|
6
|
+
def initialize(options = {})
|
7
|
+
options.each do |key, value|
|
8
|
+
instance_variable_set(:"@#{key}", value)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def get_binding
|
13
|
+
binding
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class StoveError < StandardError
|
18
|
+
def initialize(options = {})
|
19
|
+
@options = options
|
20
|
+
@filename = options.delete(:_template)
|
21
|
+
|
22
|
+
super()
|
23
|
+
end
|
24
|
+
|
25
|
+
def message
|
26
|
+
erb = ERB.new(File.read(template))
|
27
|
+
erb.result(ErrorBinding.new(@options).get_binding)
|
28
|
+
end
|
29
|
+
alias_method :to_s, :message
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def template
|
34
|
+
class_name = self.class.to_s.split('::').last
|
35
|
+
filename = @filename || Util.underscore(class_name)
|
36
|
+
Stove.root.join('templates', 'errors', "#{filename}.erb")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class GitFailed < StoveError; end
|
41
|
+
class GitTaggingFailed < StoveError; end
|
42
|
+
class MetadataNotFound < StoveError; end
|
43
|
+
class ServerUnavailable < StoveError; end
|
44
|
+
class CookbookAlreadyExists < StoveError; end
|
45
|
+
|
46
|
+
# Validations
|
47
|
+
class ValidationFailed < StoveError; end
|
48
|
+
class SupermarketCategoryValidationFailed < ValidationFailed; end
|
49
|
+
class SupermarketKeyValidationFailed < ValidationFailed; end
|
50
|
+
class SupermarketUsernameValidationFailed < ValidationFailed; end
|
51
|
+
class GitCleanValidationFailed < ValidationFailed; end
|
52
|
+
class GitRepositoryValidationFailed < ValidationFailed; end
|
53
|
+
class GitUpToDateValidationFailed < ValidationFailed; end
|
54
|
+
class ArtifactoryKeyValidationFailed < ValidationFailed; end
|
55
|
+
end
|
56
|
+
end
|