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
data/lib/stove/cli.rb CHANGED
@@ -3,122 +3,150 @@ require 'stove'
3
3
 
4
4
  module Stove
5
5
  class Cli
6
+ include Mixin::Loggable
7
+
6
8
  def initialize(argv, stdin=STDIN, stdout=STDOUT, stderr=STDERR, kernel=Kernel)
7
9
  @argv, @stdin, @stdout, @stderr, @kernel = argv, stdin, stdout, stderr, kernel
8
- $stdout, @stderr = @stdout, @stderr
9
10
  end
10
11
 
11
12
  def execute!
13
+ $stdout, $stderr = @stdout, @stderr
14
+
15
+ # Parse the options hash
12
16
  option_parser.parse!(@argv)
13
- options[:new_version] = @argv.first
14
17
 
15
- raise Stove::InvalidVersionError unless valid_version?(options[:new_version])
18
+ # Set the log level
19
+ Stove.log_level = options[:log_level]
16
20
 
17
- Stove::Logger.set_level(options.delete(:log_level))
21
+ # Parse out the version from ARGV
22
+ options[:version] = @argv.shift
18
23
 
19
- Stove::Cookbook.new(options).release!
20
- @kernel.exit(0)
21
- rescue => e
22
- @stderr.puts "#{e.class}: #{e.message}"
24
+ # Useful debugging output for when people type the wrong fucking command
25
+ # and then open an issue like it's somehow my fault
26
+ log.info("Options: #{options.inspect}")
27
+ log.info("ARGV: #{@argv.inspect}")
23
28
 
24
- if Stove::Logger.sev_threshold == ::Logger::DEBUG
25
- @stderr.puts " #{e.backtrace.join("\n ")}"
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]
26
33
  end
27
34
 
35
+ # Make a new cookbook object - this will raise an exception if there is
36
+ # no cookbook at the given path
37
+ cookbook = Cookbook.new(options[:path])
38
+
39
+ # Now execute the actual runners (validations and errors might occur)
40
+ Runner.run(cookbook, options)
41
+
42
+ # If we got this far, everything was successful :)
43
+ @kernel.exit(0)
44
+ rescue => e
45
+ log.error('Stove experienced an error!')
46
+ log.error(e.class.name)
47
+ log.error(e.message)
48
+ log.error(e.backtrace.join("\n"))
49
+
28
50
  @kernel.exit(e.respond_to?(:exit_code) ? e.exit_code : 500)
29
51
  ensure
30
52
  $stdout, $stderr = STDOUT, STDERR
31
53
  end
32
54
 
33
55
  private
34
- # The option parser for handling command line flags.
35
- #
36
- # @return [OptionParser]
37
- def option_parser
38
- @option_parser ||= OptionParser.new do |opts|
39
- opts.banner = "Usage: bake x.y.z"
40
-
41
- opts.on('-l', '--log-level [LEVEL]', [:fatal, :error, :warn, :info, :debug], 'Ruby log level') do |v|
42
- options[:log_level] = v
43
- end
44
56
 
45
- opts.on('-c', '--category [CATEGORY]', String, 'The category for the cookbook (optional for existing cookbooks)') do |v|
46
- options[:category] = v
57
+ #
58
+ # The option parser for handling command line flags.
59
+ #
60
+ # @return [OptionParser]
61
+ #
62
+ def option_parser
63
+ @option_parser ||= OptionParser.new do |opts|
64
+ opts.banner = 'Usage: bake x.y.z'
65
+
66
+ opts.separator ''
67
+ opts.separator 'Actions:'
68
+
69
+ actions = Action.constants.map(&Action.method(:const_get))
70
+ actions.select(&:id).each do |action|
71
+ opts.on("--[no-]#{action.id}", action.description) do |v|
72
+ options[action.id.to_sym] = v
47
73
  end
74
+ end
48
75
 
49
- opts.on('-p', '--path [PATH]', String, 'The path to the cookbook to release (default: PWD)') do |v|
50
- options[:path] = v
51
- end
76
+ opts.separator ''
77
+ opts.separator 'Plugins:'
52
78
 
53
- opts.on('--[no-]git', 'Automatically tag and push to git (default: true)') do |v|
54
- options[:git] = v
79
+ plugins = Plugin.constants.map(&Plugin.method(:const_get))
80
+ plugins.select(&:id).each do |plugin|
81
+ opts.on("--[no-]#{plugin.id}", plugin.description) do |v|
82
+ options[plugin.id.to_sym] = v
55
83
  end
