chef-stove 7.1.1

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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +202 -0
  3. data/bin/stove +5 -0
  4. data/lib/stove.rb +87 -0
  5. data/lib/stove/artifactory.rb +83 -0
  6. data/lib/stove/cli.rb +199 -0
  7. data/lib/stove/config.rb +76 -0
  8. data/lib/stove/cookbook.rb +120 -0
  9. data/lib/stove/cookbook/metadata.rb +245 -0
  10. data/lib/stove/error.rb +56 -0
  11. data/lib/stove/filter.rb +60 -0
  12. data/lib/stove/mash.rb +25 -0
  13. data/lib/stove/mixins/insideable.rb +13 -0
  14. data/lib/stove/mixins/instanceable.rb +24 -0
  15. data/lib/stove/mixins/optionable.rb +41 -0
  16. data/lib/stove/mixins/validatable.rb +11 -0
  17. data/lib/stove/packager.rb +156 -0
  18. data/lib/stove/plugins/artifactory.rb +14 -0
  19. data/lib/stove/plugins/base.rb +48 -0
  20. data/lib/stove/plugins/git.rb +70 -0
  21. data/lib/stove/plugins/supermarket.rb +18 -0
  22. data/lib/stove/rake_task.rb +22 -0
  23. data/lib/stove/runner.rb +39 -0
  24. data/lib/stove/supermarket.rb +79 -0
  25. data/lib/stove/util.rb +56 -0
  26. data/lib/stove/validator.rb +68 -0
  27. data/lib/stove/version.rb +3 -0
  28. data/templates/errors/abstract_method.erb +5 -0
  29. data/templates/errors/artifactory_key_validation_failed.erb +11 -0
  30. data/templates/errors/git_clean_validation_failed.erb +1 -0
  31. data/templates/errors/git_failed.erb +5 -0
  32. data/templates/errors/git_repository_validation_failed.erb +3 -0
  33. data/templates/errors/git_tagging_failed.erb +5 -0
  34. data/templates/errors/git_up_to_date_validation_failed.erb +7 -0
  35. data/templates/errors/metadata_not_found.erb +1 -0
  36. data/templates/errors/server_unavailable.erb +1 -0
  37. data/templates/errors/stove_error.erb +1 -0
  38. data/templates/errors/supermarket_already_exists.erb +5 -0
  39. data/templates/errors/supermarket_key_validation_failed.erb +3 -0
  40. data/templates/errors/supermarket_username_validation_failed.erb +3 -0
  41. metadata +212 -0
@@ -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
@@ -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