knife-changelog 1.0.6 → 1.0.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3a5811fea619f544617e97994433350e5c0206b46902d435a4320f5ef057e4e8
4
- data.tar.gz: 77d0091e020ba853008dec8b603f3ba3c7e4c4181532edfbf8456e0d76954a33
3
+ metadata.gz: 69172c86c7b7796d3ec52bc1738688d33dcef55ccdf98d4f171cff25b9ee03a1
4
+ data.tar.gz: 94329ddcd6763e6d6ab8d510ce5e06850f93798e026405bf3d114fd51e13a7cf
5
5
  SHA512:
6
- metadata.gz: eabfd3352b56cf74f7e4c3a55b9acf7ac3b20786f837b9bb9cd228e6f343411bb1d976759969f26869044a030e2f86173152d6f8b7848d292ab4a1d4c2a1eb60
7
- data.tar.gz: 8f7c33a094d3c964dfaf2e7102ba05569077e2a49658e8932bd4657d0e90c35a8e45e5b1c663529d935238b1761a0df40b5fba9f586504eb3cfa3bba2fd1275d
6
+ metadata.gz: 2eef04a07df70eae15e9d5b8158c5956fb7782e5ad678545d65164b027aacf17e80c17d2efd9100644b46aed638b85f6cdd437f0877a31d7041351d7a8b3f8f6
7
+ data.tar.gz: 43a215deba3fae621e9c156acfbc8a0ebb8101a94c15932e355fefbfba6532457fe31f25bd277c9e054bdcd8afcd868270e78ac8155780dee75220d06069bb9a
@@ -27,7 +27,13 @@ Metrics/PerceivedComplexity:
27
27
  Metrics/CyclomaticComplexity:
28
28
  Max: 25
29
29
 
30
+ Metrics/BlockLength:
31
+ Max: 20
32
+ Exclude:
33
+ - "spec/**/*"
34
+
30
35
  AllCops:
31
36
  Exclude:
32
37
  - bundle/**/*
38
+ - spec/data/Policyfile.rb
33
39
  TargetRubyVersion: 2.3