84
+ end
56
85
 
57
- opts.on('--[no-]github', 'Automatically release to GitHub (default: true)') do |v|
58
- options[:git] = v if v
59
- options[:github] = v
60
- end
86
+ opts.separator ''
87
+ opts.separator 'Global Options:'
61
88
 
62
- opts.on('-r', '--remote [REMOTE]', String, 'The name of the git remote to push to') do |v|
63
- options[:remote] = v
64
- end
89
+ opts.on('--locale [LANGUAGE]', 'Change the language to output messages') do |locale|
90
+ I18n.locale = locale
91
+ end
65
92
 
66
- opts.on('-b', '--branch [BRANCH]', String, 'The name of the git branch to push to') do |v|
67
- options[:branch] = v
68
- end
93
+ opts.on('--log-level [LEVEL]', 'Set the log verbosity') do |v|
94
+ options[:log_level] = v
95
+ end
69
96
 
70
- opts.on('--[no-]devodd', 'Automatically bump the metadata for devodd releases') do |v|
71
- options[:devodd] = v
72
- end
97
+ opts.on('--category [CATEGORY]', 'Set category for the cookbook') do |v|
98
+ options[:category] = v
99
+ end
73
100
 
74
- opts.on('--[no-]jira', 'Automatically populate the CHANGELOG from JIRA tickets and close them (default: false)') do |v|
75
- options[:jira] = v
76
- end
101
+ opts.on('--path [PATH]', 'Change the path to a cookbook') do |v|
102
+ options[:path] = v
103
+ end
77
104
 
78
- opts.on('--[no-]upload', 'Upload the cookbook to the Opscode Community Site (default: true)') do |v|
79
- options[:upload] = v
80
- end
105
+ opts.on('--remote [REMOTE]', 'The name of the git remote to push to') do |v|
106
+ options[:remote] = v
107
+ end
81
108
 
82
- opts.on('--[no-]changelog', 'Automatically generate a CHANGELOG (default: true)') do |v|
83
- options[:changelog] = v
84
- end
109
+ opts.on('--branch [BRANCH]', 'The name of the git branch to push to') do |v|
110
+ options[:branch] = v
111
+ end
85
112
 
86
- opts.on_tail('-h', '--help', 'Show this message') do
87
- puts opts
88
- exit
89
- end
113
+ opts.on_tail('-h', '--help', 'Show this message') do
114
+ puts opts
115
+ exit
116
+ end
90
117
 
91
- opts.on_tail('-v', '--version', 'Show version') do
92
- puts Stove::VERSION
93
- exit(0)
94
- end
118
+ opts.on_tail('-v', '--version', 'Show version') do
119
+ puts Stove::VERSION
120
+ exit(0)
95
121
  end
96
122
  end
123
+ end
97
124
 
98
- # The options to pass to the cookbook. Includes default values
99
- # that are manipulated by the option parser.
100
- #
101
- # @return [Hash]
102
- def options
103
- @options ||= {
104
- path: Dir.pwd,
105
- git: true,
106
- github: true,
107
- devodd: false,
108
- remote: 'origin',
109
- branch: 'master',
110
- jira: false,
111
- upload: true,
112
- changelog: true,
113
- log_level: :warn,
114
- }
125
+ # The options to pass to the cookbook. Includes default values
126
+ # that are manipulated by the option parser.
127
+ #
128
+ # @return [Hash]
129
+ def options
130
+ @options ||= Hash.new(default_value).tap do |h|
131
+ h[:path] = Dir.pwd
132
+ h[:log_level] = :warn
133
+
134
+ # Default actions/plugins
135
+ h[:jira] = false
136
+ h[:start] = true
137
+ h[:finish] = true
138
+
139
+ h[:remote] = 'origin'
140
+ h[:branch] = 'master'
115
141
  end
142
+ end
116
143
 
117
- # Determine if the given string is a valid version string.
118
- #
119
- # @return [Boolean]
120
- def valid_version?(version)
121
- version.to_s =~ /^\d+\.\d+\.\d+$/
144
+ def default_value
145
+ @default_value ||= if ENV['CLI_DEFAULT']
146
+ !!(ENV['CLI_DEFAULT'] =~ /^(true|t|yes|y|1)$/i)
147
+ else
148
+ true
122
149
  end
