stove 2.0.0 → 3.0.0

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