stove 1.1.2 → 2.0.0.beta.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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -2
  3. data/CHANGELOG.md +16 -0
  4. data/README.md +41 -29
  5. data/Rakefile +15 -0
  6. data/bin/bake +1 -2
  7. data/features/actions/bump.feature +22 -0
  8. data/features/actions/changelog.feature +45 -0
  9. data/features/actions/dev.feature +18 -0
  10. data/features/actions/upload.feature +48 -0
  11. data/features/plugins/git.feature +24 -0
  12. data/features/rake.feature +1 -2
  13. data/features/step_definitions/cli_steps.rb +1 -27
  14. data/features/step_definitions/{community_site_steps.rb → community_steps.rb} +9 -5
  15. data/features/step_definitions/config_steps.rb +24 -0
  16. data/features/step_definitions/cookbook_steps.rb +28 -6
  17. data/features/step_definitions/cucumber_steps.rb +12 -0
  18. data/features/step_definitions/git_steps.rb +10 -7
  19. data/features/support/env.rb +12 -28
  20. data/features/support/stove/git.rb +48 -0
  21. data/lib/stove.rb +102 -19
  22. data/lib/stove/actions/base.rb +21 -0
  23. data/lib/stove/actions/bump.rb +25 -0
  24. data/lib/stove/actions/changelog.rb +71 -0
  25. data/lib/stove/actions/dev.rb +22 -0
  26. data/lib/stove/actions/finish.rb +8 -0
  27. data/lib/stove/actions/start.rb +7 -0
  28. data/lib/stove/actions/upload.rb +27 -0
  29. data/lib/stove/cli.rb +107 -79
  30. data/lib/stove/community.rb +124 -0
  31. data/lib/stove/config.rb +62 -13
  32. data/lib/stove/cookbook.rb +76 -238
  33. data/lib/stove/cookbook/metadata.rb +16 -11
  34. data/lib/stove/error.rb +13 -107
  35. data/lib/stove/filter.rb +59 -0
  36. data/lib/stove/jira.rb +74 -30
  37. data/lib/stove/middlewares/chef_authentication.rb +60 -0
  38. data/lib/stove/middlewares/exceptions.rb +17 -0
  39. data/lib/stove/mixins/filterable.rb +11 -0
  40. data/lib/stove/mixins/insideable.rb +13 -0
  41. data/lib/stove/mixins/instanceable.rb +23 -0
  42. data/lib/stove/mixins/loggable.rb +32 -0
  43. data/lib/stove/mixins/optionable.rb +41 -0
  44. data/lib/stove/mixins/validatable.rb +7 -0
  45. data/lib/stove/packager.rb +23 -22
  46. data/lib/stove/plugins/base.rb +35 -0
  47. data/lib/stove/plugins/git.rb +71 -0
  48. data/lib/stove/plugins/github.rb +108 -0
  49. data/lib/stove/plugins/jira.rb +72 -0
  50. data/lib/stove/rake_task.rb +56 -37
  51. data/lib/stove/runner.rb +84 -0
  52. data/lib/stove/util.rb +56 -0
  53. data/lib/stove/validator.rb +67 -0
  54. data/lib/stove/version.rb +1 -1
  55. data/locales/en.yml +231 -0
  56. data/stove.gemspec +11 -11
  57. metadata +85 -67
  58. data/features/changelog.feature +0 -22
  59. data/features/cli.feature +0 -11
  60. data/features/devodd.feature +0 -19
  61. data/features/git.feature +0 -34
  62. data/features/upload.feature +0 -40
  63. data/lib/stove/community_site.rb +0 -85
  64. data/lib/stove/formatter.rb +0 -7
  65. data/lib/stove/formatter/base.rb +0 -32
  66. data/lib/stove/formatter/human.rb +0 -9
  67. data/lib/stove/formatter/silent.rb +0 -10
  68. data/lib/stove/git.rb +0 -82
  69. data/lib/stove/github.rb +0 -43
  70. data/lib/stove/logger.rb +0 -56
  71. data/lib/stove/uploader.rb +0 -64
  72. data/spec/support/community_site.rb +0 -33
  73. data/spec/support/git.rb +0 -52
@@ -134,7 +134,7 @@ module Stove
134
134
  self.instance_eval(IO.read(path), path, 1)
135
135
  self
136
136
  else
137
- raise Stove::MetadataNotFound.new(path)
137
+ raise Error::MetadataNotFound.new(path: path)
138
138
  end
139
139
  end
140
140
 
@@ -144,9 +144,13 @@ module Stove
144
144
  end
145
145
  end
