stove 2.0.0 → 3.0.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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +6 -1
  4. data/CHANGELOG.md +20 -0
  5. data/README.md +34 -80
  6. data/Rakefile +9 -1
  7. data/bin/bake +2 -0
  8. data/bin/stove +4 -0
  9. data/features/plugins/community.feature +11 -26
  10. data/features/plugins/git.feature +17 -6
  11. data/features/step_definitions/community_steps.rb +3 -1
  12. data/features/step_definitions/config_steps.rb +4 -21
  13. data/features/step_definitions/git_steps.rb +38 -1
  14. data/features/support/env.rb +17 -11
  15. data/features/support/stove/git.rb +28 -8
  16. data/lib/stove/cli.rb +72 -53
  17. data/lib/stove/community.rb +16 -67
  18. data/lib/stove/config.rb +55 -46
  19. data/lib/stove/cookbook/metadata.rb +3 -5
  20. data/lib/stove/cookbook.rb +2 -41
  21. data/lib/stove/error.rb +37 -8
  22. data/lib/stove/filter.rb +2 -1
  23. data/lib/stove/mixins/instanceable.rb +3 -2
  24. data/lib/stove/mixins/validatable.rb +5 -1
  25. data/lib/stove/packager.rb +11 -3
  26. data/lib/stove/plugins/base.rb +26 -13
  27. data/lib/stove/plugins/community.rb +3 -7
  28. data/lib/stove/plugins/git.rb +27 -30
  29. data/lib/stove/rake_task.rb +3 -63
  30. data/lib/stove/runner.rb +16 -65
  31. data/lib/stove/validator.rb +7 -6
  32. data/lib/stove/version.rb +1 -1
  33. data/lib/stove.rb +3 -21
  34. data/spec/spec_helper.rb +2 -0
  35. data/spec/unit/error_spec.rb +148 -0
  36. data/stove.gemspec +10 -14
  37. data/templates/errors/abstract_method.erb +5 -0
  38. data/templates/errors/community_category_validation_failed.erb +5 -0
  39. data/templates/errors/community_key_validation_failed.erb +3 -0
  40. data/templates/errors/community_username_validation_failed.erb +3 -0
  41. data/templates/errors/git_clean_validation_failed.erb +1 -0
  42. data/templates/errors/git_failed.erb +5 -0
  43. data/templates/errors/git_repository_validation_failed.erb +3 -0
  44. data/templates/errors/git_up_to_date_validation_failed.erb +7 -0
  45. data/templates/errors/metadata_not_found.erb +1 -0
  46. data/templates/errors/server_unavailable.erb +1 -0
  47. data/templates/errors/stove_error.erb +1 -0
  48. metadata +32 -114
  49. data/features/actions/bump.feature +0 -22
  50. data/features/actions/changelog.feature +0 -52
  51. data/features/actions/dev.feature +0 -18
  52. data/features/actions/upload.feature +0 -26
  53. data/features/rake.feature +0 -15
  54. data/features/step_definitions/cli_steps.rb +0 -3
  55. data/lib/stove/actions/base.rb +0 -21
  56. data/lib/stove/actions/bump.rb +0 -25
  57. data/lib/stove/actions/changelog.rb +0 -71
  58. data/lib/stove/actions/dev.rb +0 -22
  59. data/lib/stove/actions/finish.rb +0 -8
  60. data/lib/stove/actions/start.rb +0 -7
  61. data/lib/stove/actions/upload.rb +0 -11
  62. data/lib/stove/jira.rb +0 -88
  63. data/lib/stove/middlewares/chef_authentication.rb +0 -60
  64. data/lib/stove/middlewares/exceptions.rb +0 -17
  65. data/lib/stove/mixins/filterable.rb +0 -11
  66. data/lib/stove/plugins/github.rb +0 -107
  67. data/lib/stove/plugins/jira.rb +0 -72
  68. data/locales/en.yml +0 -230
data/lib/stove/cli.rb CHANGED
@@ -15,6 +15,29 @@ module Stove
15
15
  # Parse the options hash
16
16
  option_parser.parse!(@argv)
17
17
 
