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.
- checksums.yaml +4 -4
- data/.travis.yml +1 -2
- data/CHANGELOG.md +16 -0
- data/README.md +41 -29
- data/Rakefile +15 -0
- data/bin/bake +1 -2
- data/features/actions/bump.feature +22 -0
- data/features/actions/changelog.feature +45 -0
- data/features/actions/dev.feature +18 -0
- data/features/actions/upload.feature +48 -0
- data/features/plugins/git.feature +24 -0
- data/features/rake.feature +1 -2
- data/features/step_definitions/cli_steps.rb +1 -27
- data/features/step_definitions/{community_site_steps.rb → community_steps.rb} +9 -5
- data/features/step_definitions/config_steps.rb +24 -0
- data/features/step_definitions/cookbook_steps.rb +28 -6
- data/features/step_definitions/cucumber_steps.rb +12 -0
- data/features/step_definitions/git_steps.rb +10 -7
- data/features/support/env.rb +12 -28
- data/features/support/stove/git.rb +48 -0
- data/lib/stove.rb +102 -19
- data/lib/stove/actions/base.rb +21 -0
- data/lib/stove/actions/bump.rb +25 -0
- data/lib/stove/actions/changelog.rb +71 -0
- data/lib/stove/actions/dev.rb +22 -0
- data/lib/stove/actions/finish.rb +8 -0
- data/lib/stove/actions/start.rb +7 -0
- data/lib/stove/actions/upload.rb +27 -0
- data/lib/stove/cli.rb +107 -79
- data/lib/stove/community.rb +124 -0
- data/lib/stove/config.rb +62 -13
- data/lib/stove/cookbook.rb +76 -238
- data/lib/stove/cookbook/metadata.rb +16 -11
- data/lib/stove/error.rb +13 -107
- data/lib/stove/filter.rb +59 -0
- data/lib/stove/jira.rb +74 -30
- data/lib/stove/middlewares/chef_authentication.rb +60 -0
- data/lib/stove/middlewares/exceptions.rb +17 -0
- data/lib/stove/mixins/filterable.rb +11 -0
- data/lib/stove/mixins/insideable.rb +13 -0
- data/lib/stove/mixins/instanceable.rb +23 -0
- data/lib/stove/mixins/loggable.rb +32 -0
- data/lib/stove/mixins/optionable.rb +41 -0
- data/lib/stove/mixins/validatable.rb +7 -0
- data/lib/stove/packager.rb +23 -22
- data/lib/stove/plugins/base.rb +35 -0
- data/lib/stove/plugins/git.rb +71 -0
- data/lib/stove/plugins/github.rb +108 -0
- data/lib/stove/plugins/jira.rb +72 -0
- data/lib/stove/rake_task.rb +56 -37
- data/lib/stove/runner.rb +84 -0
- data/lib/stove/util.rb +56 -0
- data/lib/stove/validator.rb +67 -0
- data/lib/stove/version.rb +1 -1
- data/locales/en.yml +231 -0
- data/stove.gemspec +11 -11
- metadata +85 -67
- data/features/changelog.feature +0 -22
- data/features/cli.feature +0 -11
- data/features/devodd.feature +0 -19
- data/features/git.feature +0 -34
- data/features/upload.feature +0 -40
- data/lib/stove/community_site.rb +0 -85
- data/lib/stove/formatter.rb +0 -7
- data/lib/stove/formatter/base.rb +0 -32
- data/lib/stove/formatter/human.rb +0 -9
- data/lib/stove/formatter/silent.rb +0 -10
- data/lib/stove/git.rb +0 -82
- data/lib/stove/github.rb +0 -43
- data/lib/stove/logger.rb +0 -56
- data/lib/stove/uploader.rb +0 -64
- data/spec/support/community_site.rb +0 -33
- 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
|
-
|
18
|
+
# Set the log level
|
19
|
+
Stove.log_level = options[:log_level]
|
16
20
|
|
17
|
-
|
21
|
+
# Parse out the version from ARGV
|
22
|
+
options[:version] = @argv.shift
|
18
23
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
25
|
-
|
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
|
-
|
46
|
-
|
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
|
-
|
50
|
-
|
51
|
-
end
|
76
|
+
opts.separator ''
|
77
|
+
opts.separator 'Plugins:'
|
52
78
|
|
53
|
-
|
54
|
-
|
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
|
-
|
58
|
-
|
59
|
-
options[:github] = v
|
60
|
-
end
|
86
|
+
opts.separator ''
|
87
|
+
opts.separator 'Global Options:'
|
61
88
|
|
62
|
-
|
63
|
-
|
64
|
-
|
89
|
+
opts.on('--locale [LANGUAGE]', 'Change the language to output messages') do |locale|
|
90
|
+
I18n.locale = locale
|
91
|
+
end
|
65
92
|
|
66
|
-
|
67
|
-
|
68
|
-
|
93
|
+
opts.on('--log-level [LEVEL]', 'Set the log verbosity') do |v|
|
94
|
+
options[:log_level] = v
|
95
|
+
end
|
69
96
|
|
70
|
-
|
71
|
-
|
72
|
-
|
97
|
+
opts.on('--category [CATEGORY]', 'Set category for the cookbook') do |v|
|
98
|
+
options[:category] = v
|
99
|
+
end
|
73
100
|
|
74
|
-
|
75
|
-
|
76
|
-
|
101
|
+
opts.on('--path [PATH]', 'Change the path to a cookbook') do |v|
|
102
|
+
options[:path] = v
|
103
|
+
end
|
77
104
|
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
-
|
83
|
-
|
84
|
-
|
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
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
113
|
+
opts.on_tail('-h', '--help', 'Show this message') do
|
114
|
+
puts opts
|
115
|
+
exit
|
116
|
+
end
|
90
117
|
|
91
|
-
|
92
|
-
|
93
|
-
|
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
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
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
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
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
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
data/lib/stove/cookbook.rb
CHANGED
@@ -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
|
-
|
7
|
+
include Mixin::Loggable
|
9
8
|
|
10
|
-
|
9
|
+
require_relative 'cookbook/metadata'
|
11
10
|
|
12
|
-
# The path to this cookbook.
|
13
11
|
#
|
14
|
-
#
|
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
|
-
#
|
41
|
-
|
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 [
|
46
|
-
# the
|
47
|
-
|
48
|
-
|
49
|
-
@
|
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 ||=
|
60
|
-
rescue
|
61
|
-
|
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
|
-
|
78
|
+
URI.join(Community.base_url, 'cookbooks', name)
|
69
79
|
end
|
70
80
|
|
71
|
-
#
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
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
|
-
#
|
84
|
-
|
85
|
-
|
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
|
-
|
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
|
-
|
174
|
-
|
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
|
-
# @
|
181
|
-
|
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
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
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
|
-
|
210
|
-
|
148
|
+
metadata_path = path.join('metadata.rb')
|
149
|
+
contents = File.read(metadata_path)
|
211
150
|
|
212
|
-
|
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
|
-
|
225
|
-
|
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 =
|
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
|