150
+ end
123
151
  end
124
152
  end
@@ -0,0 +1,124 @@
1
+ require 'faraday'
2
+ require 'faraday_middleware'
3
+
4
+ module Stove
5
+ class Community
6
+ include Mixin::Instanceable
7
+ include Mixin::Loggable
8
+ include Mixin::Optionable
9
+
10
+ option :base_url,
11
+ ENV['COMMUNITY_URL'] || 'https://cookbooks.opscode.com/api/v1'
12
+
13
+ #
14
+ # Get and cache a community cookbook's JSON response from the given name
15
+ # and version.
16
+ #
17
+ # @example Find a cookbook by name
18
+ # Community.cookbook('apache2') #=> {...}
19
+ #
20
+ # @example Find a cookbook by name and version
21
+ # Community.cookbook('apache2', '1.0.0') #=> {...}
22
+ #
23
+ # @example Find a non-existent cookbook
24
+ # Community.cookbook('not-real') #=> Community::BadResponse
25
+ #
26
+ # @raise [Community::BadResponse]
27
+ # if the given cookbook (or cookbook version) does not exist on the community site
28
+ #
29
+ # @param [String] name
30
+ # the name of the cookbook on the community site
31
+ # @param [String] version (optional)
32
+ # the version of the cookbook to find
33
+ #
34
+ # @return [Hash]
35
+ # the hash of the cookbook
36
+ #
37
+ def cookbook(name, version = nil)
38
+ if version.nil?
39
+ connection.get("cookbooks/#{name}").body
40
+ else
41
+ connection.get("cookbooks/#{name}/versions/#{Util.version_for_url(version)}").body
42
+ end
43
+ end
44
+
45
+ #
46
+ # Upload a cookbook to the community site.
47
+ #
48
+ # @param [Cookbook] cookbook
49
+ # the cookbook to upload
50
+ #
51
+ def upload(cookbook)
52
+ connection.post('cookbooks', {
53
+ tarball: Faraday::UploadIO.new(cookbook.tarball, 'application/x-tar'),
54
+ cookbook: { category: cookbook.category }.to_json,
55
+ })
56
+ end
57
+
58
+ private
59
+
60
+ #
61
+ # The Faraday connection object with lots of pretty middleware.
62
+ #
63
+ 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
100
+ end
101
+ 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
+ end
124
+ end
data/lib/stove/config.rb CHANGED
@@ -1,18 +1,67 @@
1
+ require 'json'
2
+
1
3
  module Stove
2
- class Config < ::Hash
3
- class << self
4
- def [](thing)
5
- instance[thing]
6
- end
7
-
8
- def instance
9
- @instance ||= load!
10
- end
11
-
12
- private
13
- def load!
14
- JSON.parse(File.read(File.expand_path('~/.stove'))) rescue {}
4
+ class Config
5
+ include Mixin::Instanceable
6
+ include Mixin::Loggable
7
+
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__}'")
15
+
16
+ contents = File.read(__path__)
17
+ data = JSON.parse(contents, symbolize_names: true)
18
+
19
+ log.debug("Config:\n#{JSON.pretty_generate(sanitize(data))}")
20
+
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:
27
+
28
+ https://github.com/sethvargo/stove#installation
29
+ EOH
30
+
31
+ @data = {}
32
+ end
33
+
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
+ def __path__
41
+ @path ||= File.expand_path(ENV['STOVE_CONFIG'] || '~/.stove')
42
+ end
43
+
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
50
+
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 =~ /acecss|token|password/
59
+ [key, '[FILTERED]']
60
+ else
61
+ [key, value]
62
+ end
15
63
  end
64
+ end.flatten(1)]
16
65
  end
17
66
  end
18
67
  end
@@ -1,165 +1,128 @@
1
1
  require 'fileutils'
2
- require 'retryable'
3
2
  require 'tempfile'
4
3
  require 'time'
5
4
 
6
5
  module Stove
7
6
  class Cookbook
8
- require_relative 'cookbook/metadata'
7
+ include Mixin::Loggable
9
8
 
10
- include Stove::Git
9
+ require_relative 'cookbook/metadata'
11
10
 
12
- # The path to this cookbook.
13
11
  #