146
146
 
147
- def version(arg = nil)
148
- @version = Solve::Version.new(arg) if arg
149
- @version.to_s
147
+ def version(arg = UNSET_VALUE)
148
+ if arg == UNSET_VALUE
149
+ @version.to_s
150
+ else
151
+ @version = Solve::Version.new(arg)
152
+ @version.to_s
153
+ end
150
154
  end
151
155
 
152
156
  def to_hash
@@ -176,15 +180,16 @@ module Stove
176
180
  end
177
181
 
178
182
  private
179
- def set_or_return(symbol, arg)
180
- iv_symbol = "@#{symbol.to_s}".to_sym
181
183
 
182
- if arg.nil? && self.instance_variable_defined?(iv_symbol)
183
- self.instance_variable_get(iv_symbol)
184
- else
185
- self.instance_variable_set(iv_symbol, arg)
186
- end
184
+ def set_or_return(symbol, arg)
185
+ iv_symbol = "@#{symbol.to_s}".to_sym
186
+
187
+ if arg.nil? && self.instance_variable_defined?(iv_symbol)
188
+ self.instance_variable_get(iv_symbol)
189
+ else
190
+ self.instance_variable_set(iv_symbol, arg)
187
191
  end
192
+ end
188
193
  end
189
194
  end
190
195
  end
data/lib/stove/error.rb CHANGED
@@ -1,118 +1,24 @@
1
1
  module Stove
2
- class Error < StandardError
3
- class << self
4
- def set_exit_code(code)
5
- define_method(:exit_code) { code }
6
- define_singleton_method(:exit_code) { code }
7
- end
8
- end
9
-
10
- set_exit_code 100
11
- end
12
-
13
- class InvalidVersionError < Error
14
- set_exit_code 101
15
-
16
- def message
17
- 'You must specify a valid version!'
18
- end
19
- end
20
-
21
- class MetadataNotFound < Error
22
- set_exit_code 102
2
+ module Error
3
+ class StoveError < StandardError
4
+ def initialize(options = {})
5
+ return super(options[:_message]) if options[:_message]
23
6
 
24
- def initialize(filepath)
25
- @filepath = File.expand_path(filepath) rescue filepath
26
- end
7
+ class_name = self.class.to_s.split('::').last
8
+ error_key = Util.underscore(class_name)
27
9
 
28
- def message
29
- "No metadata.rb found at: '#{@filepath}'"
30
- end
31
- end
32
-
33
- class CookbookCategoryNotFound < Error
34
- set_exit_code 110
35
-
36
- def message
37
- 'The cookbook\'s category could not be inferred from the community site. ' <<
38
- 'If this is a new cookbook, you must specify the category with the ' <<
39
- '--category flag.'
40
- end
41
- end
42
-
43
- class UserCanceledError < Error
44
- set_exit_code 120
45
-
46
- def message
47
- 'Action canceled by user!'
48
- end
49
- end
50
-
51
- class GitError < Error
52
- set_exit_code 130
53
-
54
- def message
55
- 'Git Error: ' + super
56
- end
57
-
58
- class NotARepo < GitError
59
- set_exit_code 131
60
-
61
- def message
62
- 'Not a git repo!'
10
+ super I18n.t("stove.errors.#{error_key}", options)
63
11
  end
64
12
  end
65
13
 
66
- class DirtyRepo < GitError
67
- set_exit_code 132
68
-
69
- def message
70
- 'You have untracked files!'
14
+ class ValidationFailed < StoveError
15
+ def initialize(klass, id, options = {})
16
+ super _message: I18n.t("stove.validations.#{klass}.#{id}", options)
71
17
  end
72
18
  end
73
19
 
74
- class OutOfSync < GitError
75
- set_exit_code 133
76
-
77
- def message
78
- 'Your remote repository is out of sync!'
79
- end
80
- end
81
- end
82
-
83
- class UploadError < Error
84
- set_exit_code 140
85
-
86
- def initialize(response)
87
- @response = response
88
- end
89
-
90
- def message
91
- "The following errors occured when uploading:\n" <<
92
- (@response.parsed_response['error_messages'] || []).map do |error|
93
- " - #{error}"
94
- end.join("\n")
95
- end
96
- end
97
-
98
- class BadResponse < Error
99
- set_exit_code 150
100
-
101
- def initialize(response)
102
- @response = response
103
- end
104
-
105
- def message
106
- "The following errors occured when making the request:\n" <<
107
- @response.parsed_response
108
- end
109
- end
110
-
111
- class AbstractFunction < Error
112
- set_exit_code 160
113
- end
114
-
115
- class InvalidChangelogFormat < Error
116
- set_exit_code 170
20
+ class GitFailed < StoveError; end
21
+ class MetadataNotFound < StoveError; end
22
+ class ServerUnavailable < StoveError; end
117
23
  end
