class ASF::Podling

Represents a podling, drawing information from both podlings.xml and LDAP.

Constants

NAME_FIXES

Override Proposed Names that are wrong

Attributes

champion[RW]

userid of the champion, from podlings.xml

description[RW]

description of the podling, from podlings.xml

mentors[RW]

list of userids of the mentors, from podlings.xml

monthly[RW]

if reporting monthly, a list of months reports are expected. Can also ge nil or an empty list. From podlings.xml.

name[W]

name of the podling, from podlings.xml

resolutionTLP[RW]

<resolution tlp=“”>, from podlings.xml

resolutionURL[RW]

<resolution url=“”>, from podlings.xml

resource[RW]

name of the podling, from podlings.xml

resourceAliases[RW]

array of aliases for the podling, from podlings.xml

status[RW]

status of the podling, from podlings.xml. Valid values are current, graduated, or retired.

Public Class Methods

_list(status) click to toggle source
# File lib/whimsy/asf/podling.rb, line 493
def self._list(status)
  list.select { |podling| podling.status == status }
end
_listids(status) click to toggle source
# File lib/whimsy/asf/podling.rb, line 497
def self._listids(status)
  list.select { |podling| podling.status == status }.map(&:id)
end
current() click to toggle source

list of current podlings

# File lib/whimsy/asf/podling.rb, line 202
def self.current
  self._list('current')
end
currentids() click to toggle source

list of current podling ids

# File lib/whimsy/asf/podling.rb, line 207
def self.currentids
  self._listids('current')
end
each(&block) click to toggle source

provide a list of podling names and descriptions

# File lib/whimsy/asf/podling.rb, line 265
def self.each(&block)
  list.each { |podling| block.call podling.name, podling }
end
find(name) click to toggle source

find a podling by name

# File lib/whimsy/asf/podling.rb, line 238
def self.find(name)
  name = name.downcase

  result = list.find do |podling|
    podling.name == name or podling.display_name.downcase == name or
      podling.resourceAliases.any? {|aname| aname.downcase == name}
  end

  result ||= list.find do |podling|
    podling.resource == name or
    podling.tlp_name.downcase == name
  end
end
graduated() click to toggle source

list of graduated podlings

# File lib/whimsy/asf/podling.rb, line 212
def self.graduated
  self._list('graduated')
end
graduatedids() click to toggle source

list of graduated podling ids

# File lib/whimsy/asf/podling.rb, line 217
def self.graduatedids
  self._listids('graduated')
end
list() click to toggle source

list of all podlings, regardless of status

# File lib/whimsy/asf/podling.rb, line 175
def self.list
  incubator_content = ASF::SVN['incubator-content']
  podlings_xml = File.join(incubator_content, 'podlings.xml')

  # see if there is a later version
  cache = ASF::Config.get(:cache)
  if File.exist? File.join(cache, 'podlings.xml')
    if File.mtime(File.join(cache, 'podlings.xml')) > File.mtime(podlings_xml)
      podlings_xml = File.join(cache, 'podlings.xml')
    end
  end

  if @mtime != File.mtime(podlings_xml)
    @list = []
    podlings = Nokogiri::XML(File.read(podlings_xml))
    # check for errors as they adversely affect the generated output
    raise Exception.new(podlings.errors.inspect) if podlings.errors.size > 0
    podlings.search('podling').map do |node|
      @list << new(node)
    end
    @mtime = File.mtime(podlings_xml)
  end

  @list
end
mtime() click to toggle source

last modified time of podlings.xml in the local working directory, as of the last time list was called.

# File lib/whimsy/asf/podling.rb, line 233
def self.mtime
  @mtime
end
namesearch() click to toggle source

parse (and cache) names mentioned in podlingnamesearches Note: customfield_12310520 = ‘Podling’, customfield_12310521 = ‘Proposed Name’

