summaryrefslogtreecommitdiff
path: root/tasks
diff options
context:
space:
mode:
authorThom May <thom@chef.io>2015-09-07 15:11:48 +0100
committerThom May <thom@chef.io>2015-09-07 15:11:48 +0100
commitd7ce09f91e298457448b212f7ccb547de2d1a5af (patch)
treeb56d3799babb5d839d335eaa358feae1784ccb65 /tasks
parentbc890071b8b1e0eba9aa04fa51a36311848ab1cc (diff)
downloadchef-d7ce09f91e298457448b212f7ccb547de2d1a5af.tar.gz
sync maintainers with githubtm/sync_gh_maintainers
Diffstat (limited to 'tasks')
-rw-r--r--tasks/maintainers.rb169
1 files changed, 155 insertions, 14 deletions
diff --git a/tasks/maintainers.rb b/tasks/maintainers.rb
index 5a2c8d9c2d..73a422fc61 100644
--- a/tasks/maintainers.rb
+++ b/tasks/maintainers.rb
@@ -20,50 +20,191 @@ require 'rake'
SOURCE = File.join(File.dirname(__FILE__), "..", "MAINTAINERS.toml")
TARGET = File.join(File.dirname(__FILE__), "..", "MAINTAINERS.md")
+# The list of repositories that teams should own
+REPOSITORIES = ["chef/chef", "chef/chef-census", "chef/chef-repo",
+ "chef/client-docs", "chef/ffi-yajl", "chef/libyajl2-gem",
+ "chef/mixlib-authentication", "chef/mixlib-cli",
+ "chef/mixlib-config", "chef/mixlib-install", "chef/mixlib-log",
+ "chef/mixlib-shellout", "chef/ohai", "chef/omnibus-chef"]
+
begin
require 'tomlrb'
+ require 'octokit'
+ require 'pp'
task :default => :generate
namespace :maintainers do
desc "Generate MarkDown version of MAINTAINERS file"
task :generate do
- maintainers = Tomlrb.load_file SOURCE
out = "<!-- This is a generated file. Please do not edit directly -->\n\n"
- out << "# " + maintainers["Preamble"]["title"] + "\n\n"
- out << maintainers["Preamble"]["text"] + "\n"
- out << "# " + maintainers["Org"]["Lead"]["title"] + "\n\n"
- out << person(maintainers["people"], maintainers["Org"]["Lead"]["person"]) + "\n\n"
- out << components(maintainers["people"], maintainers["Org"]["Components"])
+ out << "# " + source["Preamble"]["title"] + "\n\n"
+ out << source["Preamble"]["text"] + "\n"
+
+ # The project lead is a special case
+ out << "# " + source["Org"]["Lead"]["title"] + "\n\n"
+ out << format_person(source["Org"]["Lead"]["person"]) + "\n\n"
+
+ out << format_components(source["Org"]["Components"])
File.open(TARGET, "w") { |fn|
fn.write out
}
end
+
+ desc "Synchronize GitHub teams"
+ # there's a special @chef/client-maintainers team that's everyone
+ # and then there's a team per component
+ task :synchronize do
+ Octokit.auto_paginate = true
+ get_github_teams
+ prepare_teams(source["Org"]["Components"].dup)
+ sync_teams!
+ end
+ end
+
+ def github
+ @github ||= Octokit::Client.new(:netrc => true)
+ end
+
+ def source
+ @source ||= Tomlrb.load_file SOURCE
end
- def components(list, cmp)
+ def teams
+ @teams ||= {"client-maintainers" => {"title" => "Client Maintainers"}}
+ end
+
+ def add_members(team, name)
+ teams["client-maintainers"]["members"] ||= []
+ teams["client-maintainers"]["members"] << name
+ teams[team] ||= {}
+ teams[team]["members"] ||= []
+ teams[team]["members"] << name
+ end
+
+ def set_team_title(team, title)
+ teams[team] ||= {}
+ teams[team]["title"] = title
+ end
+
+ def gh_teams
+ @gh_teams ||= {}
+ end
+
+ # we have to resolve team names to ids. While we're at it, we can get the privacy
+ # setting, so we know whether we need to update it
+ def get_github_teams
+ github.org_teams("chef").each do |team|
+ gh_teams[team[:slug]] = {"id" => team[:id], "privacy" => team[:privacy]}
+ end
+ end
+
+ def get_github_team(team)
+ github.team_members(gh_teams[team]["id"]).map do |member|
+ member[:login]
+ end.sort.uniq.map(&:downcase)
+ rescue
+ []
+ end
+
+ def create_team(team)
+ puts "creating new github team: #{team} with title: #{teams[team]["title"]} "
+ t = github.create_team("chef", name: team, description: teams[team]["title"],
+ privacy: "closed", repo_names: REPOSITORIES,
+ accept: "application/vnd.github.ironman-preview+json")
+ gh_teams[team] = { "id" => t[:id], "privacy" => t[:privacy] }
+ end
+
+ def compare_teams(current, desired)
+ # additions are the subtraction of the current state from the desired state
+ # deletions are the subtraction of the desired state from the current state
+ [desired - current, current - desired]
+ end
+
+ def prepare_teams(cmp)
+ %w(text paths).each { |k| cmp.delete(k) }
+ if cmp.key?("team")
+ team = cmp.delete("team")
+ add_members(team, cmp.delete("lieutenant")) if cmp.key?("lieutenant")
+ add_members(team, cmp.delete("maintainers")) if cmp.key?("maintainers")
+ set_team_title(team, cmp.delete("title"))
+ else
+ %w(maintainers lieutenant title).each { |k| cmp.delete(k) }
+ end
+ cmp.each { |_k, v| prepare_teams(v) }
+ end
+
+ def update_team(team, additions, deletions)
+ create_team(team) unless gh_teams.key?(team)
+ update_team_privacy(team)
+ add_team_members(team, additions)
+ remove_team_members(team, deletions)
+ rescue
+ puts "failed for #{team}"
+ end
+
+ def update_team_privacy(team)
+ return if gh_teams[team]["privacy"] == "closed"
+ puts "Setting #{team} privacy to closed from #{gh_teams[team]["privacy"]}"
+ github.update_team(gh_teams[team]["id"], privacy: "closed",
+ accept: "application/vnd.github.ironman-preview+json")
+ end
+
+ def add_team_members(team, additions)
+ additions.each do |member|
+ puts "Adding #{member} to #{team}"
+ github.add_team_membership(gh_teams[team]["id"], member, role: "member",
+ accept: "application/vnd.github.ironman-preview+json")
+ end
+ end
+
+ def remove_team_members(team, deletions)
+ deletions.each do |member|
+ puts "Removing #{member} from #{team}"
+ github.remove_team_membership(gh_teams[team]["id"], member,
+ accept: "application/vnd.github.ironman-preview+json")
+ end
+ end
+
+ def sync_teams!
+ teams.each do |name, details|
+ current = get_github_team(name)
+ desired = details["members"].flatten.sort.uniq.map(&:downcase)
+ additions, deletions = compare_teams(current, desired)
+ update_team(name, additions, deletions)
+ end
+ end
+
+ def get_person(person)
+ source["people"][person]
+ end
+
+ def format_components(cmp)
out = "## " + cmp.delete("title") + "\n\n"
out << cmp.delete("text") + "\n" if cmp.has_key?("text")
+ out << "To mention the team, use @chef/#{cmp.delete("team")}\n\n" if cmp.has_key?("team")
if cmp.has_key?("lieutenant")
out << "### Lieutenant\n\n"
- out << person(list, cmp.delete("lieutenant")) + "\n\n"
+ out << format_person(cmp.delete("lieutenant")) + "\n\n"
end
- out << maintainers(list, cmp.delete("maintainers")) + "\n" if cmp.has_key?("maintainers")
+ out << format_maintainers(cmp.delete("maintainers")) + "\n" if cmp.has_key?("maintainers")
cmp.delete("paths")
- cmp.each {|k,v| out << components(list, v) }
+ cmp.each {|k,v| out << format_components(v) }
out
end
- def maintainers(list, people)
+ def format_maintainers(people)
o = "### Maintainers\n\n"
people.each do |p|
- o << person(list, p) + "\n"
+ o << format_person(p) + "\n"
end
o
end
- def person(list, person)
- "* [#{list[person]["Name"]}](https://github.com/#{list[person]["GitHub"]})"
+ def format_person(person)
+ mnt = get_person(person)
+ "* [#{mnt["Name"]}](https://github.com/#{mnt["GitHub"]})"
end
+
rescue LoadError
STDERR.puts "\n*** TomlRb not available.\n\n"
end