| # |
| # Secretary post-meeting todo list |
| # |
| |
| TLPREQ = ASF::SVN['tlpreq-input'] |
| |
| date = params[:date].gsub('-', '_') |
| raise ArgumentError, "Invalid date #{date}" unless date =~ /\A\d+_\d+_\d+\z/ |
| datehy = date.gsub('_', '-') |
| date_established = Date.parse(datehy) |
| |
| agenda = "board_agenda_#{date}.txt" |
| |
| # fetch minutes |
| @minutes = agenda.sub('_agenda_', '_minutes_') |
| minutes_file = File.join(AGENDA_WORK, @minutes.sub('.txt', '.yml')) |
| |
| if File.exist? minutes_file |
| minutes = YAML.load_file(minutes_file) || {} |
| else |
| minutes = {} |
| end |
| |
| minutes[:todos] ||= {} |
| todos = minutes[:todos].dup |
| |
| parsed_agenda = Agenda.parse(agenda, :full) |
| |
| # iterate over the agenda, finding items where there is either comments or |
| # minutes that can be forwarded to the PMC |
| feedback = [] |
| parsed_agenda.each do |item| |
| # select exec officer, additional officer, and committee reports |
| next unless item[:attach] =~ /^(4[A-Z]|\d|[A-Z]+)$/ |
| next unless item['chair_email'] |
| |
| next unless minutes[item['title']] or |
| (item['comments'] and not item['comments'].empty?) |
| |
| unless todos[:feedback_sent] and todos[:feedback_sent].include? item['title'] |
| feedback << item['title'] |
| end |
| end |
| |
| ######################################################################## |
| # Actions # |
| ######################################################################## |
| |
| if @remove and env.password |
| chairs = ASF::Service.find('pmc-chairs') |
| |
| people = @remove.map {|id| ASF::Person.find(id)} |
| |
| ASF::LDAP.bind(env.user, env.password) do |
| chairs.remove people |
| end |
| |
| minutes[:todos][:removed] ||= [] |
| minutes[:todos][:removed] += people.map {|person| person.id} |
| end |
| |
| # update committee-info.txt |
| if (@change || @establish || @terminate) and env.password |
| cinfo = File.join(ASF::SVN['board'], 'committee-info.txt') |
| |
| todos = Array(@change) + Array(@establish) + Array(@terminate) |
| if todos.length == 1 |
| title = todos.first['title'] |
| else |
| title = 'board resolutions: ' + todos.map {|todo| todo['name']}.join(', ') |
| end |
| |
| ASF::SVN.update cinfo, title, env, _ do |_tmpdir, contents| |
| unless minutes[:todos][:next_month] |
| # update list of reports expected next month |
| missing = parsed_agenda. |
| select {|item| item[:attach] =~ /^[A-Z]+$/ and item['missing']}. |
| map {|item| item['title']} |
| rejected = minutes[:rejected] || [] |
| contents = ASF::Committee.update_next_month(contents, |
| date_established, missing, rejected, todos) |
| end |
| |
| # update chairs from establish, change, and terminate resolutions |
| contents = ASF::Committee.update_chairs(contents, todos) |
| |
| # remove terminated projects |
| Array(@terminate).each do |resolution| |
| contents = ASF::Committee.terminate(contents, resolution['display_name']) |
| end |
| |
| # add people from establish resolutions |
| Array(@establish).each do |resolution| |
| item = parsed_agenda.find do |it| |
| it['title'] == resolution['title'] |
| end |
| |
| contents = ASF::Committee.establish(contents, resolution['display_name'], |
| date_established, item['people']) |
| end |
| |
| contents |
| end |
| |
| minutes[:todos][:next_month] = true |
| File.write minutes_file, YAML.dump(minutes) |
| end |
| |
| # update LDAP, create victims.txt, update committee-info.yaml |
| if @establish and env.password |
| @establish.each do |resolution| |
| pmc = resolution['name'] |
| |
| item = parsed_agenda.find do |it| |
| it['title'] == resolution['title'] |
| end |
| |
| members = item['people'].map {|id, _hash| ASF::Person.find(id)} |
| people = item['people'].map {|id, hash| [id, hash[:name]]} |
| |
| ASF::LDAP.bind(env.user, env.password) do |
| # new style definitions |
| project = ASF::Project[pmc.downcase] |
| if not project |
| ASF::Project.find(pmc.downcase).create(members, members) |
| else # project exists |
| # sync project owners with new PMC list |
| project.add_owners(members) |
| project.remove_owners(project.owners - members) |
| project.add_members(members) |
| end |
| end |
| charter = item['charter'] |
| if charter |
| # update ci.yaml |
| cinfoy = File.join(ASF::SVN['board'], 'committee-info.yaml') |
| ASF::SVN.update cinfoy, title, env, _ do |_tmpdir, contents| |
| ASF::Committee.appendtlpmetadata(contents, pmc.downcase, charter, date_established) |
| end |
| end |
| end |
| |
| establish = @establish.map {|resolution| resolution['name']} |
| |
| # create 'victims' file for tlpreq tool |
| ASF::SVN.svn('update', TLPREQ) |
| establish -= Dir[File.join(TLPREQ, "victims-#{date}.*.txt")]. |
| map {|name| File.read(name).lines().map(&:chomp)}.flatten |
| unless establish.empty? |
| count = Dir[File.join(TLPREQ, "victims-#{date}.*.txt")].length |
| message = "record #{date} approved TLP resolutions" |
| ASF::SVN.update TLPREQ, message, env, _ do |tmpdir| |
| filename = "victims-#{date}.#{count}.txt" |
| contents = establish.join("\n") + "\n" |
| File.write File.join(tmpdir, filename), contents |
| ASF::SVN.svn_('add', File.join(tmpdir, filename), _) |
| end |
| end |
| end |
| |
| # update ci.yaml to add retirements |
| if @terminate and env.password |
| yyyymm = datehy[0, 7] # keep only yyyy-mm |
| cinfoy = File.join(ASF::SVN['board'], 'committee-info.yaml') |
| @terminate.each do |resolution| |
| ASF::SVN.update cinfoy, title, env, _ do |_tmpdir, contents| |
| ASF::Committee.record_termination(contents, resolution['name'], yyyymm) |
| end |
| end |
| end |
| |
| # update LDAP and send out congratulatory email |
| if (@change || @establish) and env.password |
| chairs = ASF::Service.find('pmc-chairs') |
| |
| # select all new chairs |
| todos = Array(@change) + Array(@establish) |
| people = todos.map {|todo| ASF::Person.find(todo['chair'])}.uniq |
| people -= chairs.members |
| |
| unless people.empty? |
| # add new chairs to pmc-chairs |
| ASF::LDAP.bind(env.user, env.password) do |
| chairs.add people |
| end |
| |
| # send out congratulations email |
| ASF::Mail.configure |
| sender = ASF::Person.new(env.user) |
| mail = Mail.new do |
| from "#{sender.public_name.inspect} <#{sender.id}@apache.org>" |
| |
| to people.map {|person| |
| "#{person.public_name.inspect} <#{person.id}@apache.org>" |
| }.to_a |
| |
| cc 'Apache Board <board@apache.org>' |
| |
| subject "Congratulations on your new role at Apache - next steps" |
| |
| body "Dear newly-appointed PMC Chair(s),\n\n" + |
| "Congratulations on being appointed by the board as a project VP and Chair of your PMC!\n\n" + |
| "Please read the following list of required to-dos for new Chairs, which includes key links to all the duties and responsibilities of being a Chair.\n\n" + |
| " https://apache.org/dev/pmc-chair" + |
| "\n\nWarm regards,\n\n#{sender.public_name}\nOn Behalf of the ASF Board" |
| end |
| |
| mail.deliver! |
| end |
| end |
| |
| ######################################################################## |
| # Update list of completed todos # |
| ######################################################################## |
| |
| if @change |
| minutes[:todos][:changed] ||= [] |
| minutes[:todos][:changed] += @change.map {|resolution| resolution['name']} |
| end |
| |
| if @establish |
| minutes[:todos][:established] ||= [] |
| minutes[:todos][:established] += |
| @establish.map {|resolution| resolution['name']} |
| end |
| |
| if @terminate |
| minutes[:todos][:terminated] ||= [] |
| minutes[:todos][:terminated] += |
| @terminate.map {|resolution| resolution['name'].downcase} |
| end |
| |
| unless todos == minutes[:todos] |
| File.write minutes_file, YAML.dump(minutes) |
| end |
| |
| ######################################################################## |
| # Response # |
| ######################################################################## |
| |
| transitioning = {} |
| establish = [] |
| terminate = {} |
| change = [] |
| |
| parsed_agenda.each do |item| |
| next unless item[:attach] =~ /^7\w$/ |
| if item['title'] =~ /^Change (.*?) Chair$/ and item['people'] |
| pmc = ASF::Committee.find($1).id |
| item['people'].each_key do |person| |
| transitioning[ASF::Person.find(person)] = item['title'] |
| end |
| next if Array(minutes[:todos][:changed]).include? pmc |
| change << {name: pmc, resolution: item['title'], chair: item['chair']} |
| elsif item['title'] =~ /^Establish\s*(.*?)\s*$/ and item['chair'] |
| pmc = ASF::Committee.find($1).id |
| transitioning[ASF::Person.find(item['chair'])] = item['title'] |
| next if Array(minutes[:todos][:established]).include? pmc |
| establish << {name: pmc, resolution: item['title'], chair: item['chair']} |
| elsif item['title'] =~ /^Terminate\s*(.*?)\s*$/ |
| pmc = ASF::Committee.find($1).id |
| next if Array(minutes[:todos][:terminated]).include? pmc |
| terminate[$1] = item['title'] |
| end |
| end |
| |
| add = transitioning.keys - ASF.pmc_chairs |
| remove = ASF.pmc_chairs - ASF::Committee.pmcs.map(&:chair) - |
| ASF::Committee.nonpmcs.map(&:chair) - transitioning.keys |
| |
| _add add.map {|person| {id: person.id, name: person.public_name, |
| email: person.mail.first, resolution: transitioning[person]}}. |
| sort_by {|person| person[:id]} |
| _remove remove.map {|person| {id: person.id, name: person.public_name}}. |
| sort_by {|person| person[:id]} |
| _change change |
| _establish establish |
| _terminate terminate. |
| map {|name, resolution| {name: name, resolution: resolution}} |
| _minutes minutes |
| _feedback feedback |