# File lib/whimsy/asf/podling.rb, line 441
def self.namesearch
  # cache JIRA response
  cache = File.join(ASF::Config.get(:cache), 'pns.jira')
  if not File.exist?(cache) or File.mtime(cache) < Time.now - 300
    query = 'https://issues.apache.org/jira/rest/api/2/search?' +
        'maxResults=1000&' +
        'jql=project=PODLINGNAMESEARCH&fields=summary,resolution,customfield_12310521'
    begin
      res = Net::HTTP.get_response(URI(query))
      res.value() # Raises error if not OK
      file = File.new(cache, 'wb') # Allow for non-UTF-8 chars
      file.write res.body
    rescue StandardError => e
      Wunderbar.warn 'ASF::Podling.namesearch: ' + e.message
      FileUtils.touch cache # Don't try again for a while
    end
  end

  # parse JIRA titles for proposed name
  issues = JSON.parse(File.read(cache))['issues'].map do |issue|
    resolution = issue['fields']['resolution']
    resolution = resolution ? resolution['name'] : 'Unresolved'
    # Ignore duplicates and abandoned entries etc.
    # PODLINGNAMESEARCH-9 is resolved as 'Not A Problem': this means it is cleared for use
    next unless %w{Fixed Unresolved Resolved Implemented}.include?(resolution) ||
      issue['key'] == 'PODLINGNAMESEARCH-9'

    name = issue['fields']['customfield_12310521']

    if name
      name.sub!(/^Apache\s+/, '')
      name.gsub!(/\s+\(.*?\)/, '')
      # Fix up incorrect 'Proposed Name' entries
      name = NAME_FIXES[name] || name
      name = nil if name =~ /^\s*This/ or name !~ /[A-Za-z]{3}/ or name =~ %r{^N/A}
    end

    next unless name

    [name, {issue: issue['key'], resolution: resolution}]
  end

  issues.compact.sort_by(&:first).to_h
end
new(node) click to toggle source

create a podling from a Nokogiri node built from podlings.xml

# File lib/whimsy/asf/podling.rb, line 57
def initialize(node)
  @mtime = nil
  @name = node['name']
  @resource = node['resource']
  # Validate resource for later use resource can contain '-' and '.' (lucene.net)
  raise ArgumentError, "Invalid resource #{@resource}" unless @resource =~ /\A[-.\w]+\z/
  @sponsor = node['sponsor']
  # Needed for matching against mailing list names
  @resourceAliases = []
  @resourceAliases = node['resourceAliases'].split(/,\s*/) if node['resourceAliases']
  @status = node['status']
  @enddate = node['enddate']
  @startdate = node['startdate']
  @description = node.at('description').text
  @mentors = node.search('mentor').map { |mentor| mentor['username'] }
  @champion = node.at('champion')['availid'] if node.at('champion')

  @reporting = node.at('reporting') || nil # ensure variable is defined
  @monthly = @reporting&.text&.split(/,\s*/)

  res = node.at('resolution')
  if res
    @resolutionLink = res.attr('link')
    @resolutionURL = res.attr('url')
    @resolutionTLP = res.attr('tlp')
  else
    @resolutionLink = nil
    @resolutionURL = nil
    @resolutionTLP = nil
  end

  # Note: the following optional elements are not currently processed:
  # - resolution (except for resolution/@link)
  # - retiring/graduating
  # The following podling attributes are not processed:
  # - longname
end
retired() click to toggle source

list of retired podlings

# File lib/whimsy/asf/podling.rb, line 222
def self.retired
  self._list('retired')
end
retiredids() click to toggle source

list of retired podling ids

# File lib/whimsy/asf/podling.rb, line 227
def self.retiredids
  self._listids('retired')
end
to_h() click to toggle source

return the entire list as a hash

# File lib/whimsy/asf/podling.rb, line 260
def self.to_h
  Hash[self.to_a]
end

Public Instance Methods

[](name) click to toggle source

allow attributes to be accessed as hash

# File lib/whimsy/asf/podling.rb, line 270
def [](name)
  return self.send name if self.respond_to? name
end
_match_mailname?(list, name) click to toggle source

Match against new and old list types

# File lib/whimsy/asf/podling.rb, line 333
def _match_mailname?(list, name)
  return true if list.start_with?("#{name}-")
  return true if list.start_with?("incubator-#{name}-")
end
as_hash() click to toggle source

Return the instance as a hash. Keys in the hash are: :name, :status, :description, :mentors, :startdate, :champion, :reporting, :resource, :resourceAliases, :sponsor, :duration, and :podlingStatus

