Skip to content

Commit

Permalink
Merge pull request #22 from mona-actions/amenocal/from-repo-list
Browse files Browse the repository at this point in the history
feat: migrate teams based on repo list
  • Loading branch information
amenocal authored Sep 11, 2024
2 parents b4ced17 + 1388ba1 commit c17b910
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 3 deletions.
77 changes: 77 additions & 0 deletions cmd/byRepos.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
Copyright © 2024 NAME HERE <EMAIL ADDRESS>
*/
package cmd

import (
"fmt"
"log"
"os"

"github.com/mona-actions/gh-migrate-teams/pkg/sync"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

// byReposCmd represents the byRepos command
var byReposCmd = &cobra.Command{
Use: "byRepos",
Short: "Migrates teams by repository",
Long: `Migrates team based on a repository list.
It will migrate all the teams that have access to the repositories in the list.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("byRepos called")

targetOrganization := cmd.Flag("target-organization").Value.String()
sourceToken := cmd.Flag("source-token").Value.String()
targetToken := cmd.Flag("target-token").Value.String()
mappingFile := cmd.Flag("mapping-file").Value.String()
ghHostname := cmd.Flag("source-hostname").Value.String()
repoFile := cmd.Flag("from-file").Value.String()

// Set ENV variables
os.Setenv("GHMT_TARGET_ORGANIZATION", targetOrganization)
os.Setenv("GHMT_SOURCE_TOKEN", sourceToken)
os.Setenv("GHMT_TARGET_TOKEN", targetToken)
os.Setenv("GHMT_MAPPING_FILE", mappingFile)
os.Setenv("GHMT_SOURCE_HOSTNAME", ghHostname)
os.Setenv("GHMT_REPO_FILE", repoFile)

// Bind ENV variables in Viper
viper.BindEnv("TARGET_ORGANIZATION")
viper.BindEnv("SOURCE_TOKEN")
viper.BindEnv("TARGET_TOKEN")
viper.BindEnv("MAPPING_FILE")
viper.BindEnv("SOURCE_HOSTNAME")
viper.BindEnv("USER_SYNC")
viper.BindEnv("SKIP_TEAMS")
viper.BindEnv("REPO_FILE")

sync.SyncTeamsByRepo()
},
}

func init() {

log.Println("byRepos init")
syncCmd.AddCommand(byReposCmd)

// Here you will define your flags and configuration settings.

byReposCmd.Flags().StringP("target-organization", "t", "", "Target Organization to sync teams from")
byReposCmd.MarkFlagRequired("target-organization")

byReposCmd.Flags().StringP("source-token", "a", "", "Source Organization GitHub token. Scopes: read:org, read:user, user:email")
byReposCmd.MarkFlagRequired("source-token")

byReposCmd.Flags().StringP("target-token", "b", "", "Target Organization GitHub token. Scopes: admin:org")
byReposCmd.MarkFlagRequired("target-token")

byReposCmd.Flags().StringP("from-file", "f", "repositories.txt", "File path to use for repository list")
byReposCmd.MarkFlagRequired("from-file")

byReposCmd.Flags().StringP("mapping-file", "m", "", "Mapping file path to use for mapping teams members handles")

byReposCmd.Flags().StringP("source-hostname", "u", os.Getenv("SOURCE_HOST"), "GitHub Enterprise source hostname url (optional) Ex. https://github.example.com")
}
17 changes: 15 additions & 2 deletions internal/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ func CreateTeam(name string, description string, privacy string, parentTeamName
if parentTeamName != "" {
parentTeamID, err := GetTeamId(parentTeamName)
if err != nil {
fmt.Println(err)
fmt.Println("Team ID Not found", err)
} else {
t.ParentTeamID = &parentTeamID
}
Expand All @@ -323,7 +323,7 @@ func CreateTeam(name string, description string, privacy string, parentTeamName

if err != nil {
if strings.Contains(err.Error(), "Name must be unique for this org") {
fmt.Println("Error creating team, team already exists: ", name)
fmt.Println("Team: ", name, "already exists in destination skipping...")
return err
} else {
fmt.Println("Unable to create team:", name, err.Error())
Expand Down Expand Up @@ -376,3 +376,16 @@ func GetTeamId(TeamName string) (int64, error) {
}
return *team.ID, nil
}

func GetRepositoryTeams(owner string, repo string) ([]*github.Team, error) {
client := newGHRestClient(viper.GetString("SOURCE_TOKEN"))
ctx := context.Background()

// Get teams for the repository
teams, _, err := client.Repositories.ListTeams(ctx, owner, repo, nil)
if err != nil {
return nil, err
}

return teams, nil
}
34 changes: 33 additions & 1 deletion internal/repository/repository.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
package repository

import "github.com/mona-actions/gh-migrate-teams/internal/api"
import (
"bufio"
"net/url"
"os"
"strings"

"github.com/mona-actions/gh-migrate-teams/internal/api"
)

type repositories []Repository

Expand Down Expand Up @@ -60,3 +67,28 @@ func (r repositories) ExportRepositoryCollaborators() [][]string {

return collaborators
}

func ParseRepositoryFile(filename string) ([]string, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()

var repos []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
u, err := url.Parse(scanner.Text())
if err != nil {
return nil, err
}
repo := strings.TrimPrefix(u.Path, "/") // format to owner/reponame
repos = append(repos, repo)
}

if err := scanner.Err(); err != nil {
return nil, err
}

return repos, nil
}
40 changes: 40 additions & 0 deletions internal/team/team.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package team

import (
"log"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -151,3 +153,41 @@ func (t Teams) ExportTeamRepositories() [][]string {

return repositories
}

func GetRepositoryTeams(repository string) Teams {
//split the repository string to get the owner and repo name
repo := strings.Split(repository, "/")
owner := repo[0]
repoName := repo[1]
viper.Set("SOURCE_ORGANIZATION", owner)
data, err := api.GetRepositoryTeams(owner, repoName)

if err != nil {
log.Println("Unable to get repository teams - ", err)
}
parentTeamID := ""
parentTeamName := ""

teams := make(Teams, 0, len(data))
for _, team := range data {
if team.Parent != nil {
parentTeamID = strconv.FormatInt(team.Parent.GetID(), 10)
parentTeamName = *team.Parent.Name
}

team := Team{
Id: strconv.FormatInt(team.GetID(), 10),
Name: team.GetName(),
Slug: team.GetSlug(),
Description: team.GetDescription(),
Privacy: team.GetPrivacy(),
ParentTeamId: parentTeamID,
ParentTeamName: parentTeamName,
Members: getTeamMemberships(*team.Slug),
Repositories: getTeamRepositories(*team.Slug),
}
teams = append(teams, team)
}

return teams
}
37 changes: 37 additions & 0 deletions pkg/sync/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"encoding/csv"
"log"
"os"
"strings"

"github.com/mona-actions/gh-migrate-teams/internal/repository"
"github.com/mona-actions/gh-migrate-teams/internal/team"
"github.com/pterm/pterm"
)
Expand Down Expand Up @@ -66,9 +68,44 @@ func getTargetHandle(filename string, source_handle string) (string, error) {
// Find target value for source value
for _, record := range records[1:] {
if record[0] == source_handle {
//if filename contains the string gei, return the third column
if strings.Contains(filename, "gei") {
return record[2], nil
}
return record[1], nil
}
}

return source_handle, nil
}

func SyncTeamsByRepo() {

teamsSpinnerSuccess, _ := pterm.DefaultSpinner.Start("Fetching teams from repository list...")
repos, err := repository.ParseRepositoryFile(os.Getenv("GHMT_REPO_FILE"))
teams := []team.Team{}

if err != nil {
log.Println("error while reading repository file - ", err)
teamsSpinnerSuccess.Fail()
return
}
for _, repo := range repos {
// get all teams that have access to the repository and add them to the teams slice
teams = append(teams, team.GetRepositoryTeams(repo)...)
}
teamsSpinnerSuccess.Success()

// Create teams in target organization
createTeamsSpinnerSuccess, _ := pterm.DefaultSpinner.Start("Creating teams in target organization...")
for _, team := range teams {
// Map members
if os.Getenv("GHMT_MAPPING_FILE") != "" {
log.Println("trying to map")
team = mapMembers(team)
}
log.Println("before create team", team)
team.CreateTeam()
}
createTeamsSpinnerSuccess.Success()
}

0 comments on commit c17b910

Please sign in to comment.