@@ -1,34 +1,36 @@
1
- # coding: utf-8
1
+ # frozen_string_literal: true
2
+
2
3
  lib = File.expand_path('../lib', __FILE__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'knife/changelog/version'
5
6
 
6
7
  Gem::Specification.new do |spec|
7
- spec.name = "knife-changelog"
8
+ spec.name = 'knife-changelog'
8
9
  spec.version = Knife::Changelog::VERSION
9
- spec.authors = ["Gregoire Seux"]
10
- spec.email = ["kamaradclimber@gmail.com"]
11
- spec.summary = %q{Facilitate access to cookbooks changelog}
12
- spec.description = %q{}
13
- spec.homepage = "https://github.com/kamaradclimber/knife-changelog"
14
- spec.license = "MIT"
10
+ spec.authors = ['Gregoire Seux']
11
+ spec.email = ['kamaradclimber@gmail.com']
12
+ spec.summary = 'Facilitate access to cookbooks changelog'
13
+ spec.description = ''
14
+ spec.homepage = 'https://github.com/kamaradclimber/knife-changelog'
15
+ spec.license = 'MIT'
15
16
 
16
17
  spec.files = `git ls-files -z`.split("\x0")
17
18
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
19
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
- spec.require_paths = ["lib"]
20
-
21
- spec.add_development_dependency "bundler", "~> 1.6"
22
- spec.add_development_dependency "rake"
23
- spec.add_development_dependency "rspec"
24
- spec.add_development_dependency "rubocop"
25
- spec.add_development_dependency "webmock"
26
- spec.add_development_dependency "pry"
20
+ spec.require_paths = ['lib']
27
21
 
22
+ spec.add_development_dependency 'bundler', '~> 1.6'
23
+ spec.add_development_dependency 'pry'
24
+ spec.add_development_dependency 'rake'
25
+ spec.add_development_dependency 'rspec'
26
+ spec.add_development_dependency 'rubocop'
27
+ spec.add_development_dependency 'webmock'
28
28
 
29
29
  spec.add_dependency 'berkshelf'
30
- spec.add_dependency 'rest-client'
31
- spec.add_dependency 'mixlib-shellout'
32
30
  spec.add_dependency 'chef'
33
31
  spec.add_dependency 'chef-dk'
32
+ spec.add_dependency 'deep_merge'
33
+ spec.add_dependency 'git'
34
+ spec.add_dependency 'mixlib-shellout'
35
+ spec.add_dependency 'rest-client'
34
36
  end
@@ -1,68 +1,68 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'chef/knife'
2
4
  require 'mixlib/shellout'
3
5
 
4
6
  class Chef
5
7
  class Knife
6
8
  class Changelog < Knife
7
-
8
9
  banner 'knife changelog COOKBOOK [COOKBOOK ...]'
9
10
 
10
11
  deps do
11
- require "knife/changelog/version"
12
- require "knife/changelog/changelog"
13
- require "knife/changelog/policyfile"
14
- require "knife/changelog/berksfile"
15
- require "berkshelf"
12
+ require 'knife/changelog/version'
13
+ require 'knife/changelog/changelog'
14
+ require 'knife/changelog/berksfile'
15
+ require 'berkshelf'
16
+ require 'policyfile'
16
17
  end
17
18
 
18
19
  option :linkify,
19
- :short => '-l',
20
- :long => '--linkify',
21
- :description => 'add markdown links where relevant',
22
- :boolean => true
20
+ short: '-l',
21
+ long: '--linkify',
22
+ description: 'add markdown links where relevant',
23
+ boolean: true
23
24
 
24
25
  option :markdown,
25
- :short => '-m',
26
- :long => '--markdown',
27
- :description => 'use markdown syntax',
28
- :boolean => true
26
+ short: '-m',
27
+ long: '--markdown',
28
+ description: 'use markdown syntax',
29
+ boolean: true
29
30
 
30
31
  option :ignore_changelog_file,
31
- :long => '--ignore-changelog-file',
32
- :description => "Ignore changelog file presence, use git history instead",
33
- :boolean => true
32
+ long: '--ignore-changelog-file',
33
+ description: 'Ignore changelog file presence, use git history instead',
34
+ boolean: true
34
35
 
35
36
  option :allow_update_all,
36
- :long => '--allow-update-all',
37
- :description => "If no cookbook given, check all Berksfile",
38
- :boolean => true,
39
- :default => true
37
+ long: '--allow-update-all',
38
+ description: 'If no cookbook given, check all Berksfile',
39
+ boolean: true,
40
+ default: true
40
41
 
41
42
  option :submodules,
42
- :long => '--submodules SUBMODULE[,SUBMODULE]',
43
- :description => 'Submoduless to check for changes as well (comma separated)'
43
+ long: '--submodules SUBMODULE[,SUBMODULE]',
44
+ description: 'Submoduless to check for changes as well (comma separated)'
44
45
 
45
46
  option :policyfile,
46
- :long => '--policyfile PATH',
47
- :description => 'Link to policyfile, defaults to Policyfile.rb',
48
- :default => 'Policyfile.rb'
47
+ long: '--policyfile PATH',
48
+ description: 'Link to policyfile, defaults to "Policyfile.rb"',
49
+ default: 'Policyfile.rb'
49
50
 
50
51
  option :update,
51
- :long => '--update',
52
- :description => 'Update Berksfile'
52
+ long: '--update',
53
+ description: 'Update Berksfile'
53
54
 
54
55
  def run
55
56
  Log.info config
56
- @changelog = if config[:policyfile] && File.exists?(config[:policyfile])
57
- KnifeChangelog::Changelog::Policyfile.new(config[:policyfile], config)
58
- else
59
- berksfile = Berkshelf::Berksfile.from_options({})
60
- KnifeChangelog::Changelog::Berksfile.new(berksfile, config)
61
- end
62
- changelog_text = @changelog.run(@name_args)
63
- puts changelog_text
57
+ if config[:policyfile] && File.exist?(config[:policyfile])
58
+ PolicyChangelog.new(@name_args, config[:policyfile]).generate_changelog
59
+ else
60
+ berksfile = Berkshelf::Berksfile.from_options({})
61
+ puts KnifeChangelog::Changelog::Berksfile
62
+ .new(berksfile, config)
63
+ .run(@name_args)
64
+ end
64
65
  end
65
66
  end
66
-
67
67
  end
68
68
  end
@@ -1,5 +1,5 @@
1
1
  module Knife
2
2
  module Changelog
3
- VERSION = '1.0.6'
3
+ VERSION = '1.0.7'.freeze
4
4
  end
5
5
  end
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'chef'
4
+ require 'chef/knife'
5
+ require 'chef-dk/command/update'
6
+ require 'deep_merge'
7
+ require 'git'
8
+ require 'json'
9
+ require 'rest-client'
10
+
11
+ class PolicyChangelog
12
+ TMP_PREFIX = 'knife-changelog'
13
+
14
+ # Initialzes Helper class
15
+ #
16
+ # @param cookbooks [Array<String>] cookbooks to update (@name_args)
17
+ # @param policyfile [String] policyfile path
18
+ def initialize(cookbooks, policyfile)
19
+ @cookbooks_to_update = cookbooks
20
+ @policyfile_path = File.expand_path(policyfile)
21
+ @policyfile_dir = File.dirname(@policyfile_path)
22
+ end
23
+
24
+ # Updates the Policyfile.lock to get version differences.
25
+ #
26
+ # @return update_dir [String] tmp directory with updated Policyfile.lock
27
+ def update_policyfile_lock
28
+ update_dir = Dir.mktmpdir
29
+ %w[Policyfile.rb Policyfile.lock.json].each do |file|
30
+ FileUtils.cp(
31
+ File.join(@policyfile_dir, file),
32
+ update_dir
33
+ )
34
+ end
35
+ updater = ChefDK::Command::Update.new
36
+ updater.run([
37
+ File.join(update_dir, 'Policyfile.rb'),
38
+ @cookbooks_to_update
39
+ ].flatten)
40
+ update_dir
41
+ end
42
+
43
+ # Parses JSON in Policyfile.lock.
44
+ #
45
+ # @param dir [String] directory containing Policyfile.lock
46
+ # @return [Hash] contents of Policyfile.lock
47
+ def read_policyfile_lock(dir)
48
+ lock = File.join(dir, 'Policyfile.lock.json')
49
+ raise "File #{lock} does not exist" unless File.exist?(lock)
50
+ content = JSON.parse(File.read(lock))
51
+ raise 'Policyfile.lock empty' if content.empty?
52
+ content
53
+ end
54
+
55
+ # Extracts current or target versions from Policyfile.lock data depending
56
+ # on the type value provided.
57
+ #
58
+ # @param locks [Hash] cookbook data from Policyfile.lock
59
+ # @param type [String] version type - current or target
60
+ # @return [Hash] cookbooks with their versions
61
+ def versions(locks, type)
62
+ raise 'Use "current" or "target" as type' unless %w[current target].include?(type)
63
+ raise 'Cookbook locks empty or nil' if locks.nil? or locks.empty?
64
+ cookbooks = {}
65
+ locks.each do |name, data|
66
+ cookbooks[name] = { "#{type}_version" => data['version'] }
67
+ end
68
+ cookbooks
69
+ end
70
+
71
+ # Extracts Git source URL from cookbook 'source_options' data depending
72
+ # on the source type - Supermarket or Git
73
+ #
74
+ # @param s [Hash] source_options for a cookbook in the Policyfile.lock
75
+ # @return [String] Git source code URL
76
+ def get_source_url(s)
77
+ if s.keys.include?('artifactserver')
78
+ { 'source_url' => supermarket_source_url(s['artifactserver'][%r{(.+)\/versions\/.*}, 1]) }
79
+ else
80
+ { 'source_url' => s['git'] }
81
+ end
82
+ end
83
+
84
+ # Fetches cookbook metadata from Supermarket and extracts Git source URL
85
+ #
86
+ # @param url [String] Supermarket cookbook URL
87
+ # @return [String] Git source code URL
88
+ def supermarket_source_url(url)
89
+ source_url = JSON.parse(RestClient::Request.execute(
90
+ url: url,
91
+ method: :get,
92
+ verify_ssl: false
93
+ ))['source_url']
94
+ source_url = "#{source_url}.git" unless source_url.end_with?('.git')
95
+ source_url
96
+ end
97
+
98
+ # Clones a Git repo in a temporary directory and generates a commit
99
+ # changelog between two version tags
100
+ #
101
+ # @param source_url [String] Git repository URL
102
+ # @param current [String] current cookbook version tag
103
+ # @param target [String] target cookbook version tag
104
+ # @return [String] changelog between tags for one cookbook
105
+ def git_changelog(source_url, current, target)
106
+ dir = Dir.mktmpdir(TMP_PREFIX)
107
+ repo = Git.clone(source_url, dir)
108
+ if tag_format(repo) == 'v'
109
+ c_tag, t_tag = correct_tags("v#{current}", "v#{target}", repo)
110
+ repo.log.between(c_tag, t_tag)
111
+ else
112
+ c_tag, t_tag = correct_tags(current, target, repo)
113
+ repo.log.between(c_tag, t_tag)
114
+ end.map do |commit|
115
+ "#{commit.sha[0, 7]} #{commit.message.lines.first.strip}"
116
+ end.join("\n")
117
+ end
118
+
119
+ # Used to make #git_changelog method more readable
120
+ #
121
+ # @param current [String] current cookbook version tag
122
+ # @param target [String] target cookbook version tag
123
+ # @param repo [Git::Base] Git repository object
124
+ # @return [true, false]
125
+ def correct_tags(current, target, repo)
126
+ [git_tag(current, repo), git_tag(target, repo)]
127
+ end
128
+
129
+ # Tries to convert a supermarket tag to a git tag
130
+ # if there is a difference in formatting between the two.
131
+ # This is issue is present for the 'java' cookbook.
132
+ # https://github.com/agileorbit-cookbooks/java/issues/450
133
+ #
134
+ # @param tag [String] version tag
135
+ # @param repo [Git::Base] Git repository object
136
+ # @return [String]
137
+ def git_tag(tag, repo)
138
+ return tag if repo.checkout(tag)
139
+ rescue ::Git::GitExecuteError
140
+ begin
141
+ rescue_tag = tag.chomp('.0') if tag[/\.0$/]
142
+ return rescue_tag if repo.checkout(rescue_tag)
143
+ rescue ::Git::GitExecuteError
144
+ raise 'Difference between Git and Supermarket tags'
145
+ end
146
+ end
147
+
148
+ # Detects the format of a Git tag - v1.0.0 or 1.0.0
149
+ #
150
+ # @param repo [Git::Base] Git repository object
151
+ # @return [String] Git tag versioning type
152
+ def tag_format(repo)
153
+ repo.tags.last.name[/^v/] ? 'v' : ''
154
+ end
155
+
156
+ # Prints out commit changelog in a nicely formatted way
157
+ #
158
+ # @param name [String] cookbook name
159
+ # @param data [Hash] cookbook versions and source url data
160
+ def print_commit_changelog(name, data)
161
+ output = "\nChangelog for #{name}: #{data['current_version']}->#{data['target_version']}"
162
+ puts output
163
+ puts '=' * output.size
164
+ puts git_changelog(data['source_url'], data['current_version'], data['target_version'])
165
+ end
166
+
167
+ # Filters out cookbooks which are not updated, are not used after update or
168
+ # are newly added as dependencies during update
169
+ #
170
+ # @param [Hash] cookbook versions and source url data
171
+ # @return [true, false]
172
+ def reject_version_filter(data)
173
+ raise 'Data containing versions is nil' if data.nil?
174
+ data['current_version'] == data['target_version'] ||
175
+ data['current_version'].nil? ||
176
+ data['target_version'].nil?
177
+ end
178
+
179
+ # Generates Policyfile changelog
180
+ def generate_changelog
181
+ lock_current = read_policyfile_lock(@policyfile_dir)
182
+ current = versions(lock_current['cookbook_locks'], 'current')
183
+
184
+ lock_target = read_policyfile_lock(update_policyfile_lock)
185
+ target = versions(lock_target['cookbook_locks'], 'target')
186
+
187
+ updated_cookbooks = current.deep_merge(target).reject { |_name, data| reject_version_filter(data) }
188
+ sources = {}
189
+ updated_cookbooks.each_key do |name|
190
+ sources[name] = get_source_url(lock_target['cookbook_locks'][name]['source_options'])
191
+ end
192
+ updated_cookbooks.deep_merge(sources).each { |name, data| print_commit_changelog(name, data) }
193
+ end
194
+ end
@@ -1,41 +1,34 @@
1
1
  {
2
- "revision_id": "61fc0aa9ad414968cced34e8cf88189559b9ce8ec69d1514714822e77b0a2449",
2
+ "revision_id": "ca956e1f3b6bcbb1bd98e6f2aa58d22ae73191e4960f4039b3dea7225915205c",
3
3
  "name": "test_policy",
4
4
  "run_list": [
5
- "recipe[uptodate::default]"
5
+ "recipe[users::default]",
6
+ "recipe[sudo::default]"
7
+ ],
8
+ "included_policy_locks": [
9
+
6
10
  ],
7
11
  "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",
12
+ "sudo": {
13
+ "version": "3.5.0",
14
+ "identifier": "35c2f73f9f88d16c8556b80297c4c00fa104fdba",
15
+ "dotted_decimal_identifier": "15132540945795281.30545904985610180.211173358501306",
16
+ "cache_key": "sudo-3.5.0-supermarket.chef.io",
17
+ "origin": "https://supermarket.chef.io:443/api/v1/cookbooks/sudo/versions/3.5.0/download",
25
18
  "source_options": {
26
- "artifactserver": "https://mysupermarket2.io:443/api/v1/cookbooks/outdated1/versions/1.0.0/download",
27
- "version": "1.0.0"
19
+ "artifactserver": "https://supermarket.chef.io:443/api/v1/cookbooks/sudo/versions/3.5.0/download",
20
+ "version": "3.5.0"
28
21
  }
29
22
  },
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",
23
+ "users": {
24
+ "version": "4.0.0",
25
+ "identifier": "5920ea9767ce2e71fb17b2107888abe89043ee53",
26
+ "dotted_decimal_identifier": "25087464861847086.32082751558547592.189015341133395",
27
+ "cache_key": "users-4.0.0-supermarket.chef.io",
28
+ "origin": "https://supermarket.chef.io:443/api/v1/cookbooks/users/versions/4.0.0/download",
36
29
  "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"
30
+ "artifactserver": "https://supermarket.chef.io:443/api/v1/cookbooks/users/versions/4.0.0/download",
31
+ "version": "4.0.0"
39
32
  }
40
33
  }
41
34
  },