14
- # @return [String]
12
+ # The path to this cookbook on disk.
13
+ #
14
+ # @return [Pathname]
15
+ #
15
16
  attr_reader :path
16
17
 
18
+ #
17
19
  # The name of the cookbook (must correspond to the name of the
18
20
  # cookbook on the community site).
19
21
  #
20
22
  # @return [String]
23
+ #
21
24
  attr_reader :name
22
25
 
26
+ #
23
27
  # The version of this cookbook (originally).
24
28
  #
25
29
  # @return [String]
30
+ #
26
31
  attr_reader :version
27
32
 
28
- # The new version of the cookbook.
29
33
  #
30
- # @return [String]
31
- attr_reader :new_version
32
-
33
34
  # The metadata for this cookbook.
34
35
  #
35
36
  # @return [Stove::Cookbook::Metadata]
37
+ #
36
38
  attr_reader :metadata
37
39
 
38
- # The list of options passed to the cookbook.
39
40
  #
40
- # @return [Hash]
41
- attr_reader :options
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
42
48
 
49
+ #
43
50
  # Create a new wrapper around the cookbook object.
44
51
  #
45
- # @param [Hash] options
46
- # the list of options
47
- def initialize(options = {})
48
- @path = options[:path] || Dir.pwd
49
- @new_version = options[:new_version]
50
- @options = options
51
-
52
+ # @param [String] path
53
+ # the relative or absolute path to the cookbook on disk
54
+ #
55
+ def initialize(path)
56
+ @path = Pathname.new(path).expand_path
52
57
  load_metadata!
53
58
  end
54
59
 
60
+ #
55
61
  # The category for this cookbook on the community site.
56
62
  #
57
63
  # @return [String]
64
+ #
58
65
  def category
59
- @category ||= options[:category] || Stove::CommunitySite.cookbook(name)['category']
60
- rescue
61
- raise Stove::CookbookCategoryNotFound
66
+ @category ||= Community.cookbook(name)['category']
67
+ rescue Faraday::Error::ResourceNotFound
68
+ log.warn("Cookbook `#{name}' not found on the Chef community site")
69
+ nil
62
70
  end
63
71
 
72
+ #
64
73
  # The URL for the cookbook on the Community Site.
65
74
  #
66
75
  # @return [String]
76
+ #
67
77
  def url
68
- "#{Stove::CommunitySite.http_uri}/cookbooks/#{name}"
78
+ URI.join(Community.base_url, 'cookbooks', name)
69
79
  end
70
80
 
71
- # Deterine if this cookbook version is released on the community site
72
- def released?
73
- @_released ||= begin
74
- Stove::CommunitySite.cookbook(name, version)
75
- true
76
- rescue Stove::BadResponse
77
- false
78
- end
81
+ #
82
+ # The tag version. This is just the current version prefixed with the
83
+ # letter "v".
84
+ #
85
+ # @example Tag version for 1.0.0
86
+ # cookbook.tag_version #=> "v1.0.0"
87
+ #
88
+ # @return [String]
89
+ #
90
+ def tag_version
91
+ "v#{version}"
79
92
  end
80
93
 
81
- # The unreleased JIRA tickets for this cookbook.
82
94
  #
83
- # @return [Hashie::Dash, Array]
84
- def unreleased_tickets
85
- @unreleased_tickets ||= Stove::JIRA.unreleased_tickets_for(name)
95
+ # Deterine if this cookbook version is released on the community site
96
+ #
97
+ # @warn
98
+ # This is a fairly expensive operation and the result cannot be
99
+ # reliably cached!
100
+ #
101
+ # @return [Boolean]
102
+ # true if this cookbook at the current version exists on the community
103
+ # site, false otherwise
104
+ #
105
+ def released?
106
+ Community.cookbook(name, version)
107
+ true
108
+ rescue Faraday::Error::ResourceNotFound
109
+ false
86
110
  end
87
111
 
88
112
  #
89
113
  def release!
90
- if options[:git]
91
- Stove::Logger.info "Running validations"
92
- validate_git_repo!
93
- validate_git_clean!
94
- validate_remote_updated!
95
- end
96
-
97
- Stove::Logger.info "Bumping version"
98
- version_bump
99
-
100
114
  if options[:changelog]
101
- Stove::Logger.info "Updating changelog"
115
+ log.info('Updating changelog')
102
116
  update_changelog