# File lib/whimsy/asf/podling.rb, line 374
def as_hash # might be confusing to use to_h here?
  hash = {
      name: @name,
      status: status,
      description: description,
      mentors: mentors,
      startdate: startdate,
  }
  hash[:enddate] = enddate if enddate
  hash[:champion] = champion if champion

  # Tidy up the reporting output
  podlingStatus = self.podlingStatus
  r = @reporting
  if r.instance_of? Nokogiri::XML::Element
    group = r['group']
    hash[:reporting] = {
        group: group
    }
    hash[:reporting][:text] = r.text if r.text.length > 0
    hash[:reporting][:monthly] = r.text.split(/,\s*/) if r['monthly']
    hash[:reporting][:schedule] = self.schedule
  elsif r
    hash[:reporting] = r
  end

  hash[:resource] = resource
  hash[:resourceAliases] = resourceAliases
  hash[:namesearch] = namesearch if namesearch
  hash[:sponsor] = @sponsor if @sponsor
  hash[:duration] = self.duration
  hash[:podlingStatus] = podlingStatus
  hash[:resolutionLink] = resolutionLink if resolutionLink
  hash[:resolutionURL] = resolutionURL if resolutionURL
  hash[:resolutionTLP] = resolutionTLP if resolutionTLP
  hash
end
current?() click to toggle source
# File lib/whimsy/asf/podling.rb, line 117
def current?
  @status == 'current'
end
default_status() click to toggle source

status information associated with this podling. Keys in the hash return include: :issueTracker, :wiki, :jira, :proposal, :asfCopyright, <tt>:distributionRights, :ipClearance, :sga, :website, :graduationDate, :resolution

# File lib/whimsy/asf/podling.rb, line 417
def default_status
  {
      issueTracker: 'jira',
      wiki: self.resource.upcase,
      jira: self.resource.upcase,
      proposal: "http://wiki.apache.org/incubator/#{self.resource.capitalize}Proposal",
      asfCopyright: nil,
      distributionRights: nil,
      ipClearance: nil,
      sga: nil,
      website: "http://#{self.resource}.incubator.apache.org",
      graduationDate: nil,
      resolution: nil
  }
end
dev_mail_list() click to toggle source

development mailing list associated with a given podling

# File lib/whimsy/asf/podling.rb, line 299
def dev_mail_list
  case name
    when 'climatemodeldiagnosticanalyzer'
      'dev@cmda.incubator.apache.org'
    when 'odftoolkit'
      'odf-dev@incubator.apache.org'
    when 'log4cxx2'
      'log4cxx-dev@logging.apache.org'
    else
      "dev@#{name}.apache.org"
  end
end
display_name() click to toggle source

display name for this podling, originally from the name attribute in podlings.xml.

# File lib/whimsy/asf/podling.rb, line 108
def display_name
  @name || @resource
end
duration() click to toggle source

number of days in incubation

# File lib/whimsy/asf/podling.rb, line 151
def duration
  last = enddate || Date.today
  first = startdate || Date.today
  (last - first).to_i
end
enddate() click to toggle source

date this podling either retired or graduated. nil for current podlings.

# File lib/whimsy/asf/podling.rb, line 141
def enddate
  return unless @enddate and @enddate.length >= 7
  # assume 15th (mid-month) if no day specified
  return Date.parse("#{@enddate}-15") if @enddate.length == 7
  Date.parse(@enddate)
rescue ArgumentError
  nil
end
graduated?() click to toggle source
# File lib/whimsy/asf/podling.rb, line 121
def graduated?
  @status == 'graduated'
end
hasLDAP?() click to toggle source
# File lib/whimsy/asf/podling.rb, line 284
def hasLDAP?
  ASF::Project.find(id).hasLDAP?
end
id() click to toggle source

also map resource to id

# File lib/whimsy/asf/podling.rb, line 102
def id
  @resource
end
mail_list() click to toggle source

base name used in constructing mailing list name.

# File lib/whimsy/asf/podling.rb, line 289
def mail_list
  case name.downcase
  when 'odftoolkit'
    'odf'
  else
    name.downcase
  end
end
mail_list?(list) click to toggle source

Is this a podling mailing list?

# File lib/whimsy/asf/podling.rb, line 323
def mail_list?(list)
  return true if _match_mailname?(list, name())
  # Also check aliases
  @resourceAliases.each { |name|
    return true if _match_mailname?(list, name)
  }
  return false
end
members() click to toggle source

list of PPMC committers from LDAP

