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
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