stove 1.0.0
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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +5 -0
- data/Gemfile +2 -0
- data/LICENSE +202 -0
- data/README.md +117 -0
- data/Rakefile +1 -0
- data/bin/bake +5 -0
- data/features/changelog.feature +26 -0
- data/features/cli.feature +15 -0
- data/features/git.feature +32 -0
- data/features/step_definitions/cli_steps.rb +23 -0
- data/features/step_definitions/community_site_steps.rb +25 -0
- data/features/step_definitions/cookbook_steps.rb +25 -0
- data/features/step_definitions/git_steps.rb +19 -0
- data/features/support/env.rb +29 -0
- data/features/upload.feature +44 -0
- data/lib/stove.rb +26 -0
- data/lib/stove/cli.rb +131 -0
- data/lib/stove/community_site.rb +85 -0
- data/lib/stove/config.rb +18 -0
- data/lib/stove/cookbook.rb +280 -0
- data/lib/stove/cookbook/metadata.rb +190 -0
- data/lib/stove/error.rb +106 -0
- data/lib/stove/formatter.rb +7 -0
- data/lib/stove/formatter/base.rb +32 -0
- data/lib/stove/formatter/human.rb +9 -0
- data/lib/stove/formatter/silent.rb +10 -0
- data/lib/stove/git.rb +74 -0
- data/lib/stove/jira.rb +44 -0
- data/lib/stove/logger.rb +35 -0
- data/lib/stove/mash.rb +25 -0
- data/lib/stove/packager.rb +82 -0
- data/lib/stove/uploader.rb +73 -0
- data/lib/stove/version.rb +3 -0
- data/spec/support/community_site.rb +33 -0
- data/spec/support/git.rb +51 -0
- data/stove.gemspec +34 -0
- metadata +251 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
Given /^the environment variable (.+) is "(.+)"$/ do |variable, value|
|
2
|
+
set_env(variable, value)
|
3
|
+
end
|
4
|
+
|
5
|
+
Then /^the exit status will be "(.+)"$/ do |error|
|
6
|
+
code = Stove.const_get(error).exit_code
|
7
|
+
assert_exit_status(code)
|
8
|
+
end
|
9
|
+
|
10
|
+
When /^the CLI options are all off$/ do
|
11
|
+
class Stove::Cli
|
12
|
+
private
|
13
|
+
def options
|
14
|
+
@options ||= {
|
15
|
+
:git => false,
|
16
|
+
:jira => false,
|
17
|
+
:upload => false,
|
18
|
+
:changelog => false,
|
19
|
+
:log_level => :debug,
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
Given /^the Community Site has the cookbooks?:$/ do |table|
|
2
|
+
table.raw.each do |name, version, category|
|
3
|
+
version ||= '0.0.0'
|
4
|
+
category ||= 'Other'
|
5
|
+
|
6
|
+
CommunityZero::Cookbook.create({
|
7
|
+
name: name,
|
8
|
+
version: version,
|
9
|
+
category: category,
|
10
|
+
})
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
Then /^the Community Site will( not)? have the cookbooks?:$/ do |negate, table|
|
15
|
+
table.raw.each do |name, version, category|
|
16
|
+
cookbook = CommunityZero::Store.find(name, version)
|
17
|
+
|
18
|
+
if negate
|
19
|
+
expect(cookbook).to be_nil
|
20
|
+
else
|
21
|
+
expect(cookbook).to_not be_nil
|
22
|
+
expect(cookbook.category).to eql(category) if category
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
Given /^I have a cookbook named "(.+)"(?: (?:at|with) version "(.+)")?$/ do |name, version|
|
2
|
+
create_cookbook(name, version)
|
3
|
+
end
|
4
|
+
|
5
|
+
Given /^I have a cookbook named "(.+)"(?: (?:at|with) version "(.+)")? with git support$/ do |name, version|
|
6
|
+
create_cookbook(name, version, git: true)
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
# Create a new cookbook with the given name and version.
|
11
|
+
#
|
12
|
+
# @param [String] name
|
13
|
+
# @param [String] version (default: 0.0.0.0)
|
14
|
+
# @param [Hash] options
|
15
|
+
def create_cookbook(name, version, options = {})
|
16
|
+
create_dir(name)
|
17
|
+
cd(name)
|
18
|
+
write_file('CHANGELOG.md', "#{name} Cookbook CHANGELOG\n=====\n\nv0.0.0\n-----")
|
19
|
+
write_file('README.md', 'This is a README')
|
20
|
+
write_file('metadata.rb', "name '#{name}'\nversion '#{version || '0.0.0'}'")
|
21
|
+
|
22
|
+
if options[:git]
|
23
|
+
git_init(current_dir)
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
Then /^the git remote will( not)? have the commit "(.+)"$/ do |negate, message|
|
2
|
+
commits = git_commits(fake_git_remote)
|
3
|
+
|
4
|
+
if negate
|
5
|
+
expect(commits).to_not include(message)
|
6
|
+
else
|
7
|
+
expect(commits).to include(message)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
Then /^the git remote will( not)? have the tag "(.+)"$/ do |negate, tag|
|
12
|
+
tags = git_tags(fake_git_remote)
|
13
|
+
|
14
|
+
if negate
|
15
|
+
expect(tags).to_not include(tag)
|
16
|
+
else
|
17
|
+
expect(tags).to include(tag)
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'aruba/api'
|
2
|
+
require 'aruba/cucumber'
|
3
|
+
require 'aruba/in_process'
|
4
|
+
require 'rspec/expectations'
|
5
|
+
require 'stove'
|
6
|
+
|
7
|
+
require_relative '../../spec/support/community_site'
|
8
|
+
require_relative '../../spec/support/git'
|
9
|
+
|
10
|
+
World(Aruba::Api)
|
11
|
+
World(Stove::RSpec::Git)
|
12
|
+
|
13
|
+
Aruba::InProcess.main_class = Stove::Cli
|
14
|
+
Aruba.process = Aruba::InProcess
|
15
|
+
|
16
|
+
Stove.set_formatter(:silent)
|
17
|
+
Stove::RSpec::CommunitySite.start(port: 3390)
|
18
|
+
Stove::CommunitySite.base_uri(Stove::RSpec::CommunitySite.server_url)
|
19
|
+
Stove::CommunitySite.http_uri(Stove::RSpec::CommunitySite.server_url)
|
20
|
+
|
21
|
+
Before do
|
22
|
+
@dirs = [Dir.mktmpdir]
|
23
|
+
Stove::RSpec::CommunitySite.reset!
|
24
|
+
end
|
25
|
+
|
26
|
+
# The path to Aruba's "stuff"
|
27
|
+
def tmp_path
|
28
|
+
File.expand_path(@dirs.first.to_s)
|
29
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
Feature: Upload
|
2
|
+
As a stove user
|
3
|
+
In order to upload cookbooks
|
4
|
+
I want to tar and push to a Community Site
|
5
|
+
|
6
|
+
Background:
|
7
|
+
* I have a cookbook named "bacon"
|
8
|
+
* the CLI options are all off
|
9
|
+
|
10
|
+
Scenario: --no-upload
|
11
|
+
* I successfully run `bake 1.0.0 --no-upload`
|
12
|
+
* the Community Site will not have the cookbook:
|
13
|
+
| bacon | 1.0.0 |
|
14
|
+
|
15
|
+
Scenario: --upload (no category, no existing)
|
16
|
+
* I run `bake 1.0.0 --upload`
|
17
|
+
* the Community Site will not have the cookbook:
|
18
|
+
| bacon | 1.0.0 |
|
19
|
+
* the exit status will be "CookbookCategoryNotFound"
|
20
|
+
|
21
|
+
Scenario: --upload (no category, existing)
|
22
|
+
* the Community Site has the cookbook:
|
23
|
+
| bacon | 0.0.0 | Application |
|
24
|
+
* I successfully run `bake 1.0.0 --upload`
|
25
|
+
* the Community Site will have the cookbook:
|
26
|
+
| bacon | 1.0.0 | Application |
|
27
|
+
|
28
|
+
Scenario: --upload (category, no existing)
|
29
|
+
* I successfully run `bake 1.0.0 --upload --category Application`
|
30
|
+
* the Community Site will have the cookbook:
|
31
|
+
| bacon | 1.0.0 | Application |
|
32
|
+
|
33
|
+
Scenario: --upload (category, existing)
|
34
|
+
* the Community Site has the cookbook:
|
35
|
+
| bacon | 0.0.0 | Application |
|
36
|
+
* I successfully run `bake 1.0.0 --upload --category Application`
|
37
|
+
* the Community Site will have the cookbook:
|
38
|
+
| bacon | 1.0.0 | Application |
|
39
|
+
|
40
|
+
Scenario: --upload (existing version)
|
41
|
+
* the Community Site has the cookbook:
|
42
|
+
| bacon | 1.0.0 | Application |
|
43
|
+
* I run `bake 1.0.0 --upload`
|
44
|
+
* the exit status will be "UploadError"
|
data/lib/stove.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module Stove
|
2
|
+
require_relative 'stove/config'
|
3
|
+
require_relative 'stove/git'
|
4
|
+
require_relative 'stove/logger'
|
5
|
+
|
6
|
+
require_relative 'stove/cli'
|
7
|
+
require_relative 'stove/community_site'
|
8
|
+
require_relative 'stove/cookbook'
|
9
|
+
require_relative 'stove/error'
|
10
|
+
require_relative 'stove/formatter'
|
11
|
+
require_relative 'stove/jira'
|
12
|
+
require_relative 'stove/mash'
|
13
|
+
require_relative 'stove/packager'
|
14
|
+
require_relative 'stove/uploader'
|
15
|
+
require_relative 'stove/version'
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def formatter
|
19
|
+
@formatter ||= Stove::Formatter::Human.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def set_formatter(name)
|
23
|
+
@formatter = Stove::Formatter::Base.formatters[name.to_sym].new
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/stove/cli.rb
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'stove'
|
3
|
+
|
4
|
+
module Stove
|
5
|
+
class Cli
|
6
|
+
def initialize(argv, stdin=STDIN, stdout=STDOUT, stderr=STDERR, kernel=Kernel)
|
7
|
+
@argv, @stdin, @stdout, @stderr, @kernel = argv, stdin, stdout, stderr, kernel
|
8
|
+
$stdout, @stderr = @stdout, @stderr
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute!
|
12
|
+
option_parser.parse!(@argv)
|
13
|
+
options[:new_version] = @argv.first
|
14
|
+
|
15
|
+
raise Stove::InvalidVersionError unless valid_version?(options[:new_version])
|
16
|
+
|
17
|
+
Stove::Cookbook.new(options).release!
|
18
|
+
@kernel.exit(0)
|
19
|
+
rescue => e
|
20
|
+
@stderr.puts "#{e.class}: #{e.message}"
|
21
|
+
|
22
|
+
if Stove::Logger.sev_threshold == ::Logger::DEBUG
|
23
|
+
@stderr.puts " #{e.backtrace.join("\n ")}"
|
24
|
+
end
|
25
|
+
|
26
|
+
@kernel.exit(e.respond_to?(:exit_code) ? e.exit_code : 500)
|
27
|
+
ensure
|
28
|
+
$stdout, $stderr = STDOUT, STDERR
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
# The option parser for handling command line flags.
|
33
|
+
#
|
34
|
+
# @return [OptionParser]
|
35
|
+
def option_parser
|
36
|
+
@option_parser ||= OptionParser.new do |opts|
|
37
|
+
opts.banner = "Usage: bake x.y.z"
|
38
|
+
|
39
|
+
opts.on('-l', '--log-level [LEVEL]', [:fatal, :error, :warn, :info, :debug], 'Ruby log level') do |v|
|
40
|
+
options[:log_level] = v
|
41
|
+
end
|
42
|
+
|
43
|
+
opts.on('-c', '--category [CATEGORY]', String, 'The category for the cookbook (optional for existing cookbooks)') do |v|
|
44
|
+
options[:category] = v
|
45
|
+
end
|
46
|
+
|
47
|
+
opts.on('-p', '--path [PATH]', String, 'The path to the cookbook to release (default: PWD)') do |v|
|
48
|
+
options[:path] = v
|
49
|
+
end
|
50
|
+
|
51
|
+
opts.on('--[no-]git', 'Automatically tag and push to git (default: true)') do |v|
|
52
|
+
options[:git] = v
|
53
|
+
end
|
54
|
+
|
55
|
+
opts.on('-r', '--remote', String, 'The name of the git remote to push to') do |v|
|
56
|
+
options[:remote] = v
|
57
|
+
end
|
58
|
+
|
59
|
+
opts.on('-b', '--branch', String, 'The name of the git branch to push to') do |v|
|
60
|
+
options[:branch] = v
|
61
|
+
end
|
62
|
+
|
63
|
+
opts.on('--[no-]jira', 'Automatically populate the CHANGELOG from JIRA tickets and close them (default: false)') do |v|
|
64
|
+
options[:jira] = v
|
65
|
+
end
|
66
|
+
|
67
|
+
opts.on('--[no-]upload', 'Upload the cookbook to the Opscode Community Site (default: true)') do |v|
|
68
|
+
options[:upload] = v
|
69
|
+
end
|
70
|
+
|
71
|
+
opts.on('--[no-]changelog', 'Automatically generate a CHANGELOG (default: true)') do |v|
|
72
|
+
options[:changelog] = v
|
73
|
+
end
|
74
|
+
|
75
|
+
opts.on_tail('-h', '--help', 'Show this message') do
|
76
|
+
puts opts
|
77
|
+
exit
|
78
|
+
end
|
79
|
+
|
80
|
+
opts.on_tail('-v', '--version', 'Show version') do
|
81
|
+
puts Stove::VERSION
|
82
|
+
exit(0)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# The options to pass to the cookbook. Includes default values
|
88
|
+
# that are manipulated by the option parser.
|
89
|
+
#
|
90
|
+
# @return [Hash]
|
91
|
+
def options
|
92
|
+
@options ||= {
|
93
|
+
:path => Dir.pwd,
|
94
|
+
:git => true,
|
95
|
+
:remote => 'origin',
|
96
|
+
:branch => 'master',
|
97
|
+
:jira => false,
|
98
|
+
:upload => true,
|
99
|
+
:changelog => true,
|
100
|
+
:log_level => :warn,
|
101
|
+
}
|
102
|
+
end
|
103
|
+
|
104
|
+
# Determine if the given string is a valid version string.
|
105
|
+
#
|
106
|
+
# @return [Boolean]
|
107
|
+
def valid_version?(version)
|
108
|
+
version.to_s =~ /^\d+\.\d+\.\d+$/
|
109
|
+
end
|
110
|
+
|
111
|
+
# Convert a string to it's logger constant.
|
112
|
+
#
|
113
|
+
# @return [Object]
|
114
|
+
def level_to_constant(level)
|
115
|
+
case level.to_s.strip.downcase.to_sym
|
116
|
+
when :fatal
|
117
|
+
::Logger::FATAL
|
118
|
+
when :error
|
119
|
+
::Logger::ERROR
|
120
|
+
when :warn
|
121
|
+
::Logger::WARN
|
122
|
+
when :info
|
123
|
+
::Logger::INFO
|
124
|
+
when :debug
|
125
|
+
::Logger::DEBUG
|
126
|
+
else
|
127
|
+
::Logger::INFO
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
|
3
|
+
module Stove
|
4
|
+
class CommunitySite
|
5
|
+
include HTTParty
|
6
|
+
base_uri 'https://cookbooks.opscode.com/api/v1'
|
7
|
+
headers 'Content-Type' => 'application/json', 'Accept' => 'application/json'
|
8
|
+
|
9
|
+
class << self
|
10
|
+
# The URI for the web-based version of the site. (default:
|
11
|
+
# https://community.opscode.com).
|
12
|
+
#
|
13
|
+
# If a parameter is given, the {http_uri} is set to that value.
|
14
|
+
#
|
15
|
+
# @return [String]
|
16
|
+
def http_uri(arg = nil)
|
17
|
+
if arg.nil?
|
18
|
+
@http_uri ||= 'https://community.opscode.com'
|
19
|
+
else
|
20
|
+
@http_uri = arg
|
21
|
+
@http_uri
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Get and cache a community cookbook's JSON response from the given name
|
26
|
+
# and version.
|
27
|
+
#
|
28
|
+
# @example Find a cookbook by name
|
29
|
+
# CommunitySite.cookbook('apache2') #=> {...}
|
30
|
+
#
|
31
|
+
# @example Find a cookbook by name and version
|
32
|
+
# CommunitySite.cookbook('apache2', '1.0.0') #=> {...}
|
33
|
+
#
|
34
|
+
# @example Find a non-existent cookbook
|
35
|
+
# CommunitySite.cookbook('not-real') #=> CommunitySite::BadResponse
|
36
|
+
#
|
37
|
+
# @raise [CommunitySite::BadResponse]
|
38
|
+
# if the given cookbook (or cookbook version) does not exist on the community site
|
39
|
+
#
|
40
|
+
# @param [String] name
|
41
|
+
# the name of the cookbook on the community site
|
42
|
+
# @param [String] version (optional)
|
43
|
+
# the version of the cookbook to find
|
44
|
+
def cookbook(name, version = nil)
|
45
|
+
if version.nil?
|
46
|
+
get("/cookbooks/#{name}")
|
47
|
+
else
|
48
|
+
get("/cookbooks/#{name}/versions/#{format_version(version)}")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
# Convert a version string (x.y.z) to a community-site friendly format
|
54
|
+
# (x_y_z).
|
55
|
+
#
|
56
|
+
# @example Convert a version to a version string
|
57
|
+
# format_version('1.2.3') #=> 1_2_3
|
58
|
+
#
|
59
|
+
# @param [#to_s] version
|
60
|
+
# the version string to convert
|
61
|
+
#
|
62
|
+
# @return [String]
|
63
|
+
def format_version(version)
|
64
|
+
version.gsub('.', '_')
|
65
|
+
end
|
66
|
+
|
67
|
+
# @override [HTTParty.get]
|
68
|
+
def get(path, options = {}, &block)
|
69
|
+
cache[path] ||= begin
|
70
|
+
Stove::Logger.debug "Getting #{path}"
|
71
|
+
response = super(path)
|
72
|
+
raise Stove::BadResponse.new(response) unless response.ok?
|
73
|
+
response.parsed_response
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# A small, unpersisted cache for storing responses
|
78
|
+
#
|
79
|
+
# @return [Hash]
|
80
|
+
def cache
|
81
|
+
@cache ||= {}
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/stove/config.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
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")))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,280 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
module Stove
|
5
|
+
class Cookbook
|
6
|
+
require_relative 'cookbook/metadata'
|
7
|
+
|
8
|
+
include Stove::Git
|
9
|
+
|
10
|
+
# The path to this cookbook.
|
11
|
+
#
|
12
|
+
# @return [String]
|
13
|
+
attr_reader :path
|
14
|
+
|
15
|
+
# The name of the cookbook (must correspond to the name of the
|
16
|
+
# cookbook on the community site).
|
17
|
+
#
|
18
|
+
# @return [String]
|
19
|
+
attr_reader :name
|
20
|
+
|
21
|
+
# The version of this cookbook (originally).
|
22
|
+
#
|
23
|
+
# @return [String]
|
24
|
+
attr_reader :version
|
25
|
+
|
26
|
+
# The new version of the cookbook.
|
27
|
+
#
|
28
|
+
# @return [String]
|
29
|
+
attr_reader :new_version
|
30
|
+
|
31
|
+
# The metadata for this cookbook.
|
32
|
+
#
|
33
|
+
# @return [Stove::Cookbook::Metadata]
|
34
|
+
attr_reader :metadata
|
35
|
+
|
36
|
+
# The list of options passed to the cookbook.
|
37
|
+
#
|
38
|
+
# @return [Hash]
|
39
|
+
attr_reader :options
|
40
|
+
|
41
|
+
# Create a new wrapper around the cookbook object.
|
42
|
+
#
|
43
|
+
# @param [Hash] options
|
44
|
+
# the list of options
|
45
|
+
def initialize(options = {})
|
46
|
+
@path = options[:path] || Dir.pwd
|
47
|
+
@new_version = options[:new_version]
|
48
|
+
@options = options
|
49
|
+
|
50
|
+
load_metadata!
|
51
|
+
end
|
52
|
+
|
53
|
+
# The category for this cookbook on the community site.
|
54
|
+
#
|
55
|
+
# @return [String]
|
56
|
+
def category
|
57
|
+
@category ||= options[:category] || Stove::CommunitySite.cookbook(name)['category']
|
58
|
+
rescue
|
59
|
+
raise Stove::CookbookCategoryNotFound
|
60
|
+
end
|
61
|
+
|
62
|
+
# The URL for the cookbook on the Community Site.
|
63
|
+
#
|
64
|
+
# @return [String]
|
65
|
+
def url
|
66
|
+
"#{Stove::CommunitySite.http_uri}/cookbooks/#{name}"
|
67
|
+
end
|
68
|
+
|
69
|
+
# Deterine if this cookbook version is released on the community site
|
70
|
+
def released?
|
71
|
+
@_released ||= begin
|
72
|
+
Stove::CommunitySite.cookbook(name, version)
|
73
|
+
true
|
74
|
+
rescue Stove::BadResponse
|
75
|
+
false
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# The unreleased JIRA tickets for this cookbook.
|
80
|
+
#
|
81
|
+
# @return [Hashie::Dash, Array]
|
82
|
+
def unreleased_tickets
|
83
|
+
@unreleased_tickets ||= Stove::JIRA.unreleased_tickets_for(name)
|
84
|
+
end
|
85
|
+
|
86
|
+
#
|
87
|
+
def release!
|
88
|
+
if options[:git]
|
89
|
+
validate_git_repo!
|
90
|
+
validate_git_clean!
|
91
|
+
end
|
92
|
+
|
93
|
+
version_bump
|
94
|
+
|
95
|
+
if options[:changelog]
|
96
|
+
update_changelog
|
97
|
+
end
|
98
|
+
|
99
|
+
if options[:git]
|
100
|
+
Dir.chdir(path) do
|
101
|
+
git "add metadata.rb"
|
102
|
+
git "add CHANGELOG.md"
|
103
|
+
git "commit -m 'Version bump to v#{version}'"
|
104
|
+
git "push #{options[:remote] || 'origin'} #{options[:branch] || 'master'}"
|
105
|
+
|
106
|
+
git "tag v#{version}"
|
107
|
+
git "push #{options[:remote] || 'origin'} v#{version}"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
if options[:upload]
|
112
|
+
upload
|
113
|
+
end
|
114
|
+
|
115
|
+
if options[:jira]
|
116
|
+
resolve_jira_issues
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
#
|
121
|
+
def upload
|
122
|
+
return true unless options[:upload]
|
123
|
+
Stove::Uploader.new(self).upload!
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
# Load the metadata and set the @metadata instance variable.
|
128
|
+
#
|
129
|
+
# @raise [ArgumentError]
|
130
|
+
# if there is no metadata.rb
|
131
|
+
#
|
132
|
+
# @return [String]
|
133
|
+
# the path to the metadata file
|
134
|
+
def load_metadata!
|
135
|
+
metadata_path = File.expand_path(File.join(path, 'metadata.rb'))
|
136
|
+
|
137
|
+
@metadata = Stove::Cookbook::Metadata.from_file(metadata_path)
|
138
|
+
@name = @metadata.name
|
139
|
+
@version = @metadata.version
|
140
|
+
|
141
|
+
metadata_path
|
142
|
+
end
|
143
|
+
alias_method :reload_metadata!, :load_metadata!
|
144
|
+
|
145
|
+
# Create a new CHANGELOG in markdown format.
|
146
|
+
#
|
147
|
+
# @example given a cookbook named bacon
|
148
|
+
# cookbook.create_changelog #=> <<EOH
|
149
|
+
# bacon Cookbook CHANGELOG
|
150
|
+
# ------------------------
|
151
|
+
# EOH
|
152
|
+
#
|
153
|
+
# @return [String]
|
154
|
+
# the path to the new CHANGELOG
|
155
|
+
def create_changelog
|
156
|
+
destination = File.join(path, 'CHANGELOG.md')
|
157
|
+
|
158
|
+
# Be idempotent :)
|
159
|
+
return destination if File.exists?(destination)
|
160
|
+
|
161
|
+
header = "#{name} Cookbook CHANGELOG\n"
|
162
|
+
header << "#{'='*header.length}\n"
|
163
|
+
header << "This file is used to list changes made in each version of the #{cookbook.name} cookbook.\n\n"
|
164
|
+
|
165
|
+
File.open(destination, 'wb') do |file|
|
166
|
+
file.write(header)
|
167
|
+
end
|
168
|
+
|
169
|
+
destination
|
170
|
+
end
|
171
|
+
|
172
|
+
# Update the CHANGELOG with the new contents, but inserting
|
173
|
+
# the newest version's CHANGELOG at the top of the file (after
|
174
|
+
# the header)
|
175
|
+
def update_changelog
|
176
|
+
changelog = create_changelog
|
177
|
+
contents = File.readlines(changelog)
|
178
|
+
|
179
|
+
index = contents.find_index { |line| line =~ /(--)+/ } - 2
|
180
|
+
contents.insert(index, "\n" + generate_changelog)
|
181
|
+
|
182
|
+
Dir.mktmpdir do |dir|
|
183
|
+
tmpfile = File.join(dir, 'CHANGELOG.md')
|
184
|
+
File.open(tmpfile, 'w') { |f| f.write(contents.join('')) }
|
185
|
+
response = shellout("$EDITOR #{tmpfile}")
|
186
|
+
|
187
|
+
unless response.success?
|
188
|
+
Stove::Logger.debug response.stderr
|
189
|
+
raise Stove::Error, response.stderr
|
190
|
+
end
|
191
|
+
|
192
|
+
FileUtils.mv(tmpfile, File.join(path, 'CHANGELOG.md'))
|
193
|
+
end
|
194
|
+
rescue SystemExit, Interrupt
|
195
|
+
raise Stove::UserCanceledError
|
196
|
+
end
|
197
|
+
|
198
|
+
# Generate a CHANGELOG in markdown format.
|
199
|
+
#
|
200
|
+
# @param [String] version
|
201
|
+
# the version string in x.y.z format
|
202
|
+
#
|
203
|
+
# @return [String]
|
204
|
+
def generate_changelog
|
205
|
+
contents = []
|
206
|
+
contents << "v#{version}"
|
207
|
+
contents << '-'*(version.length+1)
|
208
|
+
|
209
|
+
if options[:jira]
|
210
|
+
by_type = unreleased_tickets.inject({}) do |hash, ticket|
|
211
|
+
issue_type = ticket.fields.current['issuetype']['name']
|
212
|
+
hash[issue_type] ||= []
|
213
|
+
hash[issue_type] << {
|
214
|
+
number: ticket.jira_key,
|
215
|
+
details: ticket.fields.current['summary'],
|
216
|
+
}
|
217
|
+
|
218
|
+
hash
|
219
|
+
end
|
220
|
+
|
221
|
+
by_type.each do |issue_type, tickets|
|
222
|
+
contents << "### #{issue_type}"
|
223
|
+
tickets.sort { |a,b| b[:number].to_i <=> a[:number].to_i }.each do |ticket|
|
224
|
+
contents << "- **[#{ticket[:number]}](#{Stove::JIRA::JIRA_URL}/browse/#{ticket[:number]})** - #{ticket[:details]}"
|
225
|
+
end
|
226
|
+
contents << ""
|
227
|
+
end
|
228
|
+
else
|
229
|
+
contents << "_Enter CHANGELOG for #{name} (#{version}) here_"
|
230
|
+
contents << ""
|
231
|
+
end
|
232
|
+
|
233
|
+
contents.join("\n")
|
234
|
+
end
|
235
|
+
|
236
|
+
# Bump the version in the metdata.rb to the specified
|
237
|
+
# parameter.
|
238
|
+
#
|
239
|
+
# @return [String]
|
240
|
+
# the new version string
|
241
|
+
def version_bump
|
242
|
+
return true if new_version.to_s == version.to_s
|
243
|
+
|
244
|
+
metadata_path = File.join(path, 'metadata.rb')
|
245
|
+
contents = File.read(metadata_path)
|
246
|
+
|
247
|
+
contents.sub!(/^version(\s+)('|")#{version.to_s}('|")/, "version\\1\\2#{new_version.to_s}\\3")
|
248
|
+
|
249
|
+
File.open(metadata_path, 'w') { |f| f.write(contents) }
|
250
|
+
reload_metadata!
|
251
|
+
end
|
252
|
+
|
253
|
+
# Resolve all the JIRA issues that have been merged.
|
254
|
+
def resolve_jira_issues
|
255
|
+
unreleased_tickets.collect do |ticket|
|
256
|
+
Thread.new { Stove::JIRA.comment_and_close(ticket, self) }
|
257
|
+
end.map(&:join)
|
258
|
+
end
|
259
|
+
|
260
|
+
# Validate that the current working directory is git repo.
|
261
|
+
#
|
262
|
+
# @raise [Stove::GitError::NotARepo]
|
263
|
+
# if this is not currently a git repo
|
264
|
+
def validate_git_repo!
|
265
|
+
Dir.chdir(path) do
|
266
|
+
raise Stove::GitError::NotARepo unless git_repo?
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
# Validate that the current.
|
271
|
+
#
|
272
|
+
# @raise [Stove::GitError::DirtyRepo]
|
273
|
+
# if the current working directory is not clean
|
274
|
+
def validate_git_clean!
|
275
|
+
Dir.chdir(path) do
|
276
|
+
raise Stove::GitError::DirtyRepo unless git_repo_clean?
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|