From 9aabb170edeb37abdbe109a35c225ddc453b23f6 Mon Sep 17 00:00:00 2001 From: a544266477 Date: Fri, 15 Sep 2023 21:10:51 -0700 Subject: [PATCH 01/22] Update script 2.0 read me Just update the read me. Will start to build these function. --- scripts/github-repos/README.md | 108 ++++++++++++++++++++++----- scripts/github-repos/github-repos.rb | 2 + scripts/github-repos/team-test.csv | 3 + 3 files changed, 94 insertions(+), 19 deletions(-) create mode 100644 scripts/github-repos/team-test.csv diff --git a/scripts/github-repos/README.md b/scripts/github-repos/README.md index 3210ded..12a5aa1 100644 --- a/scripts/github-repos/README.md +++ b/scripts/github-repos/README.md @@ -1,22 +1,76 @@ # Bulk creation/deletion of many repos and cs169a-team ``` -Usage: ./github-repos.rb [required options] [invite|repos|remove|remove_access] +Usage: ./github-repos.rb [required options] [invite|create_teams|indiv_repos|team_repos|remove_indivi_repos|remove_team_repos|remove_teams|remove_team_repo_access] GITHUB_ORG_API_KEY for the org must be set as an environment variable. -'invite' invites students provided in .csv file and creates teams, 'repos' creates team repos, 'remove' remove students, repos, teams from the org - It's safe to run multiple times. +'invite': +Create a team called STUDENTTEAM under the org that has all students in the same semester and send invitation + +Required arguments: + -c, --csv=CSVFILE CSV file containing at leaset "Email"/"Username" named columns + -s, --studentteam=STUDENTTEAM The team name of all the students team + +'create_teams' +Create child teams for students for CHIP 10.5 + +Required arguments: + -c, --csv=CSVFILE CSV file containing at least "Team" and "Email"/"Username" named columns + -s, --studentteam=STUDENTTEAM The team name of all the students team + -p, --prefix=PREFIX Semester prefix, eg fa23. + +'indiv_repos' +Create CHIPS repo for each stedent in STUDENTTEAM. Repos' names are form like "PREFIX-[username]-FILENAME" + Required arguments: - -c, --csv=CSVFILE CSV file containing at least "Team" and "Email" named columns - -o, --orgname=ORGNAME The name of the org eg org_name - -f, --filename=FILENAME The base filename for repos, eg "fa23-actionmap-04", actionmap is the base file name of the repo - -p, --prefix=PREFIX Semester prefix, eg "fa23" create a repos prefix, "fa23-actionmap-04", etc. - -t, --template=TEMPLATE The repo name within the org to use as template eg repo_name (Assume the repo own by org) -s, --studentteam=STUDENTTEAM The team name of all the students team + -p, --prefix=PREFIX Semester prefix, eg fa23. + -t, --template=TEMPLATE The repo name within the org to use as template -g, --gsiteam=GSITEAM The team name of all the staff team + -f, --filename=FILENAME The base filename for repos + +'team_repos' +Create 10.5 repos for each child team. Repos' names are form like "PREFIX-FILENAME-[Team number]" +Make sure child teams are formed before running this method. + +Required arguments: + -p, --prefix=PREFIX Semester prefix, eg fa23. + -t, --template=TEMPLATE The repo name within the org to use as template + -f, --filename=FILENAME The base filename for repos. + -s, --studentteam=STUDENTTEAM The team name of all the students team + -g, --gsiteam=GSITEAM The team name of all the staff team + +'remove_indiv_repos' +Delete all repos whose names are formed like "PREFIX-[username]-FILENAME". + +Required arguments: + -p, --prefix=PREFIX Semester prefix, eg fa23. + -f, --filename=FILENAME The base filename for repos. + +'remove_team_repos' +Delete all repos whose names are formed like "PREFIX-FILENAME-[Team number]". + +Required arguments: + -p, --prefix=PREFIX Semester prefix, eg fa23. + -f, --filename=FILENAME The base filename for repos. + +'remove_teams' +Remove all students and child teams in STUDENTTEAM from the org. +Remove STUDENTTEAM as well. + +Required arguments: + -p, --prefix=PREFIX Semester prefix, eg fa23. + -s, --studentteam=STUDENTTEAM The team name of all the students team + +'remove_team_repo_access' +Remove students access to CHIP 10.5 repos that are formed like "PREFIX-FILENAME-[Team number]". + +Required arguments: + -p, --prefix=PREFIX Semester prefix, eg fa23. + -f, --filename=FILENAME The base filename for repos. ``` This script creates a team that include all stundents in the same semester, eg fa23. @@ -28,26 +82,42 @@ Org setting: Assume Base permissions is no permission, each students can only ac **Assumption:** You have a GITHUB ORG API key. -### Create a team under the org that includes all students in the same semester +### Create a team under the org that has all students in the same semester and send invitation **Use case:** Create a team called STUDENTTEAM, and send invitations -to the students in csv file. If STUDENTTEAM exists, delete the team and -create a new one. (Team repos still exist) -For all students NOT in that team, add/invite into that team. +to the students in csv file. If STUDENTTEAM exists, delete STUDENTTEAM and +create a new one. (Team repos still exist) +For all students NOT in STUDENTTEAM, add/invite into STUDENTTEAM. +Team column is not needed for this. + +### Create child teams for students for CHIP 10.5 + +**Use case:** Create child teams for students for CHIP 10.5. + +### Create CHIPS repo for each stedent + +**Use case:** Create CHIP repos for each student in STUDENTTEAM. +The repos' names are formed like "fa23-[username]-FILENAME". There will +be only one collabrator. Add all repos to the gsiteam with admin permission. ### Create 10.5 repos for each child team **Use case:** Each team (as identified by Team column in CSV file) -gets a repo for chip 10.5. Repos' names are formed -by concatenating the prefix, base file name, and the team ID. -(eg "fa23-actionmap-04" for team #4) For all students on that team who do -NOT already have access to the repo, give them write access on the repo. +gets a repo for chip 10.5. Repos' names are formed like "PREFIX-FILENAME-TEAMNUM" +Every one in the child team has write access to the repo. Add all repos to the gsiteam with admin permission. -### Remove all team members from students team, and delete all the repos +### Remove the CHIPS repos + +**Use case:** Delete all repos whose names are formed like "PREFIX-[username]-FILENAME". + +### Remove CHIP 10.5 repos + +**Use case:** Delete all repos whose names are formed like "PREFIX-FILENAME-[Team number]". + +### Remove all team members from students teams, then delete all teams -**Use case:** Delete all repos whose name matches the "PREFIX-FILENAME-team number". -Remove all students and subteams in STUDENTTEAM from the org. +**Use case:** Remove all students and subteams in STUDENTTEAM from the org. ### Remove student access to all the chip 10.5 repos diff --git a/scripts/github-repos/github-repos.rb b/scripts/github-repos/github-repos.rb index 7f258df..69cd1aa 100755 --- a/scripts/github-repos/github-repos.rb +++ b/scripts/github-repos/github-repos.rb @@ -77,6 +77,8 @@ def gsiteam_valid? public + + def valid? @orgname && @base_filename && @semester && @childteams.length > 0 && @template && @parentteam && gsiteam_valid? end diff --git a/scripts/github-repos/team-test.csv b/scripts/github-repos/team-test.csv new file mode 100644 index 0000000..0c6b0e9 --- /dev/null +++ b/scripts/github-repos/team-test.csv @@ -0,0 +1,3 @@ +Team,Email/Username,Name +1,ydong19@berkeley.edu,Amy +2, \ No newline at end of file From dd1f7309edddbc8bca747979b700d42baba0f0eb Mon Sep 17 00:00:00 2001 From: a544266477 Date: Sat, 16 Sep 2023 20:35:50 -0700 Subject: [PATCH 02/22] finish invite --- scripts/github-repos/README.md | 32 +- scripts/github-repos/github-repos.rb | 478 +++++++++++++++++---------- 2 files changed, 320 insertions(+), 190 deletions(-) diff --git a/scripts/github-repos/README.md b/scripts/github-repos/README.md index 12a5aa1..69b1c1f 100644 --- a/scripts/github-repos/README.md +++ b/scripts/github-repos/README.md @@ -1,7 +1,7 @@ -# Bulk creation/deletion of many repos and cs169a-team +# Bulk creation/deletion of many repos and student teams ``` -Usage: ./github-repos.rb [required options] [invite|create_teams|indiv_repos|team_repos|remove_indivi_repos|remove_team_repos|remove_teams|remove_team_repo_access] +Usage: ./github-repos.rb [required options] [invite|create_teams|indiv_repos|team_repos|remove_indivi_repos|remove_team_repos|remove_teams|remove_team_repo_access] [optional options] GITHUB_ORG_API_KEY for the org must be set as an environment variable. @@ -9,23 +9,31 @@ It's safe to run multiple times. 'invite': Create a team called STUDENTTEAM under the org that has all students in the same semester and send invitation +If STUDENTTEAM is already exist, the script will resent invitation to students. +(Temporary for special situations in Fa23) +If PREFIX is provided, it will assume the csv file contains "Team" column, and create child teams under +the STUDENTTEAM, and invites them to child teams. -Required arguments: - -c, --csv=CSVFILE CSV file containing at leaset "Email"/"Username" named columns +Required options: + -c, --csv=CSVFILE CSV file containing at leaset "Email/Username" named columns -s, --studentteam=STUDENTTEAM The team name of all the students team + -o, --orgname=ORGNAME The name of the org + +Optional options: + -p, --prefix=PREFIX Semester prefix, eg fa23. 'create_teams' Create child teams for students for CHIP 10.5 -Required arguments: - -c, --csv=CSVFILE CSV file containing at least "Team" and "Email"/"Username" named columns +Required options: + -c, --csv=CSVFILE CSV file containing at least "Team" and "Email/Username" named columns -s, --studentteam=STUDENTTEAM The team name of all the students team -p, --prefix=PREFIX Semester prefix, eg fa23. 'indiv_repos' Create CHIPS repo for each stedent in STUDENTTEAM. Repos' names are form like "PREFIX-[username]-FILENAME" -Required arguments: +Required options: -s, --studentteam=STUDENTTEAM The team name of all the students team -p, --prefix=PREFIX Semester prefix, eg fa23. -t, --template=TEMPLATE The repo name within the org to use as template @@ -36,7 +44,7 @@ Required arguments: Create 10.5 repos for each child team. Repos' names are form like "PREFIX-FILENAME-[Team number]" Make sure child teams are formed before running this method. -Required arguments: +Required options: -p, --prefix=PREFIX Semester prefix, eg fa23. -t, --template=TEMPLATE The repo name within the org to use as template -f, --filename=FILENAME The base filename for repos. @@ -46,14 +54,14 @@ Required arguments: 'remove_indiv_repos' Delete all repos whose names are formed like "PREFIX-[username]-FILENAME". -Required arguments: +Required options: -p, --prefix=PREFIX Semester prefix, eg fa23. -f, --filename=FILENAME The base filename for repos. 'remove_team_repos' Delete all repos whose names are formed like "PREFIX-FILENAME-[Team number]". -Required arguments: +Required options: -p, --prefix=PREFIX Semester prefix, eg fa23. -f, --filename=FILENAME The base filename for repos. @@ -61,14 +69,14 @@ Required arguments: Remove all students and child teams in STUDENTTEAM from the org. Remove STUDENTTEAM as well. -Required arguments: +Required options: -p, --prefix=PREFIX Semester prefix, eg fa23. -s, --studentteam=STUDENTTEAM The team name of all the students team 'remove_team_repo_access' Remove students access to CHIP 10.5 repos that are formed like "PREFIX-FILENAME-[Team number]". -Required arguments: +Required options: -p, --prefix=PREFIX Semester prefix, eg fa23. -f, --filename=FILENAME The base filename for repos. ``` diff --git a/scripts/github-repos/github-repos.rb b/scripts/github-repos/github-repos.rb index 69cd1aa..9d4fc21 100755 --- a/scripts/github-repos/github-repos.rb +++ b/scripts/github-repos/github-repos.rb @@ -7,222 +7,344 @@ ENV['GITHUB_ORG_API_KEY'] = "" def main() - puts "Script start." - org = OrgManager.new - $opts = OptionParser.new do |opt| - opt.banner = "Usage: #{__FILE__} [required options] [invite|repos|remove] - GITHUB_ORG_API_KEY for the org must be set as an environment variable. - 'invite' invites students provided in .csv file and creates teams, - 'repos' creates team repos, 'remove' remove students, repos, teams from the org." - opt.on('-cCSVFILE', '--csv=CSVFILE', 'CSV file containing at least "Team" and "Email" named columns') do |csv| - org.read_teams_and_emails_from csv - end - opt.on('-oORGNAME', '--orgname=ORGNAME', 'The name of the org') do |orgname| + puts "Script start." + org = OrgManager.new + $opts = OptionParser.new do |opt| + opt.banner = "Usage: #{__FILE__} [required options] [invite|create_teams|indiv_repos|team_repos| + remove_indivi_repos|remove_team_repos|remove_teams|remove_team_repo_access] [optional options] + GITHUB_ORG_API_KEY for the org must be set as an environment variable." + + opt.separator "" + opt.separator "Commands:" + opt.separator " invite: Create a team called STUDENTTEAM under the org..." + opt.separator " If STUDENTTEAM is already exist, the script will resent invitation to students." + opt.separator " (Temporary for special situations in Fa23)" + opt.separator " If PREFIX is provided, it will assume the csv file contains \"Team\" column, and create child teams under the STUDENTTEAM, and invites them to child teams.\n" + opt.separator " create_teams: Create child teams for students for CHIP 10.5 \n" + opt.separator " indiv_repos: Create CHIPS repo for each stedent in STUDENTTEAM. Repos' names are form like \"PREFIX-[username]-FILENAME\"\n" + opt.separator " team_repos: Create 10.5 repos for each child team. Repos' names are form like \"PREFIX-FILENAME-[Team number]\"" + opt.separator " Make sure child teams are formed before running this method.\n" + opt.separator " remove_indiv_repos: Delete all repos whose names are formed like \"PREFIX-[username]-FILENAME\".\n" + opt.separator " remove_team_repos: Delete all repos whose names are formed like \"PREFIX-FILENAME-[Team number]\".\n" + opt.separator " remove_teams: Remove all students and child teams in STUDENTTEAM from the org. Remove STUDENTTEAM as well.\n" + opt.separator " remove_team_repo_access: Remove students access to CHIP 10.5 repos that are formed like \"PREFIX-FILENAME-[Team number]\".\n" + opt.separator "Required options:" + + case ARGV[0] + when 'invite' + opt.on('-cCSVFILE', '--csv=CSVFILE', 'CSV file containing at leaset "Email" named columns') do |csv| + org.csv = csv + end + opt.on('-oORGNAME', '--orgname=ORGNAME', 'The name of the org') do |orgname| org.orgname = orgname - end - opt.on('-fFILENAME', '--filename=FILENAME', 'The base filename for repos') do |filename| + end + opt.on('-sSTUDENTTEAM', '--studentteam=STUDENTTEAM', 'The team name of all the students team') do |studentteam| + org.parentteam = studentteam + end + opt.separator "Optional options:" + opt.on('-pPREFIX', '--prefix=PREFIX', 'Semester prefix, eg fa23.') do |pfx| + org.semester = pfx + end + when 'create_teams' + opt.on('-cCSVFILE', '--csv=CSVFILE', 'CSV file containing at least "Team" and "Email" named columns') do |csv| + org.read_teams_and_users_from csv + end + opt.on('-sSTUDENTTEAM', '--studentteam=STUDENTTEAM', 'The team name of all the students team') do |studentteam| + org.parentteam = studentteam + end + opt.on('-pPREFIX', '--prefix=PREFIX', 'Semester prefix, eg fa23.') do |pfx| + org.semester = pfx + end + when 'indiv_repos' + opt.on('-fFILENAME', '--filename=FILENAME', 'The base filename for repos') do |filename| org.base_filename = filename - end - opt.on('-pPREFIX', '--prefix=PREFIX', 'Semester prefix, eg "fa23" create a repos prefix, "fa23-actionmap-04", etc') do |pfx| + end + opt.on('-pPREFIX', '--prefix=PREFIX', 'Semester prefix, eg fa23.') do |pfx| org.semester = pfx - end - opt.on('-tTEMPLATE', '--template=TEMPLATE', 'The repo name within the org to use as template eg repo_name') do |template| + end + opt.on('-tTEMPLATE', '--template=TEMPLATE', 'The repo name within the org to use as template') do |template| org.template = template - end - opt.on('-sSTUDENTTEAM', '--studentteam=STUDENTTEAM', 'The team name of all the students team') do |studentteam| + end + opt.on('-sSTUDENTTEAM', '--studentteam=STUDENTTEAM', 'The team name of all the students team') do |studentteam| org.parentteam = studentteam - end - opt.on('-gGSITEAM', '--gsiteam=GSITEAM', 'The team name of all the staff team') do |gsiteam| + end + opt.on('-gGSITEAM', '--gsiteam=GSITEAM', 'The team name of all the staff team') do |gsiteam| org.gsiteam = gsiteam - end - end - $opts.parse! - command = ARGV.pop - case command - when 'invite' then org.invite - when 'repos' then org.create_repos - when 'remove' then org.remove - when 'remove_access' then org.remove_access - else org.print_error + end + when 'team_repos' + opt.on('-fFILENAME', '--filename=FILENAME', 'The base filename for repos') do |filename| + org.base_filename = filename + end + opt.on('-pPREFIX', '--prefix=PREFIX', 'Semester prefix, eg fa23.') do |pfx| + org.semester = pfx + end + opt.on('-tTEMPLATE', '--template=TEMPLATE', 'The repo name within the org to use as template') do |template| + org.template = template + end + opt.on('-sSTUDENTTEAM', '--studentteam=STUDENTTEAM', 'The team name of all the students team') do |studentteam| + org.parentteam = studentteam + end + opt.on('-gGSITEAM', '--gsiteam=GSITEAM', 'The team name of all the staff team') do |gsiteam| + org.gsiteam = gsiteam + end + when 'remove_idiv_repos' + opt.on('-pPREFIX', '--prefix=PREFIX', 'Semester prefix, eg fa23.') do |pfx| + org.semester = pfx + end + opt.on('-fFILENAME', '--filename=FILENAME', 'The base filename for repos') do |filename| + org.base_filename = filename + end + when 'remove_team_repos' + opt.on('-pPREFIX', '--prefix=PREFIX', 'Semester prefix, eg fa23.') do |pfx| + org.semester = pfx + end + opt.on('-fFILENAME', '--filename=FILENAME', 'The base filename for repos') do |filename| + org.base_filename = filename + end + when 'remove_teams' + opt.on('-pPREFIX', '--prefix=PREFIX', 'Semester prefix, eg fa23.') do |pfx| + org.semester = pfx + end + opt.on('-sSTUDENTTEAM', '--studentteam=STUDENTTEAM', 'The team name of all the students team') do |studentteam| + org.parentteam = studentteam + end + when 'remove_team_repo_access' + opt.on('-fFILENAME', '--filename=FILENAME', 'The base filename for repos') do |filename| + org.base_filename = filename + end + opt.on('-pPREFIX', '--prefix=PREFIX', 'Semester prefix, eg fa23.') do |pfx| + org.semester = pfx + end end - puts "Run successfully." - puts "Script ends." + end + $opts.parse! + command = ARGV.pop + # case command + # when 'invite' then org.invite + # when 'repos' then org.create_repos + # when 'remove' then org.remove + # when 'remove_access' then org.remove_access + # else org.print_error + # end + case command + when 'invite' then org.invite + when 'create_teams' + when 'indiv_repos' + when 'team_repos' + when 'remove_idiv_repos' + when 'remove_team_repos' + when 'remove_teams' + when 'remove_team_repo_access' + else org.print_error + end + puts "Run successfully." + puts "Script ends." end class OrgManager - attr_accessor :orgname, :base_filename, :semester, :template, :parentteam, :gsiteam - - def initialize - @orgname = nil - @base_filename = nil - @semester = nil - @template = nil - @childteams = Hash.new { |hash, key| hash[key] = [] } # teamID => [email1, email2, ...] - print_error("GITHUB_ORG_API_KEY not defined in environment") unless (@key = ENV['GITHUB_ORG_API_KEY']) - @client = Octokit::Client.new(access_token: @key) - end - - private + attr_accessor :orgname, :base_filename, :semester, :template, :parentteam, :gsiteam, :csv, :users - def gsiteam_valid? - gsiteam_obj = nil - if !@gsiteam.nil? && @gsiteam.length > 0 - begin - gsiteam_obj = @client.team_by_name(@orgname, @gsiteam) - rescue Octokit::NotFound - end - end - !gsiteam_obj.nil? - end + def initialize + @orgname = nil + @base_filename = nil + @semester = nil + @template = nil + @has_childteams = false + @users = [] + @childteams = Hash.new { |hash, key| hash[key] = [] } # teamID => [email1, email2, ...] + print_error("GITHUB_ORG_API_KEY not defined in environment") unless (@key = ENV['GITHUB_ORG_API_KEY']) + @client = Octokit::Client.new(access_token: @key) + end - public + private + def read_users_from csv + data = CSV.parse(IO.read(csv), headers: true) + hash = data.first.to_h + print_error "Need at least 'Email' (str) columns in #{csv}" unless + hash.has_key?('Email') + data.each do |row| + @users << username + end + end - - def valid? - @orgname && @base_filename && @semester && @childteams.length > 0 && @template && @parentteam && gsiteam_valid? + def read_teams_and_users_from csv + data = CSV.parse(IO.read(csv), headers: true) + hash = data.first.to_h + print_error "Need at least 'Team' (int) and 'Email' (str) columns in #{csv}" unless + hash.has_key?('Team') && hash.has_key?('Email') + data.each do |row| + username = row['Email'] + @childteams[row['Team']] << username end + end - def print_error(msg=nil) - STDERR.puts "Error: #{msg}" if msg - STDERR.puts $opts - exit 1 + def invite_valid? + # process the csv file + if @orgname.nil? || @orgname.empty? || @csv.nil? || @parentteam.nil? || @parentteam.empty? + return false + end + if @semester.nil? + read_users_from @csv + else + read_teams_and_users_from @csv end + true + end - def read_teams_and_emails_from csv - data = CSV.parse(IO.read(csv), headers: true) - hash = data.first.to_h - print_error "Need at least 'Team' (int) and 'Email' (str) columns in #{csv}" unless - hash.has_key?('Team') && hash.has_key?('Email') - data.each do |row| - username = row['Email'] - @childteams[row['Team']] << username - end + def gsiteam_valid? + gsiteam_obj = nil + if !@gsiteam.nil? && @gsiteam.length > 0 + begin + gsiteam_obj = @client.team_by_name(@orgname, @gsiteam) + rescue Octokit::NotFound + end end + !gsiteam_obj.nil? + end - def invite - print_error "csv file, base filename, template repo name, semester prefix, students team name, and gsi team name needed." unless valid? - first_child_team_name = %Q{#{@semester}-#{@childteams.keys[0]}} + public - begin - parentteam_obj = @client.team_by_name(@orgname, @parentteam) - rescue Octokit::NotFound - parentteam_obj = nil + def valid? + @orgname && @base_filename && @semester && @childteams.length > 0 && @template && @parentteam && gsiteam_valid? + end + + def print_error(msg=nil) + STDERR.puts "Error: #{msg}" if msg + STDERR.puts $opts + exit 1 + end + + def invite + print_error "csv file, organization name, student team name are needed." unless valid? + if @semester.nil? + # Semester prefix is not provided + + # Looking for the STUDENTTEAM in the org, see if it is exist. + begin + parentteam_id = @client.team_by_name(@orgname, @parentteam)['id'] + rescue Octokit::NotFound + parentteam_id = @client.create_team(@orgname, {name: @parentteam, privacy: 'closed'})['id'] + end + + @users.each do |student_email| + if !@client.team_invitations(parentteam_id).any? {|invitations| invitations.email == member} + # invitation is not pending, send invitation + begin + @client.post(%Q{/orgs/#{@orgname}/invitations}, {org: @orgname, email: student_email , role: 'direct_member', team_ids: [parentteam_id]}) + rescue Octokit::UnprocessableEntity + # send fail, member is already a part of org + next + end end + end + else + # Semester prefix is provied + + # Looking for the STUDENTTEAM in the org, see if it is exist. + begin + parentteam_id = @client.team_by_name(@orgname, @parentteam)['id'] + rescue Octokit::NotFound + parentteam_id = @client.create_team(@orgname, {name: @parentteam, privacy: 'closed'})['id'] + end + # create child teams and invite students to the child teams + # this is for Fa23, will delete in the future. + @childteams.each_key do |team| + childteam_name = %Q{#{@semester}-#{team}} begin - if parentteam_obj - @client.team_by_name(@orgname, first_child_team_name) - parentteam_id = parentteam_obj['id'] - else - parentteam_id = @client.create_team(@orgname, {name: @parentteam, privacy: 'closed'})['id'] - end + childteam = @client.team_by_name(@orgname, childteam_name) rescue Octokit::NotFound - # students team exists before invite - # remove all members in this team, and delete the team. - parentteam_id = parentteam_obj['id'] - old_team_members = @client.team_members(parentteam_id) - old_team_members.each do |member| - if @client.organization_membership(@orgname, :user => member['login'])['role'] == 'member' - @client.remove_organization_member(@orgname, member['login']) - end - end - @client.delete_team parentteam_id - parentteam_id = @client.create_team(@orgname, {name: @parentteam, privacy: 'closed'})['id'] + childteam = @client.create_team(@orgname, {name: %Q{#{@semester}-#{team}}, parent_team_id: parentteam_id}) end - - @childteams.each_key do |team| - childteam_name = %Q{#{@semester}-#{team}} + childteam_id = childteam['id'] + @childteams[team].each do |member| + # Try no check invitations before create one. + if !@client.team_invitations(childteam_id).any? {|invitations| invitations.email == member} + # send invitation begin - childteam = @client.team_by_name(@orgname, childteam_name) - rescue Octokit::NotFound - childteam = @client.create_team(@orgname, {name: %Q{#{@semester}-#{team}}, parent_team_id: parentteam_id}) - end - childteam_id = childteam['id'] - @childteams[team].each do |member| - if !@client.team_invitations(childteam_id).any? {|invitations| invitations.email == member} - # send invitation - begin - @client.post(%Q{/orgs/#{@orgname}/invitations}, {org: @orgname, email: member, role: 'direct_member', team_ids: [childteam_id]}) - rescue Octokit::UnprocessableEntity - # member is already a part of org - next - end - end + @client.post(%Q{/orgs/#{@orgname}/invitations}, {org: @orgname, email: member, role: 'direct_member', team_ids: [childteam_id]}) + rescue Octokit::UnprocessableEntity + # member is already a part of org + next end + end end - end + end - def create_repos - print_error "csv file, base filename, template repo name, semester prefix, students team name, and gsi team name needed." unless valid? - @childteams.each_key do |team| - begin - team_id = @client.team_by_name(@orgname, %Q{#{@semester}-#{team}})['id'] - rescue Octokit::NotFound - print_error "Students teams information mismatched." - end - gsiteam_id = @client.team_by_name(@orgname, @gsiteam)['id'] - new_repo_name = %Q{#{@semester}-#{@base_filename}-#{team}} - if !@client.repository? %Q{#{@orgname}/#{new_repo_name}} - begin - new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, - {owner: @orgname, private: true}) - rescue Octokit::NotFound - print_error "Template not found." - end - @client.add_team_repository(team_id, new_repo['full_name'], {permission: 'push'}) - @client.add_team_repository(gsiteam_id, new_repo['full_name'], {permission: 'admin'}) - end - end end + end - def remove - print_error "csv file, base filename, template repo name, semester prefix, students team name, and gsi team name needed." unless valid? - # remove and delete all repos from the students team, delete all child teams - # also cancel all pending invitaions - @childteams.each_key do |team| - repo_name = %Q{#{@orgname}/#{@semester}-#{@base_filename}-#{team}} - @client.delete_repository(repo_name) - begin - childteam_id = @client.team_by_name(@orgname, %Q{#{@semester}-#{team}})['id'] # eg slug fa23-01 - rescue Octokit::NotFound - next - end - - # remove students from the student teams - team_members = @client.team_members(childteam_id) - team_members.each do |member| - if @client.organization_membership(@orgname, :user => member['login'])['role'] == 'member' - @client.remove_organization_member(@orgname, member['login']) - end - end - - @client.team_invitations(childteam_id).each do |invitation| - # cancel all pending invitations - @client.delete(%Q{/orgs/#{@orgname}/invitations/#{invitation[:id]}}) - end - @client.delete_team childteam_id - end - # delete students team + def create_repos + print_error "csv file, base filename, template repo name, semester prefix, students team name, and gsi team name needed." unless valid? + @childteams.each_key do |team| + begin + team_id = @client.team_by_name(@orgname, %Q{#{@semester}-#{team}})['id'] + rescue Octokit::NotFound + print_error "Students teams information mismatched." + end + gsiteam_id = @client.team_by_name(@orgname, @gsiteam)['id'] + new_repo_name = %Q{#{@semester}-#{@base_filename}-#{team}} + if !@client.repository? %Q{#{@orgname}/#{new_repo_name}} begin - team_id = @client.team_by_name(@orgname, @parentteam)['id'] - @client.delete_team team_id + new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, + {owner: @orgname, private: true}) rescue Octokit::NotFound - # do nothing if no such team + print_error "Template not found." end + @client.add_team_repository(team_id, new_repo['full_name'], {permission: 'push'}) + @client.add_team_repository(gsiteam_id, new_repo['full_name'], {permission: 'admin'}) + end end - - def remove_access - @childteams.each_key do |team| - repo_name = %Q{#{@orgname}/#{@semester}-#{@base_filename}-#{team}} - begin - childteam_id = @client.team_by_name(@orgname, %Q{#{@semester}-#{team}})['id'] # eg slug fa23-1 - rescue Octokit::NotFound - next - end - @client.remove_team_repository(childteam_id, repo_name) + end + + def remove + print_error "csv file, base filename, template repo name, semester prefix, students team name, and gsi team name needed." unless valid? + # remove and delete all repos from the students team, delete all child teams + # also cancel all pending invitaions + @childteams.each_key do |team| + repo_name = %Q{#{@orgname}/#{@semester}-#{@base_filename}-#{team}} + @client.delete_repository(repo_name) + begin + childteam_id = @client.team_by_name(@orgname, %Q{#{@semester}-#{team}})['id'] # eg slug fa23-01 + rescue Octokit::NotFound + next + end + + # remove students from the student teams + team_members = @client.team_members(childteam_id) + team_members.each do |member| + if @client.organization_membership(@orgname, :user => member['login'])['role'] == 'member' + @client.remove_organization_member(@orgname, member['login']) end + end + + @client.team_invitations(childteam_id).each do |invitation| + # cancel all pending invitations + @client.delete(%Q{/orgs/#{@orgname}/invitations/#{invitation[:id]}}) + end + @client.delete_team childteam_id + end + # delete students team + begin + team_id = @client.team_by_name(@orgname, @parentteam)['id'] + @client.delete_team team_id + rescue Octokit::NotFound + # do nothing if no such team + end + end + + def remove_access + @childteams.each_key do |team| + repo_name = %Q{#{@orgname}/#{@semester}-#{@base_filename}-#{team}} + begin + childteam_id = @client.team_by_name(@orgname, %Q{#{@semester}-#{team}})['id'] # eg slug fa23-1 + rescue Octokit::NotFound + next + end + @client.remove_team_repository(childteam_id, repo_name) end + end end main \ No newline at end of file From 058a873c07adac1deec0bf4e95d439fa3066dfaa Mon Sep 17 00:00:00 2001 From: a544266477 Date: Sun, 17 Sep 2023 02:07:14 -0700 Subject: [PATCH 03/22] finished script 2.0 need debug --- scripts/github-repos/README.md | 11 +- scripts/github-repos/github-repos.rb | 245 ++++++++++++++++++++------- 2 files changed, 195 insertions(+), 61 deletions(-) diff --git a/scripts/github-repos/README.md b/scripts/github-repos/README.md index 69b1c1f..a17cef8 100644 --- a/scripts/github-repos/README.md +++ b/scripts/github-repos/README.md @@ -23,17 +23,19 @@ Optional options: -p, --prefix=PREFIX Semester prefix, eg fa23. 'create_teams' -Create child teams for students for CHIP 10.5 +Assuming students are in STUDENTTEAM, create child teams for students for CHIP 10.5 and add them to the child team. Required options: -c, --csv=CSVFILE CSV file containing at least "Team" and "Email/Username" named columns -s, --studentteam=STUDENTTEAM The team name of all the students team -p, --prefix=PREFIX Semester prefix, eg fa23. + -o, --orgname=ORGNAME The name of the org 'indiv_repos' Create CHIPS repo for each stedent in STUDENTTEAM. Repos' names are form like "PREFIX-[username]-FILENAME" Required options: + -o, --orgname=ORGNAME The name of the org -s, --studentteam=STUDENTTEAM The team name of all the students team -p, --prefix=PREFIX Semester prefix, eg fa23. -t, --template=TEMPLATE The repo name within the org to use as template @@ -42,9 +44,10 @@ Required options: 'team_repos' Create 10.5 repos for each child team. Repos' names are form like "PREFIX-FILENAME-[Team number]" -Make sure child teams are formed before running this method. +Make sure child teams are formed before running this command. Required options: + -o, --orgname=ORGNAME The name of the org -p, --prefix=PREFIX Semester prefix, eg fa23. -t, --template=TEMPLATE The repo name within the org to use as template -f, --filename=FILENAME The base filename for repos. @@ -55,6 +58,7 @@ Required options: Delete all repos whose names are formed like "PREFIX-[username]-FILENAME". Required options: + -o, --orgname=ORGNAME The name of the org -p, --prefix=PREFIX Semester prefix, eg fa23. -f, --filename=FILENAME The base filename for repos. @@ -62,6 +66,7 @@ Required options: Delete all repos whose names are formed like "PREFIX-FILENAME-[Team number]". Required options: + -o, --orgname=ORGNAME The name of the org -p, --prefix=PREFIX Semester prefix, eg fa23. -f, --filename=FILENAME The base filename for repos. @@ -70,6 +75,7 @@ Remove all students and child teams in STUDENTTEAM from the org. Remove STUDENTTEAM as well. Required options: + -o, --orgname=ORGNAME The name of the org -p, --prefix=PREFIX Semester prefix, eg fa23. -s, --studentteam=STUDENTTEAM The team name of all the students team @@ -77,6 +83,7 @@ Required options: Remove students access to CHIP 10.5 repos that are formed like "PREFIX-FILENAME-[Team number]". Required options: + -o, --orgname=ORGNAME The name of the org -p, --prefix=PREFIX Semester prefix, eg fa23. -f, --filename=FILENAME The base filename for repos. ``` diff --git a/scripts/github-repos/github-repos.rb b/scripts/github-repos/github-repos.rb index 9d4fc21..3402608 100755 --- a/scripts/github-repos/github-repos.rb +++ b/scripts/github-repos/github-repos.rb @@ -11,7 +11,7 @@ def main() org = OrgManager.new $opts = OptionParser.new do |opt| opt.banner = "Usage: #{__FILE__} [required options] [invite|create_teams|indiv_repos|team_repos| - remove_indivi_repos|remove_team_repos|remove_teams|remove_team_repo_access] [optional options] + remove_indiv_repos|remove_team_repos|remove_teams|remove_team_repo_access] [optional options] GITHUB_ORG_API_KEY for the org must be set as an environment variable." opt.separator "" @@ -20,10 +20,10 @@ def main() opt.separator " If STUDENTTEAM is already exist, the script will resent invitation to students." opt.separator " (Temporary for special situations in Fa23)" opt.separator " If PREFIX is provided, it will assume the csv file contains \"Team\" column, and create child teams under the STUDENTTEAM, and invites them to child teams.\n" - opt.separator " create_teams: Create child teams for students for CHIP 10.5 \n" + opt.separator " create_teams: Assuming students are in STUDENTTEAM, create child teams for students for CHIP 10.5 and add them to the child team. \n" opt.separator " indiv_repos: Create CHIPS repo for each stedent in STUDENTTEAM. Repos' names are form like \"PREFIX-[username]-FILENAME\"\n" opt.separator " team_repos: Create 10.5 repos for each child team. Repos' names are form like \"PREFIX-FILENAME-[Team number]\"" - opt.separator " Make sure child teams are formed before running this method.\n" + opt.separator " Make sure child teams are formed before running this command.\n" opt.separator " remove_indiv_repos: Delete all repos whose names are formed like \"PREFIX-[username]-FILENAME\".\n" opt.separator " remove_team_repos: Delete all repos whose names are formed like \"PREFIX-FILENAME-[Team number]\".\n" opt.separator " remove_teams: Remove all students and child teams in STUDENTTEAM from the org. Remove STUDENTTEAM as well.\n" @@ -47,7 +47,7 @@ def main() end when 'create_teams' opt.on('-cCSVFILE', '--csv=CSVFILE', 'CSV file containing at least "Team" and "Email" named columns') do |csv| - org.read_teams_and_users_from csv + org.csv = csv end opt.on('-sSTUDENTTEAM', '--studentteam=STUDENTTEAM', 'The team name of all the students team') do |studentteam| org.parentteam = studentteam @@ -55,6 +55,9 @@ def main() opt.on('-pPREFIX', '--prefix=PREFIX', 'Semester prefix, eg fa23.') do |pfx| org.semester = pfx end + opt.on('-oORGNAME', '--orgname=ORGNAME', 'The name of the org') do |orgname| + org.orgname = orgname + end when 'indiv_repos' opt.on('-fFILENAME', '--filename=FILENAME', 'The base filename for repos') do |filename| org.base_filename = filename @@ -71,6 +74,9 @@ def main() opt.on('-gGSITEAM', '--gsiteam=GSITEAM', 'The team name of all the staff team') do |gsiteam| org.gsiteam = gsiteam end + opt.on('-oORGNAME', '--orgname=ORGNAME', 'The name of the org') do |orgname| + org.orgname = orgname + end when 'team_repos' opt.on('-fFILENAME', '--filename=FILENAME', 'The base filename for repos') do |filename| org.base_filename = filename @@ -87,13 +93,19 @@ def main() opt.on('-gGSITEAM', '--gsiteam=GSITEAM', 'The team name of all the staff team') do |gsiteam| org.gsiteam = gsiteam end - when 'remove_idiv_repos' + opt.on('-oORGNAME', '--orgname=ORGNAME', 'The name of the org') do |orgname| + org.orgname = orgname + end + when 'remove_indiv_repos' opt.on('-pPREFIX', '--prefix=PREFIX', 'Semester prefix, eg fa23.') do |pfx| org.semester = pfx end opt.on('-fFILENAME', '--filename=FILENAME', 'The base filename for repos') do |filename| org.base_filename = filename end + opt.on('-oORGNAME', '--orgname=ORGNAME', 'The name of the org') do |orgname| + org.orgname = orgname + end when 'remove_team_repos' opt.on('-pPREFIX', '--prefix=PREFIX', 'Semester prefix, eg fa23.') do |pfx| org.semester = pfx @@ -101,6 +113,9 @@ def main() opt.on('-fFILENAME', '--filename=FILENAME', 'The base filename for repos') do |filename| org.base_filename = filename end + opt.on('-oORGNAME', '--orgname=ORGNAME', 'The name of the org') do |orgname| + org.orgname = orgname + end when 'remove_teams' opt.on('-pPREFIX', '--prefix=PREFIX', 'Semester prefix, eg fa23.') do |pfx| org.semester = pfx @@ -108,6 +123,9 @@ def main() opt.on('-sSTUDENTTEAM', '--studentteam=STUDENTTEAM', 'The team name of all the students team') do |studentteam| org.parentteam = studentteam end + opt.on('-oORGNAME', '--orgname=ORGNAME', 'The name of the org') do |orgname| + org.orgname = orgname + end when 'remove_team_repo_access' opt.on('-fFILENAME', '--filename=FILENAME', 'The base filename for repos') do |filename| org.base_filename = filename @@ -115,25 +133,21 @@ def main() opt.on('-pPREFIX', '--prefix=PREFIX', 'Semester prefix, eg fa23.') do |pfx| org.semester = pfx end + opt.on('-oORGNAME', '--orgname=ORGNAME', 'The name of the org') do |orgname| + org.orgname = orgname + end end end $opts.parse! command = ARGV.pop - # case command - # when 'invite' then org.invite - # when 'repos' then org.create_repos - # when 'remove' then org.remove - # when 'remove_access' then org.remove_access - # else org.print_error - # end case command when 'invite' then org.invite - when 'create_teams' - when 'indiv_repos' - when 'team_repos' - when 'remove_idiv_repos' - when 'remove_team_repos' - when 'remove_teams' + when 'create_teams' then org.create_teams + when 'indiv_repos' then org.indiv_repos + when 'team_repos' then org.team_repos + when 'remove_indiv_repos' then org.remove_indiv_repos + when 'remove_team_repos' then org.remove_team_repos + when 'remove_teams' then org.remove_teams when 'remove_team_repo_access' else org.print_error end @@ -149,7 +163,7 @@ def initialize @base_filename = nil @semester = nil @template = nil - @has_childteams = false + @csv = nil @users = [] @childteams = Hash.new { |hash, key| hash[key] = [] } # teamID => [email1, email2, ...] print_error("GITHUB_ORG_API_KEY not defined in environment") unless (@key = ENV['GITHUB_ORG_API_KEY']) @@ -164,7 +178,7 @@ def read_users_from csv print_error "Need at least 'Email' (str) columns in #{csv}" unless hash.has_key?('Email') data.each do |row| - @users << username + @users << row['Email'] end end @@ -180,10 +194,10 @@ def read_teams_and_users_from csv end def invite_valid? - # process the csv file if @orgname.nil? || @orgname.empty? || @csv.nil? || @parentteam.nil? || @parentteam.empty? return false end + # process the csv file if @semester.nil? read_users_from @csv else @@ -192,12 +206,33 @@ def invite_valid? true end + def create_teams_valid? + if @orgname.nil? || @orgname.empty? || @csv.nil? || @parentteam.nil? || @parentteam.empty? || @semester.nil? + return false + end + read_teams_and_users_from @csv + true + end + + def repos_valid? + !(@orgname.nil? || @parentteam.nil? || @prefix.nil? || @template.nil? || gsiteam_valid? || @base_filename.nil?) + end + + def remove_valid? + !(@orgname.nil? || @semester.nil? || @base_filename.nil?) + end + + def remove_teams_valid? + !(@orgname.nil? || @semester.nil? || @parentteam.nil?) + end + def gsiteam_valid? gsiteam_obj = nil if !@gsiteam.nil? && @gsiteam.length > 0 begin gsiteam_obj = @client.team_by_name(@orgname, @gsiteam) rescue Octokit::NotFound + print_error "Can't find the gsi team in the org." end end !gsiteam_obj.nil? @@ -216,7 +251,7 @@ def print_error(msg=nil) end def invite - print_error "csv file, organization name, student team name are needed." unless valid? + print_error "csv file, organization name, student team name are needed." unless invite_valid? if @semester.nil? # Semester prefix is not provided @@ -275,16 +310,63 @@ def invite end end - def create_repos - print_error "csv file, base filename, template repo name, semester prefix, students team name, and gsi team name needed." unless valid? + def create_teams + print_error "csv file, students team name, semester prefix, org name are needed." unless create_teams_valid? + + # Looking for the STUDENTTEAM in the org, see if it is exist. + begin + parentteam_id = @client.team_by_name(@orgname, @parentteam)['id'] + rescue Octokit::NotFound + print_error "Please make sure the invite command runs first, or students team is created." + end + + email_to_username_map = {} + + @client.team_members(parent_team_id).each do |mem| + email_to_username_map[mem.login] = mem.email + end + + failed_emails = [] + + # create child teams and add students to their child teams @childteams.each_key do |team| + childteam_name = %Q{#{@semester}-#{team}} begin - team_id = @client.team_by_name(@orgname, %Q{#{@semester}-#{team}})['id'] + childteam = @client.team_by_name(@orgname, childteam_name) + print_error "Childteam has been created before the script runs." rescue Octokit::NotFound - print_error "Students teams information mismatched." + childteam = @client.create_team(@orgname, {name: %Q{#{@semester}-#{team}}, parent_team_id: parentteam_id}) + end + childteam_id = childteam['id'] + @childteams[team].each do |email| + if email_to_username_map.has_key? email + @client.add_team_member(childteam_id, email_to_username_map[email]) + else + # save the incorrect email + failed_emails << email + end end - gsiteam_id = @client.team_by_name(@orgname, @gsiteam)['id'] - new_repo_name = %Q{#{@semester}-#{@base_filename}-#{team}} + end + + # print the emails that is failed to add to child team + if !failed_emails.empty? + puts 'Failed to add these emails to child team:' + puts failed_emails + end + end + + def indiv_repos + print_error "orgname, student team name, base filename, template repo name, semester prefix, and gsi team name needed." unless repos_valid? + + # Looking for the STUDENTTEAM in the org, see if it is exist. + begin + parentteam_id = @client.team_by_name(@orgname, @parentteam)['id'] + rescue Octokit::NotFound + print_error "Please make sure students team is created." + end + + @client.team_members(parent_team_id).each do |mem| + new_repo_name = %Q{#{@semester}-#{mem.login}-#{@base_filename}} if !@client.repository? %Q{#{@orgname}/#{new_repo_name}} begin new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, @@ -292,57 +374,102 @@ def create_repos rescue Octokit::NotFound print_error "Template not found." end - @client.add_team_repository(team_id, new_repo['full_name'], {permission: 'push'}) + @client.add_collab(new_repo, mem.login) @client.add_team_repository(gsiteam_id, new_repo['full_name'], {permission: 'admin'}) end end end - def remove - print_error "csv file, base filename, template repo name, semester prefix, students team name, and gsi team name needed." unless valid? - # remove and delete all repos from the students team, delete all child teams - # also cancel all pending invitaions + def team_repos + print_error "orgname, student team name, base filename, template repo name, semester prefix, and gsi team name needed." unless repos_valid? + @childteams.each_key do |team| - repo_name = %Q{#{@orgname}/#{@semester}-#{@base_filename}-#{team}} - @client.delete_repository(repo_name) begin - childteam_id = @client.team_by_name(@orgname, %Q{#{@semester}-#{team}})['id'] # eg slug fa23-01 + team_id = @client.team_by_name(@orgname, %Q{#{@semester}-#{team}})['id'] rescue Octokit::NotFound - next + print_error "Students teams are not created." end - - # remove students from the student teams - team_members = @client.team_members(childteam_id) - team_members.each do |member| - if @client.organization_membership(@orgname, :user => member['login'])['role'] == 'member' - @client.remove_organization_member(@orgname, member['login']) + gsiteam_id = @client.team_by_name(@orgname, @gsiteam)['id'] + new_repo_name = %Q{#{@semester}-#{@base_filename}-#{team}} + if !@client.repository? %Q{#{@orgname}/#{new_repo_name}} + begin + new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, + {owner: @orgname, private: true}) + rescue Octokit::NotFound + print_error "Template not found." end + @client.add_team_repository(team_id, new_repo['full_name'], {permission: 'push'}) + @client.add_team_repository(gsiteam_id, new_repo['full_name'], {permission: 'admin'}) + end + end + + end + + def remove_indiv_repos + print_error "org name, base filename, semester prefix are needed." unless remove_valid? + + repos = @client.org_repos(@orgname, {:type => 'private'}) + repos.each do |repo| + if repo.name =~ /^#{Regexp.escape(@orgname)}\/#{Regexp.escape(@semester)}-(.*)-#{Regexp.escape(@base_filename)}$/ + @client.delete_repository(repo) end + end + end + + def remove_team_repos + print_error "org name, base filename, semester prefix are needed." unless remove_valid? - @client.team_invitations(childteam_id).each do |invitation| - # cancel all pending invitations - @client.delete(%Q{/orgs/#{@orgname}/invitations/#{invitation[:id]}}) + repos = @client.org_repos(@orgname, {:type => 'private'}) + repos.each do |repo| + if repo.name =~ /^#{Regexp.escape(@orgname)}\/#{Regexp.escape(@semester)}-#{Regexp.escape(@base_filename)}-\d+$/ + @client.delete_repository(repo) end - @client.delete_team childteam_id end - # delete students team + end + + def remove_teams + print_error "org name, semester prefix, students team namd are needed." unless remove_teams_valid? + + # Looking for the STUDENTTEAM in the org, see if it is exist. begin - team_id = @client.team_by_name(@orgname, @parentteam)['id'] - @client.delete_team team_id + parentteam_id = @client.team_by_name(@orgname, @parentteam)['id'] rescue Octokit::NotFound - # do nothing if no such team + print_error "Please make sure students team is created." + end + + # # Remove all child teams + # @client.org_teams(@orgname).each do |team| + # if team.parent.id == parentteam_id + # @client.delete_team team.id + # end + # end + + # Remove all members in the students team from org + @client.team_members(parent_team_id).each do |mem| + if @client.organization_membership(@orgname, :user => mem.login)['role'] == 'member' + @client.remove_organization_member(@orgname, mem.login) + end end + + # Remove students team, child team will be removed as well + @client.delete_team parentteam_id end - + def remove_access - @childteams.each_key do |team| - repo_name = %Q{#{@orgname}/#{@semester}-#{@base_filename}-#{team}} - begin - childteam_id = @client.team_by_name(@orgname, %Q{#{@semester}-#{team}})['id'] # eg slug fa23-1 - rescue Octokit::NotFound - next + print_error "org name, base filename, semester prefix are needed." unless remove_valid? + + repos = @client.org_repos(@orgname, {:type => 'private'}) + repos.each do |repo| + match = repo.name.match(/^#{Regexp.escape(@orgname)}\/#{Regexp.escape(@semester)}-#{Regexp.escape(@base_filename)}-(\d+)$/) + if match + team_num = match[1] + begin + childteam_id = @client.team_by_name(@orgname, %Q{#{@semester}-#{team_num}})['id'] # eg slug fa23-1 + rescue Octokit::NotFound + next + end + @client.remove_team_repository(childteam_id, repo) end - @client.remove_team_repository(childteam_id, repo_name) end end end From 1c3f9c0e121b176a9515ee046b09b7909971e493 Mon Sep 17 00:00:00 2001 From: a544266477 Date: Mon, 18 Sep 2023 14:52:53 -0700 Subject: [PATCH 04/22] finished testing All method but create teams worked. --- scripts/github-repos/Gemfile | 3 +- scripts/github-repos/Gemfile.lock | 66 +++---- scripts/github-repos/README.md | 6 +- scripts/github-repos/github-repos.rb | 272 +++++++++++---------------- scripts/github-repos/team-test.csv | 6 +- 5 files changed, 155 insertions(+), 198 deletions(-) diff --git a/scripts/github-repos/Gemfile b/scripts/github-repos/Gemfile index 680c7cf..97ed9fa 100644 --- a/scripts/github-repos/Gemfile +++ b/scripts/github-repos/Gemfile @@ -1,7 +1,6 @@ source 'https://rubygems.org' -ruby '3.2.2' +ruby '< 3.0' gem 'octokit' gem 'csv' -gem 'yaml' diff --git a/scripts/github-repos/Gemfile.lock b/scripts/github-repos/Gemfile.lock index a0cb690..03a79b7 100644 --- a/scripts/github-repos/Gemfile.lock +++ b/scripts/github-repos/Gemfile.lock @@ -1,33 +1,33 @@ -GEM - remote: https://rubygems.org/ - specs: - addressable (2.8.4) - public_suffix (>= 2.0.2, < 6.0) - csv (3.2.6) - faraday (2.7.5) - faraday-net_http (>= 2.0, < 3.1) - ruby2_keywords (>= 0.0.4) - faraday-net_http (3.0.2) - octokit (6.1.1) - faraday (>= 1, < 3) - sawyer (~> 0.9) - public_suffix (5.0.1) - ruby2_keywords (0.0.5) - sawyer (0.9.2) - addressable (>= 2.3.5) - faraday (>= 0.17.3, < 3) - yaml (0.2.1) - -PLATFORMS - x86_64-linux - -DEPENDENCIES - csv - octokit - yaml - -RUBY VERSION - ruby 3.2.2p53 - -BUNDLED WITH - 2.4.13 +GEM + remote: https://rubygems.org/ + specs: + addressable (2.8.5) + public_suffix (>= 2.0.2, < 6.0) + base64 (0.1.1) + csv (3.2.7) + faraday (2.7.11) + base64 + faraday-net_http (>= 2.0, < 3.1) + ruby2_keywords (>= 0.0.4) + faraday-net_http (3.0.2) + octokit (4.25.1) + faraday (>= 1, < 3) + sawyer (~> 0.9) + public_suffix (5.0.3) + ruby2_keywords (0.0.5) + sawyer (0.9.2) + addressable (>= 2.3.5) + faraday (>= 0.17.3, < 3) + +PLATFORMS + arm64-darwin-22 + +DEPENDENCIES + csv + octokit + +RUBY VERSION + ruby 2.6.10p210 + +BUNDLED WITH + 2.4.19 diff --git a/scripts/github-repos/README.md b/scripts/github-repos/README.md index a17cef8..6dbf279 100644 --- a/scripts/github-repos/README.md +++ b/scripts/github-repos/README.md @@ -15,18 +15,18 @@ If PREFIX is provided, it will assume the csv file contains "Team" column, and c the STUDENTTEAM, and invites them to child teams. Required options: - -c, --csv=CSVFILE CSV file containing at leaset "Email/Username" named columns + -c, --csv=CSVFILE CSV file containing at leaset "Email" named columns -s, --studentteam=STUDENTTEAM The team name of all the students team -o, --orgname=ORGNAME The name of the org Optional options: -p, --prefix=PREFIX Semester prefix, eg fa23. -'create_teams' +'create_teams' (Not functioning) Assuming students are in STUDENTTEAM, create child teams for students for CHIP 10.5 and add them to the child team. Required options: - -c, --csv=CSVFILE CSV file containing at least "Team" and "Email/Username" named columns + -c, --csv=CSVFILE CSV file containing at least "Team" and "Email" named columns -s, --studentteam=STUDENTTEAM The team name of all the students team -p, --prefix=PREFIX Semester prefix, eg fa23. -o, --orgname=ORGNAME The name of the org diff --git a/scripts/github-repos/github-repos.rb b/scripts/github-repos/github-repos.rb index 3402608..e58080a 100755 --- a/scripts/github-repos/github-repos.rb +++ b/scripts/github-repos/github-repos.rb @@ -4,7 +4,7 @@ require 'octokit' require 'csv' -ENV['GITHUB_ORG_API_KEY'] = "" +ENV['GITHUB_ORG_API_KEY'] = "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" def main() puts "Script start." @@ -28,114 +28,28 @@ def main() opt.separator " remove_team_repos: Delete all repos whose names are formed like \"PREFIX-FILENAME-[Team number]\".\n" opt.separator " remove_teams: Remove all students and child teams in STUDENTTEAM from the org. Remove STUDENTTEAM as well.\n" opt.separator " remove_team_repo_access: Remove students access to CHIP 10.5 repos that are formed like \"PREFIX-FILENAME-[Team number]\".\n" - opt.separator "Required options:" + opt.separator "Options:" - case ARGV[0] - when 'invite' - opt.on('-cCSVFILE', '--csv=CSVFILE', 'CSV file containing at leaset "Email" named columns') do |csv| - org.csv = csv - end - opt.on('-oORGNAME', '--orgname=ORGNAME', 'The name of the org') do |orgname| - org.orgname = orgname - end - opt.on('-sSTUDENTTEAM', '--studentteam=STUDENTTEAM', 'The team name of all the students team') do |studentteam| - org.parentteam = studentteam - end - opt.separator "Optional options:" - opt.on('-pPREFIX', '--prefix=PREFIX', 'Semester prefix, eg fa23.') do |pfx| - org.semester = pfx - end - when 'create_teams' - opt.on('-cCSVFILE', '--csv=CSVFILE', 'CSV file containing at least "Team" and "Email" named columns') do |csv| - org.csv = csv - end - opt.on('-sSTUDENTTEAM', '--studentteam=STUDENTTEAM', 'The team name of all the students team') do |studentteam| - org.parentteam = studentteam - end - opt.on('-pPREFIX', '--prefix=PREFIX', 'Semester prefix, eg fa23.') do |pfx| - org.semester = pfx - end - opt.on('-oORGNAME', '--orgname=ORGNAME', 'The name of the org') do |orgname| - org.orgname = orgname - end - when 'indiv_repos' - opt.on('-fFILENAME', '--filename=FILENAME', 'The base filename for repos') do |filename| - org.base_filename = filename - end - opt.on('-pPREFIX', '--prefix=PREFIX', 'Semester prefix, eg fa23.') do |pfx| - org.semester = pfx - end - opt.on('-tTEMPLATE', '--template=TEMPLATE', 'The repo name within the org to use as template') do |template| - org.template = template - end - opt.on('-sSTUDENTTEAM', '--studentteam=STUDENTTEAM', 'The team name of all the students team') do |studentteam| - org.parentteam = studentteam - end - opt.on('-gGSITEAM', '--gsiteam=GSITEAM', 'The team name of all the staff team') do |gsiteam| - org.gsiteam = gsiteam - end - opt.on('-oORGNAME', '--orgname=ORGNAME', 'The name of the org') do |orgname| - org.orgname = orgname - end - when 'team_repos' - opt.on('-fFILENAME', '--filename=FILENAME', 'The base filename for repos') do |filename| - org.base_filename = filename - end - opt.on('-pPREFIX', '--prefix=PREFIX', 'Semester prefix, eg fa23.') do |pfx| - org.semester = pfx - end - opt.on('-tTEMPLATE', '--template=TEMPLATE', 'The repo name within the org to use as template') do |template| - org.template = template - end - opt.on('-sSTUDENTTEAM', '--studentteam=STUDENTTEAM', 'The team name of all the students team') do |studentteam| - org.parentteam = studentteam - end - opt.on('-gGSITEAM', '--gsiteam=GSITEAM', 'The team name of all the staff team') do |gsiteam| - org.gsiteam = gsiteam - end - opt.on('-oORGNAME', '--orgname=ORGNAME', 'The name of the org') do |orgname| - org.orgname = orgname - end - when 'remove_indiv_repos' - opt.on('-pPREFIX', '--prefix=PREFIX', 'Semester prefix, eg fa23.') do |pfx| - org.semester = pfx - end - opt.on('-fFILENAME', '--filename=FILENAME', 'The base filename for repos') do |filename| - org.base_filename = filename - end - opt.on('-oORGNAME', '--orgname=ORGNAME', 'The name of the org') do |orgname| - org.orgname = orgname - end - when 'remove_team_repos' - opt.on('-pPREFIX', '--prefix=PREFIX', 'Semester prefix, eg fa23.') do |pfx| - org.semester = pfx - end - opt.on('-fFILENAME', '--filename=FILENAME', 'The base filename for repos') do |filename| - org.base_filename = filename - end - opt.on('-oORGNAME', '--orgname=ORGNAME', 'The name of the org') do |orgname| - org.orgname = orgname - end - when 'remove_teams' - opt.on('-pPREFIX', '--prefix=PREFIX', 'Semester prefix, eg fa23.') do |pfx| - org.semester = pfx - end - opt.on('-sSTUDENTTEAM', '--studentteam=STUDENTTEAM', 'The team name of all the students team') do |studentteam| - org.parentteam = studentteam - end - opt.on('-oORGNAME', '--orgname=ORGNAME', 'The name of the org') do |orgname| - org.orgname = orgname - end - when 'remove_team_repo_access' - opt.on('-fFILENAME', '--filename=FILENAME', 'The base filename for repos') do |filename| - org.base_filename = filename - end - opt.on('-pPREFIX', '--prefix=PREFIX', 'Semester prefix, eg fa23.') do |pfx| - org.semester = pfx - end - opt.on('-oORGNAME', '--orgname=ORGNAME', 'The name of the org') do |orgname| - org.orgname = orgname - end + opt.on('-cCSVFILE', '--csv=CSVFILE', 'CSV file containing at leaset "Email" named columns') do |csv| + org.csv = csv + end + opt.on('-oORGNAME', '--orgname=ORGNAME', 'The name of the org') do |orgname| + org.orgname = orgname + end + opt.on('-sSTUDENTTEAM', '--studentteam=STUDENTTEAM', 'The team name of all the students team') do |studentteam| + org.parentteam = studentteam + end + opt.on('-pPREFIX', '--prefix=PREFIX', 'Semester prefix, eg fa23.') do |pfx| + org.semester = pfx + end + opt.on('-fFILENAME', '--filename=FILENAME', 'The base filename for repos') do |filename| + org.base_filename = filename + end + opt.on('-tTEMPLATE', '--template=TEMPLATE', 'The repo name within the org to use as template') do |template| + org.template = template + end + opt.on('-gGSITEAM', '--gsiteam=GSITEAM', 'The team name of all the staff team') do |gsiteam| + org.gsiteam = gsiteam end end $opts.parse! @@ -167,7 +81,7 @@ def initialize @users = [] @childteams = Hash.new { |hash, key| hash[key] = [] } # teamID => [email1, email2, ...] print_error("GITHUB_ORG_API_KEY not defined in environment") unless (@key = ENV['GITHUB_ORG_API_KEY']) - @client = Octokit::Client.new(access_token: @key) + @client = Octokit::Client.new(access_token: @key, scopes: ['user', 'user:email']) end private @@ -193,6 +107,23 @@ def read_teams_and_users_from csv end end +def to_slug(input) + # Convert to lowercase + slug = input.downcase + + # Replace characters other than 0-9, a-z, '.', '_', and '-' + slug.gsub!(/[^0-9a-z.\-_]+/, '-') + + # Remove leading and trailing hyphens + slug.gsub!(/^-+/, '') + slug.gsub!(/-+$/, '') + + # Limit the string to 63 characters + slug = slug[0, 63] + + return slug +end + def invite_valid? if @orgname.nil? || @orgname.empty? || @csv.nil? || @parentteam.nil? || @parentteam.empty? return false @@ -215,7 +146,7 @@ def create_teams_valid? end def repos_valid? - !(@orgname.nil? || @parentteam.nil? || @prefix.nil? || @template.nil? || gsiteam_valid? || @base_filename.nil?) + !(@orgname.nil? || @parentteam.nil? || @semester.nil? || @template.nil? || !gsiteam_valid? || @base_filename.nil?) end def remove_valid? @@ -230,7 +161,7 @@ def gsiteam_valid? gsiteam_obj = nil if !@gsiteam.nil? && @gsiteam.length > 0 begin - gsiteam_obj = @client.team_by_name(@orgname, @gsiteam) + gsiteam_obj = @client.team_by_name(@orgname, to_slug(@gsiteam)) rescue Octokit::NotFound print_error "Can't find the gsi team in the org." end @@ -257,13 +188,13 @@ def invite # Looking for the STUDENTTEAM in the org, see if it is exist. begin - parentteam_id = @client.team_by_name(@orgname, @parentteam)['id'] + parentteam_id = @client.team_by_name(@orgname, to_slug(@parentteam))['id'] rescue Octokit::NotFound parentteam_id = @client.create_team(@orgname, {name: @parentteam, privacy: 'closed'})['id'] end @users.each do |student_email| - if !@client.team_invitations(parentteam_id).any? {|invitations| invitations.email == member} + if !@client.team_invitations(parentteam_id).any? {|invitations| invitations.email == student_email} # invitation is not pending, send invitation begin @client.post(%Q{/orgs/#{@orgname}/invitations}, {org: @orgname, email: student_email , role: 'direct_member', team_ids: [parentteam_id]}) @@ -278,7 +209,7 @@ def invite # Looking for the STUDENTTEAM in the org, see if it is exist. begin - parentteam_id = @client.team_by_name(@orgname, @parentteam)['id'] + parentteam_id = @client.team_by_name(@orgname, to_slug(@parentteam))['id'] rescue Octokit::NotFound parentteam_id = @client.create_team(@orgname, {name: @parentteam, privacy: 'closed'})['id'] end @@ -288,7 +219,7 @@ def invite @childteams.each_key do |team| childteam_name = %Q{#{@semester}-#{team}} begin - childteam = @client.team_by_name(@orgname, childteam_name) + childteam = @client.team_by_name(@orgname, to_slug(childteam_name)) rescue Octokit::NotFound childteam = @client.create_team(@orgname, {name: %Q{#{@semester}-#{team}}, parent_team_id: parentteam_id}) end @@ -315,25 +246,31 @@ def create_teams # Looking for the STUDENTTEAM in the org, see if it is exist. begin - parentteam_id = @client.team_by_name(@orgname, @parentteam)['id'] + parentteam_id = @client.team_by_name(@orgname, to_slug(@parentteam))['id'] rescue Octokit::NotFound print_error "Please make sure the invite command runs first, or students team is created." end email_to_username_map = {} - @client.team_members(parent_team_id).each do |mem| - email_to_username_map[mem.login] = mem.email + @client.team_members(parentteam_id).each do |mem| + team_mem = @client.user + puts team_mem.login + end + + @client.team_members(parentteam_id).each do |mem| + email_to_username_map[mem.email] = mem.login end + puts email_to_username_map + failed_emails = [] # create child teams and add students to their child teams @childteams.each_key do |team| childteam_name = %Q{#{@semester}-#{team}} begin - childteam = @client.team_by_name(@orgname, childteam_name) - print_error "Childteam has been created before the script runs." + childteam = @client.team_by_name(@orgname, to_slug(childteam_name)) rescue Octokit::NotFound childteam = @client.create_team(@orgname, {name: %Q{#{@semester}-#{team}}, parent_team_id: parentteam_id}) end @@ -360,22 +297,44 @@ def indiv_repos # Looking for the STUDENTTEAM in the org, see if it is exist. begin - parentteam_id = @client.team_by_name(@orgname, @parentteam)['id'] + parentteam_id = @client.team_by_name(@orgname, to_slug(@parentteam))['id'] rescue Octokit::NotFound print_error "Please make sure students team is created." end - @client.team_members(parent_team_id).each do |mem| - new_repo_name = %Q{#{@semester}-#{mem.login}-#{@base_filename}} - if !@client.repository? %Q{#{@orgname}/#{new_repo_name}} - begin - new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, - {owner: @orgname, private: true}) - rescue Octokit::NotFound - print_error "Template not found." + gsiteam_id = @client.team_by_name(@orgname, to_slug(@gsiteam)).id + self_user_name = @client.user.login + @client.team_members(parentteam_id).each do |mem| + if self_user_name != mem.login + new_repo_name = %Q{#{@semester}-#{mem.login}-#{@base_filename}} + if !@client.repository? %Q{#{@orgname}/#{new_repo_name}} + begin + new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, + {owner: @orgname, private: true}) + rescue Octokit::NotFound + print_error "Template not found." + end + @client.add_collab(new_repo['full_name'], mem.login) + @client.add_team_repository(gsiteam_id, new_repo['full_name'], {permission: 'admin'}) + end + end + end + + + # can remove the below loop because in the future, there should not be child teams while we assigning the indiv repos. + @client.child_teams(parentteam_id).each.each do |mem| + if !mem.login.nil? && self_user_name != mem.login + new_repo_name = %Q{#{@semester}-#{mem.login}-#{@base_filename}} + if !@client.repository? %Q{#{@orgname}/#{new_repo_name}} + begin + new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, + {owner: @orgname, private: true}) + rescue Octokit::NotFound + print_error "Template not found." + end + @client.add_collab(new_repo['full_name'], mem.login) + @client.add_team_repository(gsiteam_id, new_repo['full_name'], {permission: 'admin'}) end - @client.add_collab(new_repo, mem.login) - @client.add_team_repository(gsiteam_id, new_repo['full_name'], {permission: 'admin'}) end end end @@ -383,14 +342,11 @@ def indiv_repos def team_repos print_error "orgname, student team name, base filename, template repo name, semester prefix, and gsi team name needed." unless repos_valid? - @childteams.each_key do |team| - begin - team_id = @client.team_by_name(@orgname, %Q{#{@semester}-#{team}})['id'] - rescue Octokit::NotFound - print_error "Students teams are not created." - end - gsiteam_id = @client.team_by_name(@orgname, @gsiteam)['id'] - new_repo_name = %Q{#{@semester}-#{@base_filename}-#{team}} + child_teams = @client.child_teams(@client.team_by_name(@orgname, to_slug(@parentteam)).id) + gsiteam_id = @client.team_by_name(@orgname, to_slug(@gsiteam))['id'] + child_teams.each do |team| + team_num = team.slug.match(/-(\d+)$/)[1] + new_repo_name = %Q{#{@semester}-#{@base_filename}-#{team_num}} if !@client.repository? %Q{#{@orgname}/#{new_repo_name}} begin new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, @@ -398,7 +354,7 @@ def team_repos rescue Octokit::NotFound print_error "Template not found." end - @client.add_team_repository(team_id, new_repo['full_name'], {permission: 'push'}) + @client.add_team_repository(team.id, new_repo['full_name'], {permission: 'push'}) @client.add_team_repository(gsiteam_id, new_repo['full_name'], {permission: 'admin'}) end end @@ -410,8 +366,8 @@ def remove_indiv_repos repos = @client.org_repos(@orgname, {:type => 'private'}) repos.each do |repo| - if repo.name =~ /^#{Regexp.escape(@orgname)}\/#{Regexp.escape(@semester)}-(.*)-#{Regexp.escape(@base_filename)}$/ - @client.delete_repository(repo) + if repo.name =~ /^#{Regexp.escape(@semester)}-(.*)-#{Regexp.escape(@base_filename)}$/ + @client.delete_repository(repo.full_name) end end end @@ -421,33 +377,35 @@ def remove_team_repos repos = @client.org_repos(@orgname, {:type => 'private'}) repos.each do |repo| - if repo.name =~ /^#{Regexp.escape(@orgname)}\/#{Regexp.escape(@semester)}-#{Regexp.escape(@base_filename)}-\d+$/ - @client.delete_repository(repo) + if repo.name =~ /^#{Regexp.escape(@semester)}-#{Regexp.escape(@base_filename)}-\d+$/ + @client.delete_repository(repo.full_name) end end end def remove_teams - print_error "org name, semester prefix, students team namd are needed." unless remove_teams_valid? + print_error "org name, semester prefix, students team name are needed." unless remove_teams_valid? # Looking for the STUDENTTEAM in the org, see if it is exist. begin - parentteam_id = @client.team_by_name(@orgname, @parentteam)['id'] + parentteam_id = @client.team_by_name(@orgname, to_slug(@parentteam))['id'] rescue Octokit::NotFound print_error "Please make sure students team is created." end - - # # Remove all child teams - # @client.org_teams(@orgname).each do |team| - # if team.parent.id == parentteam_id - # @client.delete_team team.id - # end - # end # Remove all members in the students team from org - @client.team_members(parent_team_id).each do |mem| - if @client.organization_membership(@orgname, :user => mem.login)['role'] == 'member' + self_user_name = @client.user.login + @client.team_members(parentteam_id).each do |mem| + if !mem.login.nil? && mem.login != self_user_name @client.remove_organization_member(@orgname, mem.login) + end + end + + @client.child_teams(parentteam_id).each do |childteam| + @client.team_members(childteam.id).each do |mem| + if !mem.login.nil? && mem.login != self_user_name + @client.remove_organization_member(@orgname, mem.login) + end end end @@ -460,11 +418,11 @@ def remove_access repos = @client.org_repos(@orgname, {:type => 'private'}) repos.each do |repo| - match = repo.name.match(/^#{Regexp.escape(@orgname)}\/#{Regexp.escape(@semester)}-#{Regexp.escape(@base_filename)}-(\d+)$/) + match = repo.name.match(/^#{Regexp.escape(@semester)}-#{Regexp.escape(@base_filename)}-(\d+)$/) if match team_num = match[1] begin - childteam_id = @client.team_by_name(@orgname, %Q{#{@semester}-#{team_num}})['id'] # eg slug fa23-1 + childteam_id = @client.team_by_name(@orgname, to_slug(%Q{#{@semester}-#{team_num}}))['id'] # eg slug fa23-1 rescue Octokit::NotFound next end diff --git a/scripts/github-repos/team-test.csv b/scripts/github-repos/team-test.csv index 0c6b0e9..5b9f232 100644 --- a/scripts/github-repos/team-test.csv +++ b/scripts/github-repos/team-test.csv @@ -1,3 +1,3 @@ -Team,Email/Username,Name -1,ydong19@berkeley.edu,Amy -2, \ No newline at end of file +Team,Email,Name +1,609847732amy@gmail.com,Amy +2,ghu@berkeley.edu,colin \ No newline at end of file From d0be6aa9bb44976f3912e222e35cfc3858e46447 Mon Sep 17 00:00:00 2001 From: a544266477 Date: Mon, 18 Sep 2023 20:14:50 -0700 Subject: [PATCH 05/22] change to use username to add students --- scripts/github-repos/README.md | 6 +- scripts/github-repos/github-repos.rb | 83 ++++++++++------------------ scripts/github-repos/team-test.csv | 6 +- 3 files changed, 36 insertions(+), 59 deletions(-) diff --git a/scripts/github-repos/README.md b/scripts/github-repos/README.md index 6dbf279..bcee5ef 100644 --- a/scripts/github-repos/README.md +++ b/scripts/github-repos/README.md @@ -15,7 +15,7 @@ If PREFIX is provided, it will assume the csv file contains "Team" column, and c the STUDENTTEAM, and invites them to child teams. Required options: - -c, --csv=CSVFILE CSV file containing at leaset "Email" named columns + -c, --csv=CSVFILE CSV file containing at leaset "Username" named columns -s, --studentteam=STUDENTTEAM The team name of all the students team -o, --orgname=ORGNAME The name of the org @@ -26,7 +26,7 @@ Optional options: Assuming students are in STUDENTTEAM, create child teams for students for CHIP 10.5 and add them to the child team. Required options: - -c, --csv=CSVFILE CSV file containing at least "Team" and "Email" named columns + -c, --csv=CSVFILE CSV file containing at least "Team" and "Username" named columns -s, --studentteam=STUDENTTEAM The team name of all the students team -p, --prefix=PREFIX Semester prefix, eg fa23. -o, --orgname=ORGNAME The name of the org @@ -90,7 +90,7 @@ Required options: This script creates a team that include all stundents in the same semester, eg fa23. Then creates different child teams for them for the chip10.5. At a minimum, -you need a CSV file listing all enrolled students with columns 'Email' and 'Team'. +you need a CSV file listing all enrolled students with columns 'Username' and 'Team'. The values in the Team columns should be nonnegative integers identifying teams. Org setting: Assume Base permissions is no permission, each students can only access their team repos. diff --git a/scripts/github-repos/github-repos.rb b/scripts/github-repos/github-repos.rb index e58080a..ffe9151 100755 --- a/scripts/github-repos/github-repos.rb +++ b/scripts/github-repos/github-repos.rb @@ -4,7 +4,7 @@ require 'octokit' require 'csv' -ENV['GITHUB_ORG_API_KEY'] = "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" +ENV['GITHUB_ORG_API_KEY'] = "" def main() puts "Script start." @@ -30,7 +30,7 @@ def main() opt.separator " remove_team_repo_access: Remove students access to CHIP 10.5 repos that are formed like \"PREFIX-FILENAME-[Team number]\".\n" opt.separator "Options:" - opt.on('-cCSVFILE', '--csv=CSVFILE', 'CSV file containing at leaset "Email" named columns') do |csv| + opt.on('-cCSVFILE', '--csv=CSVFILE', 'CSV file containing at leaset "Username" named columns') do |csv| org.csv = csv end opt.on('-oORGNAME', '--orgname=ORGNAME', 'The name of the org') do |orgname| @@ -81,7 +81,7 @@ def initialize @users = [] @childteams = Hash.new { |hash, key| hash[key] = [] } # teamID => [email1, email2, ...] print_error("GITHUB_ORG_API_KEY not defined in environment") unless (@key = ENV['GITHUB_ORG_API_KEY']) - @client = Octokit::Client.new(access_token: @key, scopes: ['user', 'user:email']) + @client = Octokit::Client.new(access_token: @key, scopes: ['user']) end private @@ -89,20 +89,20 @@ def initialize def read_users_from csv data = CSV.parse(IO.read(csv), headers: true) hash = data.first.to_h - print_error "Need at least 'Email' (str) columns in #{csv}" unless - hash.has_key?('Email') + print_error "Need at least 'Username' (str) columns in #{csv}" unless + hash.has_key?('Username') data.each do |row| - @users << row['Email'] + @users << row['Username'] end end def read_teams_and_users_from csv data = CSV.parse(IO.read(csv), headers: true) hash = data.first.to_h - print_error "Need at least 'Team' (int) and 'Email' (str) columns in #{csv}" unless - hash.has_key?('Team') && hash.has_key?('Email') + print_error "Need at least 'Team' (int) and 'Username' (str) columns in #{csv}" unless + hash.has_key?('Team') && hash.has_key?('Username') data.each do |row| - username = row['Email'] + username = row['Username'] @childteams[row['Team']] << username end end @@ -171,10 +171,6 @@ def gsiteam_valid? public - def valid? - @orgname && @base_filename && @semester && @childteams.length > 0 && @template && @parentteam && gsiteam_valid? - end - def print_error(msg=nil) STDERR.puts "Error: #{msg}" if msg STDERR.puts $opts @@ -193,15 +189,11 @@ def invite parentteam_id = @client.create_team(@orgname, {name: @parentteam, privacy: 'closed'})['id'] end - @users.each do |student_email| - if !@client.team_invitations(parentteam_id).any? {|invitations| invitations.email == student_email} - # invitation is not pending, send invitation - begin - @client.post(%Q{/orgs/#{@orgname}/invitations}, {org: @orgname, email: student_email , role: 'direct_member', team_ids: [parentteam_id]}) - rescue Octokit::UnprocessableEntity - # send fail, member is already a part of org - next - end + @users.each do |username| + begin + @client.put(%Q{/orgs/#{@orgname}/teams/#{to_slug(@parentteam)}/memberships/#{username}}) + rescue Octokit::UnprocessableEntity + next end end else @@ -226,14 +218,12 @@ def invite childteam_id = childteam['id'] @childteams[team].each do |member| # Try no check invitations before create one. - if !@client.team_invitations(childteam_id).any? {|invitations| invitations.email == member} - # send invitation - begin - @client.post(%Q{/orgs/#{@orgname}/invitations}, {org: @orgname, email: member, role: 'direct_member', team_ids: [childteam_id]}) - rescue Octokit::UnprocessableEntity - # member is already a part of org - next - end + # send invitation + begin + @client.put(%Q{/orgs/#{@orgname}/teams/#{childteam.slug}/memberships/#{member}}) + rescue Octokit::UnprocessableEntity + # member is already a part of org + next end end end @@ -251,20 +241,7 @@ def create_teams print_error "Please make sure the invite command runs first, or students team is created." end - email_to_username_map = {} - - @client.team_members(parentteam_id).each do |mem| - team_mem = @client.user - puts team_mem.login - end - - @client.team_members(parentteam_id).each do |mem| - email_to_username_map[mem.email] = mem.login - end - - puts email_to_username_map - - failed_emails = [] + failed_username = [] # create child teams and add students to their child teams @childteams.each_key do |team| @@ -275,20 +252,20 @@ def create_teams childteam = @client.create_team(@orgname, {name: %Q{#{@semester}-#{team}}, parent_team_id: parentteam_id}) end childteam_id = childteam['id'] - @childteams[team].each do |email| - if email_to_username_map.has_key? email - @client.add_team_member(childteam_id, email_to_username_map[email]) - else - # save the incorrect email - failed_emails << email + @childteams[team].each do |username| + begin + @client.add_team_member(childteam_id, username) + rescue Octokit::UnprocessableEntity + # save the incorrect username + failed_username << username end end end # print the emails that is failed to add to child team - if !failed_emails.empty? - puts 'Failed to add these emails to child team:' - puts failed_emails + if !failed_username.empty? + puts 'Failed to add these usernames to child team:' + puts failed_username end end diff --git a/scripts/github-repos/team-test.csv b/scripts/github-repos/team-test.csv index 5b9f232..e17168a 100644 --- a/scripts/github-repos/team-test.csv +++ b/scripts/github-repos/team-test.csv @@ -1,3 +1,3 @@ -Team,Email,Name -1,609847732amy@gmail.com,Amy -2,ghu@berkeley.edu,colin \ No newline at end of file +Team,Username,Name +1,KCsama,Amy +2,Gangster-Who,Gang hu \ No newline at end of file From 9e220d8d983679f8d93833645dc05b81f6206bd1 Mon Sep 17 00:00:00 2001 From: a544266477 Date: Tue, 19 Sep 2023 12:13:28 -0700 Subject: [PATCH 06/22] fix the indiv_repos child team loop --- scripts/github-repos/github-repos.rb | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/scripts/github-repos/github-repos.rb b/scripts/github-repos/github-repos.rb index ffe9151..40d6948 100755 --- a/scripts/github-repos/github-repos.rb +++ b/scripts/github-repos/github-repos.rb @@ -299,18 +299,20 @@ def indiv_repos # can remove the below loop because in the future, there should not be child teams while we assigning the indiv repos. - @client.child_teams(parentteam_id).each.each do |mem| - if !mem.login.nil? && self_user_name != mem.login - new_repo_name = %Q{#{@semester}-#{mem.login}-#{@base_filename}} - if !@client.repository? %Q{#{@orgname}/#{new_repo_name}} - begin - new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, - {owner: @orgname, private: true}) - rescue Octokit::NotFound - print_error "Template not found." + @client.child_teams(parentteam_id).each do |childteam| + @client.team_members(childteam.id).each do |mem| + if !mem.login.nil? && self_user_name != mem.login + new_repo_name = %Q{#{@semester}-#{mem.login}-#{@base_filename}} + if !@client.repository? %Q{#{@orgname}/#{new_repo_name}} + begin + new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, + {owner: @orgname, private: true}) + rescue Octokit::NotFound + print_error "Template not found." + end + @client.add_collab(new_repo['full_name'], mem.login) + @client.add_team_repository(gsiteam_id, new_repo['full_name'], {permission: 'admin'}) end - @client.add_collab(new_repo['full_name'], mem.login) - @client.add_team_repository(gsiteam_id, new_repo['full_name'], {permission: 'admin'}) end end end From 5fcfe4bd31da3bbed691cb978208609cbe81cc73 Mon Sep 17 00:00:00 2001 From: a544266477 Date: Tue, 19 Sep 2023 12:58:49 -0700 Subject: [PATCH 07/22] add remove team repo access command --- scripts/github-repos/github-repos.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/github-repos/github-repos.rb b/scripts/github-repos/github-repos.rb index 40d6948..21430c7 100755 --- a/scripts/github-repos/github-repos.rb +++ b/scripts/github-repos/github-repos.rb @@ -62,7 +62,7 @@ def main() when 'remove_indiv_repos' then org.remove_indiv_repos when 'remove_team_repos' then org.remove_team_repos when 'remove_teams' then org.remove_teams - when 'remove_team_repo_access' + when 'remove_team_repo_access' then org.remove_team_repo_access else org.print_error end puts "Run successfully." @@ -392,7 +392,7 @@ def remove_teams @client.delete_team parentteam_id end - def remove_access + def remove_team_repo_access print_error "org name, base filename, semester prefix are needed." unless remove_valid? repos = @client.org_repos(@orgname, {:type => 'private'}) From 452d6470c187d28fe14c2c69117999c907901165 Mon Sep 17 00:00:00 2001 From: a544266477 Date: Wed, 20 Sep 2023 14:42:47 -0700 Subject: [PATCH 08/22] Delete team-test.csv --- scripts/github-repos/team-test.csv | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 scripts/github-repos/team-test.csv diff --git a/scripts/github-repos/team-test.csv b/scripts/github-repos/team-test.csv deleted file mode 100644 index e17168a..0000000 --- a/scripts/github-repos/team-test.csv +++ /dev/null @@ -1,3 +0,0 @@ -Team,Username,Name -1,KCsama,Amy -2,Gangster-Who,Gang hu \ No newline at end of file From 00bb0e2af4059537400299edf74a80a060fccee4 Mon Sep 17 00:00:00 2001 From: a544266477 Date: Wed, 20 Sep 2023 16:38:39 -0700 Subject: [PATCH 09/22] Update some typo errors --- scripts/github-repos/github-repos.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/github-repos/github-repos.rb b/scripts/github-repos/github-repos.rb index 21430c7..69e8551 100755 --- a/scripts/github-repos/github-repos.rb +++ b/scripts/github-repos/github-repos.rb @@ -262,7 +262,7 @@ def create_teams end end - # print the emails that is failed to add to child team + # print the usernames that is failed to add to child team if !failed_username.empty? puts 'Failed to add these usernames to child team:' puts failed_username @@ -405,7 +405,7 @@ def remove_team_repo_access rescue Octokit::NotFound next end - @client.remove_team_repository(childteam_id, repo) + @client.remove_team_repository(childteam_id, repo.full_name) end end end From 59a341e8aea7ed72200e41d9bcf2278f5c0efce1 Mon Sep 17 00:00:00 2001 From: a544266477 Date: Thu, 21 Sep 2023 18:57:26 -0700 Subject: [PATCH 10/22] update the opts parse and readme file --- scripts/github-repos/README.md | 107 +++++++++++++-------------- scripts/github-repos/github-repos.rb | 71 +++++++++--------- 2 files changed, 87 insertions(+), 91 deletions(-) diff --git a/scripts/github-repos/README.md b/scripts/github-repos/README.md index bcee5ef..750d50c 100644 --- a/scripts/github-repos/README.md +++ b/scripts/github-repos/README.md @@ -1,141 +1,136 @@ # Bulk creation/deletion of many repos and student teams ``` -Usage: ./github-repos.rb [required options] [invite|create_teams|indiv_repos|team_repos|remove_indivi_repos|remove_team_repos|remove_teams|remove_team_repo_access] [optional options] +Usage: ./github-repos.rb [required options] [invite|create_teams|indiv_repos|group_repos|remove_indiv_repos|remove_group_repos|remove_teams|remove_group_repos_access] [optional options] GITHUB_ORG_API_KEY for the org must be set as an environment variable. It's safe to run multiple times. 'invite': -Create a team called STUDENTTEAM under the org that has all students in the same semester and send invitation -If STUDENTTEAM is already exist, the script will resent invitation to students. -(Temporary for special situations in Fa23) -If PREFIX is provided, it will assume the csv file contains "Team" column, and create child teams under -the STUDENTTEAM, and invites them to child teams. +Create a team called STUDENTTEAM under the org and send invitations to students to the team. +If STUDENTTEAM already exists, the script will resend invitation to students. + +If PREFIX is provided, it will assume the csv file contains "Group" column, and create groups(child teams) under +the STUDENTTEAM, and invites them to groups. (Temporary for special situations in Fa23) Required options: - -c, --csv=CSVFILE CSV file containing at leaset "Username" named columns - -s, --studentteam=STUDENTTEAM The team name of all the students team + -c, --csv=CSVFILE CSV file containing at least "Username" named columns + -s, --studentteam=STUDENTTEAM The team name for students' team -o, --orgname=ORGNAME The name of the org Optional options: - -p, --prefix=PREFIX Semester prefix, eg fa23. + -p, --prefix=PREFIX Semester prefix, eg fa23. If prefix is not provided, it will not create groups. -'create_teams' (Not functioning) -Assuming students are in STUDENTTEAM, create child teams for students for CHIP 10.5 and add them to the child team. +'create_groups' +Assuming students are in STUDENTTEAM, create groups for students for CHIP 10.5 and add them to corresponding groups. Required options: - -c, --csv=CSVFILE CSV file containing at least "Team" and "Username" named columns - -s, --studentteam=STUDENTTEAM The team name of all the students team + -c, --csv=CSVFILE CSV file containing at least "Group" and "Username" named columns + -s, --studentteam=STUDENTTEAM The team name for students' team -p, --prefix=PREFIX Semester prefix, eg fa23. -o, --orgname=ORGNAME The name of the org 'indiv_repos' -Create CHIPS repo for each stedent in STUDENTTEAM. Repos' names are form like "PREFIX-[username]-FILENAME" +Create CHIPS repos for each student in STUDENTTEAM. Repos' names are form like "[PREFIX]-[username]-[ASSIGNMENT]" +[username] stands for the GitHub username. Required options: -o, --orgname=ORGNAME The name of the org - -s, --studentteam=STUDENTTEAM The team name of all the students team + -s, --studentteam=STUDENTTEAM The team name for students' team -p, --prefix=PREFIX Semester prefix, eg fa23. -t, --template=TEMPLATE The repo name within the org to use as template - -g, --gsiteam=GSITEAM The team name of all the staff team - -f, --filename=FILENAME The base filename for repos + -g, --gsiteam=GSITEAM The team name of staff team + -a, --assignment=ASSIGNMENT The assignment name -'team_repos' -Create 10.5 repos for each child team. Repos' names are form like "PREFIX-FILENAME-[Team number]" -Make sure child teams are formed before running this command. +'group_repos' +Create repos for each group. Repos' names are form like "[PREFIX]-[ASSIGNMENT]-[GROUPNUM]" +Make sure groups are formed before running this command. Required options: -o, --orgname=ORGNAME The name of the org -p, --prefix=PREFIX Semester prefix, eg fa23. -t, --template=TEMPLATE The repo name within the org to use as template - -f, --filename=FILENAME The base filename for repos. - -s, --studentteam=STUDENTTEAM The team name of all the students team - -g, --gsiteam=GSITEAM The team name of all the staff team + -a, --assignment=ASSIGNMENT The assignment name. eg chip-10.5 + -s, --studentteam=STUDENTTEAM The team name for students' team + -g, --gsiteam=GSITEAM The team name of staff team 'remove_indiv_repos' -Delete all repos whose names are formed like "PREFIX-[username]-FILENAME". +Delete all repos whose names are formed like "[PREFIX]-[username]-[ASSIGNMENT]". Required options: -o, --orgname=ORGNAME The name of the org -p, --prefix=PREFIX Semester prefix, eg fa23. - -f, --filename=FILENAME The base filename for repos. + -a, --assignment=ASSIGNMENT The assignment name. eg chip-10.5 -'remove_team_repos' -Delete all repos whose names are formed like "PREFIX-FILENAME-[Team number]". +'remove_group_repos' +Delete all repos whose names are formed like "[PREFIX]-[ASSIGNMENT]-[GROUPNUM]". Required options: -o, --orgname=ORGNAME The name of the org -p, --prefix=PREFIX Semester prefix, eg fa23. - -f, --filename=FILENAME The base filename for repos. + -a, --assignment=ASSIGNMENT The assignment name. eg chip-10.5 'remove_teams' -Remove all students and child teams in STUDENTTEAM from the org. +Remove all students and groups in STUDENTTEAM from the org. Remove STUDENTTEAM as well. Required options: -o, --orgname=ORGNAME The name of the org -p, --prefix=PREFIX Semester prefix, eg fa23. - -s, --studentteam=STUDENTTEAM The team name of all the students team + -s, --studentteam=STUDENTTEAM The team name for students' team -'remove_team_repo_access' -Remove students access to CHIP 10.5 repos that are formed like "PREFIX-FILENAME-[Team number]". +'remove_group_repos_access' +Remove group access to group repos that are formed like "[PREFIX]-[ASSIGNMENT]-[GROUPNUM]". Required options: -o, --orgname=ORGNAME The name of the org -p, --prefix=PREFIX Semester prefix, eg fa23. - -f, --filename=FILENAME The base filename for repos. + -a, --assignment=ASSIGNMENT The assignment name. eg chip-10.5 ``` -This script creates a team that include all stundents in the same semester, eg fa23. -Then creates different child teams for them for the chip10.5. At a minimum, -you need a CSV file listing all enrolled students with columns 'Username' and 'Team'. -The values in the Team columns should be nonnegative integers identifying teams. +This script offer commands to manage student teams and groups, repositories. Org setting: Assume Base permissions is no permission, each students can only access their team repos. **Assumption:** You have a GITHUB ORG API key. -### Create a team under the org that has all students in the same semester and send invitation +### Create a student team under the org and send invitations to students -**Use case:** Create a team called STUDENTTEAM, and send invitations -to the students in csv file. If STUDENTTEAM exists, delete STUDENTTEAM and -create a new one. (Team repos still exist) -For all students NOT in STUDENTTEAM, add/invite into STUDENTTEAM. -Team column is not needed for this. +**Use case:** Create a team called STUDENTTEAM if it don't exist, and send invitations +to the students in csv file. For students NOT in STUDENTTEAM, invite them to STUDENTTEAM. -### Create child teams for students for CHIP 10.5 +### Create groups for students for CHIP 10.5 -**Use case:** Create child teams for students for CHIP 10.5. +**Use case:** Create groups for students for CHIP 10.5. ### Create CHIPS repo for each stedent **Use case:** Create CHIP repos for each student in STUDENTTEAM. -The repos' names are formed like "fa23-[username]-FILENAME". There will -be only one collabrator. Add all repos to the gsiteam with admin permission. +The repos' names are formed like "[PREFIX]-[username]-[ASSIGNMENT]". There will +be only one collabrator. Give GSITEAM admin access to the created repos. -### Create 10.5 repos for each child team +### Create 10.5 repos for each group -**Use case:** Each team (as identified by Team column in CSV file) -gets a repo for chip 10.5. Repos' names are formed like "PREFIX-FILENAME-TEAMNUM" -Every one in the child team has write access to the repo. -Add all repos to the gsiteam with admin permission. +**Use case:** Each group (as identified by "Group" column in CSV file) +gets a repo for chip 10.5. Repos' names are formed like "[PREFIX]-[ASSIGNMENT]-[TEAMNUM]" +Every one in the same group has write access to the repo. +Give GSITEAM admin access to the created repos. ### Remove the CHIPS repos -**Use case:** Delete all repos whose names are formed like "PREFIX-[username]-FILENAME". +**Use case:** Delete all repos whose names are formed like "[PREFIX]-[username]-[ASSIGNMENT]". ### Remove CHIP 10.5 repos -**Use case:** Delete all repos whose names are formed like "PREFIX-FILENAME-[Team number]". +**Use case:** Delete all repos whose names are formed like "[PREFIX]-[ASSIGNMENT]-[GROUPNUM]". ### Remove all team members from students teams, then delete all teams -**Use case:** Remove all students and subteams in STUDENTTEAM from the org. +**Use case:** Remove all students and groups in STUDENTTEAM from the org. ### Remove student access to all the chip 10.5 repos -**Use case:** Only remove the access of students teams, repos still can be accessed by +**Use case:** Only remove the access of students groups, repos can still be accessed by gsi team. diff --git a/scripts/github-repos/github-repos.rb b/scripts/github-repos/github-repos.rb index 69e8551..e9ce989 100755 --- a/scripts/github-repos/github-repos.rb +++ b/scripts/github-repos/github-repos.rb @@ -10,45 +10,46 @@ def main() puts "Script start." org = OrgManager.new $opts = OptionParser.new do |opt| - opt.banner = "Usage: #{__FILE__} [required options] [invite|create_teams|indiv_repos|team_repos| - remove_indiv_repos|remove_team_repos|remove_teams|remove_team_repo_access] [optional options] + opt.banner = "Usage: #{__FILE__} [required options] [invite|create_teams|indiv_repos|group_repos| + remove_indiv_repos|remove_group_repos|remove_teams|remove_group_repos_access] [optional options] GITHUB_ORG_API_KEY for the org must be set as an environment variable." opt.separator "" opt.separator "Commands:" - opt.separator " invite: Create a team called STUDENTTEAM under the org..." - opt.separator " If STUDENTTEAM is already exist, the script will resent invitation to students." - opt.separator " (Temporary for special situations in Fa23)" - opt.separator " If PREFIX is provided, it will assume the csv file contains \"Team\" column, and create child teams under the STUDENTTEAM, and invites them to child teams.\n" - opt.separator " create_teams: Assuming students are in STUDENTTEAM, create child teams for students for CHIP 10.5 and add them to the child team. \n" - opt.separator " indiv_repos: Create CHIPS repo for each stedent in STUDENTTEAM. Repos' names are form like \"PREFIX-[username]-FILENAME\"\n" - opt.separator " team_repos: Create 10.5 repos for each child team. Repos' names are form like \"PREFIX-FILENAME-[Team number]\"" - opt.separator " Make sure child teams are formed before running this command.\n" - opt.separator " remove_indiv_repos: Delete all repos whose names are formed like \"PREFIX-[username]-FILENAME\".\n" - opt.separator " remove_team_repos: Delete all repos whose names are formed like \"PREFIX-FILENAME-[Team number]\".\n" - opt.separator " remove_teams: Remove all students and child teams in STUDENTTEAM from the org. Remove STUDENTTEAM as well.\n" - opt.separator " remove_team_repo_access: Remove students access to CHIP 10.5 repos that are formed like \"PREFIX-FILENAME-[Team number]\".\n" + opt.separator " invite: Create a team called STUDENTTEAM under the org and send invitations to students to the team. + If STUDENTTEAM already exists, the script will resend invitation to students. + If PREFIX is provided, it will assume the csv file contains \"Group\" column, + and create groups (child teams) under the STUDENTTEAM, and invites them to groups.(Temporary for special situations in Fa23)\n" + opt.separator " create_groups: Assuming students are in STUDENTTEAM, create groups for students for CHIP 10.5 and add them to corresponding groups.\n" + opt.separator " indiv_repos: Create CHIPS repo for each stedent in STUDENTTEAM. Repos' names are form like \"[PREFIX]-[username]-[ASSIGNMENT]\" + [username] stands for the GitHub username.\n" + opt.separator " group_repos: Create 10.5 repos for each child team. Repos' names are form like \"[PREFIX]-[ASSIGNMENT]-[GROUPNUM]\" + Make sure groups are formed before running this command.\n" + opt.separator " remove_indiv_repos: Delete all repos whose names are formed like \"[PREFIX]-[username]-[ASSIGNMENT]\".\n" + opt.separator " remove_group_repos: Delete all repos whose names are formed like \"[PREFIX]-[ASSIGNMENT]-[GROUPNUM]\".\n" + opt.separator " remove_teams: Remove all students and groups in STUDENTTEAM from the org. Remove STUDENTTEAM as well.\n" + opt.separator " remove_group_repos_access: Remove students access to CHIP 10.5 repos that are formed like \"[PREFIX]-[ASSIGNMENT]-[GROUPNUM]\".\n" opt.separator "Options:" - opt.on('-cCSVFILE', '--csv=CSVFILE', 'CSV file containing at leaset "Username" named columns') do |csv| + opt.on('-cCSVFILE', '--csv=CSVFILE', 'CSV file containing at least "Username" named columns') do |csv| org.csv = csv end opt.on('-oORGNAME', '--orgname=ORGNAME', 'The name of the org') do |orgname| org.orgname = orgname end - opt.on('-sSTUDENTTEAM', '--studentteam=STUDENTTEAM', 'The team name of all the students team') do |studentteam| + opt.on('-sSTUDENTTEAM', '--studentteam=STUDENTTEAM', 'The team name for students\' team') do |studentteam| org.parentteam = studentteam end opt.on('-pPREFIX', '--prefix=PREFIX', 'Semester prefix, eg fa23.') do |pfx| org.semester = pfx end - opt.on('-fFILENAME', '--filename=FILENAME', 'The base filename for repos') do |filename| - org.base_filename = filename + opt.on('-aASSIGNMENT', '--assignment=ASSIGNMENT', 'The assignment name. eg chip-10.5') do |filename| + org.assignment = assignment end opt.on('-tTEMPLATE', '--template=TEMPLATE', 'The repo name within the org to use as template') do |template| org.template = template end - opt.on('-gGSITEAM', '--gsiteam=GSITEAM', 'The team name of all the staff team') do |gsiteam| + opt.on('-gGSITEAM', '--gsiteam=GSITEAM', 'The team name of staff team') do |gsiteam| org.gsiteam = gsiteam end end @@ -58,11 +59,11 @@ def main() when 'invite' then org.invite when 'create_teams' then org.create_teams when 'indiv_repos' then org.indiv_repos - when 'team_repos' then org.team_repos + when 'group_repos' then org.group_repos when 'remove_indiv_repos' then org.remove_indiv_repos - when 'remove_team_repos' then org.remove_team_repos + when 'remove_group_repos' then org.remove_group_repos when 'remove_teams' then org.remove_teams - when 'remove_team_repo_access' then org.remove_team_repo_access + when 'remove_group_repos_access' then org.remove_group_repos_access else org.print_error end puts "Run successfully." @@ -70,11 +71,11 @@ def main() end class OrgManager - attr_accessor :orgname, :base_filename, :semester, :template, :parentteam, :gsiteam, :csv, :users + attr_accessor :orgname, :assignment, :semester, :template, :parentteam, :gsiteam, :csv, :users def initialize @orgname = nil - @base_filename = nil + @assignment = nil @semester = nil @template = nil @csv = nil @@ -146,11 +147,11 @@ def create_teams_valid? end def repos_valid? - !(@orgname.nil? || @parentteam.nil? || @semester.nil? || @template.nil? || !gsiteam_valid? || @base_filename.nil?) + !(@orgname.nil? || @parentteam.nil? || @semester.nil? || @template.nil? || !gsiteam_valid? || @assignment.nil?) end def remove_valid? - !(@orgname.nil? || @semester.nil? || @base_filename.nil?) + !(@orgname.nil? || @semester.nil? || @assignment.nil?) end def remove_teams_valid? @@ -283,7 +284,7 @@ def indiv_repos self_user_name = @client.user.login @client.team_members(parentteam_id).each do |mem| if self_user_name != mem.login - new_repo_name = %Q{#{@semester}-#{mem.login}-#{@base_filename}} + new_repo_name = %Q{#{@semester}-#{mem.login}-#{@assignment}} if !@client.repository? %Q{#{@orgname}/#{new_repo_name}} begin new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, @@ -302,7 +303,7 @@ def indiv_repos @client.child_teams(parentteam_id).each do |childteam| @client.team_members(childteam.id).each do |mem| if !mem.login.nil? && self_user_name != mem.login - new_repo_name = %Q{#{@semester}-#{mem.login}-#{@base_filename}} + new_repo_name = %Q{#{@semester}-#{mem.login}-#{@assignment}} if !@client.repository? %Q{#{@orgname}/#{new_repo_name}} begin new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, @@ -318,14 +319,14 @@ def indiv_repos end end - def team_repos + def group_repos print_error "orgname, student team name, base filename, template repo name, semester prefix, and gsi team name needed." unless repos_valid? child_teams = @client.child_teams(@client.team_by_name(@orgname, to_slug(@parentteam)).id) gsiteam_id = @client.team_by_name(@orgname, to_slug(@gsiteam))['id'] child_teams.each do |team| team_num = team.slug.match(/-(\d+)$/)[1] - new_repo_name = %Q{#{@semester}-#{@base_filename}-#{team_num}} + new_repo_name = %Q{#{@semester}-#{@assignment}-#{team_num}} if !@client.repository? %Q{#{@orgname}/#{new_repo_name}} begin new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, @@ -345,18 +346,18 @@ def remove_indiv_repos repos = @client.org_repos(@orgname, {:type => 'private'}) repos.each do |repo| - if repo.name =~ /^#{Regexp.escape(@semester)}-(.*)-#{Regexp.escape(@base_filename)}$/ + if repo.name =~ /^#{Regexp.escape(@semester)}-(.*)-#{Regexp.escape(@assignment)}$/ @client.delete_repository(repo.full_name) end end end - def remove_team_repos + def remove_group_repos print_error "org name, base filename, semester prefix are needed." unless remove_valid? repos = @client.org_repos(@orgname, {:type => 'private'}) repos.each do |repo| - if repo.name =~ /^#{Regexp.escape(@semester)}-#{Regexp.escape(@base_filename)}-\d+$/ + if repo.name =~ /^#{Regexp.escape(@semester)}-#{Regexp.escape(@assignment)}-\d+$/ @client.delete_repository(repo.full_name) end end @@ -392,12 +393,12 @@ def remove_teams @client.delete_team parentteam_id end - def remove_team_repo_access + def remove_group_repos_access print_error "org name, base filename, semester prefix are needed." unless remove_valid? repos = @client.org_repos(@orgname, {:type => 'private'}) repos.each do |repo| - match = repo.name.match(/^#{Regexp.escape(@semester)}-#{Regexp.escape(@base_filename)}-(\d+)$/) + match = repo.name.match(/^#{Regexp.escape(@semester)}-#{Regexp.escape(@assignment)}-(\d+)$/) if match team_num = match[1] begin From d4c05a1a1411e5d28a206ebeaaab063136a57b40 Mon Sep 17 00:00:00 2001 From: a544266477 Date: Thu, 21 Sep 2023 21:09:18 -0700 Subject: [PATCH 11/22] update the readme file and corresponding opt descriptions. --- scripts/github-repos/README.md | 2 +- scripts/github-repos/github-repos.rb | 37 ++++++++++++++-------------- scripts/github-repos/team-test.csv | 3 +++ 3 files changed, 22 insertions(+), 20 deletions(-) create mode 100644 scripts/github-repos/team-test.csv diff --git a/scripts/github-repos/README.md b/scripts/github-repos/README.md index 750d50c..b886a2d 100644 --- a/scripts/github-repos/README.md +++ b/scripts/github-repos/README.md @@ -104,7 +104,7 @@ to the students in csv file. For students NOT in STUDENTTEAM, invite them to STU **Use case:** Create groups for students for CHIP 10.5. -### Create CHIPS repo for each stedent +### Create CHIPS repo for each student **Use case:** Create CHIP repos for each student in STUDENTTEAM. The repos' names are formed like "[PREFIX]-[username]-[ASSIGNMENT]". There will diff --git a/scripts/github-repos/github-repos.rb b/scripts/github-repos/github-repos.rb index e9ce989..72db14f 100755 --- a/scripts/github-repos/github-repos.rb +++ b/scripts/github-repos/github-repos.rb @@ -4,7 +4,7 @@ require 'octokit' require 'csv' -ENV['GITHUB_ORG_API_KEY'] = "" +ENV['GITHUB_ORG_API_KEY'] = "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" def main() puts "Script start." @@ -43,7 +43,7 @@ def main() opt.on('-pPREFIX', '--prefix=PREFIX', 'Semester prefix, eg fa23.') do |pfx| org.semester = pfx end - opt.on('-aASSIGNMENT', '--assignment=ASSIGNMENT', 'The assignment name. eg chip-10.5') do |filename| + opt.on('-aASSIGNMENT', '--assignment=ASSIGNMENT', 'The assignment name. eg chip-10.5') do |assignment| org.assignment = assignment end opt.on('-tTEMPLATE', '--template=TEMPLATE', 'The repo name within the org to use as template') do |template| @@ -55,17 +55,7 @@ def main() end $opts.parse! command = ARGV.pop - case command - when 'invite' then org.invite - when 'create_teams' then org.create_teams - when 'indiv_repos' then org.indiv_repos - when 'group_repos' then org.group_repos - when 'remove_indiv_repos' then org.remove_indiv_repos - when 'remove_group_repos' then org.remove_group_repos - when 'remove_teams' then org.remove_teams - when 'remove_group_repos_access' then org.remove_group_repos_access - else org.print_error - end + org.invoke_command command puts "Run successfully." puts "Script ends." end @@ -97,14 +87,14 @@ def read_users_from csv end end - def read_teams_and_users_from csv + def read_groups_and_users_from csv data = CSV.parse(IO.read(csv), headers: true) hash = data.first.to_h - print_error "Need at least 'Team' (int) and 'Username' (str) columns in #{csv}" unless - hash.has_key?('Team') && hash.has_key?('Username') + print_error "Need at least 'Group' (int) and 'Username' (str) columns in #{csv}" unless + hash.has_key?('Group') && hash.has_key?('Username') data.each do |row| username = row['Username'] - @childteams[row['Team']] << username + @childteams[row['Group']] << username end end @@ -133,7 +123,7 @@ def invite_valid? if @semester.nil? read_users_from @csv else - read_teams_and_users_from @csv + read_groups_and_users_from @csv end true end @@ -142,7 +132,7 @@ def create_teams_valid? if @orgname.nil? || @orgname.empty? || @csv.nil? || @parentteam.nil? || @parentteam.empty? || @semester.nil? return false end - read_teams_and_users_from @csv + read_groups_and_users_from @csv true end @@ -172,6 +162,15 @@ def gsiteam_valid? public + def invoke_command command + command = command.to_sym + if respond_to? command + send(command) + else + print_error + end + end + def print_error(msg=nil) STDERR.puts "Error: #{msg}" if msg STDERR.puts $opts diff --git a/scripts/github-repos/team-test.csv b/scripts/github-repos/team-test.csv new file mode 100644 index 0000000..3472ea9 --- /dev/null +++ b/scripts/github-repos/team-test.csv @@ -0,0 +1,3 @@ +Group,Username,Name +1,KCsama,Amy +2,Gangster-Who,Gang \ No newline at end of file From d3ccfbbfb21b6ffd0c6b046d9cf0387f0cbec9ef Mon Sep 17 00:00:00 2001 From: a544266477 Date: Thu, 21 Sep 2023 21:26:13 -0700 Subject: [PATCH 12/22] fix creating repos --- scripts/github-repos/github-repos.rb | 58 ++++++++++++++-------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/scripts/github-repos/github-repos.rb b/scripts/github-repos/github-repos.rb index 72db14f..2479ec6 100755 --- a/scripts/github-repos/github-repos.rb +++ b/scripts/github-repos/github-repos.rb @@ -4,7 +4,7 @@ require 'octokit' require 'csv' -ENV['GITHUB_ORG_API_KEY'] = "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" +ENV['GITHUB_ORG_API_KEY'] = "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" def main() puts "Script start." @@ -284,16 +284,16 @@ def indiv_repos @client.team_members(parentteam_id).each do |mem| if self_user_name != mem.login new_repo_name = %Q{#{@semester}-#{mem.login}-#{@assignment}} - if !@client.repository? %Q{#{@orgname}/#{new_repo_name}} - begin - new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, - {owner: @orgname, private: true}) - rescue Octokit::NotFound - print_error "Template not found." - end - @client.add_collab(new_repo['full_name'], mem.login) - @client.add_team_repository(gsiteam_id, new_repo['full_name'], {permission: 'admin'}) + begin + new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, + {owner: @orgname, private: true}) + rescue Octokit::NotFound + print_error "Template not found." + rescue Octokit::UnprocessableEntity + new_repo = @client.repo(%Q{#{orgname}/#{new_repo_name}}) end + @client.add_collab(new_repo['full_name'], mem.login) + @client.add_team_repository(gsiteam_id, new_repo['full_name'], {permission: 'admin'}) end end @@ -303,16 +303,18 @@ def indiv_repos @client.team_members(childteam.id).each do |mem| if !mem.login.nil? && self_user_name != mem.login new_repo_name = %Q{#{@semester}-#{mem.login}-#{@assignment}} - if !@client.repository? %Q{#{@orgname}/#{new_repo_name}} - begin - new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, - {owner: @orgname, private: true}) - rescue Octokit::NotFound - print_error "Template not found." - end - @client.add_collab(new_repo['full_name'], mem.login) - @client.add_team_repository(gsiteam_id, new_repo['full_name'], {permission: 'admin'}) + + begin + new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, + {owner: @orgname, private: true}) + rescue Octokit::NotFound + print_error "Template not found." + rescue Octokit::UnprocessableEntity + new_repo = @client.repo(%Q{#{orgname}/#{new_repo_name}}) end + @client.add_collab(new_repo['full_name'], mem.login) + @client.add_team_repository(gsiteam_id, new_repo['full_name'], {permission: 'admin'}) + end end end @@ -326,16 +328,16 @@ def group_repos child_teams.each do |team| team_num = team.slug.match(/-(\d+)$/)[1] new_repo_name = %Q{#{@semester}-#{@assignment}-#{team_num}} - if !@client.repository? %Q{#{@orgname}/#{new_repo_name}} - begin - new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, - {owner: @orgname, private: true}) - rescue Octokit::NotFound - print_error "Template not found." - end - @client.add_team_repository(team.id, new_repo['full_name'], {permission: 'push'}) - @client.add_team_repository(gsiteam_id, new_repo['full_name'], {permission: 'admin'}) + begin + new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, + {owner: @orgname, private: true}) + rescue Octokit::NotFound + print_error "Template not found." + rescue Octokit::UnprocessableEntity + new_repo = @client.repo(%Q{#{orgname}/#{new_repo_name}}) end + @client.add_team_repository(team.id, new_repo['full_name'], {permission: 'push'}) + @client.add_team_repository(gsiteam_id, new_repo['full_name'], {permission: 'admin'}) end end From 70eaf2c268e1b32e0e297e1414dd511081e05626 Mon Sep 17 00:00:00 2001 From: a544266477 Date: Thu, 21 Sep 2023 23:39:15 -0700 Subject: [PATCH 13/22] Turn on the auto pagination --- scripts/github-repos/github-repos.rb | 37 ++++++++++++++-------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/scripts/github-repos/github-repos.rb b/scripts/github-repos/github-repos.rb index 2479ec6..5b846a8 100755 --- a/scripts/github-repos/github-repos.rb +++ b/scripts/github-repos/github-repos.rb @@ -4,7 +4,7 @@ require 'octokit' require 'csv' -ENV['GITHUB_ORG_API_KEY'] = "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" +ENV['GITHUB_ORG_API_KEY'] = "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" def main() puts "Script start." @@ -73,6 +73,7 @@ def initialize @childteams = Hash.new { |hash, key| hash[key] = [] } # teamID => [email1, email2, ...] print_error("GITHUB_ORG_API_KEY not defined in environment") unless (@key = ENV['GITHUB_ORG_API_KEY']) @client = Octokit::Client.new(access_token: @key, scopes: ['user']) + @client.auto_paginate = true # auto pagination on end private @@ -206,7 +207,7 @@ def invite parentteam_id = @client.create_team(@orgname, {name: @parentteam, privacy: 'closed'})['id'] end - # create child teams and invite students to the child teams + # create groups and invite students to the groups # this is for Fa23, will delete in the future. @childteams.each_key do |team| childteam_name = %Q{#{@semester}-#{team}} @@ -243,7 +244,7 @@ def create_teams failed_username = [] - # create child teams and add students to their child teams + # create groups and add students to their groups @childteams.each_key do |team| childteam_name = %Q{#{@semester}-#{team}} begin @@ -262,15 +263,15 @@ def create_teams end end - # print the usernames that is failed to add to child team + # print the usernames that is failed to add to groups if !failed_username.empty? - puts 'Failed to add these usernames to child team:' + puts 'Failed to add these usernames to groups:' puts failed_username end end def indiv_repos - print_error "orgname, student team name, base filename, template repo name, semester prefix, and gsi team name needed." unless repos_valid? + print_error "orgname, student team name, assignment name, template repo name, semester prefix, and gsi team name needed." unless repos_valid? # Looking for the STUDENTTEAM in the org, see if it is exist. begin @@ -290,7 +291,7 @@ def indiv_repos rescue Octokit::NotFound print_error "Template not found." rescue Octokit::UnprocessableEntity - new_repo = @client.repo(%Q{#{orgname}/#{new_repo_name}}) + new_repo = @client.repo(%Q{#{@orgname}/#{new_repo_name}}) end @client.add_collab(new_repo['full_name'], mem.login) @client.add_team_repository(gsiteam_id, new_repo['full_name'], {permission: 'admin'}) @@ -298,7 +299,7 @@ def indiv_repos end - # can remove the below loop because in the future, there should not be child teams while we assigning the indiv repos. + # can remove the below loop because in the future, there should not be groups while we assigning the indiv repos. @client.child_teams(parentteam_id).each do |childteam| @client.team_members(childteam.id).each do |mem| if !mem.login.nil? && self_user_name != mem.login @@ -310,7 +311,7 @@ def indiv_repos rescue Octokit::NotFound print_error "Template not found." rescue Octokit::UnprocessableEntity - new_repo = @client.repo(%Q{#{orgname}/#{new_repo_name}}) + new_repo = @client.repo(%Q{#{@orgname}/#{new_repo_name}}) end @client.add_collab(new_repo['full_name'], mem.login) @client.add_team_repository(gsiteam_id, new_repo['full_name'], {permission: 'admin'}) @@ -321,20 +322,20 @@ def indiv_repos end def group_repos - print_error "orgname, student team name, base filename, template repo name, semester prefix, and gsi team name needed." unless repos_valid? + print_error "orgname, student team name, assignment name, template repo name, semester prefix, and gsi team name needed." unless repos_valid? child_teams = @client.child_teams(@client.team_by_name(@orgname, to_slug(@parentteam)).id) gsiteam_id = @client.team_by_name(@orgname, to_slug(@gsiteam))['id'] child_teams.each do |team| - team_num = team.slug.match(/-(\d+)$/)[1] - new_repo_name = %Q{#{@semester}-#{@assignment}-#{team_num}} + group_num = team.slug.match(/-(\d+)$/)[1] + new_repo_name = %Q{#{@semester}-#{@assignment}-#{group_num}} begin new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, {owner: @orgname, private: true}) rescue Octokit::NotFound print_error "Template not found." rescue Octokit::UnprocessableEntity - new_repo = @client.repo(%Q{#{orgname}/#{new_repo_name}}) + new_repo = @client.repo(%Q{#{@orgname}/#{new_repo_name}}) end @client.add_team_repository(team.id, new_repo['full_name'], {permission: 'push'}) @client.add_team_repository(gsiteam_id, new_repo['full_name'], {permission: 'admin'}) @@ -343,7 +344,7 @@ def group_repos end def remove_indiv_repos - print_error "org name, base filename, semester prefix are needed." unless remove_valid? + print_error "org name, assignment name, semester prefix are needed." unless remove_valid? repos = @client.org_repos(@orgname, {:type => 'private'}) repos.each do |repo| @@ -354,7 +355,7 @@ def remove_indiv_repos end def remove_group_repos - print_error "org name, base filename, semester prefix are needed." unless remove_valid? + print_error "org name, assignment name, semester prefix are needed." unless remove_valid? repos = @client.org_repos(@orgname, {:type => 'private'}) repos.each do |repo| @@ -395,15 +396,15 @@ def remove_teams end def remove_group_repos_access - print_error "org name, base filename, semester prefix are needed." unless remove_valid? + print_error "org name, assignment name, semester prefix are needed." unless remove_valid? repos = @client.org_repos(@orgname, {:type => 'private'}) repos.each do |repo| match = repo.name.match(/^#{Regexp.escape(@semester)}-#{Regexp.escape(@assignment)}-(\d+)$/) if match - team_num = match[1] + group_num = match[1] begin - childteam_id = @client.team_by_name(@orgname, to_slug(%Q{#{@semester}-#{team_num}}))['id'] # eg slug fa23-1 + childteam_id = @client.team_by_name(@orgname, to_slug(%Q{#{@semester}-#{group_num}}))['id'] # eg slug fa23-1 rescue Octokit::NotFound next end From 6d36cdc61b48fc621987217eb7635b81120b4939 Mon Sep 17 00:00:00 2001 From: a544266477 Date: Fri, 22 Sep 2023 09:10:12 -0700 Subject: [PATCH 14/22] add showing progress on looping --- scripts/github-repos/Gemfile | 1 + scripts/github-repos/Gemfile.lock | 2 ++ scripts/github-repos/github-repos.rb | 31 ++++++++++++++-------------- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/scripts/github-repos/Gemfile b/scripts/github-repos/Gemfile index 97ed9fa..92aa6ae 100644 --- a/scripts/github-repos/Gemfile +++ b/scripts/github-repos/Gemfile @@ -4,3 +4,4 @@ ruby '< 3.0' gem 'octokit' gem 'csv' +gem 'tqdm' diff --git a/scripts/github-repos/Gemfile.lock b/scripts/github-repos/Gemfile.lock index 03a79b7..8a5cbcd 100644 --- a/scripts/github-repos/Gemfile.lock +++ b/scripts/github-repos/Gemfile.lock @@ -18,6 +18,7 @@ GEM sawyer (0.9.2) addressable (>= 2.3.5) faraday (>= 0.17.3, < 3) + tqdm (0.4.1) PLATFORMS arm64-darwin-22 @@ -25,6 +26,7 @@ PLATFORMS DEPENDENCIES csv octokit + tqdm RUBY VERSION ruby 2.6.10p210 diff --git a/scripts/github-repos/github-repos.rb b/scripts/github-repos/github-repos.rb index 5b846a8..2ec97ed 100755 --- a/scripts/github-repos/github-repos.rb +++ b/scripts/github-repos/github-repos.rb @@ -3,8 +3,9 @@ require 'optparse' require 'octokit' require 'csv' +require 'tqdm' -ENV['GITHUB_ORG_API_KEY'] = "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" +ENV['GITHUB_ORG_API_KEY'] = "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" def main() puts "Script start." @@ -190,7 +191,7 @@ def invite parentteam_id = @client.create_team(@orgname, {name: @parentteam, privacy: 'closed'})['id'] end - @users.each do |username| + @users.tqdm.each do |username| begin @client.put(%Q{/orgs/#{@orgname}/teams/#{to_slug(@parentteam)}/memberships/#{username}}) rescue Octokit::UnprocessableEntity @@ -208,8 +209,8 @@ def invite end # create groups and invite students to the groups - # this is for Fa23, will delete in the future. - @childteams.each_key do |team| + # this is for Fa23, delete in the future. + @childteams.tqdm.each_key do |team| childteam_name = %Q{#{@semester}-#{team}} begin childteam = @client.team_by_name(@orgname, to_slug(childteam_name)) @@ -217,7 +218,7 @@ def invite childteam = @client.create_team(@orgname, {name: %Q{#{@semester}-#{team}}, parent_team_id: parentteam_id}) end childteam_id = childteam['id'] - @childteams[team].each do |member| + @childteams[team].tqdm.each do |member| # Try no check invitations before create one. # send invitation begin @@ -245,7 +246,7 @@ def create_teams failed_username = [] # create groups and add students to their groups - @childteams.each_key do |team| + @childteams.tqdm.each_key do |team| childteam_name = %Q{#{@semester}-#{team}} begin childteam = @client.team_by_name(@orgname, to_slug(childteam_name)) @@ -253,7 +254,7 @@ def create_teams childteam = @client.create_team(@orgname, {name: %Q{#{@semester}-#{team}}, parent_team_id: parentteam_id}) end childteam_id = childteam['id'] - @childteams[team].each do |username| + @childteams[team].tqdm.each do |username| begin @client.add_team_member(childteam_id, username) rescue Octokit::UnprocessableEntity @@ -282,7 +283,7 @@ def indiv_repos gsiteam_id = @client.team_by_name(@orgname, to_slug(@gsiteam)).id self_user_name = @client.user.login - @client.team_members(parentteam_id).each do |mem| + @client.team_members(parentteam_id).tqdm.each do |mem| if self_user_name != mem.login new_repo_name = %Q{#{@semester}-#{mem.login}-#{@assignment}} begin @@ -300,7 +301,7 @@ def indiv_repos # can remove the below loop because in the future, there should not be groups while we assigning the indiv repos. - @client.child_teams(parentteam_id).each do |childteam| + @client.child_teams(parentteam_id).tqdm.each do |childteam| @client.team_members(childteam.id).each do |mem| if !mem.login.nil? && self_user_name != mem.login new_repo_name = %Q{#{@semester}-#{mem.login}-#{@assignment}} @@ -326,7 +327,7 @@ def group_repos child_teams = @client.child_teams(@client.team_by_name(@orgname, to_slug(@parentteam)).id) gsiteam_id = @client.team_by_name(@orgname, to_slug(@gsiteam))['id'] - child_teams.each do |team| + child_teams.tqdm.each do |team| group_num = team.slug.match(/-(\d+)$/)[1] new_repo_name = %Q{#{@semester}-#{@assignment}-#{group_num}} begin @@ -347,7 +348,7 @@ def remove_indiv_repos print_error "org name, assignment name, semester prefix are needed." unless remove_valid? repos = @client.org_repos(@orgname, {:type => 'private'}) - repos.each do |repo| + repos.tqdm.each do |repo| if repo.name =~ /^#{Regexp.escape(@semester)}-(.*)-#{Regexp.escape(@assignment)}$/ @client.delete_repository(repo.full_name) end @@ -358,7 +359,7 @@ def remove_group_repos print_error "org name, assignment name, semester prefix are needed." unless remove_valid? repos = @client.org_repos(@orgname, {:type => 'private'}) - repos.each do |repo| + repos.tqdm.each do |repo| if repo.name =~ /^#{Regexp.escape(@semester)}-#{Regexp.escape(@assignment)}-\d+$/ @client.delete_repository(repo.full_name) end @@ -377,13 +378,13 @@ def remove_teams # Remove all members in the students team from org self_user_name = @client.user.login - @client.team_members(parentteam_id).each do |mem| + @client.team_members(parentteam_id).tqdm.each do |mem| if !mem.login.nil? && mem.login != self_user_name @client.remove_organization_member(@orgname, mem.login) end end - @client.child_teams(parentteam_id).each do |childteam| + @client.child_teams(parentteam_id).tqdm.each do |childteam| @client.team_members(childteam.id).each do |mem| if !mem.login.nil? && mem.login != self_user_name @client.remove_organization_member(@orgname, mem.login) @@ -399,7 +400,7 @@ def remove_group_repos_access print_error "org name, assignment name, semester prefix are needed." unless remove_valid? repos = @client.org_repos(@orgname, {:type => 'private'}) - repos.each do |repo| + repos.tqdm.each do |repo| match = repo.name.match(/^#{Regexp.escape(@semester)}-#{Regexp.escape(@assignment)}-(\d+)$/) if match group_num = match[1] From 96ba81214aa6422b04fcf951b9a43c9fcf37a35c Mon Sep 17 00:00:00 2001 From: a544266477 Date: Fri, 22 Sep 2023 12:31:39 -0700 Subject: [PATCH 15/22] Update the temporary function. I decide to keep the temporary option which creates groups in invite command. --- scripts/github-repos/README.md | 2 +- scripts/github-repos/github-repos.rb | 38 +++++++++++----------------- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/scripts/github-repos/README.md b/scripts/github-repos/README.md index b886a2d..6f218eb 100644 --- a/scripts/github-repos/README.md +++ b/scripts/github-repos/README.md @@ -12,7 +12,7 @@ Create a team called STUDENTTEAM under the org and send invitations to students If STUDENTTEAM already exists, the script will resend invitation to students. If PREFIX is provided, it will assume the csv file contains "Group" column, and create groups(child teams) under -the STUDENTTEAM, and invites them to groups. (Temporary for special situations in Fa23) +the STUDENTTEAM, and invites them to groups. Required options: -c, --csv=CSVFILE CSV file containing at least "Username" named columns diff --git a/scripts/github-repos/github-repos.rb b/scripts/github-repos/github-repos.rb index 2ec97ed..dbe140b 100755 --- a/scripts/github-repos/github-repos.rb +++ b/scripts/github-repos/github-repos.rb @@ -20,7 +20,7 @@ def main() opt.separator " invite: Create a team called STUDENTTEAM under the org and send invitations to students to the team. If STUDENTTEAM already exists, the script will resend invitation to students. If PREFIX is provided, it will assume the csv file contains \"Group\" column, - and create groups (child teams) under the STUDENTTEAM, and invites them to groups.(Temporary for special situations in Fa23)\n" + and create groups (child teams) under the STUDENTTEAM, and invites them to groups.\n" opt.separator " create_groups: Assuming students are in STUDENTTEAM, create groups for students for CHIP 10.5 and add them to corresponding groups.\n" opt.separator " indiv_repos: Create CHIPS repo for each stedent in STUDENTTEAM. Repos' names are form like \"[PREFIX]-[username]-[ASSIGNMENT]\" [username] stands for the GitHub username.\n" @@ -181,16 +181,15 @@ def print_error(msg=nil) def invite print_error "csv file, organization name, student team name are needed." unless invite_valid? + # Looking for the STUDENTTEAM in the org, see if it is exist. + begin + parentteam_id = @client.team_by_name(@orgname, to_slug(@parentteam))['id'] + rescue Octokit::NotFound + parentteam_id = @client.create_team(@orgname, {name: @parentteam, privacy: 'closed'})['id'] + end if @semester.nil? # Semester prefix is not provided - # Looking for the STUDENTTEAM in the org, see if it is exist. - begin - parentteam_id = @client.team_by_name(@orgname, to_slug(@parentteam))['id'] - rescue Octokit::NotFound - parentteam_id = @client.create_team(@orgname, {name: @parentteam, privacy: 'closed'})['id'] - end - @users.tqdm.each do |username| begin @client.put(%Q{/orgs/#{@orgname}/teams/#{to_slug(@parentteam)}/memberships/#{username}}) @@ -200,16 +199,7 @@ def invite end else # Semester prefix is provied - - # Looking for the STUDENTTEAM in the org, see if it is exist. - begin - parentteam_id = @client.team_by_name(@orgname, to_slug(@parentteam))['id'] - rescue Octokit::NotFound - parentteam_id = @client.create_team(@orgname, {name: @parentteam, privacy: 'closed'})['id'] - end - # create groups and invite students to the groups - # this is for Fa23, delete in the future. @childteams.tqdm.each_key do |team| childteam_name = %Q{#{@semester}-#{team}} begin @@ -283,6 +273,8 @@ def indiv_repos gsiteam_id = @client.team_by_name(@orgname, to_slug(@gsiteam)).id self_user_name = @client.user.login + + # create individual repos and give each student in the student team write access to the repos @client.team_members(parentteam_id).tqdm.each do |mem| if self_user_name != mem.login new_repo_name = %Q{#{@semester}-#{mem.login}-#{@assignment}} @@ -299,13 +291,11 @@ def indiv_repos end end - - # can remove the below loop because in the future, there should not be groups while we assigning the indiv repos. + # create individual repos and give each student in each group write access to the repos @client.child_teams(parentteam_id).tqdm.each do |childteam| @client.team_members(childteam.id).each do |mem| if !mem.login.nil? && self_user_name != mem.login new_repo_name = %Q{#{@semester}-#{mem.login}-#{@assignment}} - begin new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, {owner: @orgname, private: true}) @@ -316,7 +306,6 @@ def indiv_repos end @client.add_collab(new_repo['full_name'], mem.login) @client.add_team_repository(gsiteam_id, new_repo['full_name'], {permission: 'admin'}) - end end end @@ -327,6 +316,8 @@ def group_repos child_teams = @client.child_teams(@client.team_by_name(@orgname, to_slug(@parentteam)).id) gsiteam_id = @client.team_by_name(@orgname, to_slug(@gsiteam))['id'] + + # create tean repos and give each team a write access child_teams.tqdm.each do |team| group_num = team.slug.match(/-(\d+)$/)[1] new_repo_name = %Q{#{@semester}-#{@assignment}-#{group_num}} @@ -376,7 +367,7 @@ def remove_teams print_error "Please make sure students team is created." end - # Remove all members in the students team from org + # Remove students in the students team from org self_user_name = @client.user.login @client.team_members(parentteam_id).tqdm.each do |mem| if !mem.login.nil? && mem.login != self_user_name @@ -384,6 +375,7 @@ def remove_teams end end + # Remove students in each group @client.child_teams(parentteam_id).tqdm.each do |childteam| @client.team_members(childteam.id).each do |mem| if !mem.login.nil? && mem.login != self_user_name @@ -392,7 +384,7 @@ def remove_teams end end - # Remove students team, child team will be removed as well + # Remove students team, groups will be removed as well @client.delete_team parentteam_id end From 5676c092b54a0a3609b41c866ab674104d64c510 Mon Sep 17 00:00:00 2001 From: a544266477 Date: Fri, 22 Sep 2023 12:53:11 -0700 Subject: [PATCH 16/22] update commands' names and rescue exceptions in invite command --- scripts/github-repos/README.md | 2 +- scripts/github-repos/github-repos.rb | 29 +++++++++++++++------------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/scripts/github-repos/README.md b/scripts/github-repos/README.md index 6f218eb..0ecf84c 100644 --- a/scripts/github-repos/README.md +++ b/scripts/github-repos/README.md @@ -1,7 +1,7 @@ # Bulk creation/deletion of many repos and student teams ``` -Usage: ./github-repos.rb [required options] [invite|create_teams|indiv_repos|group_repos|remove_indiv_repos|remove_group_repos|remove_teams|remove_group_repos_access] [optional options] +Usage: ./github-repos.rb [required options] [invite|create_groups|indiv_repos|group_repos|remove_indiv_repos|remove_group_repos|remove_teams|remove_group_repos_access] [optional options] GITHUB_ORG_API_KEY for the org must be set as an environment variable. diff --git a/scripts/github-repos/github-repos.rb b/scripts/github-repos/github-repos.rb index dbe140b..23f1114 100755 --- a/scripts/github-repos/github-repos.rb +++ b/scripts/github-repos/github-repos.rb @@ -5,14 +5,14 @@ require 'csv' require 'tqdm' -ENV['GITHUB_ORG_API_KEY'] = "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" +ENV['GITHUB_ORG_API_KEY'] = "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" def main() puts "Script start." org = OrgManager.new $opts = OptionParser.new do |opt| - opt.banner = "Usage: #{__FILE__} [required options] [invite|create_teams|indiv_repos|group_repos| - remove_indiv_repos|remove_group_repos|remove_teams|remove_group_repos_access] [optional options] + opt.banner = "Usage: #{__FILE__} [required options] [invite|create_groups|indiv_repos|group_repos| + remove_indiv_repos|remove_group_repos|remove_groups|remove_group_repos_access] [optional options] GITHUB_ORG_API_KEY for the org must be set as an environment variable." opt.separator "" @@ -28,7 +28,7 @@ def main() Make sure groups are formed before running this command.\n" opt.separator " remove_indiv_repos: Delete all repos whose names are formed like \"[PREFIX]-[username]-[ASSIGNMENT]\".\n" opt.separator " remove_group_repos: Delete all repos whose names are formed like \"[PREFIX]-[ASSIGNMENT]-[GROUPNUM]\".\n" - opt.separator " remove_teams: Remove all students and groups in STUDENTTEAM from the org. Remove STUDENTTEAM as well.\n" + opt.separator " remove_groups: Remove all students and groups in STUDENTTEAM from the org. Remove STUDENTTEAM as well.\n" opt.separator " remove_group_repos_access: Remove students access to CHIP 10.5 repos that are formed like \"[PREFIX]-[ASSIGNMENT]-[GROUPNUM]\".\n" opt.separator "Options:" @@ -130,7 +130,7 @@ def invite_valid? true end - def create_teams_valid? + def create_groups_valid? if @orgname.nil? || @orgname.empty? || @csv.nil? || @parentteam.nil? || @parentteam.empty? || @semester.nil? return false end @@ -146,7 +146,7 @@ def remove_valid? !(@orgname.nil? || @semester.nil? || @assignment.nil?) end - def remove_teams_valid? + def remove_groups_valid? !(@orgname.nil? || @semester.nil? || @parentteam.nil?) end @@ -193,8 +193,10 @@ def invite @users.tqdm.each do |username| begin @client.put(%Q{/orgs/#{@orgname}/teams/#{to_slug(@parentteam)}/memberships/#{username}}) + rescue Octokit::Forbidden + print_error "Can't invite or add students if team synchronization is set up" rescue Octokit::UnprocessableEntity - next + print_error "Incorrect GitHub username (An org name provided)" end end else @@ -213,9 +215,10 @@ def invite # send invitation begin @client.put(%Q{/orgs/#{@orgname}/teams/#{childteam.slug}/memberships/#{member}}) + rescue Octokit::Forbidden + print_error "Can't invite or add students if team synchronization is set up" rescue Octokit::UnprocessableEntity - # member is already a part of org - next + print_error "Incorrect GitHub username (An org name provided)" end end end @@ -223,8 +226,8 @@ def invite end end - def create_teams - print_error "csv file, students team name, semester prefix, org name are needed." unless create_teams_valid? + def create_groups + print_error "csv file, students team name, semester prefix, org name are needed." unless create_groups_valid? # Looking for the STUDENTTEAM in the org, see if it is exist. begin @@ -357,8 +360,8 @@ def remove_group_repos end end - def remove_teams - print_error "org name, semester prefix, students team name are needed." unless remove_teams_valid? + def remove_groups + print_error "org name, semester prefix, students team name are needed." unless remove_groups_valid? # Looking for the STUDENTTEAM in the org, see if it is exist. begin From 8ea510765b95a14dd72fa568b49c4118371dfd09 Mon Sep 17 00:00:00 2001 From: a544266477 Date: Mon, 25 Sep 2023 14:01:49 -0700 Subject: [PATCH 17/22] Remove the ENV variable in script --- scripts/github-repos/github-repos.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/github-repos/github-repos.rb b/scripts/github-repos/github-repos.rb index 23f1114..e197992 100755 --- a/scripts/github-repos/github-repos.rb +++ b/scripts/github-repos/github-repos.rb @@ -5,8 +5,6 @@ require 'csv' require 'tqdm' -ENV['GITHUB_ORG_API_KEY'] = "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" - def main() puts "Script start." org = OrgManager.new From 07953994124e9c0e54fddb8471fad51c61e024f3 Mon Sep 17 00:00:00 2001 From: a544266477 Date: Mon, 25 Sep 2023 15:10:57 -0700 Subject: [PATCH 18/22] Check if repo exists before calling clone to avoid rate limit --- scripts/github-repos/github-repos.rb | 42 ++++++++++++++++------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/scripts/github-repos/github-repos.rb b/scripts/github-repos/github-repos.rb index e197992..1da491a 100755 --- a/scripts/github-repos/github-repos.rb +++ b/scripts/github-repos/github-repos.rb @@ -279,12 +279,14 @@ def indiv_repos @client.team_members(parentteam_id).tqdm.each do |mem| if self_user_name != mem.login new_repo_name = %Q{#{@semester}-#{mem.login}-#{@assignment}} - begin - new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, - {owner: @orgname, private: true}) - rescue Octokit::NotFound - print_error "Template not found." - rescue Octokit::UnprocessableEntity + if !@client.repository? %Q{#{@orgname}/#{new_repo_name}} + begin + new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, + {owner: @orgname, private: true}) + rescue Octokit::NotFound + print_error "Template not found." + end + else new_repo = @client.repo(%Q{#{@orgname}/#{new_repo_name}}) end @client.add_collab(new_repo['full_name'], mem.login) @@ -297,12 +299,14 @@ def indiv_repos @client.team_members(childteam.id).each do |mem| if !mem.login.nil? && self_user_name != mem.login new_repo_name = %Q{#{@semester}-#{mem.login}-#{@assignment}} - begin - new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, - {owner: @orgname, private: true}) - rescue Octokit::NotFound - print_error "Template not found." - rescue Octokit::UnprocessableEntity + if !@client.repository? %Q{#{@orgname}/#{new_repo_name}} + begin + new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, + {owner: @orgname, private: true}) + rescue Octokit::NotFound + print_error "Template not found." + end + else new_repo = @client.repo(%Q{#{@orgname}/#{new_repo_name}}) end @client.add_collab(new_repo['full_name'], mem.login) @@ -322,12 +326,14 @@ def group_repos child_teams.tqdm.each do |team| group_num = team.slug.match(/-(\d+)$/)[1] new_repo_name = %Q{#{@semester}-#{@assignment}-#{group_num}} - begin - new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, - {owner: @orgname, private: true}) - rescue Octokit::NotFound - print_error "Template not found." - rescue Octokit::UnprocessableEntity + if !@client.repository? %Q{#{@orgname}/#{new_repo_name}} + begin + new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, + {owner: @orgname, private: true}) + rescue Octokit::NotFound + print_error "Template not found." + end + else new_repo = @client.repo(%Q{#{@orgname}/#{new_repo_name}}) end @client.add_team_repository(team.id, new_repo['full_name'], {permission: 'push'}) From 4feb1fd1d4b9126c5a546a38da1d6a8640a4bd62 Mon Sep 17 00:00:00 2001 From: a544266477 Date: Sun, 8 Oct 2023 22:56:52 -0700 Subject: [PATCH 19/22] correct code indent --- scripts/github-repos/github-repos.rb | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/scripts/github-repos/github-repos.rb b/scripts/github-repos/github-repos.rb index 1da491a..dbd5852 100755 --- a/scripts/github-repos/github-repos.rb +++ b/scripts/github-repos/github-repos.rb @@ -98,22 +98,22 @@ def read_groups_and_users_from csv end end -def to_slug(input) - # Convert to lowercase - slug = input.downcase + def to_slug(input) + # Convert to lowercase + slug = input.downcase - # Replace characters other than 0-9, a-z, '.', '_', and '-' - slug.gsub!(/[^0-9a-z.\-_]+/, '-') + # Replace characters other than 0-9, a-z, '.', '_', and '-' + slug.gsub!(/[^0-9a-z.\-_]+/, '-') - # Remove leading and trailing hyphens - slug.gsub!(/^-+/, '') - slug.gsub!(/-+$/, '') + # Remove leading and trailing hyphens + slug.gsub!(/^-+/, '') + slug.gsub!(/-+$/, '') - # Limit the string to 63 characters - slug = slug[0, 63] + # Limit the string to 63 characters + slug = slug[0, 63] - return slug -end + return slug + end def invite_valid? if @orgname.nil? || @orgname.empty? || @csv.nil? || @parentteam.nil? || @parentteam.empty? From 2026642aaecb080d5f233daad5f4b3ee1da62f59 Mon Sep 17 00:00:00 2001 From: a544266477 Date: Mon, 13 Nov 2023 19:30:10 -0800 Subject: [PATCH 20/22] update remove function to remove all repos not just private repo --- scripts/github-repos/github-repos.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/github-repos/github-repos.rb b/scripts/github-repos/github-repos.rb index dbd5852..91ea836 100755 --- a/scripts/github-repos/github-repos.rb +++ b/scripts/github-repos/github-repos.rb @@ -345,7 +345,7 @@ def group_repos def remove_indiv_repos print_error "org name, assignment name, semester prefix are needed." unless remove_valid? - repos = @client.org_repos(@orgname, {:type => 'private'}) + repos = @client.org_repos(@orgname) repos.tqdm.each do |repo| if repo.name =~ /^#{Regexp.escape(@semester)}-(.*)-#{Regexp.escape(@assignment)}$/ @client.delete_repository(repo.full_name) @@ -356,7 +356,7 @@ def remove_indiv_repos def remove_group_repos print_error "org name, assignment name, semester prefix are needed." unless remove_valid? - repos = @client.org_repos(@orgname, {:type => 'private'}) + repos = @client.org_repos(@orgname) repos.tqdm.each do |repo| if repo.name =~ /^#{Regexp.escape(@semester)}-#{Regexp.escape(@assignment)}-\d+$/ @client.delete_repository(repo.full_name) @@ -398,7 +398,7 @@ def remove_groups def remove_group_repos_access print_error "org name, assignment name, semester prefix are needed." unless remove_valid? - repos = @client.org_repos(@orgname, {:type => 'private'}) + repos = @client.org_repos(@orgname) repos.tqdm.each do |repo| match = repo.name.match(/^#{Regexp.escape(@semester)}-#{Regexp.escape(@assignment)}-(\d+)$/) if match From 21b3cedb4aa778645b7f979baf14b709e7e69919 Mon Sep 17 00:00:00 2001 From: a544266477 Date: Mon, 13 Nov 2023 21:20:43 -0800 Subject: [PATCH 21/22] add optional option to create public repos and set up the repo permission. --- scripts/github-repos/README.md | 8 ++++++++ scripts/github-repos/github-repos.rb | 28 ++++++++++++++++++---------- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/scripts/github-repos/README.md b/scripts/github-repos/README.md index 0ecf84c..98af182 100644 --- a/scripts/github-repos/README.md +++ b/scripts/github-repos/README.md @@ -43,6 +43,10 @@ Required options: -g, --gsiteam=GSITEAM The team name of staff team -a, --assignment=ASSIGNMENT The assignment name +Optional options: + -perm, --permission=PERMISSION The permission of the created repos, can be 'pull', 'push', or 'admin'. Default: 'push' + -u, --public A flag infering to create public repository, otherwise the new repo is private. + 'group_repos' Create repos for each group. Repos' names are form like "[PREFIX]-[ASSIGNMENT]-[GROUPNUM]" Make sure groups are formed before running this command. @@ -55,6 +59,10 @@ Required options: -s, --studentteam=STUDENTTEAM The team name for students' team -g, --gsiteam=GSITEAM The team name of staff team +Optional options: + -perm, --permission=PERMISSION The permission of the created repos, can be 'pull', 'push', or 'admin'. Default: 'push' + -u, --public A flag infering to create public repository, otherwise the new repo is private. + 'remove_indiv_repos' Delete all repos whose names are formed like "[PREFIX]-[username]-[ASSIGNMENT]". diff --git a/scripts/github-repos/github-repos.rb b/scripts/github-repos/github-repos.rb index 91ea836..769cba2 100755 --- a/scripts/github-repos/github-repos.rb +++ b/scripts/github-repos/github-repos.rb @@ -51,6 +51,12 @@ def main() opt.on('-gGSITEAM', '--gsiteam=GSITEAM', 'The team name of staff team') do |gsiteam| org.gsiteam = gsiteam end + opt.on('-perm', '--permission=PERMISSION', 'The permission of the created repos, can be \'pull\', \'push\', or \'admin\'. Default: \'push\'') do |permission| + org.permission = permission + end + opts.on('-u', '--public', 'Create public repository, otherwise the new repo is private.') do + org.isPublic = true + end end $opts.parse! command = ARGV.pop @@ -60,7 +66,7 @@ def main() end class OrgManager - attr_accessor :orgname, :assignment, :semester, :template, :parentteam, :gsiteam, :csv, :users + attr_accessor :orgname, :assignment, :semester, :template, :parentteam, :gsiteam, :csv, :users, :permission, :isPublic def initialize @orgname = nil @@ -68,6 +74,8 @@ def initialize @semester = nil @template = nil @csv = nil + @isPublic = false + @permission = 'push' @users = [] @childteams = Hash.new { |hash, key| hash[key] = [] } # teamID => [email1, email2, ...] print_error("GITHUB_ORG_API_KEY not defined in environment") unless (@key = ENV['GITHUB_ORG_API_KEY']) @@ -137,7 +145,7 @@ def create_groups_valid? end def repos_valid? - !(@orgname.nil? || @parentteam.nil? || @semester.nil? || @template.nil? || !gsiteam_valid? || @assignment.nil?) + !(@orgname.nil? || @parentteam.nil? || @semester.nil? || @template.nil? || !gsiteam_valid? || @assignment.nil? || !['pull', 'push', 'admin'].include? (@permission)) end def remove_valid? @@ -263,7 +271,7 @@ def create_groups end def indiv_repos - print_error "orgname, student team name, assignment name, template repo name, semester prefix, and gsi team name needed." unless repos_valid? + print_error "Missing required options or invalid options." unless repos_valid? # Looking for the STUDENTTEAM in the org, see if it is exist. begin @@ -282,14 +290,14 @@ def indiv_repos if !@client.repository? %Q{#{@orgname}/#{new_repo_name}} begin new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, - {owner: @orgname, private: true}) + {owner: @orgname, private: !@isPublic}) rescue Octokit::NotFound print_error "Template not found." end else new_repo = @client.repo(%Q{#{@orgname}/#{new_repo_name}}) end - @client.add_collab(new_repo['full_name'], mem.login) + @client.add_collab(new_repo['full_name'], mem.login, permission: @permission) @client.add_team_repository(gsiteam_id, new_repo['full_name'], {permission: 'admin'}) end end @@ -302,14 +310,14 @@ def indiv_repos if !@client.repository? %Q{#{@orgname}/#{new_repo_name}} begin new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, - {owner: @orgname, private: true}) + {owner: @orgname, private: !@isPublic}) rescue Octokit::NotFound print_error "Template not found." end else new_repo = @client.repo(%Q{#{@orgname}/#{new_repo_name}}) end - @client.add_collab(new_repo['full_name'], mem.login) + @client.add_collab(new_repo['full_name'], mem.login, permission: @permission) @client.add_team_repository(gsiteam_id, new_repo['full_name'], {permission: 'admin'}) end end @@ -317,7 +325,7 @@ def indiv_repos end def group_repos - print_error "orgname, student team name, assignment name, template repo name, semester prefix, and gsi team name needed." unless repos_valid? + print_error "Missing required options or invalid options." unless repos_valid? child_teams = @client.child_teams(@client.team_by_name(@orgname, to_slug(@parentteam)).id) gsiteam_id = @client.team_by_name(@orgname, to_slug(@gsiteam))['id'] @@ -329,14 +337,14 @@ def group_repos if !@client.repository? %Q{#{@orgname}/#{new_repo_name}} begin new_repo = @client.create_repository_from_template(%Q{#{@orgname}/#{@template}}, new_repo_name, - {owner: @orgname, private: true}) + {owner: @orgname, private: !@isPublic}) rescue Octokit::NotFound print_error "Template not found." end else new_repo = @client.repo(%Q{#{@orgname}/#{new_repo_name}}) end - @client.add_team_repository(team.id, new_repo['full_name'], {permission: 'push'}) + @client.add_team_repository(team.id, new_repo['full_name'], {permission: @permission}) @client.add_team_repository(gsiteam_id, new_repo['full_name'], {permission: 'admin'}) end From 978e057bf02aa790003f254d50f38add9b76e38c Mon Sep 17 00:00:00 2001 From: a544266477 Date: Mon, 13 Nov 2023 21:50:22 -0800 Subject: [PATCH 22/22] bugs fixed change -perm to -e. type error on opts. syntax error in repos_valid. update the private property of repos if they had existed. --- scripts/github-repos/github-repos.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/github-repos/github-repos.rb b/scripts/github-repos/github-repos.rb index 769cba2..d01a46b 100755 --- a/scripts/github-repos/github-repos.rb +++ b/scripts/github-repos/github-repos.rb @@ -51,10 +51,10 @@ def main() opt.on('-gGSITEAM', '--gsiteam=GSITEAM', 'The team name of staff team') do |gsiteam| org.gsiteam = gsiteam end - opt.on('-perm', '--permission=PERMISSION', 'The permission of the created repos, can be \'pull\', \'push\', or \'admin\'. Default: \'push\'') do |permission| + opt.on('-ePERMISSION', '--permission=PERMISSION', 'The permission of the created repos, can be \'pull\', \'push\', or \'admin\'. Default: \'push\'') do |permission| org.permission = permission end - opts.on('-u', '--public', 'Create public repository, otherwise the new repo is private.') do + opt.on('-u', '--public', 'Create public repository, otherwise the new repo is private.') do org.isPublic = true end end @@ -145,7 +145,7 @@ def create_groups_valid? end def repos_valid? - !(@orgname.nil? || @parentteam.nil? || @semester.nil? || @template.nil? || !gsiteam_valid? || @assignment.nil? || !['pull', 'push', 'admin'].include? (@permission)) + !(@orgname.nil? || @parentteam.nil? || @semester.nil? || @template.nil? || !gsiteam_valid? || @assignment.nil? || !(['pull', 'push', 'admin'].include? (@permission))) end def remove_valid? @@ -296,6 +296,7 @@ def indiv_repos end else new_repo = @client.repo(%Q{#{@orgname}/#{new_repo_name}}) + @client.update_repository(new_repo.full_name, { private: !@isPublic }) end @client.add_collab(new_repo['full_name'], mem.login, permission: @permission) @client.add_team_repository(gsiteam_id, new_repo['full_name'], {permission: 'admin'}) @@ -316,6 +317,7 @@ def indiv_repos end else new_repo = @client.repo(%Q{#{@orgname}/#{new_repo_name}}) + @client.update_repository(new_repo.full_name, { private: !@isPublic }) end @client.add_collab(new_repo['full_name'], mem.login, permission: @permission) @client.add_team_repository(gsiteam_id, new_repo['full_name'], {permission: 'admin'}) @@ -343,6 +345,7 @@ def group_repos end else new_repo = @client.repo(%Q{#{@orgname}/#{new_repo_name}}) + @client.update_repository(new_repo.full_name, { private: !@isPublic }) end @client.add_team_repository(team.id, new_repo['full_name'], {permission: @permission}) @client.add_team_repository(gsiteam_id, new_repo['full_name'], {permission: 'admin'})