Skip to content

Commit

Permalink
First working version
Browse files Browse the repository at this point in the history
  • Loading branch information
dkrizic committed Mar 27, 2024
1 parent d7b279a commit 6c27863
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 18 deletions.
7 changes: 7 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ const (
keyVerboseEnvironment = "VERBOSE"
keyDryRun = "dry-run"
keyDryRunEnvironment = "DRY_RUN"
keyTeam = "team"
keyTeamEnvironment = "TEAM"
)

type Config struct {
Expand All @@ -31,6 +33,7 @@ type Config struct {
SourceOrganization string
TargetOrganization string
DryRun bool
Team string
}

func New() (*Config, error) {
Expand All @@ -40,6 +43,7 @@ func New() (*Config, error) {
flag.StringVar(&c.SourceOrganization, keySourceOrganization, lookupEnvOrString(keySourceOrganizationEnvironment, ""), "The Source organization.")
flag.StringVar(&c.TargetOrganization, keyTargetOrganization, lookupEnvOrString(keyTargetOrganizationEnvironment, ""), "The Target organization.")
flag.BoolVar(&c.DryRun, keyDryRun, lookupEnvOrBool(keyDryRunEnvironment, false), "Dry run mode.")
flag.StringVar(&c.Team, keyTeam, lookupEnvOrString(keyTeamEnvironment, ""), "The team to add the members to.")
verbose := flag.Int(keyVerbose, lookupEnvOrInt(keyVerboseEnvironment, 0), "Verbosity level, 0=info, 1=debug. Overrides the environment variable VERBOSE.")

level := slog.LevelError
Expand Down Expand Up @@ -70,6 +74,9 @@ func New() (*Config, error) {
if c.TargetOrganization == "" {
return nil, errors.New("Target Organization is required")
}
if c.Team == "" {
return nil, errors.New("Team is required")
}

return &c, nil
}
Expand Down
15 changes: 13 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,28 @@ func main() {
"sourceOrganization", c.SourceOrganization,
"targetOrganization", c.TargetOrganization,
"dryRun", c.DryRun,
"team", c.Team,
"githubToken", "***")

sourceOrganization := organization.New(c.GithubToken, c.SourceOrganization, c.DryRun)
sourceOrganization := organization.New(organization.OrganizationConfig{
GithubToken: c.GithubToken,
DryRun: c.DryRun,
Organization: c.SourceOrganization,
Team: c.Team,
})
sourceMembers, err := sourceOrganization.Members(ctx)
if err != nil {
slog.ErrorContext(ctx, "Unable to load members", "error", err, "organization", c.SourceOrganization)
os.Exit(1)
}
slog.InfoContext(ctx, "Loaded members", "organization", c.SourceOrganization, "members", len(*sourceMembers))

targetOrganization := organization.New(c.GithubToken, c.TargetOrganization, c.DryRun)
targetOrganization := organization.New(organization.OrganizationConfig{
GithubToken: c.GithubToken,
DryRun: c.DryRun,
Organization: c.TargetOrganization,
Team: c.Team,
})
targetMembers, err := targetOrganization.Members(ctx)
if err != nil {
slog.ErrorContext(ctx, "Unable to load members", "error", err, "organization", c.TargetOrganization)
Expand Down
74 changes: 58 additions & 16 deletions organization/organization.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,53 @@ import (
"log/slog"
)

type OrganizationConfig struct {
GithubToken string
DryRun bool
Organization string
Team string
}

type Organization struct {
Name string
name string
restClient *github.Client
graphClient *githubv4.Client
loaded bool
dryRun bool
members *[]Member
team string
}

type Member struct {
ID string
Login string
Name string
}

func New(githubToken string, name string, dryRun bool) (organization *Organization) {
restclient := github.NewClient(nil).WithAuthToken(githubToken)
httpClient := oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(&oauth2.Token{AccessToken: githubToken}))
func New(config OrganizationConfig) *Organization {
restclient := github.NewClient(nil).WithAuthToken(config.GithubToken)
httpClient := oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(&oauth2.Token{AccessToken: config.GithubToken}))
graphClient := githubv4.NewClient(httpClient)

return &Organization{
restClient: restclient,
graphClient: graphClient,
Name: name,
name: config.Organization,
loaded: false,
dryRun: dryRun,
dryRun: config.DryRun,
team: config.Team,
members: new([]Member),
}
}

func (o Organization) Members(ctx context.Context) (members *[]Member, err error) {
if !o.loaded {
slog.DebugContext(ctx, "Organization members not loaded", "organization", o.Name)
slog.DebugContext(ctx, "Organization members not loaded", "organization", o.name)
err = o.loadMembers(ctx)
if err != nil {
return nil, err
}
slog.DebugContext(ctx, "Organization members loaded", "organization", o.Name, "members", len(*o.members))
slog.DebugContext(ctx, "Organization members loaded", "organization", o.name, "members", len(*o.members))
}
return o.members, nil
}
Expand All @@ -59,6 +69,7 @@ func (o *Organization) loadMembers(ctx context.Context) error {
}
Edges []struct {
Node struct {
ID string
Login string
Name string
}
Expand All @@ -68,7 +79,7 @@ func (o *Organization) loadMembers(ctx context.Context) error {
}

variables := map[string]interface{}{
"organization": githubv4.String(o.Name),
"organization": githubv4.String(o.name),
"cursor": (*githubv4.String)(nil),
}

Expand All @@ -78,11 +89,12 @@ func (o *Organization) loadMembers(ctx context.Context) error {
return err
}
for _, e := range query.Organization.MembersWithRole.Edges {
slog.DebugContext(ctx, "Loaded member", "organization", o.Name, "login", e.Node.Login, "name", e.Node.Name)
*o.members = append(*o.members, Member{
ID: e.Node.ID,
Login: e.Node.Login,
Name: e.Node.Name,
})
slog.DebugContext(ctx, "Loaded member", "organization", o.name, "login", e.Node.Login, "name", e.Node.Name, "id", e.Node.ID)
}
if !query.Organization.MembersWithRole.PageInfo.HasNextPage {
break
Expand Down Expand Up @@ -112,19 +124,49 @@ func (o *Organization) MissingMembers(members []Member) (missing []Member) {
}

func (o Organization) Invite(ctx context.Context, enterprise string, members *[]Member) error {
slog.InfoContext(ctx, "Inviting members", "organization", o.Name, "members", len(*members))
slog.DebugContext(ctx, "Finding team", "organization", o.name, "team", o.team)
team, _, err := o.restClient.Teams.GetTeamBySlug(ctx, o.name, o.team)
if err != nil {
slog.ErrorContext(ctx, "Unable to find team", "organization", o.name, "team", o.team, "error", err)
return err
}
slog.InfoContext(ctx, "Found team", "organization", o.name, "team", o.team, "id", team.GetID())

slog.DebugContext(ctx, "Checking for already invited members", "organization", o.name, "members", len(*members))
invitationsMap := make(map[string]bool)
page := 0
pageSize := 30
for {
invitations, _, err := o.restClient.Organizations.ListPendingOrgInvitations(ctx, o.name, &github.ListOptions{Page: page, PerPage: pageSize})
if err != nil {
slog.ErrorContext(ctx, "Unable to list pending invitations", "organization", o.name, "error", err)
return err
}
for _, i := range invitations {
invitationsMap[i.GetLogin()] = true
}
if len(invitations) < pageSize {
break
}
page++
}

slog.InfoContext(ctx, "Inviting members", "organization", o.name, "members", len(*members))
if o.dryRun {
slog.InfoContext(ctx, "Dry run - skipping invite", "organization", o.Name, "members", len(*members))
slog.InfoContext(ctx, "Dry run - skipping invite", "organization", o.name, "members", len(*members))
return nil
}

for _, m := range *members {
slog.InfoContext(ctx, "Invite member", "organization", o.Name, "login", m.Login, "name", m.Name)
_, _, err := o.restClient.Organizations.CreateOrgInvitation(ctx, o.Name, nil)
if _, ok := invitationsMap[m.Login]; ok {
slog.DebugContext(ctx, "Already invited member", "organization", o.name, "login", m.Login, "name", m.Name, "id", m.ID)
continue
}
slog.InfoContext(ctx, "Invite member", "organization", o.name, "login", m.Login, "name", m.Name, "id", m.ID)
_, _, err := o.restClient.Teams.AddTeamMembershipBySlug(ctx, o.name, team.GetSlug(), m.Login, nil)
if err != nil {
slog.ErrorContext(ctx, "Unable to invite member", "organization", o.Name, "login", m.Login, "name", m.Name, "error", err)
slog.ErrorContext(ctx, "Unable to invite member", "organization", o.name, "login", m.Login, "name", m.Name, "error", err)
}
break
}

// invite members
Expand Down

0 comments on commit 6c27863

Please sign in to comment.