118
24
  end
@@ -0,0 +1,59 @@
1
+ module Stove
2
+ class Filter
3
+ include Mixin::Insideable
4
+ include Mixin::Loggable
5
+
6
+ #
7
+ # The class that created this filter.
8
+ #
9
+ # @return [~Plugin::Base]
10
+ #
11
+ attr_reader :klass
12
+
13
+ #
14
+ # The message given by the filter.
15
+ #
16
+ # @return [String]
17
+ #
18
+ attr_reader :message
19
+
20
+ #
21
+ # The block captured by the filter.
22
+ #
23
+ # @return [Proc]
24
+ #
25
+ attr_reader :block
26
+
27
+ #
28
+ # Create a new filter object.
29
+ #
30
+ # @param [~Plugin::Base] klass
31
+ # the class that created this filter
32
+ # @param [String] message
33
+ # the message given by the filter
34
+ # @param [Proc] block
35
+ # the block captured by this filter
36
+ #
37
+ def initialize(klass, message, &block)
38
+ @klass = klass
39
+ @message = message
40
+ @block = block
41
+ end
42
+
43
+ #
44
+ # Execute this filter in the context of the creating class, inside the
45
+ # given cookbook's path.
46
+ #
47
+ # @param [Cookbook]
48
+ # the cookbook to run this filter against
49
+ #
50
+ def run(cookbook, options = {})
51
+ log.info(message)
52
+ instance = klass.new(cookbook, options)
53
+
54
+ inside(cookbook) do
55
+ instance.instance_eval(&block)
56
+ end
57
+ end
58
+ end
59
+ end
data/lib/stove/jira.rb CHANGED
@@ -1,43 +1,87 @@
1
- require 'jiralicious'
2
- require 'json'
1
+ require 'faraday'
2
+ require 'faraday_middleware'
3
3
 
4
4
  module Stove
5
5
  class JIRA
6
- JIRA_URL = 'https://tickets.opscode.com'
6
+ include Mixin::Instanceable
7
+ include Mixin::Loggable
8
+ include Mixin::Optionable
7
9
 
