Class: KnifeChangelog::Changelog

Inherits:
Object
  • Object
show all
Defined in:
lib/knife/changelog/berksfile.rb,
lib/knife/changelog/changelog.rb

Direct Known Subclasses

Berksfile, GitSubmodule

Defined Under Namespace

Classes: Berksfile, Location

Constant Summary collapse

GERRIT_REGEXP =
%r{^(.*)/[^/]+/[^/]+(?:\.git)$}

Instance Method Summary collapse

Constructor Details

#initialize(config = {}) ⇒ Changelog

Returns a new instance of Changelog.



21
22
23
24
25
# File 'lib/knife/changelog/changelog.rb', line 21

def initialize(config = {})
  @tmp_prefix = 'knife-changelog'
  @config   = config
  @tmp_dirs = []
end

Instance Method Details

#all_cookbooksObject

returns a list of all cookbooks names

Raises:

  • (NotImplementedError)


28
29
30
# File 'lib/knife/changelog/changelog.rb', line 28

def all_cookbooks
  raise NotImplementedError
end

#cleanObject



95
96
97
98
99
# File 'lib/knife/changelog/changelog.rb', line 95

def clean
  @tmp_dirs.each do |dir|
    FileUtils.rm_rf dir
  end
end

#cookbook_highest_version(json) ⇒ Object



164
165
166
167
168
169
# File 'lib/knife/changelog/changelog.rb', line 164

