knife-changelog 0.5.20 → 1.0.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.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/.rubocop.yml +31 -3
- data/.travis.yml +2 -2
- data/README.md +2 -0
- data/knife-changelog.gemspec +3 -0
- data/lib/chef/knife/changelog.rb +13 -2
- data/lib/knife/changelog/berksfile.rb +63 -0
- data/lib/knife/changelog/changelog.rb +98 -92
- data/lib/knife/changelog/git.rb +46 -0
- data/lib/knife/changelog/policyfile.rb +65 -0
- data/lib/knife/changelog/version.rb +1 -1
- data/spec/data/Berksfile +9 -0
- data/spec/data/Berksfile.lock +11 -0
- data/spec/data/Policyfile.lock.json +72 -0
- data/spec/data/Policyfile.rb +10 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/unit/changelog_spec.rb +124 -0
- metadata +60 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 705a36f063f6249e387aa5d52cd1784cb38f1ac9
|
4
|
+
data.tar.gz: 521f2a3658ca50040b0f119437fb38d37111fefa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6f32a1750c57c95b8dc9144c4783d95d0d6281bcd76bd2e0e77def896d0b6eaca27d88b4b0f2a5e1eb4dd4aa7ddd856f90f06a2a303b95944cb5c090f6c30898
|
7
|
+
data.tar.gz: 87ad561196aa635e2508eb0871f235d216fd66e5141bb12a10d334c4b72e807e349a68c88e48c24bdff0c519e4fa3d86d579792901b5ecf8c271aa2c53471056
|
data/.gitignore
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
.bundle/
|
2
|
-
*
|
2
|
+
Gemfile.lock*
|
data/.rubocop.yml
CHANGED
@@ -1,5 +1,33 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes.
|
2
|
+
# URISchemes: http, https
|
3
|
+
Metrics/ClassLength:
|
4
|
+
Max: 200
|
3
5
|
|
4
6
|
Metrics/LineLength:
|
5
|
-
Max:
|
7
|
+
Max: 230
|
8
|
+
|
9
|
+
Metrics/MethodLength:
|
10
|
+
Max: 30
|
11
|
+
|
12
|
+
# Cop supports --auto-correct.
|
13
|
+
# Configuration parameters: SupportedStyles, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols.
|
14
|
+
# SupportedStyles: ruby19, ruby19_no_mixed_keys, hash_rockets
|
15
|
+
Style/HashSyntax:
|
16
|
+
EnforcedStyle: ruby19_no_mixed_keys
|
17
|
+
|
18
|
+
Style/Documentation:
|
19
|
+
Enabled: false
|
20
|
+
|
21
|
+
Metrics/AbcSize:
|
22
|
+
Max: 52
|
23
|
+
|
24
|
+
Metrics/PerceivedComplexity:
|
25
|
+
Max: 10
|
26
|
+
|
27
|
+
Metrics/CyclomaticComplexity:
|
28
|
+
Max: 25
|
29
|
+
|
30
|
+
AllCops:
|
31
|
+
Exclude:
|
32
|
+
- bundle/**/*
|
33
|
+
TargetRubyVersion: 2.3
|
data/.travis.yml
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
language: ruby
|
2
2
|
rvm:
|
3
|
-
- 2.
|
3
|
+
- 2.4
|
4
4
|
deploy:
|
5
5
|
provider: rubygems
|
6
6
|
api_key:
|
@@ -8,5 +8,5 @@ deploy:
|
|
8
8
|
gem: knife-changelog
|
9
9
|
on:
|
10
10
|
tags: true
|
11
|
-
repo:
|
11
|
+
repo: criteo/knife-changelog
|
12
12
|
sudo: false # use docker based infra
|
data/README.md
CHANGED
@@ -15,6 +15,8 @@ Options:
|
|
15
15
|
- generate changelogs for some supermarket hosted cookbooks
|
16
16
|
- generate changelogs for all git located cookbooks
|
17
17
|
|
18
|
+
This plugin works in policyfile style repositories or classical repositories with a Berksfile
|
19
|
+
|
18
20
|
## Todos
|
19
21
|
|
20
22
|
- (optionaly) link commit ref to their web page to ease reviews
|
data/knife-changelog.gemspec
CHANGED
@@ -22,10 +22,13 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.add_development_dependency "rake"
|
23
23
|
spec.add_development_dependency "rspec"
|
24
24
|
spec.add_development_dependency "rubocop"
|
25
|
+
spec.add_development_dependency "webmock"
|
26
|
+
spec.add_development_dependency "pry"
|
25
27
|
|
26
28
|
|
27
29
|
spec.add_dependency 'berkshelf'
|
28
30
|
spec.add_dependency 'rest-client'
|
29
31
|
spec.add_dependency 'mixlib-shellout'
|
30
32
|
spec.add_dependency 'chef'
|
33
|
+
spec.add_dependency 'chef-dk'
|
31
34
|
end
|
data/lib/chef/knife/changelog.rb
CHANGED
@@ -10,13 +10,19 @@ class Chef
|
|
10
10
|
deps do
|
11
11
|
require "knife/changelog/version"
|
12
12
|
require "knife/changelog/changelog"
|
13
|
+
require "knife/changelog/policyfile"
|
14
|
+
require "knife/changelog/berksfile"
|
13
15
|
require "berkshelf"
|
14
16
|
end
|
15
17
|
|
16
18
|
def initialize(options)
|
17
19
|
super
|
18
|
-
|
19
|
-
|
20
|
+
@changelog = if File.exists?(config[:policyfile])
|
21
|
+
KnifeChangelog::Changelog::Policyfile.new(config[:policyfile], config)
|
22
|
+
else
|
23
|
+
berksfile = Berkshelf::Berksfile.from_options({})
|
24
|
+
KnifeChangelog::Changelog::Berksfile.new(berksfile.lockfile.locks, config, berksfile.sources)
|
25
|
+
end
|
20
26
|
end
|
21
27
|
|
22
28
|
option :linkify,
|
@@ -46,6 +52,11 @@ class Chef
|
|
46
52
|
:long => '--submodules SUBMODULE[,SUBMODULE]',
|
47
53
|
:description => 'Submoduless to check for changes as well (comma separated)'
|
48
54
|
|
55
|
+
option :policyfile,
|
56
|
+
:long => '--policyfile PATH',
|
57
|
+
:description => 'Link to policyfile, defaults to Policyfile.rb',
|
58
|
+
:default => 'Policyfile.rb'
|
59
|
+
|
49
60
|
|
50
61
|
def run
|
51
62
|
Log.info config
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'changelog'
|
4
|
+
|
5
|
+
class KnifeChangelog
|
6
|
+
class Changelog
|
7
|
+
class Berksfile < Changelog
|
8
|
+
def initialize(locked_versions, config, sources)
|
9
|
+
require 'berkshelf'
|
10
|
+
@locked_versions = locked_versions
|
11
|
+
@sources = sources
|
12
|
+
super(config)
|
13
|
+
end
|
14
|
+
|
15
|
+
def all_cookbooks
|
16
|
+
@locked_versions.keys
|
17
|
+
end
|
18
|
+
|
19
|
+
def new_cookbook?(name)
|
20
|
+
ck_dep(name).nil?
|
21
|
+
end
|
22
|
+
|
23
|
+
# return true if cookbook is downloaded from supermarket
|
24
|
+
def supermarket?(name)
|
25
|
+
# here is berkshelf "expressive" way to say cookbook
|
26
|
+
# comes from supermarket
|
27
|
+
ck_dep(name).location.is_a?(NilClass)
|
28
|
+
end
|
29
|
+
|
30
|
+
# return true if cookbook is downloaded from git
|
31
|
+
def git?(name)
|
32
|
+
ck_dep(name).location.is_a?(Berkshelf::GitLocation)
|
33
|
+
end
|
34
|
+
|
35
|
+
# return true if cookbook is downloaded from local path
|
36
|
+
def local?(name)
|
37
|
+
ck_dep(name).location.is_a?(Berkshelf::PathLocation)
|
38
|
+
end
|
39
|
+
|
40
|
+
# return a Changelog::Location for this cookbook
|
41
|
+
def git_location(name)
|
42
|
+
raise "#{name} has not a git location" unless git?(name)
|
43
|
+
Location.from_berk_git_location(ck_dep(name).location)
|
44
|
+
end
|
45
|
+
|
46
|
+
# return a list of supermarket uri for a given cookbook
|
47
|
+
# example: [ 'https://supermarket.chef.io' ]
|
48
|
+
def supermarkets_for(_name)
|
49
|
+
@sources.map(&:uri)
|
50
|
+
end
|
51
|
+
|
52
|
+
def guess_version_for(name)
|
53
|
+
@locked_versions[name].locked_version.to_s
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def ck_dep(name)
|
59
|
+
@locked_versions[name]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -1,28 +1,77 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
require 'chef/log'
|
3
3
|
require 'chef/knife'
|
4
|
+
require 'chef/version_class'
|
4
5
|
require 'rest-client'
|
5
6
|
require 'json'
|
7
|
+
require_relative 'git'
|
6
8
|
|
7
9
|
class KnifeChangelog
|
8
10
|
class Changelog
|
9
|
-
|
10
|
-
|
11
|
+
|
12
|
+
Location = Struct.new(:uri, :revision, :rev_parse) do
|
13
|
+
# todo move this method to Changelog::Berkshelf
|
14
|
+
def self.from_berk_git_location(location)
|
15
|
+
Location.new(location.uri,
|
16
|
+
location.revision.strip,
|
17
|
+
location.instance_variable_get(:@rev_parse))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(config)
|
11
22
|
@tmp_prefix = 'knife-changelog'
|
12
|
-
@locked_versions = locked_versions
|
13
23
|
@config = config
|
14
24
|
@tmp_dirs = []
|
15
|
-
@sources = sources
|
16
|
-
if sources.empty? # preserve api compat
|
17
|
-
@sources = [ Berkshelf::Source.new("https://supermarket.chef.io") ]
|
18
|
-
end
|
19
25
|
end
|
20
26
|
|
27
|
+
# returns a list of all cookbooks names
|
28
|
+
def all_cookbooks
|
29
|
+
raise NotImplementedError
|
30
|
+
end
|
31
|
+
|
32
|
+
# return true if cookbook is not already listed as dependency
|
33
|
+
def new_cookbook?(name)
|
34
|
+
raise NotImplementedError
|
35
|
+
end
|
36
|
+
|
37
|
+
# return true if cookbook is downloaded from supermarket
|
38
|
+
def supermarket?(name)
|
39
|
+
raise NotImplementedError
|
40
|
+
end
|
41
|
+
|
42
|
+
# return true if cookbook is downloaded from git
|
43
|
+
def git?(name)
|
44
|
+
raise NotImplementedError
|
45
|
+
end
|
46
|
+
|
47
|
+
# return true if cookbook is downloaded from local path
|
48
|
+
def local?(name)
|
49
|
+
raise NotImplementedError
|
50
|
+
end
|
51
|
+
|
52
|
+
# return a Changelog::Location for a given cookbook
|
53
|
+
def git_location(name)
|
54
|
+
raise NotImplementedError
|
55
|
+
end
|
56
|
+
|
57
|
+
# return a list of supermarket uri for a given cookbook
|
58
|
+
# example: [ 'https://supermarket.chef.io' ]
|
59
|
+
def supermarkets_for(name)
|
60
|
+
raise NotImplementedError
|
61
|
+
end
|
62
|
+
|
63
|
+
# return current locked version for a given cookbook
|
64
|
+
def guess_version_for(name)
|
65
|
+
raise NotImplementedError
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
|
21
70
|
def run(cookbooks)
|
22
71
|
changelog = []
|
23
72
|
begin
|
24
73
|
if cookbooks.empty? and @config[:allow_update_all]
|
25
|
-
cks =
|
74
|
+
cks = all_cookbooks
|
26
75
|
else
|
27
76
|
cks = cookbooks
|
28
77
|
end
|
@@ -44,47 +93,29 @@ class KnifeChangelog
|
|
44
93
|
|
45
94
|
def clean
|
46
95
|
@tmp_dirs.each do |dir|
|
47
|
-
FileUtils.
|
96
|
+
FileUtils.rm_rf dir
|
48
97
|
end
|
49
98
|
end
|
50
99
|
|
51
|
-
def
|
52
|
-
@locked_versions[name]
|
53
|
-
end
|
54
|
-
|
55
|
-
def ck_location(name)
|
56
|
-
begin
|
57
|
-
ck_dep(name).location
|
58
|
-
rescue
|
59
|
-
puts "Failed to get location for cookbook: #{name}"
|
60
|
-
raise
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def guess_version_for(name)
|
65
|
-
@locked_versions[name].locked_version.to_s
|
66
|
-
end
|
67
|
-
|
68
|
-
def handle_new_cookbook(name)
|
100
|
+
def handle_new_cookbook
|
69
101
|
stars = '**' if @config[:markdown]
|
70
102
|
["#{stars}Cookbook was not in the berksfile#{stars}"]
|
71
103
|
end
|
72
104
|
|
73
|
-
def execute(name, submodule=false)
|
105
|
+
def execute(name, submodule = false)
|
74
106
|
version_change, changelog = if submodule
|
75
107
|
handle_submodule(name)
|
76
|
-
elsif
|
77
|
-
[
|
108
|
+
elsif new_cookbook?(name)
|
109
|
+
['', handle_new_cookbook]
|
78
110
|
else
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
when Berkshelf::PathLocation
|
111
|
+
case true
|
112
|
+
when supermarket?(name)
|
113
|
+
handle_source(name)
|
114
|
+
when git?(name)
|
115
|
+
handle_git(name, git_location(name))
|
116
|
+
when local?(name)
|
86
117
|
Chef::Log.debug "path location are always at the last version"
|
87
|
-
[
|
118
|
+
['', '']
|
88
119
|
else
|
89
120
|
raise "Cannot handle #{loc.class} yet"
|
90
121
|
end
|
@@ -105,12 +136,14 @@ class KnifeChangelog
|
|
105
136
|
end
|
106
137
|
|
107
138
|
def get_from_supermarket_sources(name)
|
108
|
-
|
139
|
+
supermarkets_for(name).map do |uri|
|
109
140
|
begin
|
141
|
+
# TODO: we could call /universe endpoint once
|
142
|
+
# instead of calling /api/v1/cookbooks/ for each cookbook
|
110
143
|
RestClient::Request.execute(
|
111
|
-
url: "#{
|
144
|
+
url: "#{uri}/api/v1/cookbooks/#{name}",
|
112
145
|
method: :get,
|
113
|
-
verify_ssl: false #
|
146
|
+
verify_ssl: false # TODO make this configurable
|
114
147
|
)
|
115
148
|
rescue => e
|
116
149
|
Chef::Log.debug "Error fetching package from supermarket #{e.class.name} #{e.message}"
|
@@ -134,35 +167,24 @@ class KnifeChangelog
|
|
134
167
|
.last
|
135
168
|
end
|
136
169
|
|
137
|
-
def handle_source(name
|
170
|
+
def handle_source(name)
|
138
171
|
url = get_from_supermarket_sources(name)
|
139
172
|
raise "No source found in supermarket for cookbook '#{name}'" unless url
|
140
173
|
Chef::Log.debug("Using #{url} as source url")
|
141
174
|
case url.strip
|
142
175
|
when /(gitlab.*|github).com\/([^.]+)(.git)?/
|
143
176
|
url = "https://#{$1}.com/#{$2.chomp('/')}.git"
|
144
|
-
|
145
|
-
:git => url,
|
146
|
-
:revision => guess_version_for(name),
|
147
|
-
}
|
148
|
-
location = Berkshelf::GitLocation.new dep, options
|
177
|
+
location = Location.new(url, guess_version_for(name), 'master')
|
149
178
|
handle_git(name, location)
|
150
179
|
else
|
151
180
|
fail "External url #{url} points to unusable location! (cookbook: #{name})"
|
152
181
|
end
|
153
182
|
end
|
154
183
|
|
155
|
-
def
|
156
|
-
|
157
|
-
revlist = Mixlib::ShellOut.new("git rev-list --quiet #{revision}", :cwd => dir)
|
158
|
-
revlist.run_command
|
159
|
-
not revlist.error?
|
160
|
-
end
|
161
|
-
|
162
|
-
def detect_cur_revision(name, dir, rev)
|
163
|
-
unless revision_exists?(dir, rev)
|
184
|
+
def detect_cur_revision(name, rev, git)
|
185
|
+
unless git.revision_exists?(rev)
|
164
186
|
prefixed_rev = 'v' + rev
|
165
|
-
return prefixed_rev if revision_exists?(
|
187
|
+
return prefixed_rev if git.revision_exists?(prefixed_rev)
|
166
188
|
fail "#{rev} is not an existing revision (#{name}), not a tag/commit/branch name."
|
167
189
|
end
|
168
190
|
rev
|
@@ -178,43 +200,38 @@ class KnifeChangelog
|
|
178
200
|
subm_revision.error!
|
179
201
|
revision = subm_revision.stdout.strip.split(' ').first
|
180
202
|
revision.gsub!(/^\+/, '')
|
181
|
-
loc =
|
203
|
+
loc = Location.new(url, revision, 'master')
|
182
204
|
handle_git(name, loc)
|
183
205
|
end
|
184
206
|
|
207
|
+
# take cookbook name and Changelog::Location instance
|
185
208
|
def handle_git(name, location)
|
186
|
-
|
209
|
+
# todo: remove this compat check
|
210
|
+
raise "should be a location" unless location.is_a?(Changelog::Location)
|
211
|
+
git = Git.new(@tmp_prefix, location.uri)
|
212
|
+
@tmp_dirs << git.shallow_clone
|
187
213
|
|
188
|
-
rev_parse = location.
|
189
|
-
cur_rev = location.revision
|
190
|
-
|
191
|
-
ls_tree = Mixlib::ShellOut.new("git ls-tree -r #{rev_parse}", :cwd => tmp_dir)
|
192
|
-
ls_tree.run_command
|
193
|
-
changelog_file = ls_tree.stdout.lines.find { |line| line =~ /\s(changelog.*$)/i }
|
214
|
+
rev_parse = location.rev_parse
|
215
|
+
cur_rev = detect_cur_revision(name, location.revision, git)
|
216
|
+
changelog_file = git.files(rev_parse).find { |line| line =~ /\s(changelog.*$)/i }
|
194
217
|
changelog = if changelog_file and !@config[:ignore_changelog_file]
|
195
218
|
Chef::Log.info "Found changelog file : " + $1
|
196
|
-
generate_from_changelog_file($1, cur_rev, rev_parse,
|
219
|
+
generate_from_changelog_file($1, cur_rev, rev_parse, git)
|
197
220
|
end
|
198
|
-
changelog ||= generate_from_git_history(
|
199
|
-
[
|
221
|
+
changelog ||= generate_from_git_history(git, location, cur_rev, rev_parse)
|
222
|
+
["#{cur_rev}->#{rev_parse}", changelog]
|
200
223
|
end
|
201
224
|
|
202
|
-
def generate_from_changelog_file(filename, current_rev, rev_parse,
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
lines.
|
208
|
-
collect {|line| $1.strip if line =~ /^{\+(.*)\+}$/}.compact.
|
209
|
-
map { |line| line.gsub(/^#+(.*)$/, "\\1\n---")}. # replace section by smaller header
|
210
|
-
select { |line| !(line =~ /^===+/)}.compact # remove header lines
|
225
|
+
def generate_from_changelog_file(filename, current_rev, rev_parse, git)
|
226
|
+
ch = git.diff(filename, current_rev, rev_parse)
|
227
|
+
.collect { |line| $1.strip if line =~ /^{\+(.*)\+}$/ }.compact
|
228
|
+
.map { |line| line.gsub(/^#+(.*)$/, "\\1\n---")} # replace section by smaller header
|
229
|
+
.select { |line| !(line =~ /^===+/)}.compact # remove header lines
|
211
230
|
ch.empty? ? nil : ch
|
212
231
|
end
|
213
232
|
|
214
|
-
def generate_from_git_history(
|
215
|
-
|
216
|
-
log.run_command
|
217
|
-
c = log.stdout.lines
|
233
|
+
def generate_from_git_history(git, location, current_rev, rev_parse)
|
234
|
+
c = git.log(current_rev, rev_parse)
|
218
235
|
n = https_url(location)
|
219
236
|
c = linkify(n, c) if @config[:linkify] and n
|
220
237
|
c = c.map { |line| "* " + line } if @config[:markdown]
|
@@ -241,16 +258,5 @@ class KnifeChangelog
|
|
241
258
|
"%s/%s" % [$1,$2]
|
242
259
|
end
|
243
260
|
end
|
244
|
-
|
245
|
-
def shallow_clone(tmp_prefix, uri)
|
246
|
-
Chef::Log.debug "Cloning #{uri} in #{tmp_prefix}"
|
247
|
-
dir = Dir.mktmpdir(tmp_prefix)
|
248
|
-
@tmp_dirs << dir
|
249
|
-
clone = Mixlib::ShellOut.new("git clone --bare #{uri} bare-clone", :cwd => dir)
|
250
|
-
clone.run_command
|
251
|
-
clone.error!
|
252
|
-
::File.join(dir, 'bare-clone')
|
253
|
-
end
|
254
|
-
|
255
261
|
end
|
256
262
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class KnifeChangelog
|
2
|
+
class Git
|
3
|
+
attr_accessor :tmp_prefix, :uri
|
4
|
+
|
5
|
+
def initialize(tmp_prefix, uri)
|
6
|
+
@tmp_prefix = tmp_prefix
|
7
|
+
@uri = uri
|
8
|
+
end
|
9
|
+
|
10
|
+
def shallow_clone
|
11
|
+
Chef::Log.debug "Cloning #{uri} in #{tmp_prefix}"
|
12
|
+
dir = Dir.mktmpdir(tmp_prefix)
|
13
|
+
clone = Mixlib::ShellOut.new("git clone --bare #{uri} bare-clone", cwd: dir)
|
14
|
+
clone.run_command
|
15
|
+
clone.error!
|
16
|
+
@clone_dir = ::File.join(dir, 'bare-clone')
|
17
|
+
@clone_dir
|
18
|
+
end
|
19
|
+
|
20
|
+
def files(rev_parse)
|
21
|
+
ls_tree = Mixlib::ShellOut.new("git ls-tree -r #{rev_parse}", cwd: @clone_dir)
|
22
|
+
ls_tree.run_command
|
23
|
+
ls_tree.error!
|
24
|
+
ls_tree.stdout.lines.map(&:strip)
|
25
|
+
end
|
26
|
+
|
27
|
+
def diff(filename, current_rev, rev_parse)
|
28
|
+
diff = Mixlib::ShellOut.new("git diff #{current_rev}..#{rev_parse} --word-diff -- #{filename}", cwd: @clone_dir)
|
29
|
+
diff.run_command
|
30
|
+
diff.stdout.lines
|
31
|
+
end
|
32
|
+
|
33
|
+
def log(current_rev, rev_parse)
|
34
|
+
log = Mixlib::ShellOut.new("git log --no-merges --abbrev-commit --pretty=oneline #{current_rev}..#{rev_parse}", cwd: @clone_dir)
|
35
|
+
log.run_command
|
36
|
+
log.stdout.lines
|
37
|
+
end
|
38
|
+
|
39
|
+
def revision_exists?(revision)
|
40
|
+
Chef::Log.debug "Testing existence of #{revision}"
|
41
|
+
revlist = Mixlib::ShellOut.new("git rev-list --quiet #{revision}", cwd: @clone_dir)
|
42
|
+
revlist.run_command
|
43
|
+
!revlist.error?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'changelog'
|
4
|
+
|
5
|
+
class KnifeChangelog
|
6
|
+
class Changelog
|
7
|
+
class Policyfile < Changelog
|
8
|
+
attr_reader :policy, :lock
|
9
|
+
|
10
|
+
def initialize(policyfile_path, config)
|
11
|
+
require 'chef-dk'
|
12
|
+
require 'chef-dk/policyfile_compiler'
|
13
|
+
lock_path = policyfile_path.gsub(/.rb$/, '.lock.json')
|
14
|
+
@policy = ChefDK::PolicyfileCompiler.evaluate(File.read(policyfile_path), policyfile_path)
|
15
|
+
@lock = ChefDK::PolicyfileLock.new(policy.storage_config).build_from_lock_data(JSON.parse(File.read(lock_path)))
|
16
|
+
super(config)
|
17
|
+
end
|
18
|
+
|
19
|
+
def all_cookbooks
|
20
|
+
policy.all_possible_dep_names
|
21
|
+
end
|
22
|
+
|
23
|
+
# return true if cookbook is not already listed as dependency
|
24
|
+
def new_cookbook?(name)
|
25
|
+
policy.send(:best_source_for, name).nil?
|
26
|
+
end
|
27
|
+
|
28
|
+
# return true if cookbook is downloaded from supermarket
|
29
|
+
def supermarket?(name)
|
30
|
+
# it's hard to get location_specs for supermarket cookbooks without having policy_compiler starting to download all cookbooks
|
31
|
+
# in the meantime, we procede by elimination
|
32
|
+
!(git?(name) || local?(name))
|
33
|
+
end
|
34
|
+
|
35
|
+
def guess_version_for(name)
|
36
|
+
lock.solution_dependencies.cookbook_dependencies.keys.find { |dep| dep.name == name }.version
|
37
|
+
end
|
38
|
+
|
39
|
+
# return true if cookbook is downloaded from git
|
40
|
+
def git?(name)
|
41
|
+
# cookbook_location_specs contains only cookbooks refered via git and path
|
42
|
+
policy.cookbook_location_specs[name] && policy.cookbook_location_specs[name].source_type == :git
|
43
|
+
end
|
44
|
+
|
45
|
+
# return true if cookbook is downloaded from local path
|
46
|
+
def local?(name)
|
47
|
+
# cookbook_location_specs contains only cookbooks refered via git and path
|
48
|
+
policy.cookbook_location_specs[name] && policy.cookbook_location_specs[name].source_type.nil?
|
49
|
+
end
|
50
|
+
|
51
|
+
# return a Changelog::Location for a given cookbook
|
52
|
+
def git_location(name)
|
53
|
+
return nil unless git?(name)
|
54
|
+
spec = lock.cookbook_locks[name].source_options
|
55
|
+
Location.new(spec[:git], spec[:revision], spec[:branch])
|
56
|
+
end
|
57
|
+
|
58
|
+
# return a list of supermarket uri for a given cookbook
|
59
|
+
# example: [ 'https://supermarket.chef.io' ]
|
60
|
+
def supermarkets_for(name)
|
61
|
+
[policy.send(:best_source_for, name).uri]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/spec/data/Berksfile
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
{
|
2
|
+
"revision_id": "61fc0aa9ad414968cced34e8cf88189559b9ce8ec69d1514714822e77b0a2449",
|
3
|
+
"name": "test_policy",
|
4
|
+
"run_list": [
|
5
|
+
"recipe[uptodate::default]"
|
6
|
+
],
|
7
|
+
"cookbook_locks": {
|
8
|
+
"uptodate": {
|
9
|
+
"version": "1.0.0",
|
10
|
+
"identifier": "c29076b3a9aae74c7edd9790343eb1502e2fa8f2",
|
11
|
+
"dotted_decimal_identifier": "54764984976648935.21531588425692222.194957930375410",
|
12
|
+
"cache_key": "uptodate-1.0.0-mysupermarket2.io",
|
13
|
+
"origin": "https://mysupermarket2.io:443/api/v1/cookbooks/uptodate/versions/1.0.0/download",
|
14
|
+
"source_options": {
|
15
|
+
"artifactserver": "https://mysupermarket2.io:443/api/v1/cookbooks/uptodate/versions/1.0.0/download",
|
16
|
+
"version": "1.0.0"
|
17
|
+
}
|
18
|
+
},
|
19
|
+
"outdated1": {
|
20
|
+
"version": "1.0.0",
|
21
|
+
"identifier": "c29076b3a9aae74c7edd9790343eb1502e2fa8f1",
|
22
|
+
"dotted_decimal_identifier": "54764984976648935.21531588425692222.194957930375411",
|
23
|
+
"cache_key": "outdated1-1.0.0-mysupermarket2.io",
|
24
|
+
"origin": "https://mysupermarket2.io:443/api/v1/cookbooks/outdated1/versions/1.0.0/download",
|
25
|
+
"source_options": {
|
26
|
+
"artifactserver": "https://mysupermarket2.io:443/api/v1/cookbooks/outdated1/versions/1.0.0/download",
|
27
|
+
"version": "1.0.0"
|
28
|
+
}
|
29
|
+
},
|
30
|
+
"second_out_of_date": {
|
31
|
+
"version": "1.0.0",
|
32
|
+
"identifier": "c29076b3a9aae74c7edd9790343eb1502e2fa8f1",
|
33
|
+
"dotted_decimal_identifier": "54764984976648935.21531588425692222.194957930375411",
|
34
|
+
"cache_key": "outdated1-1.0.0-mysupermarket2.io",
|
35
|
+
"origin": "https://mysupermarket2.io:443/api/v1/cookbooks/second_out_of_date/versions/1.0.0/download",
|
36
|
+
"source_options": {
|
37
|
+
"artifactserver": "https://mysupermarket2.io:443/api/v1/cookbooks/second_out_of_date/versions/1.0.0/download",
|
38
|
+
"version": "1.0.0"
|
39
|
+
}
|
40
|
+
}
|
41
|
+
},
|
42
|
+
"default_attributes": {
|
43
|
+
|
44
|
+
},
|
45
|
+
"override_attributes": {
|
46
|
+
|
47
|
+
},
|
48
|
+
"solution_dependencies": {
|
49
|
+
"Policyfile": [
|
50
|
+
[
|
51
|
+
"uptodate",
|
52
|
+
"= 1.0.0"
|
53
|
+
],
|
54
|
+
[
|
55
|
+
"outdated1",
|
56
|
+
"= 1.0.0"
|
57
|
+
],
|
58
|
+
[
|
59
|
+
"second_out_of_date",
|
60
|
+
"= 1.0.0"
|
61
|
+
]
|
62
|
+
],
|
63
|
+
"dependencies": {
|
64
|
+
"uptodate (1.0.0)": [
|
65
|
+
],
|
66
|
+
"outdated1 (1.0.0)": [
|
67
|
+
],
|
68
|
+
"second_out_of_date (1.0.0)": [
|
69
|
+
]
|
70
|
+
}
|
71
|
+
}
|
72
|
+
}
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../lib/knife/changelog/changelog.rb'
|
4
|
+
require_relative '../lib/knife/changelog/berksfile'
|
5
|
+
require_relative '../lib/knife/changelog/policyfile'
|
6
|
+
|
7
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
8
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
9
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
10
|
+
# loaded once.
|
11
|
+
#
|
12
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
13
|
+
RSpec.configure do |config|
|
14
|
+
config.run_all_when_everything_filtered = true
|
15
|
+
config.filter_run :focus
|
16
|
+
|
17
|
+
# Run specs in random order to surface order dependencies. If you find an
|
18
|
+
# order dependency and want to debug it, you can fix the order by providing
|
19
|
+
# the seed, which is printed after each run.
|
20
|
+
# --seed 1234
|
21
|
+
config.order = 'random'
|
22
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../spec_helper'
|
4
|
+
require 'webmock/rspec'
|
5
|
+
require 'berkshelf'
|
6
|
+
|
7
|
+
WebMock.disable_net_connect!
|
8
|
+
|
9
|
+
RSpec.shared_examples 'changelog generation' do
|
10
|
+
# this supposes that "changelog" is an instance of KnifeChangelog::Changelog
|
11
|
+
it 'detects basic changelog' do
|
12
|
+
changelog_txt = changelog.run(%w[new_cookbook uptodate outdated1 second_out_of_date])
|
13
|
+
expect(changelog_txt).to match(/commit in outdated1/)
|
14
|
+
expect(changelog_txt).to match(/commit in second_out_of_date/)
|
15
|
+
expect(changelog_txt).not_to match(/uptodate/)
|
16
|
+
expect(changelog_txt).to match(/new_cookbook: \n.*\nCookbook was not/)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe KnifeChangelog::Changelog do
|
21
|
+
before(:each) do
|
22
|
+
stub_request(:get, %r{https://mysupermarket.io/api/v1/cookbooks/})
|
23
|
+
.to_return(status: 404, body: '{}')
|
24
|
+
|
25
|
+
mock_supermarket('uptodate', %w[1.0.0])
|
26
|
+
mock_supermarket('outdated1', %w[1.0.0 1.1.0])
|
27
|
+
# TODO: we should make second_out_of_date a git location
|
28
|
+
mock_supermarket('second_out_of_date', %w[1.0.0 1.2.0])
|
29
|
+
|
30
|
+
mock_universe('https://mysupermarket2.io', uptodate: %w[1.0.0], outdated1: %w[1.0.0 1.1.0], second_out_of_date: %w[1.0.0 1.2.0])
|
31
|
+
mock_universe('https://mysupermarket.io', {})
|
32
|
+
|
33
|
+
mock_git('second_out_of_date', <<-EOH)
|
34
|
+
aaaaaa commit in second_out_of_date
|
35
|
+
bbbbbb bugfix in second_out_of_date
|
36
|
+
EOH
|
37
|
+
mock_git('outdated1', <<-EOH)
|
38
|
+
aaaaaa commit in outdated1
|
39
|
+
bbbbbb bugfix in outdated1
|
40
|
+
EOH
|
41
|
+
mock_git('uptodate', '')
|
42
|
+
end
|
43
|
+
|
44
|
+
def mock_git(name, changelog)
|
45
|
+
expect(KnifeChangelog::Git).to receive(:new)
|
46
|
+
.with(anything, /#{name}(.git|$)/)
|
47
|
+
.and_return(double(name,
|
48
|
+
shallow_clone: '/tmp/randomdir12345',
|
49
|
+
revision_exists?: true,
|
50
|
+
files: [],
|
51
|
+
log: changelog.split("\n")))
|
52
|
+
end
|
53
|
+
|
54
|
+
def mock_supermarket(name, versions)
|
55
|
+
stub_request(:get, %r{https://mysupermarket2.io/api/v1/cookbooks/#{name}})
|
56
|
+
.to_return(status: 200, body: supermarket_versions(name, versions))
|
57
|
+
end
|
58
|
+
|
59
|
+
def supermarket_versions(name, versions)
|
60
|
+
{
|
61
|
+
name: name,
|
62
|
+
maintainer: 'Linus',
|
63
|
+
description: 'small project on the side',
|
64
|
+
category: 'Operating System',
|
65
|
+
source_url: "https://github.com/chef-cookbooks/#{name}",
|
66
|
+
versions: []
|
67
|
+
}.tap do |json|
|
68
|
+
versions.each do |v|
|
69
|
+
json[:versions] << "https://source.io/#{name}/#{v}"
|
70
|
+
end
|
71
|
+
end.to_json
|
72
|
+
end
|
73
|
+
|
74
|
+
def mock_universe(supermarket_url, cookbooks)
|
75
|
+
universe = cookbooks.transform_values do |versions|
|
76
|
+
versions.map do |v|
|
77
|
+
[v, {
|
78
|
+
location_type: 'opscode',
|
79
|
+
location_path: "#{supermarket_url}/api/v1",
|
80
|
+
download_url: "#{supermarket_url}/api/v1/cookbooks/insertnamehere/versions/#{v}/download",
|
81
|
+
dependencies: {}
|
82
|
+
}]
|
83
|
+
end.to_h
|
84
|
+
end
|
85
|
+
stub_request(:get, "#{supermarket_url}/universe")
|
86
|
+
.to_return(status: 200, body: universe.to_json)
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'in Berksfile mode' do
|
90
|
+
let(:berksfile) do
|
91
|
+
Berkshelf::Berksfile.from_options(
|
92
|
+
berksfile: File.join(File.dirname(__FILE__), '../data/Berksfile')
|
93
|
+
)
|
94
|
+
end
|
95
|
+
|
96
|
+
let(:changelog) do
|
97
|
+
KnifeChangelog::Changelog::Berksfile.new(berksfile.lockfile.locks, {}, berksfile.sources)
|
98
|
+
end
|
99
|
+
|
100
|
+
include_examples 'changelog generation'
|
101
|
+
end
|
102
|
+
|
103
|
+
context 'in policyfile mode' do
|
104
|
+
let(:policyfile_path) { File.join(File.dirname(__FILE__), '../data/Policyfile.rb') }
|
105
|
+
|
106
|
+
let(:changelog) do
|
107
|
+
KnifeChangelog::Changelog::Policyfile.new(policyfile_path, {})
|
108
|
+
end
|
109
|
+
|
110
|
+
before(:each) do
|
111
|
+
allow(changelog.policy).to receive(:cache_fixed_version_cookbooks)
|
112
|
+
end
|
113
|
+
|
114
|
+
include_examples 'changelog generation'
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class Hash
|
119
|
+
unless Chef::Version.new(RUBY_VERSION) >= Chef::Version.new('2.4')
|
120
|
+
def transform_values
|
121
|
+
map { |k, v| [k, (yield v)] }.to_h
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: knife-changelog
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gregoire Seux
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-10-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -66,6 +66,34 @@ dependencies:
|
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: webmock
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pry
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
69
97
|
- !ruby/object:Gem::Dependency
|
70
98
|
name: berkshelf
|
71
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,6 +150,20 @@ dependencies:
|
|
122
150
|
- - ">="
|
123
151
|
- !ruby/object:Gem::Version
|
124
152
|
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: chef-dk
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :runtime
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
125
167
|
description: ''
|
126
168
|
email:
|
127
169
|
- kamaradclimber@gmail.com
|
@@ -138,10 +180,19 @@ files:
|
|
138
180
|
- Rakefile
|
139
181
|
- knife-changelog.gemspec
|
140
182
|
- lib/chef/knife/changelog.rb
|
183
|
+
- lib/knife/changelog/berksfile.rb
|
141
184
|
- lib/knife/changelog/changelog.rb
|
185
|
+
- lib/knife/changelog/git.rb
|
186
|
+
- lib/knife/changelog/policyfile.rb
|
142
187
|
- lib/knife/changelog/version.rb
|
143
188
|
- resources/Berksfile
|
144
189
|
- resources/Berksfile.lock
|
190
|
+
- spec/data/Berksfile
|
191
|
+
- spec/data/Berksfile.lock
|
192
|
+
- spec/data/Policyfile.lock.json
|
193
|
+
- spec/data/Policyfile.rb
|
194
|
+
- spec/spec_helper.rb
|
195
|
+
- spec/unit/changelog_spec.rb
|
145
196
|
homepage: https://github.com/kamaradclimber/knife-changelog
|
146
197
|
licenses:
|
147
198
|
- MIT
|
@@ -166,4 +217,10 @@ rubygems_version: 2.6.13
|
|
166
217
|
signing_key:
|
167
218
|
specification_version: 4
|
168
219
|
summary: Facilitate access to cookbooks changelog
|
169
|
-
test_files:
|
220
|
+
test_files:
|
221
|
+
- spec/data/Berksfile
|
222
|
+
- spec/data/Berksfile.lock
|
223
|
+
- spec/data/Policyfile.lock.json
|
224
|
+
- spec/data/Policyfile.rb
|
225
|
+
- spec/spec_helper.rb
|
226
|
+
- spec/unit/changelog_spec.rb
|