18
+ # Stupid special use cases
19
+ if @argv.first == 'login'
20
+ if options[:username].nil? || options[:username].to_s.strip.empty?
21
+ raise "Missing argument `--username'!"
22
+ end
23
+
24
+ if options[:key].nil? || options[:key].to_s.strip.empty?
25
+ raise "Missing argument `--key'!"
26
+ end
27
+
28
+ Config.username = options[:username]
29
+ Config.key = options[:key]
30
+ Config.save
31
+
32
+ @stdout.puts "Successfully saved config to `#{Config.__path__}'!"
33
+ return
34
+ end
35
+
36
+ # Override configs
37
+ Config.endpoint = options[:endpoint] if options[:endpoint]
38
+ Config.username = options[:username] if options[:username]
39
+ Config.key = options[:key] if options[:key]
40
+
18
41
  # Set the log level
19
42
  Stove.log_level = options[:log_level]
20
43
 
@@ -26,12 +49,6 @@ module Stove
26
49
  log.info("Options: #{options.inspect}")
27
50
  log.info("ARGV: #{@argv.inspect}")
28
51
 
29
- # Unless the user specified --no-bump, version is a required argument, so
30
- # blow up if we don't get it or if it's not a nice version string
31
- if options[:bump]
32
- raise OptionParser::MissingArgument.new(:version) unless options[:version]
33
- end
34
-
35
52
  # Make a new cookbook object - this will raise an exception if there is
36
53
  # no cookbook at the given path
37
54
  cookbook = Cookbook.new(options[:path])
@@ -42,7 +59,8 @@ module Stove
42
59
  end
43
60
 
44
61
  # Now execute the actual runners (validations and errors might occur)
45
- Runner.run(cookbook, options)
62
+ runner = Runner.new(cookbook, options)
63
+ runner.run
46
64
 
47
65
  # If we got this far, everything was successful :)
48
66
  @kernel.exit(0)
@@ -66,55 +84,60 @@ module Stove
66
84
  #
67
85
  def option_parser
68
86
  @option_parser ||= OptionParser.new do |opts|
69
- opts.banner = 'Usage: bake x.y.z'
87
+ opts.banner = 'Usage: stove [OPTIONS]'
70
88
 
71
89
  opts.separator ''
72
- opts.separator 'Actions:'
90
+ opts.separator 'Plugins:'
73
91
 
74
- actions = Action.constants.map(&Action.method(:const_get))
75
- actions.select(&:id).each do |action|
76
- opts.on("--[no-]#{action.id}", action.description) do |v|
77
- options[action.id.to_sym] = v
78
- end
92
+ opts.on('--no-git', 'Do not use the git plugin') do
93
+ options[:no_git] = true
79
94
  end
80
95
 
81
96
  opts.separator ''
82
- opts.separator 'Plugins:'
97
+ opts.separator 'Upload Options:'
83
98
 
84
- plugins = Plugin.constants.map(&Plugin.method(:const_get))
85
- plugins.select(&:id).each do |plugin|
86
- opts.on("--[no-]#{plugin.id}", plugin.description) do |v|
87
- options[plugin.id.to_sym] = v
88
- end
99
+ opts.on('--endpoint [URL]', 'Upload URL endpoint') do |v|
100
+ options[:endpoint] = v
89
101
  end
90
102
 
91
- opts.separator ''
92
- opts.separator 'Global Options:'
93
-
94
- opts.on('--locale [LANGUAGE]', 'Change the language to output messages') do |locale|
95
- I18n.locale = locale
103
+ opts.on('--username [USERNAME]', 'Username to authenticate with') do |v|
104
+ options[:username] = v
96
105
  end
97
106
 
98
- opts.on('--log-level [LEVEL]', 'Set the log verbosity') do |v|
99
- options[:log_level] = v
107
+ opts.on('--key [PATH]', 'Path to the private key on disk') do |v|
108
+ options[:key] = v
100
109
  end
101
110
 
102
- opts.on('--category [CATEGORY]', 'Set category for the cookbook') do |v|
111
+ opts.on('--category [CATEGORY]', 'Category for the cookbook') do |v|
103
112
  options[:category] = v
104
113
  end
105
114
 
106
- opts.on('--path [PATH]', 'Change the path to a cookbook') do |v|
107
- options[:path] = v
108
- end
115
+ opts.separator ''
116
+ opts.separator 'Git Options:'
109
117
 
110
- opts.on('--remote [REMOTE]', 'The name of the git remote to push to') do |v|
118
+ opts.on('--remote [REMOTE]', 'Name of the git remote') do |v|
111
119
  options[:remote] = v