# File lib/whimsy/asf/podling.rb, line 280
def members
  ASF::Project.find(id).members
end
name() click to toggle source

name for this podling, originally from the resource attribute in podlings.xml.

# File lib/whimsy/asf/podling.rb, line 97
def name
  @resource
end
namesearch() click to toggle source

return podlingnamesearch for this podling

# File lib/whimsy/asf/podling.rb, line 487
def namesearch
  Podling.namesearch[display_name]
end
owners() click to toggle source

list of PPMC owners from LDAP

# File lib/whimsy/asf/podling.rb, line 275
def owners
  ASF::Project.find(id).owners
end
podlingStatus() click to toggle source

status information associated with this podling. Keys in the hash return include: :ipClearance, :sourceControl, :wiki, :jira, :proposal, :website, :news

# File lib/whimsy/asf/podling.rb, line 341
def podlingStatus
  incubator_content = ASF::SVN['incubator-podlings']
  resource_yml = File.join(incubator_content, "#{@resource}.yml")
  if File.exist?(resource_yml)
    rawYaml = Psych.load_file(resource_yml, permitted_classes: [Date, Symbol])
    hash = { }
    hash[:sga] = rawYaml[:sga].strftime('%Y-%m-%d') if rawYaml[:sga]&.class == Date
    hash[:asfCopyright] = rawYaml[:asfCopyright].strftime('%Y-%m-%d') if rawYaml[:asfCopyright]&.class == Date
    hash[:distributionRights] = rawYaml[:distributionRights].strftime('%Y-%m-%d') if rawYaml[:distributionRights]&.class == Date
    hash[:ipClearance] = rawYaml[:ipClearance].strftime('%Y-%m-%d') if rawYaml[:ipClearance]&.class == Date
    hash[:sourceControl] = rawYaml[:sourceControl]
    hash[:wiki] = rawYaml[:wiki]
    hash[:jira] = rawYaml[:jira]
    hash[:proposal] = rawYaml[:proposal]
    hash[:website] = rawYaml[:website]
    hash[:news] = []
    rawYaml[:news]&.each do |ni|
      newsItem = {}
      newsItem[:date] = ni[:date].strftime('%Y-%m-%d') if ni[:date]&.class == Date
      newsItem[:note] = ni[:note]
      hash[:news].push(newsItem)
    end
    hash
  else
    {news: [], website: "http://#{self.resource}.incubator.apache.org"}
  end
end
private_mail_list() click to toggle source

private mailing list associated with a given podling

# File lib/whimsy/asf/podling.rb, line 313
def private_mail_list
  if name == 'log4cxx2'
    'private@logging.apache.org'
  else
    list = dev_mail_list
    list ? list.sub('dev', 'private') : 'private@incubator.apache.org'
  end
end
quarter() click to toggle source

three consecutive months, starting with this one

# File lib/whimsy/asf/podling.rb, line 39
def quarter
  [
    Date.today.strftime('%B'),
    Date.today.next_month.strftime('%B'),
    Date.today.next_month.next_month.strftime('%B')
  ]
end
reporting() click to toggle source

lazy evaluation of reporting

# File lib/whimsy/asf/podling.rb, line 158
def reporting
  if @reporting.instance_of? Nokogiri::XML::Element
    group = @reporting['group']
    @reporting = %w(January April July October) if group == '1'
    @reporting = %w(February May August November) if group == '2'
    @reporting = %w(March June September December) if group == '3'
  end

  @reporting
end
retired?() click to toggle source
# File lib/whimsy/asf/podling.rb, line 125
def retired?
  @status == 'retired'
end
schedule() click to toggle source

provides a concatenated reporting schedule

# File lib/whimsy/asf/podling.rb, line 170
def schedule
  self.reporting + self.monthly
end
startdate() click to toggle source

date this podling was accepted for incubation

# File lib/whimsy/asf/podling.rb, line 130
def startdate
  return unless @startdate and @startdate.length >= 7 # "YYYY-MM"
  # assume 15th (mid-month) if no day specified
  return Date.parse("#{@startdate}-15") if @startdate.length == 7
  Date.parse(@startdate)
rescue ArgumentError
  nil
end
tlp_name() click to toggle source

TLP name (name differ from podling name)

# File lib/whimsy/asf/podling.rb, line 113
def tlp_name
  @resolutionLink || name
end