@@ -48,24 +41,20 @@
48
41
  "solution_dependencies": {
49
42
  "Policyfile": [
50
43
  [
51
- "uptodate",
52
- "= 1.0.0"
44
+ "sudo",
45
+ "= 3.5.0"
53
46
  ],
54
47
  [
55
- "outdated1",
56
- "= 1.0.0"
57
- ],
58
- [
59
- "second_out_of_date",
60
- "= 1.0.0"
48
+ "users",
49
+ "= 4.0.0"
61
50
  ]
62
51
  ],
63
52
  "dependencies": {
64
- "uptodate (1.0.0)": [
65
- ],
66
- "outdated1 (1.0.0)": [
53
+ "sudo (3.5.0)": [
54
+
67
55
  ],
68
- "second_out_of_date (1.0.0)": [
56
+ "users (4.0.0)": [
57
+
69
58
  ]
70
59
  }
71
60
  }
@@ -1,10 +1,8 @@
1
1
  name 'test_policy'
2
2
 
3
3
  run_list [
4
- 'recipe[uptodate]',
5
- 'recipe[second_out_of_date]',
6
- 'recipe[outdated1]'
4
+ 'recipe[users]',
5
+ 'recipe[sudo]',
7
6
  ]
8
7
 
9
- default_source :supermarket, 'https://mysupermarket.io'
10
- default_source :supermarket, 'https://mysupermarket2.io'
8
+ default_source :supermarket, "https://supermarket.chef.io"
@@ -0,0 +1,61 @@
1
+ {
2
+ "revision_id": "3ec29adf59408f8bac3408f9e284bae035b470eccda904544c4d1f541018289d",
3
+ "name": "test_policy",
4
+ "run_list": [
5
+ "recipe[users::default]",
6
+ "recipe[sudo::default]"
7
+ ],
8
+ "included_policy_locks": [
9
+
10
+ ],
11
+ "cookbook_locks": {
12
+ "sudo": {
13
+ "version": "3.5.0",
14
+ "identifier": "35c2f73f9f88d16c8556b80297c4c00fa104fdba",
15
+ "dotted_decimal_identifier": "15132540945795281.30545904985610180.211173358501306",
16
+ "cache_key": "sudo-3.5.0-supermarket.chef.io",
17
+ "origin": "https://supermarket.chef.io:443/api/v1/cookbooks/sudo/versions/3.5.0/download",
18
+ "source_options": {
19
+ "artifactserver": "https://supermarket.chef.io:443/api/v1/cookbooks/sudo/versions/3.5.0/download",
20
+ "version": "3.5.0"
21
+ }
22
+ },
23
+ "users": {
24
+ "version": "5.3.1",
25
+ "identifier": "41c2bf84fe2476bbbe2dbe79362e6726a3ea47c7",
26
+ "dotted_decimal_identifier": "18510001311982710.52844924323313198.113415656458183",
27
+ "cache_key": "users-5.3.1-supermarket.chef.io",
28
+ "origin": "https://supermarket.chef.io:443/api/v1/cookbooks/users/versions/5.3.1/download",
29
+ "source_options": {
30
+ "artifactserver": "https://supermarket.chef.io:443/api/v1/cookbooks/users/versions/5.3.1/download",
31
+ "version": "5.3.1"
32
+ }
33
+ }
34
+ },
35
+ "default_attributes": {
36
+
37
+ },
38
+ "override_attributes": {
39
+
40
+ },
41
+ "solution_dependencies": {
42
+ "Policyfile": [
43
+ [
44
+ "sudo",
45
+ "= 3.5.0"
46
+ ],
47
+ [
48
+ "users",
49
+ "= 5.3.1"
50
+ ]
51
+ ],
52
+ "dependencies": {
53
+ "sudo (3.5.0)": [
54
+
55
+ ],
56
+ "users (5.3.1)": [
57
+
58
+ ]
59
+ }
60
+ }
61
+ }
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../lib/knife/changelog/changelog.rb'
4
- require_relative '../lib/knife/changelog/berksfile'
5
- require_relative '../lib/knife/changelog/policyfile'
3
+ require 'knife/changelog/changelog'
4
+ require 'knife/changelog/berksfile'
5
+ require 'policyfile'
6
+
7
+ require 'webmock/rspec'
8
+
9
+ WebMock.disable_net_connect!(allow_localhost: true)
6
10
 
7
11
  # This file was generated by the `rspec --init` command. Conventionally, all
8
12
  # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
@@ -1,10 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../spec_helper'
4
- require 'webmock/rspec'
5
3
  require 'berkshelf'
6
-
7
- WebMock.disable_net_connect!
4
+ require 'spec_helper'
8
5
 
9
6
  RSpec.shared_examples 'changelog generation' do
10
7
  # this supposes that "changelog" is an instance of KnifeChangelog::Changelog
@@ -117,63 +114,6 @@ describe KnifeChangelog::Changelog do
117
114
  end
118
115
  end
119
116
  end
120
-
121
- context 'in policyfile mode' do
122
- let(:policyfile_path) { File.join(File.dirname(__FILE__), '../data/Policyfile.rb') }
123
-
124
- let(:options) do
125
- {}
126
- end
127
-
128
- let(:changelog) do
129
- KnifeChangelog::Changelog::Policyfile.new(policyfile_path, options)
130
- end
131
-
132
- before(:each) do
133
- allow(changelog.policy).to receive(:cache_fixed_version_cookbooks)
134
- end
135
-
136
- include_examples 'changelog generation'
137
-
138
- context 'with --update' do
139
- let(:options) do
140
- { update: true }
141
- end
142
- it 'fails with not implemented error' do
143
- mock_git('outdated1', <<-EOH)
144
- aaaaaa commit in outdated1
145
- bbbbbb bugfix in outdated1
146
- EOH
147
- expect { changelog.run(%w[outdated1]) }.to raise_error(NotImplementedError)
148
- end
149
- end
150
-
151
- context 'whith --allow-update-all ' do
152
- let(:options) do
153
- { "allow_update_all": true }
154
- end
155
-
156
- it 'should compute the changelog of all dependencies' do
157
- mock_git('second_out_of_date', <<-EOH)
158
- aaaaaa commit in second_out_of_date
159
- bbbbbb bugfix in second_out_of_date
160
- EOH
161
- mock_git('outdated1', <<-EOH)
162
- aaaaaa commit in outdated1
163
- bbbbbb bugfix in outdated1
164
- EOH
165
- mock_git('uptodate', '')
166
-
167
- expect(changelog).to receive(:supermarkets_for).with('outdated1')
168
- .and_return(["https://mysupermarket2.io"])
169
- expect(changelog).to receive(:supermarkets_for).with('second_out_of_date')
170
- .and_return(["https://mysupermarket2.io"])
171
- expect(changelog).to receive(:supermarkets_for).with('uptodate')
172
- .and_return(["https://mysupermarket2.io"])
173
- changelog.run([])
174
- end
175
- end
176
- end
177
117
  end
178
118
 
179
119
  class Hash
@@ -0,0 +1,260 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'git'
4
+ require 'spec_helper'
5
+
6
+ RSpec.describe PolicyChangelog do
7
+ let(:pf_dir) do
8
+ File.expand_path(File.join(File.dirname(__FILE__), '../data'))
9
+ end
10
+
11
+ let(:tmp_dir) do
12
+ File.join(pf_dir, 'tmp_dir')
13
+ end
14
+
15
+ let(:changelog) do
16
+ PolicyChangelog.new('users', File.join(pf_dir, 'Policyfile.rb'))
17
+ end
18
+
19
+ let(:lock_current) do
20
+ JSON.parse(File.read(File.join(pf_dir, 'Policyfile.lock.json')))
21
+ end
22
+
23
+ let(:lock_target) do
24
+ JSON.parse(File.read(File.join(tmp_dir, 'Policyfile.lock.json')))
25
+ end
26
+
27
+ let(:current_versions) do
28
+ {
29
+ 'sudo' => { 'current_version' => '3.5.0' },
30
+ 'users' => { 'current_version' => '4.0.0' }
31
+ }
32
+ end
33
+
34
+ let(:target_versions) do
35
+ {
36
+ 'sudo' => { 'target_version' => '3.5.0' },
37
+ 'users' => { 'target_version' => '5.3.1' }
38
+ }
39
+ end
40
+
41
+ let(:url) { 'https://supermarket.chef.io/api/v1/cookbooks/users' }
42
+
43
+ before(:each) do
44
+ stub_request(:get, 'https://supermarket.chef.io/api/v1/cookbooks/users').to_return(
45
+ status: 200,
46
+ body: '{
47
+ "name": "users",
48
+ "maintainer": "chef",
49
+ "description": "Creates users from a databag search",
50
+ "category": "Other",
51
+ "latest_version": "https://supermarket.chef.io/api/v1/cookbooks/users/versions/5.3.1",
52
+ "external_url": "https://github.com/chef-cookbooks/users",
53
+ "source_url": "https://github.com/chef-cookbooks/users",
54
+ "issues_url": "https://github.com/chef-cookbooks/users/issues",
55
+ "average_rating": null,
56
+ "created_at": "2010-07-27T05:34:01.000Z",
57
+ "updated_at": "2017-12-15T18:08:26.990Z",
58
+ "up_for_adoption": null,
59
+ "deprecated": false,
60
+ "versions": [],
61
+ "metrics": {}
62
+ }'
63
+ )
64
+ end
65
+
66
+ describe '#read_policyfile_lock' do
67
+ context 'when Policyfile.lock.json does not exist' do
68
+ it 'raises an exception' do
69
+ expect { changelog.read_policyfile_lock('/does/not/exist') }.to raise_error(RuntimeError)
70
+ end
71
+ end
72
+
73
+ context 'when Policyfile.lock.json is empty' do
74
+ it 'raises an exception' do
75
+ allow(File).to receive(:read).and_return('')
76
+ expect { changelog.read_policyfile_lock('') }.to raise_error(RuntimeError)
77
+ end
78
+ end
79
+ end
80
+
81
+ describe '#versions' do
82
+ context 'when type is current' do
83
+ it 'returns correct current versions' do
84
+ expect(changelog.versions(lock_current['cookbook_locks'], 'current')).to eq(current_versions)
85
+ end
86
+ end
87
+
88
+ context 'when type is target' do
89
+ it 'returns correct target versions' do
90
+ expect(changelog.versions(lock_target['cookbook_locks'], 'target')).to eq(target_versions)
91
+ end
92
+ end
93
+
94
+ context 'when type is not current nor target' do
95
+ it 'raises an exception' do
96
+ expect { changelog.versions(lock_current['cookbook_locks'], 'toto') }.to raise_error(RuntimeError)
97
+ end
98
+ end
99
+
100
+ context 'when cookbooks locks are empty or nil' do
101
+ it 'raises an exception' do
102
+ expect { changelog.versions({}, 'current') }.to raise_error(RuntimeError)
103
+ expect { changelog.versions(nil, 'current') }.to raise_error(RuntimeError)
104
+ end
105
+ end
106
+ end
107
+
108
+ describe '#get_source_url' do
109
+ context 'when extracting source url' do
110
+ it 'returns an http url for supermarket' do
111
+ supermarket_source = { 'artifactserver' => 'https://url.example/cookbook/name/versions/3.5.0/download' }
112
+ allow(changelog).to receive(:supermarket_source_url).and_return('https://url.example')
113
+ expect(changelog.get_source_url(supermarket_source)['source_url'])
114
+ .to match(%r{^(http|https):\/\/.+$})
115
+ end
116
+
117
+ it 'raises exception for invalid supermarket url' do
118
+ bad_supermarket_source = { 'artifactserver' => 'https://url.example' }
119
+ expect { changelog.get_source_url(bad_supermarket_source)['source_url'] }
120
+ .to raise_error(ArgumentError)
121
+ end
122
+
123
+ it 'returns a git repo url for git' do
124
+ git_source = { 'git' => 'https://url.example.git' }
125
+ expect(changelog.get_source_url(git_source)['source_url'])
126
+ .to match(%r{^(http|https):\/\/.+\.git$})
127
+ end
128
+ end
129
+ end
130
+
131
+ describe '#supermarket_source_url' do
132
+ context 'when response not empty' do
133
+ it 'returns valid git repository url' do
134
+ expect(changelog.supermarket_source_url(url)).to match(%r{^(http|https):\/\/.+\.git$})
135
+ end
136
+ end
137
+
138
+ context 'when cookbook does not exist' do
139
+ it 'raises an error' do
140
+ response = '{ "error_messages":["Resource does not exist."],"error_code":"NOT_FOUND" }'
141
+ stub_request(:get, url).to_return(status: 404, body: response)
142
+ expect { changelog.supermarket_source_url(url) }.to raise_error(RestClient::NotFound)
143
+ end
144
+ end
145
+ end
146
+
147
+ describe '#git_changelog' do
148
+ let(:git) { Git }
149
+
150
+ let(:git_repo) { double(Git::Base.new) }
151
+
152
+ let(:git_commit) do
153
+ double(
154
+ sha: 'e1b971a32f3a582766e4f62022ef7ed88e5eb8ba',
155
+ message: "Add test commit message\nThis line should not be shown"
156
+ )
157
+ end
158
+
159
+ context 'when given two tags' do
160
+ it 'generates a changelog between two tags' do
161
+ allow(changelog).to receive(:tag_format).and_return('v')
162
+ allow(git).to receive(:clone).and_return(git_repo)
163
+ allow(changelog).to receive(:correct_tags)
164
+ .and_return(['v1.0.0', 'v1.0.1'])
165
+ allow(git_repo).to receive_message_chain(:log, :between)
166
+ .with('v1.0.0', 'v1.0.1')
167
+ .and_return([git_commit])
168
+
169
+ expect(changelog.git_changelog('https://url.example.git', '1.0.0', '1.0.1'))
170
+ .to eq('e1b971a Add test commit message')
171
+ end
172
+ end
173
+ end
174
+
175
+ describe '#git_tag' do
176
+ let(:repo) { double('repo') }
177
+
178
+ context 'when tag valid' do
179
+ it 'returns correct git tag' do
180
+ allow(repo).to receive(:checkout).with('1.0.0').and_return(true)
181
+
182
+ expect(changelog.git_tag('1.0.0', repo)).to eq('1.0.0')
183
+ end
184
+ end
185
+
186
+ context 'when tag invalid and able to correct' do
187
+ it 'returns correct git tag' do
188
+ allow(repo).to receive(:checkout).with('1.0.0').and_raise(::Git::GitExecuteError)
189
+ allow(repo).to receive(:checkout).with('1.0').and_return(true)
190
+
191
+ expect(changelog.git_tag('1.0.0', repo)).to eq('1.0')
192
+ end
193
+ end
194
+
195
+ context 'when tags invalid and unable to correct' do
196
+ it 'raises exception' do
197
+ allow(repo).to receive(:checkout).with('1.0.0').and_raise(::Git::GitExecuteError)
198
+ allow(repo).to receive(:checkout).with('1.0').and_raise(::Git::GitExecuteError)
199
+
200
+ expect { changelog.git_tag('1.0.0', repo) }
201
+ .to raise_error(RuntimeError, 'Difference between Git and Supermarket tags')
202
+ end
203
+ end
204
+ end
205
+
206
+ describe '#tag_format' do
207
+ context 'when it receives a tag' do
208
+ let(:repo) { Git::Base }
209
+
210
+ it 'detects type for regular tag' do
211
+ allow(repo).to receive_message_chain(:tags, :last, :name).and_return('1.0.0')
212
+ expect(changelog.tag_format(repo)).to eq('')
213
+ end
214
+
215
+ it 'detects type for v-tag' do
216
+ allow(repo).to receive_message_chain(:tags, :last, :name).and_return('v1.0.0')
217
+ expect(changelog.tag_format(repo)).to eq('v')
218
+ end
219
+ end
220
+ end
221
+
222
+ describe '#reject_version_filter' do
223
+ context 'when current equal to target' do
224
+ it 'returns true' do
225
+ data = { 'current_version' => '1.0.0', 'target_version' => '1.0.0' }
226
+ expect(changelog.reject_version_filter(data)).to be true
227
+ end
228
+ end
229
+ context 'when current not equal to target' do
230
+ it 'returns false' do
231
+ data = { 'current_version' => '1.0.0', 'target_version' => '1.0.1' }
232
+ expect(changelog.reject_version_filter(data)).to be false
233
+ end
234
+ end
235
+ context 'when current or target do not exist' do
236
+ it 'returns true' do
237
+ expect(changelog.reject_version_filter('target_version' => '1.0.0')).to be true
238
+ expect(changelog.reject_version_filter('current_version' => '1.0.0')).to be true
239
+ end
240
+ end
241
+ context 'when data is nil' do
242
+ it 'raises an exception' do
243
+ expect { changelog.reject_version_filter(nil) }.to raise_error
244
+ end
245
+ end
246
+ end
247
+
248
+ describe '#generate_changelog' do
249
+ context 'when generating a changelog' do
250
+ it 'detects type for regular tag' do
251
+ allow(changelog).to receive(:update_policyfile_lock)
252
+ .and_return(tmp_dir)
253
+ allow(changelog).to receive(:git_changelog)
254
+ .and_return('e1b971a Add test commit message')
255
+
256
+ expect { changelog.generate_changelog }.not_to raise_error(RuntimeError)
257
+ end
258
+ end
259
+ end
260
+ 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: 1.0.6
4
+ version: 1.0.7
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-12-15 00:00:00.000000000 Z
11
+ date: 2018-01-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rake
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -81,13 +95,13 @@ dependencies:
81
95
  - !ruby/object:Gem::Version
82
96
  version: '0'
83
97
  - !ruby/object:Gem::Dependency
84
- name: pry
98
+ name: berkshelf
85
99
  requirement: !ruby/object:Gem::Requirement
86
100
  requirements:
87
101
  - - ">="
88
102
  - !ruby/object:Gem::Version
89
103
  version: '0'
90
- type: :development
104
+ type: :runtime
91
105
  prerelease: false
92
106
  version_requirements: !ruby/object:Gem::Requirement
93
107
  requirements:
@@ -95,7 +109,7 @@ dependencies:
95
109
  - !ruby/object:Gem::Version
96
110
  version: '0'
97
111
  - !ruby/object:Gem::Dependency
98
- name: berkshelf
112
+ name: chef
99
113
  requirement: !ruby/object:Gem::Requirement
100
114
  requirements:
101
115
  - - ">="
@@ -109,7 +123,7 @@ dependencies:
109
123
  - !ruby/object:Gem::Version
110
124
  version: '0'
111
125
  - !ruby/object:Gem::Dependency
112
- name: rest-client
126
+ name: chef-dk
113
127
  requirement: !ruby/object:Gem::Requirement
114
128
  requirements:
115
129
  - - ">="
@@ -123,7 +137,7 @@ dependencies:
123
137
  - !ruby/object:Gem::Version
124
138
  version: '0'
125
139
  - !ruby/object:Gem::Dependency
126
- name: mixlib-shellout
140
+ name: deep_merge
127
141
  requirement: !ruby/object:Gem::Requirement
128
142
  requirements:
129
143
  - - ">="
@@ -137,7 +151,7 @@ dependencies:
137
151
  - !ruby/object:Gem::Version
138
152
  version: '0'
139
153
  - !ruby/object:Gem::Dependency
140
- name: chef
154
+ name: git
141
155
  requirement: !ruby/object:Gem::Requirement
142
156
  requirements:
143
157
  - - ">="
@@ -151,7 +165,21 @@ dependencies:
151
165
  - !ruby/object:Gem::Version
152
166
  version: '0'
153
167
  - !ruby/object:Gem::Dependency
154
- name: chef-dk
168
+ name: mixlib-shellout
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :runtime
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: rest-client
155
183
  requirement: !ruby/object:Gem::Requirement
156
184
  requirements:
157
185
  - - ">="
@@ -183,16 +211,18 @@ files:
183
211
  - lib/knife/changelog/berksfile.rb
184
212
  - lib/knife/changelog/changelog.rb
185
213
  - lib/knife/changelog/git.rb
186
- - lib/knife/changelog/policyfile.rb
187
214
  - lib/knife/changelog/version.rb
215
+ - lib/policyfile.rb
188
216
  - resources/Berksfile
189
217
  - resources/Berksfile.lock
190
218
  - spec/data/Berksfile
191
219
  - spec/data/Berksfile.lock
192
220
  - spec/data/Policyfile.lock.json
193
221
  - spec/data/Policyfile.rb
222
+ - spec/data/tmp_dir/Policyfile.lock.json
194
223
  - spec/spec_helper.rb
195
224
  - spec/unit/changelog_spec.rb
225
+ - spec/unit/policyfile_spec.rb
196
226
  homepage: https://github.com/kamaradclimber/knife-changelog
197
227
  licenses:
198
228
  - MIT
@@ -213,7 +243,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
213
243
  version: '0'
214
244
  requirements: []
215
245
  rubyforge_project:
216
- rubygems_version: 2.7.3
246
+ rubygems_version: 2.7.4
217
247
  signing_key:
218
248
  specification_version: 4
219
249
  summary: Facilitate access to cookbooks changelog
@@ -222,5 +252,7 @@ test_files:
222
252
  - spec/data/Berksfile.lock
223
253
  - spec/data/Policyfile.lock.json
224
254
  - spec/data/Policyfile.rb
255
+ - spec/data/tmp_dir/Policyfile.lock.json
225
256
  - spec/spec_helper.rb
226
257
  - spec/unit/changelog_spec.rb
258
+ - spec/unit/policyfile_spec.rb
@@ -1,69 +0,0 @@
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.solution_dependencies.cookbook_deps_for_lock.map { |k, v| k.scan(/(.*) \(.*\)/).last.first }
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
- def update(cookbooks)
59
- raise NotImplementedError
60
- end
61
-
62
- # return a list of supermarket uri for a given cookbook
63
- # example: [ 'https://supermarket.chef.io' ]
64
- def supermarkets_for(name)
65
- [policy.send(:best_source_for, name).uri]
66
- end
67
- end
68
- end
69
- end