diff --git a/README.md b/README.md index ad2004f..929f87b 100644 --- a/README.md +++ b/README.md @@ -33,3 +33,9 @@ and change roles/functions/laptops. ## Rake Tasks There exist rake tasks that can be used to propogate some of these templates. + +## Scripts + +* [./script/grant_revoke_gem_authority.rb](./script/grant_revoke_gem_authority.rb) + is a convenience script to synchronize authorship of Samvera + rubygems. diff --git a/data/definitive-rubygems-authors-list.txt b/data/definitive-rubygems-authors-list.txt new file mode 100644 index 0000000..ec88941 --- /dev/null +++ b/data/definitive-rubygems-authors-list.txt @@ -0,0 +1,39 @@ +# This file has the definitive and declarative list of Samvera ruby +# gems owners. Each line must either be either an email address +# of the associated Rubygems profile. +aaron.collier@stanford.edu +adam.wead@protonmail.com +anna3lc@gmail.com +armintor@gmail.com +bess +bmquinn +cam156@psu.edu +chris@cbeer.info +cjcolvar +dan.coughlin@gmail.com +dchandekstark@gmail.com +digger250@gmail.com +drew@codhicitta.com +dweber.consulting@gmail.com +elrayle +escowles@ticklefish.org +glen.horton@gmail.com +jenlindner@gmail.com +jeremy.n.friesen@gmail.com +jessie.keck@gmail.com +johnson.tom@gmail.com +jonathan@dnil.net +jpstroop@gmail.com +jrgriffiniii@gmail.com +lawhorkl@mail.uc.edu +leftwing@alumni.rutgers.edu +mark@curationexperts.com +mbklein@gmail.com +mkorcy@gmail.com +ndushay@stanford.edu +ohiocore@gmail.com +rachelkg@gmail.com +randalldfloyd@gmail.com +revgum@gmail.com +thomas.scherz@uc.edu +tpendragon@princeton.edu diff --git a/script/grant_revoke_gem_authority.rb b/script/grant_revoke_gem_authority.rb index 17ce7a6..aab5f5b 100755 --- a/script/grant_revoke_gem_authority.rb +++ b/script/grant_revoke_gem_authority.rb @@ -1,5 +1,8 @@ # frozen_string_literal: true +# The purpose of this script is to manage the authors of the rubygems +# managed by the Samvera community. +# # 1. Get a personal access token from GitHub (https://github.com/settings/tokens) # with the following scopes enabled: # * public_repo @@ -7,75 +10,49 @@ # * user:email # 2. Set an ENV variable named 'GITHUB_SAMVERA_TOKEN' containing your token # 3. Then run this script: -# $ ruby ./script/grant_revoke_gem_authority.rb +# $ bundle exec ruby ./script/grant_revoke_gem_authority.rb +# +# By default the above script will iterate over samvera and +# samvera-labs repositories. If you set the WITH_DEPRECATED +# environment variable, then the script will include +# samvera-deprecated organization. # # To also revoke ownership from users whose email addresses are not in the list: -# $ WITH_REVOKE=true ruby ./script/grant_revoke_gem_authority.rb +# +# $ WITH_REVOKE=true bundle exec ruby ./script/grant_revoke_gem_authority.rb # # To print more information on what the script is doing: +# # $ VERBOSE=true ruby ./script/grant_revoke_gem_authority.rb require 'github_api' require 'open3' AUTHORIZATION_TOKEN = ENV['GITHUB_SAMVERA_TOKEN'] || raise("GitHub authorization token was not found in the GITHUB_SAMVERA_TOKEN environment variable") -ORGANIZATION_NAMES = ['samvera', 'samvera-labs', 'samvera-deprecated'] -# Some GitHub user instances do not have an email address defined, -# so start with the prior list of addresses (registered with Rubygems.org) -KNOWN_COMMITTER_EMAIL_ADDRESSES = { - 'aaron-collier' => 'aaron.collier@stanford.edu', - 'awead' => "awead@users.noreply.github.com", - 'atz' => 'ohiocore@gmail.com', - 'barmintor' => "armintor@gmail.com", - 'bess' => "bess@curationexperts.com", - 'cam156' => "cam156@psu.edu", - 'cbeer' => "chris@cbeer.info", - 'cjcolvar' => "cjcolvar@indiana.edu", - 'coblej' => "jim.coble@duke.edu", - 'DanCoughlin' => "dan.coughlin@gmail.com", - 'darrenleeweber' => 'dweber.consulting@gmail.com', - 'dchandekstark' => "dchandekstark@gmail.com", - 'dheles' => 'drew@codhicitta.com', - 'dunn' => 'dunn.alex@gmail.com', - 'elrayle' => 'elr37@cornell.edu', - 'escowles' => 'escowles@ticklefish.org', - 'grosscol' => 'grosscol@gmail.com', - 'hackmastera' => 'anna3lc@gmail.com', - 'hortongn' => 'glen.horton@gmail.com', - 'jeremyf' => "jeremy.n.friesen@gmail.com", - 'jenlindner' => 'jenlindner@gmail.com', - 'jkeck' => "jessie.keck@gmail.com", - 'jpstroop' => "jpstroop@gmail.com", - 'jrgriffiniii' => 'jrgriffiniii@gmail.com', - 'jrochkind' => 'jonathan@dnil.net', - 'jcoyne' => "digger250@gmail.com", - 'lawhorkl' => 'lawhorkl@mail.uc.edu', - 'mjgiarlo' => "leftwing@alumni.rutgers.edu", - 'mark-dce' => "mark@curationexperts.com", - 'mbklein' => "mbklein@gmail.com", - 'mkorcy' => "mkorcy@gmail.com", - 'ndushay' => "ndushay@stanford.edu", - 'tpendragon' => "tpendragon@princeton.edu", - 'carrickr' => 'carrickr@umich.edu', - 'no_reply' => 'johnson.tom@gmail.com', - 'revgum' => 'revgum@gmail.com', - 'rmkadel' => 'rachelkg@gmail.com', - 'randalldfloyd' => 'randalldfloyd@gmail.com' -} -# Some GitHub repositories are named differently from their gems + +if ENV.fetch("WITH_DEPRECATED", false) + ORGANIZATION_NAMES = ['samvera', 'samvera-labs', 'samvera-deprecated'] +else + ORGANIZATION_NAMES = ['samvera', 'samvera-labs'] +end + +# Some GitHub repositories are named differently from their gems. We +# could read each repositories .gemspec to determine its +# name. However, this is a far quicker short-cut. KNOWN_MISMATCHED_GEM_NAMES = { 'active_fedora' => 'active-fedora', 'fcrepo-admin' => 'fcrepo_admin', 'questioning_authority' => 'qa' } + # GitHub repositories with matching gems that aren't from Samvera FALSE_POSITIVES = [ 'hypatia', 'rdf-vocab', 'lerna', - 'hydrangea', - 'valkyrie' + 'hydrangea' ] + # Gems that do not have their own GitHub repositories HANGERS_ON = [ 'hydra-core', @@ -83,56 +60,38 @@ 'sufia-models', 'curation_concerns-models' ] -# Email addresses that are known not to be registered at rubygems.org -SKIP_EMAILS = [ - 'geisler@stanford.edu', - 'dlrueda@stanford.edu', - 'jgreben@stanford.edu', - 'lmcglohon@gmail.com', - 'ssklar@stanford.edu', - 'tony.zanella@gmail.com', - 'blalbritton@gmail.com', - 'dgcliff@northeastern.edu', - 'jessica@ucsd.edu' -] + VERBOSE = ENV.fetch('VERBOSE', false) -puts "(Hang in there! This script takes a couple minutes to run.)" +# See https://guides.rubygems.org/rubygems-org-api/#rate-limits I'm +# opting for the slower 0.2 so as to not come close to the rate limit. +GEM_SLEEP_INTERVAL = 0.2 -github = Github.new(oauth_token: AUTHORIZATION_TOKEN, auto_pagination: true) +$stdout.puts "Hang in there! This script takes a couple minutes to run." -# Get the ID of the GitHub-provided "Owners" team from the samvera org -# We don't currently grant gem ownership to the folks in the other two orgs -# (This just preserves the prior behavior.) -owner_team_id = github.orgs.teams.list(org: 'samvera').select { |team| team.name == 'Admins' }.first.id -owners = github.orgs.teams.list_members(owner_team_id) -# Start with the prior (known to work) list of email addresses -committer_map = KNOWN_COMMITTER_EMAIL_ADDRESSES.dup -owners.each do |owner| - user = github.users.get(user: owner.login) - # Move along if the user doesn't have an email addy or if there's already an entry in the map - next if !user.respond_to?(:email) || user.email.nil? || user.email.empty? || !committer_map[user.login].nil? - committer_map[user.login] = user.email -end -committer_emails = committer_map.values.sort.uniq +github = Github.new(oauth_token: AUTHORIZATION_TOKEN, auto_pagination: true) # Keep track of things @errors = [] @bogus_gem_names = [] @gem_names = HANGERS_ON -def exists?(name) - system("gem owner #{name} > /dev/null 2>&1") -end - -def replace_known_mismatch(name) - KNOWN_MISMATCHED_GEM_NAMES.fetch(name, name) -end +@definitive_committers = File.read( + File.expand_path("../../data/definitive-rubygems-authors-list.txt", __FILE__) +).split("\n").map { |line| line.sub(/ *#.*$/, "") }.select { |line| !line.empty? } ORGANIZATION_NAMES.each do |org_name| github.repos.list(org: org_name).each do |repo| - name = replace_known_mismatch(repo.name) - if exists?(name) + # Replace known mismatched names + name = KNOWN_MISMATCHED_GEM_NAMES.fetch(repo.name, repo.name) + + # Let's move on to any gem that we know will be a false positive. + next if FALSE_POSITIVES.include?(name) + + # TODO: We are twice calling `gem owner` for information. We could + # rework the logic to only call it once. + sleep(GEM_SLEEP_INTERVAL) + if system("gem owner #{name} > /dev/null 2>&1") @gem_names << name else @bogus_gem_names << repo.full_name @@ -140,27 +99,44 @@ def replace_known_mismatch(name) end end -def gem_owner_with_error_check(gemname, params) - command = "gem owner #{gemname} #{params}" - puts "running: #{command}" if VERBOSE +OWNERSHIP_ACTIONS = { + add: '-a', + remove: '-r' +} + +# @param gemname [String] +# @param profile [String] either an email associated with Rubygems or the +# Rubygems.org profile name +# @param action [Symbol] either :add or :remove (see OWNERSHIP_ACTIONS) +def modify_gem_ownership(gemname:, profile:, action:) + switch = OWNERSHIP_ACTIONS.fetch(action) + sleep(GEM_SLEEP_INTERVAL) + command = "gem owner #{gemname} #{switch} #{profile}" + $stdout.puts "running: #{command}" if VERBOSE Open3.popen3(command) do |stdin, stdout, stderr, wait_thr| - @errors << "#{gemname} #{params}: #{stdout.read.chomp}" unless wait_thr.value.success? + @errors << "#{command}: #{stdout.read.chomp}" unless wait_thr.value.success? end end -@gem_names.reject { |gemname| FALSE_POSITIVES.include?(gemname) }.sort.each do |gemname| + +@gem_names.sort.each do |gemname| + $stdout.puts "Updating #{gemname}..." + + sleep(GEM_SLEEP_INTERVAL) current_committers = `gem owner #{gemname} | grep -e ^-`.split("\n") current_committers.collect! { |cc| cc.sub(/^.\s+/,'')} + committers_to_add = @definitive_committers - current_committers + committers_to_add.each do |profile| + modify_gem_ownership(gemname: gemname, profile: profile, action: :add) + end + if ENV.fetch('WITH_REVOKE', false) committers_to_remove = current_committers - committer_emails - remove_params = committers_to_remove.map { |email| "-r #{email}" }.join(' ') - gem_owner_with_error_check(gemname, remove_params) + committers_to_remove.each do |profile| + modify_gem_ownership(gemname: gemname, profile: profile, action: :remove) + end end - - committers_to_add = committer_emails - current_committers - SKIP_EMAILS - add_params = committers_to_add.map { |email| "-a #{email}" }.join(' ') - gem_owner_with_error_check(gemname, add_params) end if @bogus_gem_names.any?