8
- Jiralicious.configure do |config|
9
- config.username = Stove::Config['jira_username']
10
- config.password = Stove::Config['jira_password']
11
- config.uri = JIRA_URL
10
+ option :base_url,
11
+ ENV['JIRA_URL'] || 'https://tickets.opscode.com/rest/api/2'
12
+
13
+ def issue(key, options = {})
14
+ connection.get("issue/#{key}", options).body
15
+ end
16
+
17
+ def search(query = {})
18
+ jql = query.map { |k,v| %Q|#{k} = "#{v}"| }.join(' AND ')
19
+ connection.get('search', jql: jql).body
12
20
  end
13
21
 
14
- class << self
15
- def unreleased_tickets_for(component)
16
- jql = [
17
- 'project = COOK',
18
- 'resolution = Fixed',
19
- 'status = "Fix Committed"',
20
- 'component = ' + component.inspect
21
- ].join(' AND ')
22
- Stove::Logger.debug "JQL: #{jql.inspect}"
23
-
24
- Jiralicious.search(jql).issues
22
+ def close_and_comment(key, comment)
23
+ transitions = issue(key, expand: 'transitions')['transitions']
24
+ close = transitions.first { |transition| transition['name'] == 'Close' }
25
+
26
+ if close.nil?
27
+ log.warn("Issue #{key} does not have a `Close' transition")
28
+ return
25
29
  end
26
30
 
27
- # Comment and close a particular issue.
28
- #
29
- # @param [Jiralicious::Issue] ticket
30
- # the JIRA ticket
31
- # @param [Stove::Cookbook] cookbook
32
- # the cookbook to release
33
- def comment_and_close(ticket, cookbook)
34
- comment = "Released in [#{cookbook.version}|#{cookbook.url}]"
31
+ connection.post("issue/#{key}/transitions", {
32
+ transition: { id: close['id'] },
33
+ update: {
34
+ comment: [
35
+ { add: { body: comment.to_s } }
36
+ ]
37
+ },
38
+ fields: {
39
+ resolution: {
40
+ name: 'Fixed'
41
+ },
42
+ assignee: {
43
+ name: nil
44
+ }
45
+ }
46
+ })
47
+ end
48
+
49
+ private
50
+
51
+ def connection
52
+ @connection ||= Faraday.new(base_url) do |builder|
53
+ # Encode request bodies as JSON
54
+ builder.request :json
55
+
56
+ # Add basic authentication information
57
+ builder.request :basic_auth, Stove::Config[:jira][:username],
58
+ Stove::Config[:jira][:password]
59
+
60
+ # Handle any common errors
61
+ builder.use Stove::Middleware::Exceptions
62
+
63
+ # Decode responses as JSON if the Content-Type is json
64
+ builder.response :json
65
+ builder.response :json_fix
66
+
67
+ # Allow up to 3 redirects
68
+ builder.response :follow_redirects, limit: 3
69
+
70
+ # Log all requests and responses (useful for development)
71
+ builder.response :logger, log
72
+
73
+ # Raise errors on 40x and 50x responses
74
+ builder.response :raise_error
75
+
76
+ # Use the default adapter (Net::HTTP)
77
+ builder.adapter :net_http
35
78
 
36
- transition = Jiralicious::Issue::Transitions.find(ticket.jira_key).find do |key, value|
37
- !value.is_a?(String) && value.name == 'Close'
38
- end.last
79
+ # Set the User-Agent header for logging purposes
80
+ builder.headers[:user_agent] = Stove::USER_AGENT
39
81
 
40
- Jiralicious::Issue::Transitions.go(ticket.jira_key, transition.id, { comment: comment })
82
+ # Set some options, such as timeouts
83
+ builder.options[:timeout] = 30
84
+ builder.options[:open_timeout] = 30
41
85
  end
42
86
  end
43
87
  end
@@ -0,0 +1,60 @@
1
+ require 'pp'
2
+
3
+ module Stove
4
+ class Middleware::ChefAuthentication < Faraday::Middleware
5
+ dependency do
6
+ require 'mixlib/authentication/signedheaderauth'
7
+ require 'openssl'
8
+ require 'uri'
9
+ end
10
+
11
+ #
12
+ # @param [Faraday::Application] app
13
+ # @param [String] client
14
+ # the name of the client to use for Chef
15
+ # @param [OpenSSL::PKey::RSA] key
16
+ # the RSA private key to sign with
17
+ #
18
+ def initialize(app, client, key)
19
+ super(app)
20
+
21
+ @client = client
22
+ @key = OpenSSL::PKey::RSA.new(File.read(key))
23
+ end
24
+
25
+ def call(env)
26
+ env[:request_headers].merge!(signing_object(env))
27
+ @app.call(env)
28
+ end
29
+
30
+ private
31
+
32
+ def signing_object(env)
33
+ params = {
34
+ :http_method => env[:method],
35
+ :timestamp => Time.now.utc.iso8601,
36
+ :user_id => @client,
37
+ :path => env[:url].path,
38
+ :body => env[:body] || '',
39
+ }
40
+
41
+ # Royal fucking hack
42
+ # 1. (n.) This code sample
43
+ # 2. (v.) Having to decompose a Faraday response because Mixlib
44
+ # Authentication couldn't get a date to the prom
45
+ if env[:body] && env[:body].is_a?(Faraday::CompositeReadIO)
46
+ file = env[:body]
47
+ .instance_variable_get(:@parts)
48
+ .first { |part| part.is_a?(Faraday::Parts::FilePart) }
49
+ .instance_variable_get(:@io)
50
+ .instance_variable_get(:@ios)[1]
51
+ .instance_variable_get(:@local_path)
52
+
53
+ params[:file] = File.new(file)
54
+ end
55
+
56
+ object = Mixlib::Authentication::SignedHeaderAuth.signing_object(params)
57
+ object.sign(@key)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,17 @@
1
+ module Stove
2
+ class Middleware::Exceptions < Faraday::Middleware
3
+ include Mixin::Loggable
4
+
5
+ def call(env)
6
+ begin
7
+ @app.call(env)
8
+ rescue Faraday::Error::ConnectionFailed
9
+ url = env[:url].to_s.gsub(env[:url].path, '')
10
+ raise Error::ServerUnavailable.new(url: url)
11
+ rescue Faraday::Error::ClientError => e
12
+ log.debug(env.inspect)
13
+ raise
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ module Stove
2
+ module Mixin::Filterable
3
+ def before(action, message, &block)
4
+ Runner.filters[action][:before] << Filter.new(self, message, &block)
5
+ end
6
+
7
+ def after(action, message, &block)
8
+ Runner.filters[action][:after] << Filter.new(self, message, &block)
9
+ end
10
+ end
11
+ end