stove 1.1.2 → 2.0.0.beta.1

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