def cookbook_highest_version(json)
  json['versions']
    .map { |version_url| Chef::Version.new(version_url.gsub(/.*\//, '')) }
    .sort
    .last
end

#detect_cur_revision(name, rev, git) ⇒ Object



184
185
186
187
188
189
190
191
# File 'lib/knife/changelog/changelog.rb', line 184

def detect_cur_revision(name, rev, git)
  unless git.revision_exists?(rev)
    prefixed_rev = 'v' + rev
    return prefixed_rev if git.revision_exists?(prefixed_rev)
    fail "#{rev} is not an existing revision (#{name}), not a tag/commit/branch name."
  end
  rev
end

#execute(name, submodule = false) ⇒ Object



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/knife/changelog/changelog.rb', line 106

def execute(name, submodule = false)
  version_change, changelog = if submodule
                                handle_submodule(name)
                              elsif new_cookbook?(name)
                                ['', handle_new_cookbook]
                              else
                                case true
                                when supermarket?(name)
                                  handle_source(name)
                                when git?(name)
                                  handle_git(name, git_location(name))
                                when local?(name)
                                  Chef::Log.debug "path location are always at the last version"
                                  ['', '']
                                else
                                  raise "Cannot handle #{loc.class} yet"
                                end
                              end
  format_changelog(name, version_change, changelog)
end

#format_changelog(name, version_change, changelog) ⇒ Object



127
128
129
130
131
132
133
134
135
136
137
# File 'lib/knife/changelog/changelog.rb', line 127

def format_changelog(name, version_change, changelog)
  if changelog.empty?
    nil
  else
    full = ["Changelog for #{name}: #{version_change}"]
    full << '=' * full.first.size
    full << changelog
    full << ''
    full.compact.join("\n")
  end
end

#generate_from_changelog_file(filename, current_rev, rev_parse, git) ⇒ Object



225
226
227
228
229
230
231
# File 'lib/knife/changelog/changelog.rb', line 225

def generate_from_changelog_file(filename, current_rev, rev_parse, git)
  ch = git.diff(filename, current_rev, rev_parse)
          .collect { |line| $1.strip if line =~ /^{\+(.*)\+}$/ }.compact
          .map { |line| line.gsub(/^#+(.*)$/, "\\1\n---")} # replace section by smaller header
          .select { |line| !(line =~ /^===+/)}.compact # remove header lines
  ch.empty? ? nil : ch
end

#generate_from_git_history(git, location, current_rev, rev_parse) ⇒ Object



233
234
235
236
237
238
239
240
# File 'lib/knife/changelog/changelog.rb', line 233

def generate_from_git_history(git, location, current_rev, rev_parse)
  c = git.log(current_rev, rev_parse)
  n = https_url(location)
  c = linkify(n, c) if @config[:linkify] and n
  c = c.map { |line| "* " + line } if @config[:markdown]
  c = c.map { |line| line.strip } # clean end of line
  c
end

#get_from_supermarket_sources(name) ⇒ Object



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/knife/changelog/changelog.rb', line 139

def get_from_supermarket_sources(name)
  supermarkets_for(name).map do |uri|
    begin
      # TODO: we could call /universe endpoint once
      # instead of calling /api/v1/cookbooks/ for each cookbook
      RestClient::Request.execute(
        url: "#{uri}/api/v1/cookbooks/#{name}",
        method: :get,
        verify_ssl: false # TODO make this configurable
      )
    rescue => e
      Chef::Log.debug "Error fetching package from supermarket #{e.class.name} #{e.message}"
      nil
    end
  end
    .compact
    .map { |json| JSON.parse(json) }
    .sort_by { |ck| cookbook_highest_version(ck) }
    .map { |ck| ck['source_url'] || ck ['external_url'] }
    .last
    .tap do |source|
      raise "Cannot find any changelog source for #{name}" unless source
    end
end

#git?(name) ⇒ Boolean

return true if cookbook is downloaded from git

Returns:

  • (Boolean)

Raises:

  • (NotImplementedError)


43
44
45
# File 'lib/knife/changelog/changelog.rb', line 43

def git?(name)
  raise NotImplementedError
end

#git_location(name) ⇒ Object

return a Changelog::Location for a given cookbook

Raises:

  • (NotImplementedError)


53
54
55
# File 'lib/knife/changelog/changelog.rb', line 53

def git_location(name)
  raise NotImplementedError
end

#guess_version_for(name) ⇒ Object

return current locked version for a given cookbook

Raises:

  • (NotImplementedError)


64
65
66
# File 'lib/knife/changelog/changelog.rb', line 64

def guess_version_for(name)
  raise NotImplementedError
end

#handle_git(name, location) ⇒ Object

take cookbook name and Changelog::Location instance



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/knife/changelog/changelog.rb', line 208

def handle_git(name, location)
  # todo: remove this compat check
  raise "should be a location" unless location.is_a?(Changelog::Location)
  git = Git.new(@tmp_prefix, location.uri)
  @tmp_dirs << git.shallow_clone

  rev_parse = location.rev_parse
  cur_rev = detect_cur_revision(name, location.revision, git)
  changelog_file = git.files(rev_parse).find { |line| line =~ /\s(changelog.*$)/i }
  changelog = if changelog_file and !@config[:ignore_changelog_file]
                Chef::Log.info "Found changelog file : " + $1
                generate_from_changelog_file($1, cur_rev, rev_parse, git)
              end
  changelog ||= generate_from_git_history(git, location, cur_rev, rev_parse)
  ["#{cur_rev}->#{rev_parse}", changelog]
end

#handle_new_cookbookObject



101
102
103
104
# File 'lib/knife/changelog/changelog.rb', line 101

def handle_new_cookbook
  stars = '**' if @config[:markdown]
  ["#{stars}Cookbook was not in the berksfile#{stars}"]
end

#handle_source(name) ⇒ Object



171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/knife/changelog/changelog.rb', line 171

def handle_source(name)
  url = get_from_supermarket_sources(name)
  raise "No source found in supermarket for cookbook '#{name}'" unless url
  Chef::Log.debug("Using #{url} as source url")
  # Workaround source_url not being written in a clonable way.
  # github.com/blah/cookbook works but git clone requires github.com/blah/cookbook.git
  if !url.end_with?('.git')
    url = "#{url}.git"
  end
  location = Location.new(url, guess_version_for(name), 'HEAD')
  handle_git(name, location)
end

#handle_submodule(name) ⇒ Object



193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/knife/changelog/changelog.rb', line 193

def handle_submodule(name)
  subm_url = Mixlib::ShellOut.new("git config --list| grep ^submodule | grep ^submodule.#{name}.url")
  subm_url.run_command
  subm_url.error!
  url = subm_url.stdout.lines.first.split('=')[1].chomp
  subm_revision = Mixlib::ShellOut.new("git submodule status #{name}")
  subm_revision.run_command
  subm_revision.error!
  revision = subm_revision.stdout.strip.split(' ').first
  revision.gsub!(/^\+/, '')
  loc = Location.new(url, revision, 'HEAD')
  handle_git(name, loc)
end

#https_url(location) ⇒ Object



253
254
255
256
257
258
259
# File 'lib/knife/changelog/changelog.rb', line 253

def https_url(location)
  if location.uri =~ /^\w+:\/\/(.*@)?(.*)(\.git)?/
    "https://%s" % [ $2 ]
  else
    fail "Cannot guess http url from git remote url: #{location.uri}"
  end
end

#linkify(url, changelog) ⇒ Object



243
244
245
246
247
248
249
250
251
# File 'lib/knife/changelog/changelog.rb', line 243

def linkify(url, changelog)
  format = case url
           when /gitlab/, /github/
             "\\2 (#{url.chomp('.git')}/commit/\\1)"
           when GERRIT_REGEXP
             "\\2 (#{::Regexp.last_match(1)}/#/q/\\1)"
           end
  format ? changelog.map { |line| line.sub(/^([a-f0-9]+) (.*)$/, format) } : changelog
end

#local?(name) ⇒ Boolean

return true if cookbook is downloaded from local path

Returns:

  • (Boolean)

Raises:

  • (NotImplementedError)


48
49
50
# File 'lib/knife/changelog/changelog.rb', line 48

def local?(name)
  raise NotImplementedError
end

#new_cookbook?(name) ⇒ Boolean

return true if cookbook is not already listed as dependency

Returns:

  • (Boolean)

Raises:

  • (NotImplementedError)


33
34
35
# File 'lib/knife/changelog/changelog.rb', line 33

def new_cookbook?(name)
  raise NotImplementedError
end

#run(cookbooks) ⇒ Object



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/knife/changelog/changelog.rb', line 70

def run(cookbooks)
  changelog = []
  begin
    if cookbooks.empty? and @config[:allow_update_all]
      cks = all_cookbooks
    else
      cks = cookbooks
    end
    changelog += cks.map do |cookbook|
      Chef::Log.debug "Checking changelog for #{cookbook} (cookbook)"
      execute cookbook
    end
    subs = @config[:submodules] || []
    subs = subs.split(',') if subs.is_a? String
    changelog += subs.map do |submodule|
      Chef::Log.debug "Checking changelog for #{submodule} (submodule)"
      execute(submodule, true)
    end
    update(cks) if @config[:update]
  ensure
    clean
  end
  changelog.compact.join("\n")
end

#short(location) ⇒ Object



261
262
263
264
265
# File 'lib/knife/changelog/changelog.rb', line 261

def short(location)
  if location.uri =~ /([\w-]+)\/([\w-]+)(\.git)?$/
    "%s/%s" % [$1,$2]
  end
end

#supermarket?(name) ⇒ Boolean

 return true if cookbook is downloaded from supermarket

Returns:

  • (Boolean)

Raises:

  • (NotImplementedError)


38
39
40
# File 'lib/knife/changelog/changelog.rb', line 38

def supermarket?(name)
  raise NotImplementedError
end

#supermarkets_for(name) ⇒ Object

 return a list of supermarket uri for a given cookbook example: [ ‘supermarket.chef.io’ ]

Raises:

  • (NotImplementedError)


59
60
61
# File 'lib/knife/changelog/changelog.rb', line 59

def supermarkets_for(name)
  raise NotImplementedError
end