knife-changelog 1.0.6 → 1.0.7

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