112
120
  end
113
121
 
114
- opts.on('--branch [BRANCH]', 'The name of the git branch to push to') do |v|
122
+ opts.on('--branch [BRANCH]', 'Name of the git branch') do |v|
115
123
  options[:branch] = v
116
124
  end
117
125
 
126
+ opts.on('--sign', 'Sign git tags') do
127
+ options[:sign] = true
128
+ end
129
+
130
+ opts.separator ''
131
+ opts.separator 'Global Options:'
132
+
133
+ opts.on('--log-level [LEVEL]', 'Set the log verbosity') do |v|
134
+ options[:log_level] = v
135
+ end
136
+
137
+ opts.on('--path [PATH]', 'Change the path to a cookbook') do |v|
138
+ options[:path] = v
139
+ end
140
+
118
141
  opts.on_tail('-h', '--help', 'Show this message') do
119
142
  puts opts
120
143
  exit
@@ -132,26 +155,22 @@ module Stove
132
155
  #
133
156
  # @return [Hash]
134
157
  def options
135
- @options ||= Hash.new(default_value).tap do |h|
136
- h[:path] = Dir.pwd
137
- h[:log_level] = :warn
138
-
139
- # Default actions/plugins
140
- h[:jira] = false
141
- h[:start] = true
142
- h[:finish] = true
143
-
144
- h[:remote] = 'origin'
145
- h[:branch] = 'master'
146
- end
147
- end
148
-
149
- def default_value
150
- @default_value ||= if ENV['CLI_DEFAULT']
151
- !!(ENV['CLI_DEFAULT'] =~ /^(true|t|yes|y|1)$/i)
152
- else
153
- true
154
- end
158
+ @options ||= {
159
+ # Upload options
160
+ :endpoint => nil,
161
+ :username => Config.username,
162
+ :key => Config.key,
163
+ :category => nil,
164
+
165
+ # Git options
166
+ :remote => 'origin',
167
+ :branch => 'master',
168
+ :sign => false,
169
+
170
+ # Global options
171
+ :log_level => :warn,
172
+ :path => Dir.pwd,
173
+ }
155
174
  end
156
175
  end
157
176
  end
@@ -1,14 +1,16 @@
1
- require 'faraday'
2
- require 'faraday_middleware'
1
+ require 'chef-api'
3
2
 
4
3
  module Stove
5
4
  class Community
6
5
  include Mixin::Instanceable
7
6
  include Mixin::Optionable
8
- include Logify
9
7
 
10
- option :base_url,
11
- ENV['COMMUNITY_URL'] || 'https://cookbooks.opscode.com/api/v1'
8
+ #
9
+ # The default endpoint where the community site lives.
10
+ #
11
+ # @return [String]
12
+ #
13
+ DEFAULT_ENDPOINT = 'https://supermarket.getchef.com/api/v1'
12
14
 
13
15
  #
14
16
  # Get and cache a community cookbook's JSON response from the given name
@@ -36,9 +38,9 @@ module Stove
36
38
  #
37
39
  def cookbook(name, version = nil)
38
40
  if version.nil?
39
- connection.get("cookbooks/#{name}").body
41
+ connection.get("cookbooks/#{name}")
40
42
  else
41
- connection.get("cookbooks/#{name}/versions/#{Util.version_for_url(version)}").body
43
+ connection.get("cookbooks/#{name}/versions/#{Util.version_for_url(version)}")
42
44
  end
43
45
  end
44
46
 
@@ -50,75 +52,22 @@ module Stove
50
52
  #
51
53
  def upload(cookbook)
52
54
  connection.post('cookbooks', {
53
- tarball: Faraday::UploadIO.new(cookbook.tarball, 'application/x-tar'),
54
- cookbook: { category: cookbook.category }.to_json,
55
+ 'tarball' => File.open(cookbook.tarball, 'rb'),
56
+ 'cookbook' => { 'category' => cookbook.category }.to_json,
55
57
  })
56
58
  end
57
59
 
58
60
  private
59
61
 
60
62
  #
61
- # The Faraday connection object with lots of pretty middleware.
63
+ # The ChefAPI connection object with lots of pretty middleware.
62
64
  #
63
65
  def connection
