Class: PolicyChangelog
- Inherits:
-
Object
- Object
- PolicyChangelog
- Defined in:
- lib/knife/changelog/policyfile.rb
Constant Summary collapse
- TMP_PREFIX =
'knife-changelog'
- VERSION_REGEX =
Regex matching Chef cookbook version syntax See docs.chef.io/cookbook_versioning.html#syntax
/^[1-9]*[0-9](\.[0-9]+){1,2}$/
Instance Method Summary collapse
-
#format_output(name, data, work_dir: nil) ⇒ String
Formats commit changelog to be more readable.
-
#generate_changelog(prevent_downgrade: false) ⇒ String
Generates Policyfile changelog.
-
#generate_changelog_from_versions(cookbook_versions, work_dir: nil) ⇒ String
Generates Policyfile changelog.
-
#get_source_url(s) ⇒ String
Extracts Git source URL from cookbook ‘source_options’ data depending on the source type - Supermarket or Git.
-
#git_changelog(source_url, current, target, cookbook = nil, work_dir: nil) ⇒ String
Clones a Git repo in a temporary directory and generates a commit changelog between two version tags.
-
#git_cookbook_path(repo, cookbook) ⇒ String
Tries to find the location of a specific cookbook in the given repo.
-
#git_ref(myref, repo, cookbook_name = nil) ⇒ String
Tries to convert a supermarket tag to a git reference if there is a difference in formatting between the two.
-
#initialize(cookbooks, policyfile, with_dependencies) ⇒ PolicyChangelog
constructor
Initialzes Helper class.
-
#read_policyfile_lock(dir) ⇒ Hash
Parses JSON in Policyfile.lock.
-
#reject_version_filter(data) ⇒ true, false
Filters out cookbooks which are not updated, are not used after update.
-
#sort_by_version(tags) ⇒ Array
Sort tags by version and filter out invalid version tags.
-
#supermarket_source_url(url) ⇒ String
Fetches cookbook metadata from Supermarket and extracts Git source URL.
-
#update_policyfile_lock(work_dir: nil) ⇒ Object
Updates the Policyfile.lock to get version differences.
-
#validate_downgrade!(data) ⇒ Object
Search for cookbook downgrade and raise an error if any.
-
#versions(locks, type) ⇒ Hash
Extracts current or target versions from Policyfile.lock data depending on the type value provided.
Constructor Details
#initialize(cookbooks, policyfile, with_dependencies) ⇒ PolicyChangelog
Initialzes Helper class
21 22 23 24 25 26 |
# File 'lib/knife/changelog/policyfile.rb', line 21 def initialize(cookbooks, policyfile, with_dependencies) @cookbooks_to_update = cookbooks @policyfile_path = File.(policyfile) @policyfile_dir = File.dirname(@policyfile_path) @with_dependencies = with_dependencies end |
Instance Method Details
#format_output(name, data, work_dir: nil) ⇒ String
Formats commit changelog to be more readable
178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/knife/changelog/policyfile.rb', line 178 def format_output(name, data, work_dir: nil) output = ["\nChangelog for #{name}: #{data['current_version']}->#{data['target_version']}"] output << '=' * output.first.size output << if data['current_version'] git_changelog(data['source_url'], data['current_version'], data['target_version'], name, work_dir: work_dir) else 'Cookbook was not in the Policyfile.lock.json' end output.join("\n") end |
#generate_changelog(prevent_downgrade: false) ⇒ String
Generates Policyfile changelog
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/knife/changelog/policyfile.rb', line 216 def generate_changelog(prevent_downgrade: false) ::Dir.mktmpdir(TMP_PREFIX) do |dir| lock_current = read_policyfile_lock(@policyfile_dir) current = versions(lock_current['cookbook_locks'], 'current') lock_target = update_policyfile_lock(work_dir: dir) target = versions(lock_target['cookbook_locks'], 'target') updated_cookbooks = current.deep_merge(target).reject { |_name, data| reject_version_filter(data) } changelog_cookbooks = if @with_dependencies || @cookbooks_to_update.nil? updated_cookbooks else updated_cookbooks.select { |name, _data| @cookbooks_to_update.include?(name) } end validate_downgrade!(updated_cookbooks) if prevent_downgrade generate_changelog_from_versions(changelog_cookbooks, work_dir: dir) end end |
#generate_changelog_from_versions(cookbook_versions, work_dir: nil) ⇒ String
Generates Policyfile changelog
237 238 239 240 241 242 243 |
# File 'lib/knife/changelog/policyfile.rb', line 237 def generate_changelog_from_versions(cookbook_versions, work_dir: nil) lock_current = read_policyfile_lock(@policyfile_dir) sources = cookbook_versions.map do |name, data| [name, get_source_url(lock_current['cookbook_locks'][name]['source_options'])] if data['current_version'] end.compact.to_h cookbook_versions.deep_merge(sources).map { |name, data| format_output(name, data, work_dir: work_dir) }.join("\n") end |
#get_source_url(s) ⇒ String
Extracts Git source URL from cookbook ‘source_options’ data depending on the source type - Supermarket or Git
80 81 82 83 84 85 86 |
# File 'lib/knife/changelog/policyfile.rb', line 80 def get_source_url(s) if s.keys.include?('artifactserver') { 'source_url' => supermarket_source_url(s['artifactserver'][%r{(.+)\/versions\/.*}, 1]) } else { 'source_url' => s['git'] } end end |
#git_changelog(source_url, current, target, cookbook = nil, work_dir: nil) ⇒ String
Clones a Git repo in a temporary directory and generates a commit changelog between two version tags
109 110 111 112 113 114 115 116 |
# File 'lib/knife/changelog/policyfile.rb', line 109 def git_changelog(source_url, current, target, cookbook = nil, work_dir: nil) repo_dir = ::File.join(work_dir || Dir.mktmpdir(TMP_PREFIX), "git-#{source_url.gsub(/[\/:]+/, '-')}") repo = ::Dir.exist?(repo_dir) ? ::Git.open(repo_dir) : ::Git.clone(source_url, repo_dir) cookbook_path = cookbook ? git_cookbook_path(repo, cookbook) : '.' repo.log.path(cookbook_path).between(git_ref(current, repo, cookbook), git_ref(target, repo, cookbook)).map do |commit| "#{commit.sha[0, 7]} #{commit..lines.first.strip}" end.join("\n") end |
#git_cookbook_path(repo, cookbook) ⇒ String
Tries to find the location of a specific cookbook in the given repo
123 124 125 126 127 128 129 130 131 |
# File 'lib/knife/changelog/policyfile.rb', line 123 def git_cookbook_path(repo, cookbook) = ['metadata.rb', '*/metadata.rb'].flat_map { |location| repo.ls_files(location).keys } = .find do |path| path = ::File.join(repo.dir.to_s, path) ::Chef::Cookbook::Metadata.new.tap { |m| m.from_file(path) }.name == cookbook end raise "Impossible to find matching metadata for #{cookbook} in #{repo.remote.url}" unless ::File.dirname() end |
#git_ref(myref, repo, cookbook_name = nil) ⇒ String
Tries to convert a supermarket tag to a git reference if there is a difference in formatting between the two. This is issue is present for the ‘java’ cookbook. github.com/agileorbit-cookbooks/java/issues/450
142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/knife/changelog/policyfile.rb', line 142 def git_ref(myref, repo, cookbook_name = nil) possible_refs = ['v' + myref, myref] possible_refs += possible_refs.map { |ref| "#{cookbook_name}-#{ref}" } if cookbook_name possible_refs += possible_refs.map { |ref| ref.chomp('.0') } if myref[/\.0$/] existing_ref = possible_refs.find do |ref| begin repo.checkout(ref) rescue ::Git::Error false end end raise "Impossible to find existing references to #{possible_refs} in #{repo.remote.url}" unless existing_ref existing_ref end |
#read_policyfile_lock(dir) ⇒ Hash
Parses JSON in Policyfile.lock.
47 48 49 50 51 52 53 |
# File 'lib/knife/changelog/policyfile.rb', line 47 def read_policyfile_lock(dir) lock = File.join(dir, 'Policyfile.lock.json') raise "File #{lock} does not exist" unless File.exist?(lock) content = JSON.parse(File.read(lock)) raise 'Policyfile.lock empty' if content.empty? content end |
#reject_version_filter(data) ⇒ true, false
Filters out cookbooks which are not updated, are not used after update
194 195 196 197 |
# File 'lib/knife/changelog/policyfile.rb', line 194 def reject_version_filter(data) raise 'Data containing versions is nil' if data.nil? data['current_version'] == data['target_version'] || data['target_version'].nil? end |
#sort_by_version(tags) ⇒ Array
Sort tags by version and filter out invalid version tags
161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/knife/changelog/policyfile.rb', line 161 def sort_by_version() .sort_by do |t| begin Gem::Version.new(t.name.gsub(/^v/, '')) rescue ArgumentError => e # Skip tag if version is not valid (i.e. a String) raise unless e. && e..include?('Malformed version number string') Gem::Version.new('0.0.0') end end end |
#supermarket_source_url(url) ⇒ String
Fetches cookbook metadata from Supermarket and extracts Git source URL
92 93 94 95 96 97 98 99 100 |
# File 'lib/knife/changelog/policyfile.rb', line 92 def supermarket_source_url(url) source_url = JSON.parse(RestClient::Request.execute( url: url, method: :get, verify_ssl: false ))['source_url'] source_url = "#{source_url}.git" unless source_url.end_with?('.git') source_url end |
#update_policyfile_lock(work_dir: nil) ⇒ Object
Updates the Policyfile.lock to get version differences.
31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/knife/changelog/policyfile.rb', line 31 def update_policyfile_lock(work_dir: nil) work_dir ||= ::Dir.mktmpdir(TMP_PREFIX) FileUtils.cp(File.join(@policyfile_dir, 'Policyfile.lock.json'), work_dir) installer = ChefCLI::Command::Install.new raise "Cannot install Policyfile lock #{@policyfile_path}" unless installer.run([@policyfile_relative_path]).zero? updater = ChefCLI::Command::Update.new raise "Error updating Policyfile lock #{@policyfile_path}" unless updater.run([@policyfile_path, @cookbooks_to_update].flatten).zero? updated_policyfile_lock = read_policyfile_lock(@policyfile_dir) FileUtils.cp(File.join(work_dir, 'Policyfile.lock.json'), @policyfile_dir) updated_policyfile_lock end |
#validate_downgrade!(data) ⇒ Object
Search for cookbook downgrade and raise an error if any
200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/knife/changelog/policyfile.rb', line 200 def validate_downgrade!(data) downgrade = data.select do |_, ck| # Do not try to validate downgrade on non-sementic versions (e.g. git revision) ck['target_version'] =~ VERSION_REGEX && ck['current_version'] =~ VERSION_REGEX && ::Gem::Version.new(ck['target_version']) < ::Gem::Version.new(ck['current_version']) end return if downgrade.empty? details = downgrade.map { |name, data| "#{name} (#{data['current_version']} -> #{data['target_version']})" } raise "Trying to downgrade following cookbooks: #{details.join(', ')}" end |
#versions(locks, type) ⇒ Hash
Extracts current or target versions from Policyfile.lock data depending on the type value provided.
61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/knife/changelog/policyfile.rb', line 61 def versions(locks, type) raise 'Use "current" or "target" as type' unless %w[current target].include?(type) raise 'Cookbook locks empty or nil' if locks.nil? || locks.empty? cookbooks = {} locks.each do |name, data| cookbooks[name] = if data['source_options'].keys.include?('git') { "#{type}_version" => data['source_options']['revision'] } else { "#{type}_version" => data['version'] } end end cookbooks end |