chef-stove 7.1.1

Sign up to get free protection for your applications and to get access to all the features.
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