64
- @connection ||= Faraday.new(base_url) do |builder|
65
- # Enable multi-part requests (for uploading)
66
- builder.request :multipart
67
- builder.request :url_encoded
68
-
69
- # Encode request bodies as JSON
70
- builder.request :json
71
-
72
- # Add Mixlib authentication headers
73
- builder.use Stove::Middleware::ChefAuthentication, client, key
74
-
75
- # Handle any common errors
76
- builder.use Stove::Middleware::Exceptions
77
-
78
- # Decode responses as JSON if the Content-Type is json
79
- builder.response :json
80
- builder.response :json_fix
81
-
82
- # Allow up to 3 redirects
83
- builder.response :follow_redirects, limit: 3
84
-
85
- # Log all requests and responses (useful for development)
86
- builder.response :logger, log
87
-
88
- # Raise errors on 40x and 50x responses
89
- builder.response :raise_error
90
-
91
- # Use the default adapter (Net::HTTP)
92
- builder.adapter :net_http
93
-
94
- # Set the User-Agent header for logging purposes
95
- builder.headers[:user_agent] = Stove::USER_AGENT
96
-
97
- # Set some options, such as timeouts
98
- builder.options[:timeout] = 30
99
- builder.options[:open_timeout] = 30
66
+ @connection ||= ChefAPI::Connection.new do |conn|
67
+ conn.endpoint = ENV['STOVE_ENDPOINT'] || Config.endpoint || DEFAULT_ENDPOINT
68
+ conn.client = ENV['STOVE_USERNAME'] || Config.username
69
+ conn.key = ENV['STOVE_KEY'] || Config.key
100
70
  end
101
71
  end
102
-
103
- #
104
- # The name of the client to use (by default, this is the username).
105
- #
106
- # @return [String]
107
- #
108
- def client
109
- Config[:community][:username]
110
- end
111
-
112
- #
113
- # The path to the key on disk for authentication with the community site.
114
- # If a relative path is given, it is expanded relative to the configuration
115
- # file on disk.
116
- #
117
- # @return [String]
118
- # the path to the key on disk
119
- #
120
- def key
121
- File.expand_path(Config[:community][:key], Config.__path__)
122
- end
123
72
  end
124
73
  end
data/lib/stove/config.rb CHANGED
@@ -1,67 +1,76 @@
1
+ require 'fileutils'
1
2
  require 'json'
2
3
 
3
4
  module Stove
4
5
  class Config
5
- include Mixin::Instanceable
6
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
7
16
 
8
- #
9
- # Create a new configuration object. If a configuration file does not
10
- # exist, this method will output a warning to the UI and use an empty
11
- # hash as the data structure.
12
- #
13
- def initialize
14
- log.debug("Reading from config at `#{__path__}'")
17
+ def respond_to_missing?(m, include_private = false)
18
+ __has__?(m) || super
19
+ end
15
20
 
16
- contents = File.read(__path__)
17
- data = JSON.parse(contents, symbolize_names: true)
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
18
27
 
19
- log.debug("Config:\n#{JSON.pretty_generate(sanitize(data))}")
28
+ def to_s
29
+ "#<#{self.class.name} #{__raw__.to_s}>"
30
+ end
20
31
 
21
- @data = data
22
- rescue Errno::ENOENT
23
- log.warn(<<-EOH.gsub(/^ {8}/, ''))
24
- No Stove configuration file found at `#{__path__}'. Stove will assume an
25
- empty configuration, which may cause problems with some plugins. It is
26
- recommended that you create a Stove configuration file as documented:
32
+ def inspect
33
+ "#<#{self.class.name} #{__raw__.inspect}>"
34
+ end
27
35
 
28
- https://github.com/sethvargo/stove#installation
29
- EOH
36
+ def __get__(key)
37
+ __raw__[key.to_sym]
38
+ end
30
39
 
31
- @data = {}
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)
32
50
  end
33
51
 
34
- #
35
- # This is a special key that tells me where stove lives. If you actually
36
- # have a key in your config called +__path__+, then it sucks to be you.
37
- #
38
- # @return [String]
39
- #
40
52
  def __path__
41
53
  @path ||= File.expand_path(ENV['STOVE_CONFIG'] || '~/.stove')
42
54
  end
43
55
 
44
- #
45
- # Deletegate all method calls to the underlyng hash.
46
- #
47
- def method_missing(m, *args, &block)
48
- @data.send(m, *args, &block)
49
- end
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
50
69
 
51
- private
52
-
53
- def sanitize(data)
54
- Hash[*data.map do |key, value|
55
- if value.is_a?(Hash)
56
- [key, sanitize(value)]
57
- else
58
- if key =~ /access|token|password/
59
- [key, '[FILTERED]']
60
- else
61
- [key, value]
62
- end
63
- end
64
- end.flatten(1)]
70
+ @__raw__
71
+ rescue Errno::ENOENT => e
72
+ log.warn { "No config file found at `#{__path__}'!" }
73
+ @__raw__ = {}
65
74
  end