103
117
  end
104
-
105
- if options[:git]
106
- Dir.chdir(path) do
107
- Stove::Logger.info "Committing git changes in '#{path}'"
108
-
109
- git "add metadata.rb"
110
- git "add CHANGELOG.md"
111
- git "commit -m 'Version bump to #{tag_version}'"
112
- git "push #{options[:remote]} #{options[:branch]}"
113
-
114
- if options[:github]
115
- Stove::Logger.info "Pushing release to GitHub"
116
- Stove::GitHub.new(self).publish_release!
117
- else
118
- Stove::Logger.info "Tagging a release"
119
- git "tag #{tag_version}"
120
- git "push #{options[:remote]} #{tag_version}"
121
- end
122
- end
123
- end
124
-
125
- if options[:upload]
126
- Stove::Logger.info "Uploading cookbook"
127
- retryable(tries: 3) do
128
- upload
129
- end
130
- end
131
-
132
- if options[:jira]
133
- Stove::Logger.info "Resolving JIRA issues"
134
- resolve_jira_issues
135
- end
136
-
137
- if options[:devodd]
138
- Stove::Logger.info "Bumping devodd release"
139
- split = version.split('.').map(&:to_i)
140
- split[2] += 1
141
- devodd = split.join('.')
142
-
143
- version_bump(devodd)
144
-
145
- if options[:git]
146
- Dir.chdir(path) do
147
- git "add metadata.rb"
148
- git "commit -m 'Version bump to #{tag_version}'"
149
- git "push #{options[:remote]} #{options[:branch]}"
150
- end
151
- end
152
- end
153
- end
154
-
155
- def tag_version
156
- "v#{version}"
157
118
  end
158
119
 
120
+ #
159
121
  # So there's this really really crazy bug that the tmp directory could
160
122
  # be deleted mid-request...
161
123
  #
162
124
  # @return [File]
125
+ #
163
126
  def tarball
164
127
  return @tarball if @tarball && File.exists?(@tarball)
165
128
 
@@ -170,59 +133,25 @@ module Stove
170
133
  end
171
134
 
172
135
  #
173
- def upload
174
- Stove::Uploader.new(self).upload!
175
- end
176
-
177
- # The URL for this repository on GitHub. This method automatically
178
- # translates SSH and git:// URLs to https:// URLs.
136
+ # Bump the version in the metdata.rb to the specified
137
+ # parameter.
179
138
  #
180
- # @return [String]
181
- def repository_url
182
- @repository_url ||= git("config --get remote.#{options[:remote]}.url")
183
- .strip
184
- .gsub(/\.git$/, '')
185
- .gsub(':', '/')
186
- .gsub('@', '://')
187
- .gsub('git://', 'https://')
188
- end
189
-
190
- # The set of changes for this diff/patch in markdown format.
139
+ # @param [String] new_version
140
+ # the version to bump to
191
141
  #
192
142
  # @return [String]
193
- def changeset
194
- return @changeset if @changeset
195
-
196
- contents = []
197
- contents << "v#{version}"
198
- contents << '-'*(version.length+1)
199
-
200
- if options[:jira]
201
- by_type = unreleased_tickets.inject({}) do |hash, ticket|
202
- issue_type = ticket.fields.current['issuetype']['name']
203
- hash[issue_type] ||= []
204
- hash[issue_type] << {
205
- number: ticket.jira_key,
206
- details: ticket.fields.current['summary'],
207
- }
143
+ # the new version string
144
+ #
145
+ def bump(new_version)
146
+ return true if new_version.to_s == version.to_s
208
147
 
209
- hash
210
- end
148
+ metadata_path = path.join('metadata.rb')
149
+ contents = File.read(metadata_path)
211
150
 