66
75
  end
67
76
  end
@@ -1,5 +1,4 @@
1
1
  require 'json'
2
- require 'solve'
3
2
 
4
3
  module Stove
5
4
  class Cookbook
@@ -41,7 +40,7 @@ module Stove
41
40
  class_eval <<-EOM, __FILE__, __LINE__ + 1
42
41
  def #{field}(thing, *args)
43
42
  version = args.first
44
- @#{instance_variable}[thing] = Solve::Constraint.new(version).to_s
43
+ @#{instance_variable}[thing] = version.to_s
45
44
  @#{instance_variable}[thing]
46
45
  end
47
46
  EOM
@@ -146,10 +145,9 @@ module Stove
146
145
 
147
146
  def version(arg = UNSET_VALUE)
148
147
  if arg == UNSET_VALUE
149
- @version.to_s
148
+ @version
150
149
  else
151
- @version = Solve::Version.new(arg)
152
- @version.to_s
150
+ @version = arg.to_s
153
151
  end
154
152
  end
155
153
 
@@ -72,20 +72,11 @@ module Stove
72
72
  #
73
73
  def category
74
74
  @category ||= Community.cookbook(name)['category']
75
- rescue Faraday::Error::ResourceNotFound
75
+ rescue ChefAPI::Error::HTTPError
76
76
  log.warn("Cookbook `#{name}' not found on the Chef community site")
77
77
  nil
78
78
  end
79
79
 
80
- #
81
- # The URL for the cookbook on the Community Site.
82
- #
83
- # @return [String]
84
- #
85
- def url
86
- URI.join(Community.base_url, 'cookbooks', name)
87
- end
88
-
89
80
  #
90
81
  # The tag version. This is just the current version prefixed with the
91
82
  # letter "v".
@@ -113,18 +104,10 @@ module Stove
113
104
  def released?
114
105
  Community.cookbook(name, version)
115
106
  true
116
- rescue Faraday::Error::ResourceNotFound
107
+ rescue ChefAPI::Error::HTTPNotFound
117
108
  false
118
109
  end
119
110
 
120
- #
121
- def release!
122
- if options[:changelog]
123
- log.info('Updating changelog')
124
- update_changelog
125
- end
126
- end
127
-
128
111
  #
129
112
  # So there's this really really crazy bug that the tmp directory could
130
113
  # be deleted mid-request...
@@ -140,28 +123,6 @@ module Stove
140
123
  @tarball
141
124
  end
142
125
 