212
- by_type.each do |issue_type, tickets|
213
- contents << "### #{issue_type}"
214
- tickets.sort { |a,b| b[:number].to_i <=> a[:number].to_i }.each do |ticket|
215
- contents << "- **[#{ticket[:number]}](#{Stove::JIRA::JIRA_URL}/browse/#{ticket[:number]})** - #{ticket[:details]}"
216
- end
217
- contents << ""
218
- end
219
- else
220
- contents << "_Enter CHANGELOG for #{name} (#{version}) here_"
221
- contents << ""
222
- end
151
+ contents.sub!(/^version(\s+)('|")#{version}('|")/, "version\\1\\2#{new_version}\\3")
223
152
 
224
- @changeset = contents.join("\n")
225
- @changeset
153
+ File.open(metadata_path, 'w') { |f| f.write(contents) }
154
+ reload_metadata!
226
155
  end
227
156
 
228
157
  private
@@ -234,7 +163,7 @@ module Stove
234
163
  # @return [String]
235
164
  # the path to the metadata file
236
165
  def load_metadata!
237
- metadata_path = File.expand_path(File.join(path, 'metadata.rb'))
166
+ metadata_path = path.join('metadata.rb')
238
167
 
239
168
  @metadata = Stove::Cookbook::Metadata.from_file(metadata_path)
240
169
  @name = @metadata.name
@@ -243,96 +172,5 @@ module Stove
243
172
  metadata_path
244
173
  end
245
174
  alias_method :reload_metadata!, :load_metadata!
246
-
247
- # Update the CHANGELOG with the new contents, but inserting
248
- # the newest version's CHANGELOG at the top of the file (after
249
- # the header)
250
- def update_changelog
251
- changelog = File.join(path, 'CHANGELOG.md')
252
- contents = File.readlines(changelog)
253
- index = contents.find_index { |line| line =~ /(--)+/ }
254
-
255
- if index.nil?
256
- raise Stove::InvalidChangelogError, "Your CHANGELOG does not exist" \
257
- " or is not in a valid format!"
258
- end
259
-
260
- tmpfile = Tempfile.new(['changes', '.md'])
261
- tmpfile.write(changeset)
262
- tmpfile.rewind
263
- response = shellout("$EDITOR #{tmpfile.path}")
264
-
265
- unless response.success?
266
- Stove::Logger.debug response.stdout
267
- Stove::Logger.debug response.stderr
268
- raise Stove::Error, response.stderr
269
- end
270
-
271
- @changeset = File.read(tmpfile.path).strip
272
-
273
- contents.insert(index - 2, "\n" + @changeset + "\n\n")
274
- File.open(changelog, 'w') { |f| f.write(contents.join('')) }
275
- rescue SystemExit, Interrupt
276
- raise Stove::UserCanceledError
277
- ensure
278
- if defined?(tmpfile)
279
- tmpfile.close
280
- tmpfile.unlink
281
- end
282
- end
283
-
284
- # Bump the version in the metdata.rb to the specified
285
- # parameter.
286
- #
287
- # @return [String]
288
- # the new version string
289
- def version_bump(bump_version = new_version)
290
- return true if bump_version.to_s == version.to_s
291
-
292
- metadata_path = File.join(path, 'metadata.rb')
293
- contents = File.read(metadata_path)
294
-
295
- contents.sub!(/^version(\s+)('|")#{version.to_s}('|")/, "version\\1\\2#{bump_version.to_s}\\3")
296
-
297
- File.open(metadata_path, 'w') { |f| f.write(contents) }
298
- reload_metadata!
299
- end
300
-
301
- # Resolve all the JIRA issues that have been merged.
302
- def resolve_jira_issues
303
- unreleased_tickets.collect do |ticket|
304
- Thread.new { Stove::JIRA.comment_and_close(ticket, self) }
305
- end.map(&:join)
306
- end
307
-
308
- # Validate that the current working directory is git repo.
309
- #
310
- # @raise [Stove::GitError::NotARepo]
311
- # if this is not currently a git repo
312
- def validate_git_repo!
313
- Dir.chdir(path) do
314
- raise Stove::GitError::NotARepo unless git_repo?
315
- end
316
- end
317
-
318
- # Validate that the current.
319
- #
320
- # @raise [Stove::GitError::DirtyRepo]
321
- # if the current working directory is not clean
322
- def validate_git_clean!
323
- Dir.chdir(path) do
324
- raise Stove::GitError::DirtyRepo unless git_repo_clean?
325
- end
326
- end
327
-
328
- # Validate that the remote git repository is up to date.
329
- #
330
- # @raise [Stove::GitError::OutOfSync]
331
- # if the current git repo is not up to date with the remote
332
- def validate_remote_updated!
333
- Dir.chdir(path) do
334
- raise Stove::GitError::OutOfSync unless git_remote_uptodate?(options)
335
- end
336
- end
337
175
  end
338
176
  end