143
- #
144
- # Bump the version in the metdata.rb to the specified
145
- # parameter.
146
- #
147
- # @param [String] new_version
148
- # the version to bump to
149
- #
150
- # @return [String]
151
- # the new version string
152
- #
153
- def bump(new_version)
154
- return true if new_version.to_s == version.to_s
155
-
156
- metadata_path = path.join('metadata.rb')
157
- contents = File.read(metadata_path)
158
-
159
- contents.sub!(/^version(\s+)('|")#{version}('|")/, "version\\1\\2#{new_version}\\3")
160
-
161
- File.open(metadata_path, 'w') { |f| f.write(contents) }
162
- reload_metadata!
163
- end
164
-
165
126
  private
166
127
  # Load the metadata and set the @metadata instance variable.
167
128
  #
data/lib/stove/error.rb CHANGED
@@ -1,24 +1,53 @@
1
+ require 'erb'
2
+
1
3
  module Stove
2
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
+
3
17
  class StoveError < StandardError
4
18
  def initialize(options = {})
5
- return super(options[:_message]) if options[:_message]
19
+ @options = options
20
+ @filename = options.delete(:_template)
6
21
 
7
- class_name = self.class.to_s.split('::').last
8
- error_key = Util.underscore(class_name)
22
+ super()
23
+ end
9
24
 
10
- super I18n.t("stove.errors.#{error_key}", options)
25
+ def message
26
+ erb = ERB.new(File.read(template))
27
+ erb.result(ErrorBinding.new(@options).get_binding)
11
28
  end
12
- end
29
+ alias_method :to_s, :message
13
30
 
14
- class ValidationFailed < StoveError
15
- def initialize(klass, id, options = {})
16
- super _message: I18n.t("stove.validations.#{klass}.#{id}", options)
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")
17
37
  end
18
38
  end
19
39
 
20
40
  class GitFailed < StoveError; end
21
41
  class MetadataNotFound < StoveError; end
22
42
  class ServerUnavailable < StoveError; end
43
+
44
+ # Validations
45
+ class ValidationFailed < StoveError; end
46
+ class CommunityCategoryValidationFailed < ValidationFailed; end
47
+ class CommunityKeyValidationFailed < ValidationFailed; end
48
+ class CommunityUsernameValidationFailed < ValidationFailed; end
49
+ class GitCleanValidationFailed < ValidationFailed; end
50
+ class GitRepositoryValidationFailed < ValidationFailed; end
51
+ class GitUpToDateValidationFailed < ValidationFailed; end
23
52
  end
24
53
  end
data/lib/stove/filter.rb CHANGED
@@ -1,8 +1,9 @@
1
1
  module Stove
2
2
  class Filter
3
- include Mixin::Insideable
4
3
  include Logify
5
4
 
5
+ include Mixin::Insideable
6
+
6
7
  #
7
8
  # The class that created this filter.
8
9
  #
@@ -4,17 +4,18 @@ module Stove
4
4
  module Mixin::Instanceable
5
5
  def self.included(base)
6
6
  base.send(:include, Singleton)
7
- base.send(:undef_method, :inspect, :to_s)
8
7
  base.send(:extend, ClassMethods)
9
8
  end
10
9
 
11
10
  def self.extended(base)
12
11
  base.send(:include, Singleton)
13
- base.send(:undef_method, :inspect, :to_s)
14
12
  base.send(:extend, ClassMethods)
15
13
  end
16
14
 
17
15
  module ClassMethods
16
+ def to_s; instance.to_s; end
17
+ def inspect; instance.inspect; end
18
+
18
19
  def method_missing(m, *args, &block)
19
20
  instance.send(m, *args, &block)
20
21
  end
@@ -1,7 +1,11 @@
1
1
  module Stove
2
2
  module Mixin::Validatable
3
3
  def validate(id, &block)
4
- Runner.validations << Validator.new(self, id, &block)
4
+ validations[id] = Validator.new(self, id, &block)
5
+ end
6
+
7
+ def validations
8
+ @validations ||= {}
5
9
  end
6
10
  end
7
11
  end
@@ -18,7 +18,14 @@ module Stove
18
18
  'recipes',
19
19
  'resources',
20
20
  'templates',
21
- ]
21
+ ].freeze
22
+
23
+ ACCEPTABLE_FILES_LIST = ACCEPTABLE_FILES.join(',').freeze
24
+
25
+ TMP_FILES = [
26
+ /^(?:.*[\\\/])?\.[^\\\/]+\.sw[p-z]$/,
27
+ /~$/,
28
+ ].freeze
22
29
 
23
30
  # The cookbook to package.
24
31
  #
@@ -38,7 +45,8 @@ module Stove
38
45
  # @return [Array]
39
46
  # the array of file paths
40
47
  def cookbook_files
41
- Dir.glob("#{File.expand_path(cookbook.path)}/{#{ACCEPTABLE_FILES.join(',')}}")
48
+ path = File.expand_path("#{cookbook.path}/{#{ACCEPTABLE_FILES_LIST}}")
49
+ Dir[path].reject { |f| TMP_FILES.any? { |regex| f.match(regex) } }
42
50
  end
43
51
 
44
52
  # The path to the tar.gz package in the temporary directory.
@@ -51,7 +59,7 @@ module Stove
51
59
  private
52
60
 
53
61
  def pack!
54
- destination = Tempfile.new(cookbook.name).path
62
+ destination = Tempfile.new([cookbook.name, '.tar.gz']).path
55
63
 
56
64
  # Sandbox
57
65
  sandbox = Dir.mktmpdir