From c8a0c120a350dce182dd1c1a3a2691555e65ddd1 Mon Sep 17 00:00:00 2001 From: Nicolas Zin Date: Mon, 14 Oct 2024 09:03:37 -0400 Subject: [PATCH] adding properties AutoMergeAllowed, DeleteBranchOnMerge, AllowUpdateBranch --- README.md | 8 +- .../src/components/RepositoryApp.vue | 12 ++ docs/api_docs/bundle.yaml | 24 ++++ internal/engine/goliac_reconciliator.go | 75 ++++++------ internal/engine/goliac_reconciliator_test.go | 54 +++++---- internal/engine/mutable_remote.go | 26 +++-- internal/engine/reconciliator_executor.go | 5 +- internal/engine/remote.go | 107 ++++++++++-------- internal/engine/remote_test.go | 8 +- internal/entity/repository.go | 3 + internal/github_batch_executor.go | 80 +++++-------- internal/goliac_server.go | 22 ++-- swagger/index.yaml | 24 ++++ swagger_gen/models/repository.go | 9 ++ swagger_gen/models/repository_details.go | 9 ++ swagger_gen/restapi/embedded_spec.go | 60 ++++++++++ 16 files changed, 343 insertions(+), 183 deletions(-) diff --git a/README.md b/README.md index 20dbe82..3be3f21 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,9 @@ kind: Repository name: awesome-repository spec: public: true + allow_auto_merge: true + delete_branch_on_merge: true + allow_update_branch: true writers: - anotherteamA - anotherteamB @@ -76,7 +79,10 @@ spec: ``` In this last example: -- the repository is now publci +- the repository is now public +- the repository allows auto merge +- the repository will delete the branch on merge +- the repository allows to update the branch - other teams have write (`anotherteamA`, `anotherteamB`) or read (`anotherteamC`, `anotherteamD`) access ### Archive a repository diff --git a/browser/goliac-ui/src/components/RepositoryApp.vue b/browser/goliac-ui/src/components/RepositoryApp.vue index 8091510..b7ba851 100644 --- a/browser/goliac-ui/src/components/RepositoryApp.vue +++ b/browser/goliac-ui/src/components/RepositoryApp.vue @@ -22,6 +22,18 @@ Archived : {{ repository.archived}} +
+ Auto Merge Allowed : + {{ repository.autoMergeAllowed}} +
+
+ Delete Branch on Merge : + {{ repository.deleteBranchOnMerge}} +
+
+ Allow Update Branch : + {{ repository.allowUpdateBranch}} +
diff --git a/docs/api_docs/bundle.yaml b/docs/api_docs/bundle.yaml index 52a6c7c..df46e2d 100644 --- a/docs/api_docs/bundle.yaml +++ b/docs/api_docs/bundle.yaml @@ -295,6 +295,18 @@ definitions: type: boolean x-isnullable: false x-omitempty: false + autoMergeAllowed: + type: boolean + x-isnullable: false + x-omitempty: false + deleteBranchOnMerge: + type: boolean + x-isnullable: false + x-omitempty: false + allowUpdateBranch: + type: boolean + x-isnullable: false + x-omitempty: false archived: type: boolean x-isnullable: false @@ -317,6 +329,18 @@ definitions: type: boolean x-isnullable: false x-omitempty: false + autoMergeAllowed: + type: boolean + x-isnullable: false + x-omitempty: false + deleteBranchOnMerge: + type: boolean + x-isnullable: false + x-omitempty: false + allowUpdateBranch: + type: boolean + x-isnullable: false + x-omitempty: false teams: type: array items: diff --git a/internal/engine/goliac_reconciliator.go b/internal/engine/goliac_reconciliator.go index f06082a..f97a0e9 100644 --- a/internal/engine/goliac_reconciliator.go +++ b/internal/engine/goliac_reconciliator.go @@ -196,8 +196,7 @@ func (r *GoliacReconciliatorImpl) reconciliateTeams(ctx context.Context, local G } type GithubRepoComparable struct { - IsPublic bool - IsArchived bool + BoolProperties map[string]bool Writers []string Readers []string ExternalUserReaders []string // githubids @@ -213,13 +212,15 @@ func (r *GoliacReconciliatorImpl) reconciliateRepositories(ctx context.Context, rRepos := make(map[string]*GithubRepoComparable) for k, v := range ghRepos { repo := &GithubRepoComparable{ - IsPublic: !v.IsPrivate, - IsArchived: v.IsArchived, + BoolProperties: map[string]bool{}, Writers: []string{}, Readers: []string{}, ExternalUserReaders: []string{}, ExternalUserWriters: []string{}, } + for pk, pv := range v.BoolProperties { + repo.BoolProperties[pk] = pv + } for cGithubid, cPermission := range v.ExternalUsers { if cPermission == "WRITE" { @@ -288,8 +289,13 @@ func (r *GoliacReconciliatorImpl) reconciliateRepositories(ctx context.Context, } lRepos[slug.Make(reponame)] = &GithubRepoComparable{ - IsPublic: lRepo.Spec.IsPublic, - IsArchived: lRepo.Archived, + BoolProperties: map[string]bool{ + "private": !lRepo.Spec.IsPublic, + "archived": lRepo.Archived, + "allow_auto_merge": lRepo.Spec.AllowAutoMerge, + "delete_branch_on_merge": lRepo.Spec.DeleteBranchOnMerge, + "allow_update_branch": lRepo.Spec.AllowUpdateBranch, + }, Readers: readers, Writers: writers, ExternalUserReaders: eReaders, @@ -300,11 +306,10 @@ func (r *GoliacReconciliatorImpl) reconciliateRepositories(ctx context.Context, // now we compare local (slugTeams) and remote (rTeams) compareRepos := func(lRepo *GithubRepoComparable, rRepo *GithubRepoComparable) bool { - if lRepo.IsArchived != rRepo.IsArchived { - return false - } - if lRepo.IsPublic != rRepo.IsPublic { - return false + for lk, lv := range lRepo.BoolProperties { + if rv, ok := rRepo.BoolProperties[lk]; !ok || rv != lv { + return false + } } if res, _, _ := entity.StringArrayEquivalent(lRepo.Readers, rRepo.Readers); !res { @@ -327,16 +332,11 @@ func (r *GoliacReconciliatorImpl) reconciliateRepositories(ctx context.Context, } onChanged := func(reponame string, lRepo *GithubRepoComparable, rRepo *GithubRepoComparable) { - // reconciliate repositories public/private - if lRepo.IsPublic != rRepo.IsPublic { - // UPDATE private repository - r.UpdateRepositoryUpdatePrivate(ctx, dryrun, remote, reponame, !lRepo.IsPublic) - } - - // reconciliate repositories archived - if lRepo.IsArchived != rRepo.IsArchived { - // UPDATE archived repository - r.UpdateRepositoryUpdateArchived(ctx, dryrun, remote, reponame, lRepo.IsArchived) + // reconciliate repositories boolean properties + for lk, lv := range lRepo.BoolProperties { + if rv, ok := rRepo.BoolProperties[lk]; !ok || rv != lv { + r.UpdateRepositoryUpdateBoolProperty(ctx, dryrun, remote, reponame, lk, lv) + } } if res, readToRemove, readToAdd := entity.StringArrayEquivalent(lRepo.Readers, rRepo.Readers); !res { @@ -406,11 +406,11 @@ func (r *GoliacReconciliatorImpl) reconciliateRepositories(ctx context.Context, // if the repo was just archived in a previous commit and we "resume it" if aRepo, ok := toArchive[reponame]; ok { delete(toArchive, reponame) - r.UpdateRepositoryUpdateArchived(ctx, dryrun, remote, reponame, false) + r.UpdateRepositoryUpdateBoolProperty(ctx, dryrun, remote, reponame, "archived", false) // calling onChanged to update the repository permissions onChanged(reponame, aRepo, rRepo) } else { - r.CreateRepository(ctx, dryrun, remote, reponame, reponame, lRepo.Writers, lRepo.Readers, lRepo.IsPublic) + r.CreateRepository(ctx, dryrun, remote, reponame, reponame, lRepo.Writers, lRepo.Readers, lRepo.BoolProperties) } } @@ -420,7 +420,7 @@ func (r *GoliacReconciliatorImpl) reconciliateRepositories(ctx context.Context, // but if we have ArchiveOnDelete... if r.repoconfig.ArchiveOnDelete { if r.repoconfig.DestructiveOperations.AllowDestructiveRepositories { - r.UpdateRepositoryUpdateArchived(ctx, dryrun, remote, reponame, true) + r.UpdateRepositoryUpdateBoolProperty(ctx, dryrun, remote, reponame, "archived", true) toArchive[reponame] = rRepo } } else { @@ -602,15 +602,15 @@ func (r *GoliacReconciliatorImpl) DeleteTeam(ctx context.Context, dryrun bool, r } } } -func (r *GoliacReconciliatorImpl) CreateRepository(ctx context.Context, dryrun bool, remote *MutableGoliacRemoteImpl, reponame string, descrition string, writers []string, readers []string, public bool) { +func (r *GoliacReconciliatorImpl) CreateRepository(ctx context.Context, dryrun bool, remote *MutableGoliacRemoteImpl, reponame string, descrition string, writers []string, readers []string, boolProperties map[string]bool) { author := "unknown" if a := ctx.Value(KeyAuthor); a != nil { author = a.(string) } - logrus.WithFields(map[string]interface{}{"dryrun": dryrun, "author": author, "command": "create_repository"}).Infof("repositoryname: %s, readers: %s, writers: %s, public: %v", reponame, strings.Join(readers, ","), strings.Join(writers, ","), public) - remote.CreateRepository(reponame, reponame, writers, readers, public) + logrus.WithFields(map[string]interface{}{"dryrun": dryrun, "author": author, "command": "create_repository"}).Infof("repositoryname: %s, readers: %s, writers: %s, boolProperties: %v", reponame, strings.Join(readers, ","), strings.Join(writers, ","), boolProperties) + remote.CreateRepository(reponame, reponame, writers, readers, boolProperties) if r.executor != nil { - r.executor.CreateRepository(dryrun, reponame, reponame, writers, readers, public) + r.executor.CreateRepository(dryrun, reponame, reponame, writers, readers, boolProperties) } } func (r *GoliacReconciliatorImpl) UpdateRepositoryAddTeamAccess(ctx context.Context, dryrun bool, remote *MutableGoliacRemoteImpl, reponame string, teamslug string, permission string) { @@ -661,26 +661,15 @@ func (r *GoliacReconciliatorImpl) DeleteRepository(ctx context.Context, dryrun b } } } -func (r *GoliacReconciliatorImpl) UpdateRepositoryUpdatePrivate(ctx context.Context, dryrun bool, remote *MutableGoliacRemoteImpl, reponame string, private bool) { - author := "unknown" - if a := ctx.Value(KeyAuthor); a != nil { - author = a.(string) - } - logrus.WithFields(map[string]interface{}{"dryrun": dryrun, "author": author, "command": "update_repository_update_private"}).Infof("repositoryname: %s private:%v", reponame, private) - remote.UpdateRepositoryUpdatePrivate(reponame, private) - if r.executor != nil { - r.executor.UpdateRepositoryUpdatePrivate(dryrun, reponame, private) - } -} -func (r *GoliacReconciliatorImpl) UpdateRepositoryUpdateArchived(ctx context.Context, dryrun bool, remote *MutableGoliacRemoteImpl, reponame string, archived bool) { +func (r *GoliacReconciliatorImpl) UpdateRepositoryUpdateBoolProperty(ctx context.Context, dryrun bool, remote *MutableGoliacRemoteImpl, reponame string, propertyName string, propertyValue bool) { author := "unknown" if a := ctx.Value(KeyAuthor); a != nil { author = a.(string) } - logrus.WithFields(map[string]interface{}{"dryrun": dryrun, "author": author, "command": "update_repository_update_archived"}).Infof("repositoryname: %s archived:%v", reponame, archived) - remote.UpdateRepositoryUpdateArchived(reponame, archived) + logrus.WithFields(map[string]interface{}{"dryrun": dryrun, "author": author, "command": "update_repository_update_bool_property"}).Infof("repositoryname: %s %s:%v", reponame, propertyName, propertyValue) + remote.UpdateRepositoryUpdateBoolProperty(reponame, propertyName, propertyValue) if r.executor != nil { - r.executor.UpdateRepositoryUpdateArchived(dryrun, reponame, archived) + r.executor.UpdateRepositoryUpdateBoolProperty(dryrun, reponame, propertyName, propertyValue) } } func (r *GoliacReconciliatorImpl) AddRuleset(ctx context.Context, dryrun bool, ruleset *GithubRuleSet) { diff --git a/internal/engine/goliac_reconciliator_test.go b/internal/engine/goliac_reconciliator_test.go index 5e6fbd3..033378f 100644 --- a/internal/engine/goliac_reconciliator_test.go +++ b/internal/engine/goliac_reconciliator_test.go @@ -187,7 +187,7 @@ func (r *ReconciliatorListenerRecorder) UpdateTeamRemoveMember(dryrun bool, team func (r *ReconciliatorListenerRecorder) DeleteTeam(dryrun bool, teamslug string) { r.TeamDeleted[teamslug] = true } -func (r *ReconciliatorListenerRecorder) CreateRepository(dryrun bool, reponame string, descrition string, writers []string, readers []string, public bool) { +func (r *ReconciliatorListenerRecorder) CreateRepository(dryrun bool, reponame string, descrition string, writers []string, readers []string, boolProperties map[string]bool) { r.RepositoryCreated[reponame] = true } func (r *ReconciliatorListenerRecorder) UpdateRepositoryAddTeamAccess(dryrun bool, reponame string, teamslug string, permission string) { @@ -202,12 +202,9 @@ func (r *ReconciliatorListenerRecorder) UpdateRepositoryRemoveTeamAccess(dryrun func (r *ReconciliatorListenerRecorder) DeleteRepository(dryrun bool, reponame string) { r.RepositoriesDeleted[reponame] = true } -func (r *ReconciliatorListenerRecorder) UpdateRepositoryUpdatePrivate(dryrun bool, reponame string, private bool) { +func (r *ReconciliatorListenerRecorder) UpdateRepositoryUpdateBoolProperty(dryrun bool, reponame string, propertyName string, propertyValue bool) { r.RepositoriesUpdatePrivate[reponame] = true } -func (r *ReconciliatorListenerRecorder) UpdateRepositoryUpdateArchived(dryrun bool, reponame string, archived bool) { - r.RepositoriesUpdateArchived[reponame] = true -} func (r *ReconciliatorListenerRecorder) UpdateRepositorySetExternalUser(dryrun bool, reponame string, githubid string, permission string) { r.RepositoriesSetExternalUser[githubid] = permission } @@ -685,7 +682,9 @@ func TestReconciliation(t *testing.T) { } remote.teams["existing"] = existing rRepo := GithubRepository{ - Name: "myrepo", + Name: "myrepo", + ExternalUsers: map[string]string{}, + BoolProperties: map[string]bool{}, } remote.repos["myrepo"] = &rRepo @@ -749,7 +748,9 @@ func TestReconciliation(t *testing.T) { } remote.teams["existing"] = existing rRepo := GithubRepository{ - Name: "myrepo", + Name: "myrepo", + ExternalUsers: map[string]string{}, + BoolProperties: map[string]bool{}, } remote.repos["myrepo"] = &rRepo @@ -824,7 +825,9 @@ func TestReconciliation(t *testing.T) { remote.teams["existing"] = existing remote.teams["reader"] = reader rRepo := GithubRepository{ - Name: "myrepo", + Name: "myrepo", + ExternalUsers: map[string]string{}, + BoolProperties: map[string]bool{}, } remote.repos["myrepo"] = &rRepo @@ -898,7 +901,9 @@ func TestReconciliation(t *testing.T) { remote.teams["existing"] = existing remote.teams["reader"] = reader rRepo := GithubRepository{ - Name: "myrepo", + Name: "myrepo", + ExternalUsers: map[string]string{}, + BoolProperties: map[string]bool{}, } remote.repos["myrepo"] = &rRepo @@ -965,7 +970,9 @@ func TestReconciliation(t *testing.T) { } remote.teams["existing"] = existing rRepo := GithubRepository{ - Name: "myrepo", + Name: "myrepo", + ExternalUsers: map[string]string{}, + BoolProperties: map[string]bool{}, } remote.repos["myrepo"] = &rRepo @@ -1033,7 +1040,9 @@ func TestReconciliation(t *testing.T) { } remote.teams["existing"] = existing rRepo := GithubRepository{ - Name: "myrepo", + Name: "myrepo", + ExternalUsers: map[string]string{}, + BoolProperties: map[string]bool{}, } remote.repos["myrepo"] = &rRepo @@ -1101,8 +1110,9 @@ func TestReconciliation(t *testing.T) { } remote.teams["existing"] = existing rRepo := GithubRepository{ - Name: "myrepo", - ExternalUsers: make(map[string]string), + Name: "myrepo", + ExternalUsers: make(map[string]string), + BoolProperties: make(map[string]bool), } remote.repos["myrepo"] = &rRepo @@ -1169,8 +1179,9 @@ func TestReconciliation(t *testing.T) { } remote.teams["existing"] = existing rRepo := GithubRepository{ - Name: "myrepo", - ExternalUsers: make(map[string]string), + Name: "myrepo", + ExternalUsers: make(map[string]string), + BoolProperties: make(map[string]bool), } rRepo.ExternalUsers["outside1-githubid"] = "WRITE" remote.repos["myrepo"] = &rRepo @@ -1244,8 +1255,9 @@ func TestReconciliation(t *testing.T) { } remote.teams["existing"] = existing rRepo := GithubRepository{ - Name: "myrepo", - ExternalUsers: make(map[string]string), + Name: "myrepo", + ExternalUsers: make(map[string]string), + BoolProperties: make(map[string]bool), } rRepo.ExternalUsers["outside1-githubid"] = "WRITE" remote.repos["myrepo"] = &rRepo @@ -1325,7 +1337,9 @@ func TestReconciliation(t *testing.T) { appids: make(map[string]int), } removing := &GithubRepository{ - Name: "removing", + Name: "removing", + ExternalUsers: map[string]string{}, + BoolProperties: map[string]bool{}, } remote.repos["removing"] = removing @@ -1360,7 +1374,9 @@ func TestReconciliation(t *testing.T) { appids: make(map[string]int), } removing := &GithubRepository{ - Name: "removing", + Name: "removing", + ExternalUsers: map[string]string{}, + BoolProperties: map[string]bool{}, } remote.repos["removing"] = removing diff --git a/internal/engine/mutable_remote.go b/internal/engine/mutable_remote.go index df84476..101a81f 100644 --- a/internal/engine/mutable_remote.go +++ b/internal/engine/mutable_remote.go @@ -136,11 +136,11 @@ func (m *MutableGoliacRemoteImpl) DeleteTeam(teamslug string) { delete(m.teamRepos, teamslug) } } -func (m *MutableGoliacRemoteImpl) CreateRepository(reponame string, descrition string, writers []string, readers []string, public bool) { +func (m *MutableGoliacRemoteImpl) CreateRepository(reponame string, descrition string, writers []string, readers []string, boolProperties map[string]bool) { r := GithubRepository{ - Name: reponame, - IsArchived: false, - IsPrivate: !public, + Name: reponame, + BoolProperties: boolProperties, + ExternalUsers: map[string]string{}, } m.repositories[reponame] = &r } @@ -168,14 +168,18 @@ func (m *MutableGoliacRemoteImpl) UpdateRepositoryRemoveTeamAccess(reponame stri func (m *MutableGoliacRemoteImpl) DeleteRepository(reponame string) { delete(m.repositories, reponame) } -func (m *MutableGoliacRemoteImpl) UpdateRepositoryUpdatePrivate(reponame string, private bool) { - if r, ok := m.repositories[reponame]; ok { - r.IsPrivate = private - } -} -func (m *MutableGoliacRemoteImpl) UpdateRepositoryUpdateArchived(reponame string, archived bool) { + +/* +UpdateRepositoryUpdateBoolProperty is used for +- private +- archived +- allow_auto_merge +- delete_branch_on_merge +- allow_update_branch +*/ +func (m *MutableGoliacRemoteImpl) UpdateRepositoryUpdateBoolProperty(reponame string, propertyName string, propertyValue bool) { if r, ok := m.repositories[reponame]; ok { - r.IsArchived = archived + r.BoolProperties[propertyName] = propertyValue } } func (m *MutableGoliacRemoteImpl) UpdateRepositorySetExternalUser(reponame string, collaboatorGithubId string, permission string) { diff --git a/internal/engine/reconciliator_executor.go b/internal/engine/reconciliator_executor.go index 6a8a07f..83bcb2c 100644 --- a/internal/engine/reconciliator_executor.go +++ b/internal/engine/reconciliator_executor.go @@ -10,9 +10,8 @@ type ReconciliatorExecutor interface { UpdateTeamRemoveMember(dryrun bool, teamslug string, username string) DeleteTeam(dryrun bool, teamslug string) - CreateRepository(dryrun bool, reponame string, descrition string, writers []string, readers []string, public bool) - UpdateRepositoryUpdateArchived(dryrun bool, reponame string, archived bool) - UpdateRepositoryUpdatePrivate(dryrun bool, reponame string, private bool) + CreateRepository(dryrun bool, reponame string, descrition string, writers []string, readers []string, boolProperties map[string]bool) + UpdateRepositoryUpdateBoolProperty(dryrun bool, reponame string, propertyName string, propertyValue bool) UpdateRepositoryAddTeamAccess(dryrun bool, reponame string, teamslug string, permission string) // permission can be "pull", "push", or "admin" which correspond to read, write, and admin access. UpdateRepositoryUpdateTeamAccess(dryrun bool, reponame string, teamslug string, permission string) // permission can be "pull", "push", or "admin" which correspond to read, write, and admin access. UpdateRepositoryRemoveTeamAccess(dryrun bool, reponame string, teamslug string) diff --git a/internal/engine/remote.go b/internal/engine/remote.go index 5b33419..af81138 100644 --- a/internal/engine/remote.go +++ b/internal/engine/remote.go @@ -44,12 +44,11 @@ type GoliacRemoteExecutor interface { } type GithubRepository struct { - Name string - Id int - RefId string - IsArchived bool - IsPrivate bool - ExternalUsers map[string]string // [githubid]permission + Name string + Id int + RefId string + BoolProperties map[string]bool // archived, private, allow_auto_merge, delete_branch_on_merge, allow_update_branch + ExternalUsers map[string]string // [githubid]permission } type GithubTeam struct { @@ -364,6 +363,9 @@ query listAllReposInOrg($orgLogin: String!, $endCursor: String) { databaseId isArchived isPrivate + autoMergeAllowed + deleteBranchOnMerge + allowUpdateBranch collaborators(affiliation: OUTSIDE, first: 100) { edges { node { @@ -388,12 +390,15 @@ type GraplQLRepositories struct { Organization struct { Repositories struct { Nodes []struct { - Name string - Id string - DatabaseId int - IsArchived bool - IsPrivate bool - Collaborators struct { + Name string + Id string + DatabaseId int + IsArchived bool + IsPrivate bool + AutoMergeAllowed bool + DeleteBranchOnMerge bool + AllowUpdateBranch bool + Collaborators struct { Edges []struct { Node struct { Login string @@ -450,11 +455,16 @@ func (g *GoliacRemoteImpl) loadRepositories() (map[string]*GithubRepository, map for _, c := range gResult.Data.Organization.Repositories.Nodes { repo := &GithubRepository{ - Name: c.Name, - Id: c.DatabaseId, - RefId: c.Id, - IsArchived: c.IsArchived, - IsPrivate: c.IsPrivate, + Name: c.Name, + Id: c.DatabaseId, + RefId: c.Id, + BoolProperties: map[string]bool{ + "archived": c.IsArchived, + "private": c.IsPrivate, + "allow_auto_merge": c.AutoMergeAllowed, + "delete_branch_on_merge": c.DeleteBranchOnMerge, + "allow_update_branch": c.AllowUpdateBranch, + }, ExternalUsers: make(map[string]string), } for _, collaborator := range c.Collaborators.Edges { @@ -1498,16 +1508,33 @@ type CreateRepositoryResponse struct { NodeId string `json:"node_id"` } -func (g *GoliacRemoteImpl) CreateRepository(dryrun bool, reponame string, description string, writers []string, readers []string, public bool) { +/* +boolProperties are: +- private +- archived +- allow_auto_merge +- delete_branch_on_merge +- allow_update_branch +- ... +*/ +func (g *GoliacRemoteImpl) CreateRepository(dryrun bool, reponame string, description string, writers []string, readers []string, boolProperties map[string]bool) { repoId := 0 repoRefId := reponame // create repository // https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#create-an-organization-repository if !dryrun { + props := map[string]interface{}{ + "name": reponame, + "description": description, + } + for k, v := range boolProperties { + props[k] = v + } + body, err := g.client.CallRestAPI( fmt.Sprintf("/orgs/%s/repos", config.Config.GithubAppOrganization), "POST", - map[string]interface{}{"name": reponame, "description": description, "private": !public}, + props, ) if err != nil { logrus.Errorf("failed to create repository: %v. %s", err, string(body)) @@ -1527,11 +1554,10 @@ func (g *GoliacRemoteImpl) CreateRepository(dryrun bool, reponame string, descri // update the repositories list newRepo := &GithubRepository{ - Name: reponame, - Id: repoId, - RefId: repoRefId, - IsArchived: false, - IsPrivate: !public, + Name: reponame, + Id: repoId, + RefId: repoRefId, + BoolProperties: boolProperties, } g.repositories[reponame] = newRepo g.repositoriesByRefId[repoRefId] = newRepo @@ -1664,38 +1690,29 @@ func (g *GoliacRemoteImpl) UpdateRepositoryRemoveTeamAccess(dryrun bool, reponam } } -func (g *GoliacRemoteImpl) UpdateRepositoryUpdatePrivate(dryrun bool, reponame string, private bool) { - // https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#update-a-repository - if !dryrun { - body, err := g.client.CallRestAPI( - fmt.Sprintf("repos/%s/%s", config.Config.GithubAppOrganization, reponame), - "PATCH", - map[string]interface{}{"private": private}, - ) - if err != nil { - logrus.Errorf("failed to update repository private setting: %v. %s", err, string(body)) - } - } - - if repo, ok := g.repositories[reponame]; ok { - repo.IsPrivate = private - } -} -func (g *GoliacRemoteImpl) UpdateRepositoryUpdateArchived(dryrun bool, reponame string, archived bool) { +/* +Used for +- private +- allow_auto_merge +- delete_branch_on_merge +- allow_update_branch +- archived +*/ +func (g *GoliacRemoteImpl) UpdateRepositoryUpdateBoolProperty(dryrun bool, reponame string, propertyName string, propertyValue bool) { // https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#update-a-repository if !dryrun { body, err := g.client.CallRestAPI( fmt.Sprintf("repos/%s/%s", config.Config.GithubAppOrganization, reponame), "PATCH", - map[string]interface{}{"archived": archived}, + map[string]interface{}{propertyName: propertyValue}, ) if err != nil { - logrus.Errorf("failed to update repository archive setting: %v. %s", err, string(body)) + logrus.Errorf("failed to update repository %s setting: %v. %s", propertyName, err, string(body)) } } if repo, ok := g.repositories[reponame]; ok { - repo.IsArchived = archived + repo.BoolProperties[propertyName] = propertyValue } } diff --git a/internal/engine/remote_test.go b/internal/engine/remote_test.go index b6a8939..3279c6a 100644 --- a/internal/engine/remote_test.go +++ b/internal/engine/remote_test.go @@ -408,10 +408,10 @@ func TestRemoteRepository(t *testing.T) { repositories, _, err := remoteImpl.loadRepositories() assert.Nil(t, err) assert.Equal(t, 133, len(repositories)) - assert.Equal(t, false, repositories["repo_1"].IsArchived) - assert.Equal(t, true, repositories["repo_3"].IsArchived) - assert.Equal(t, false, repositories["repo_1"].IsPrivate) - assert.Equal(t, true, repositories["repo_10"].IsPrivate) + assert.Equal(t, false, repositories["repo_1"].BoolProperties["archived"]) + assert.Equal(t, true, repositories["repo_3"].BoolProperties["archived"]) + assert.Equal(t, false, repositories["repo_1"].BoolProperties["private"]) + assert.Equal(t, true, repositories["repo_10"].BoolProperties["private"]) }) t.Run("happy path: load remote teams", func(t *testing.T) { // MockGithubClient doesn't support concurrent access diff --git a/internal/entity/repository.go b/internal/entity/repository.go index a87a3e7..fd4d9d6 100644 --- a/internal/entity/repository.go +++ b/internal/entity/repository.go @@ -17,6 +17,9 @@ type Repository struct { ExternalUserReaders []string `yaml:"externalUserReaders,omitempty"` ExternalUserWriters []string `yaml:"externalUserWriters,omitempty"` IsPublic bool `yaml:"public,omitempty"` + AllowAutoMerge bool `yaml:"allow_auto_merge,omitempty"` + DeleteBranchOnMerge bool `yaml:"delete_branch_on_merge,omitempty"` + AllowUpdateBranch bool `yaml:"allow_update_branch,omitempty"` } `yaml:"spec,omitempty"` Archived bool `yaml:"archived,omitempty"` // implicit: will be set by Goliac Owner *string `yaml:"owner,omitempty"` // implicit. team name owning the repo (if any) diff --git a/internal/github_batch_executor.go b/internal/github_batch_executor.go index 7d416a0..6f82be6 100644 --- a/internal/github_batch_executor.go +++ b/internal/github_batch_executor.go @@ -95,15 +95,15 @@ func (g *GithubBatchExecutor) DeleteTeam(dryrun bool, teamslug string) { }) } -func (g *GithubBatchExecutor) CreateRepository(dryrun bool, reponame string, description string, writers []string, readers []string, public bool) { +func (g *GithubBatchExecutor) CreateRepository(dryrun bool, reponame string, description string, writers []string, readers []string, boolProperties map[string]bool) { g.commands = append(g.commands, &GithubCommandCreateRepository{ - client: g.client, - dryrun: dryrun, - reponame: reponame, - description: description, - readers: readers, - writers: writers, - public: public, + client: g.client, + dryrun: dryrun, + reponame: reponame, + description: description, + readers: readers, + writers: writers, + boolProperties: boolProperties, }) } @@ -136,21 +136,13 @@ func (g *GithubBatchExecutor) UpdateRepositoryRemoveTeamAccess(dryrun bool, repo }) } -func (g *GithubBatchExecutor) UpdateRepositoryUpdatePrivate(dryrun bool, reponame string, private bool) { - g.commands = append(g.commands, &GithubCommandUpdateRepositoryUpdatePrivate{ - client: g.client, - dryrun: dryrun, - reponame: reponame, - private: private, - }) -} - -func (g *GithubBatchExecutor) UpdateRepositoryUpdateArchived(dryrun bool, reponame string, archived bool) { - g.commands = append(g.commands, &GithubCommandUpdateRepositoryUpdateArchived{ - client: g.client, - dryrun: dryrun, - reponame: reponame, - archived: archived, +func (g *GithubBatchExecutor) UpdateRepositoryUpdateBoolProperty(dryrun bool, reponame string, propertyName string, propertyValue bool) { + g.commands = append(g.commands, &GithubCommandUpdateRepositoryUpdateBoolProperty{ + client: g.client, + dryrun: dryrun, + reponame: reponame, + propertyName: propertyName, + propertyValue: propertyValue, }) } @@ -233,17 +225,17 @@ func (g *GithubCommandAddUserToOrg) Apply() { } type GithubCommandCreateRepository struct { - client engine.ReconciliatorExecutor - dryrun bool - reponame string - description string - writers []string - readers []string - public bool + client engine.ReconciliatorExecutor + dryrun bool + reponame string + description string + writers []string + readers []string + boolProperties map[string]bool } func (g *GithubCommandCreateRepository) Apply() { - g.client.CreateRepository(g.dryrun, g.reponame, g.description, g.writers, g.readers, g.public) + g.client.CreateRepository(g.dryrun, g.reponame, g.description, g.writers, g.readers, g.boolProperties) } type GithubCommandCreateTeam struct { @@ -323,17 +315,6 @@ func (g *GithubCommandUpdateRepositoryUpdateTeamAccess) Apply() { g.client.UpdateRepositoryUpdateTeamAccess(g.dryrun, g.reponame, g.teamslug, g.permission) } -type GithubCommandUpdateRepositoryUpdateArchived struct { - client engine.ReconciliatorExecutor - dryrun bool - reponame string - archived bool -} - -func (g *GithubCommandUpdateRepositoryUpdateArchived) Apply() { - g.client.UpdateRepositoryUpdateArchived(g.dryrun, g.reponame, g.archived) -} - type GithubCommandUpdateRepositorySetExternalUser struct { client engine.ReconciliatorExecutor dryrun bool @@ -357,15 +338,16 @@ func (g *GithubCommandUpdateRepositoryRemoveExternalUser) Apply() { g.client.UpdateRepositoryRemoveExternalUser(g.dryrun, g.reponame, g.githubid) } -type GithubCommandUpdateRepositoryUpdatePrivate struct { - client engine.ReconciliatorExecutor - dryrun bool - reponame string - private bool +type GithubCommandUpdateRepositoryUpdateBoolProperty struct { + client engine.ReconciliatorExecutor + dryrun bool + reponame string + propertyName string + propertyValue bool } -func (g *GithubCommandUpdateRepositoryUpdatePrivate) Apply() { - g.client.UpdateRepositoryUpdatePrivate(g.dryrun, g.reponame, g.private) +func (g *GithubCommandUpdateRepositoryUpdateBoolProperty) Apply() { + g.client.UpdateRepositoryUpdateBoolProperty(g.dryrun, g.reponame, g.propertyName, g.propertyValue) } type GithubCommandUpdateTeamAddMember struct { diff --git a/internal/goliac_server.go b/internal/goliac_server.go index 3e90e0c..c09e257 100644 --- a/internal/goliac_server.go +++ b/internal/goliac_server.go @@ -138,11 +138,14 @@ func (g *GoliacServerImpl) GetRepository(params app.GetRepositoryParams) middlew } repositoryDetails := models.RepositoryDetails{ - Name: repository.Name, - Public: repository.Spec.IsPublic, - Archived: repository.Archived, - Teams: teams, - Collaborators: collaborators, + Name: repository.Name, + Public: repository.Spec.IsPublic, + AutoMergeAllowed: repository.Spec.AllowAutoMerge, + DeleteBranchOnMerge: repository.Spec.DeleteBranchOnMerge, + AllowUpdateBranch: repository.Spec.AllowUpdateBranch, + Archived: repository.Archived, + Teams: teams, + Collaborators: collaborators, } return app.NewGetRepositoryOK().WithPayload(&repositoryDetails) @@ -195,9 +198,12 @@ func (g *GoliacServerImpl) GetTeam(params app.GetTeamParams) middleware.Responde repositories := make([]*models.Repository, 0, len(repos)) for reponame, repo := range repos { r := models.Repository{ - Name: reponame, - Archived: repo.Archived, - Public: repo.Spec.IsPublic, + Name: reponame, + Archived: repo.Archived, + Public: repo.Spec.IsPublic, + AutoMergeAllowed: repo.Spec.AllowAutoMerge, + DeleteBranchOnMerge: repo.Spec.DeleteBranchOnMerge, + AllowUpdateBranch: repo.Spec.AllowUpdateBranch, } repositories = append(repositories, &r) } diff --git a/swagger/index.yaml b/swagger/index.yaml index 1289ff8..41e55a7 100644 --- a/swagger/index.yaml +++ b/swagger/index.yaml @@ -112,6 +112,18 @@ definitions: type: boolean x-isnullable: false x-omitempty: false + autoMergeAllowed: + type: boolean + x-isnullable: false + x-omitempty: false + deleteBranchOnMerge: + type: boolean + x-isnullable: false + x-omitempty: false + allowUpdateBranch: + type: boolean + x-isnullable: false + x-omitempty: false archived: type: boolean x-isnullable: false @@ -137,6 +149,18 @@ definitions: type: boolean x-isnullable: false x-omitempty: false + autoMergeAllowed: + type: boolean + x-isnullable: false + x-omitempty: false + deleteBranchOnMerge: + type: boolean + x-isnullable: false + x-omitempty: false + allowUpdateBranch: + type: boolean + x-isnullable: false + x-omitempty: false teams: type: array items: diff --git a/swagger_gen/models/repository.go b/swagger_gen/models/repository.go index 31bcb4b..683aa14 100644 --- a/swagger_gen/models/repository.go +++ b/swagger_gen/models/repository.go @@ -17,9 +17,18 @@ import ( // swagger:model repository type Repository struct { + // allow update branch + AllowUpdateBranch bool `json:"allowUpdateBranch"` + // archived Archived bool `json:"archived"` + // auto merge allowed + AutoMergeAllowed bool `json:"autoMergeAllowed"` + + // delete branch on merge + DeleteBranchOnMerge bool `json:"deleteBranchOnMerge"` + // name Name string `json:"name,omitempty"` diff --git a/swagger_gen/models/repository_details.go b/swagger_gen/models/repository_details.go index 3ef4631..cd23db5 100644 --- a/swagger_gen/models/repository_details.go +++ b/swagger_gen/models/repository_details.go @@ -20,12 +20,21 @@ import ( // swagger:model repositoryDetails type RepositoryDetails struct { + // allow update branch + AllowUpdateBranch bool `json:"allowUpdateBranch"` + // archived Archived bool `json:"archived"` + // auto merge allowed + AutoMergeAllowed bool `json:"autoMergeAllowed"` + // collaborators Collaborators []*RepositoryDetailsCollaboratorsItems0 `json:"collaborators"` + // delete branch on merge + DeleteBranchOnMerge bool `json:"deleteBranchOnMerge"` + // name Name string `json:"name,omitempty"` diff --git a/swagger_gen/restapi/embedded_spec.go b/swagger_gen/restapi/embedded_spec.go index 01b783c..c2b553a 100644 --- a/swagger_gen/restapi/embedded_spec.go +++ b/swagger_gen/restapi/embedded_spec.go @@ -414,11 +414,26 @@ func init() { "repository": { "type": "object", "properties": { + "allowUpdateBranch": { + "type": "boolean", + "x-isnullable": false, + "x-omitempty": false + }, "archived": { "type": "boolean", "x-isnullable": false, "x-omitempty": false }, + "autoMergeAllowed": { + "type": "boolean", + "x-isnullable": false, + "x-omitempty": false + }, + "deleteBranchOnMerge": { + "type": "boolean", + "x-isnullable": false, + "x-omitempty": false + }, "name": { "type": "string", "x-isnullable": false @@ -433,11 +448,21 @@ func init() { "repositoryDetails": { "type": "object", "properties": { + "allowUpdateBranch": { + "type": "boolean", + "x-isnullable": false, + "x-omitempty": false + }, "archived": { "type": "boolean", "x-isnullable": false, "x-omitempty": false }, + "autoMergeAllowed": { + "type": "boolean", + "x-isnullable": false, + "x-omitempty": false + }, "collaborators": { "type": "array", "items": { @@ -454,6 +479,11 @@ func init() { } } }, + "deleteBranchOnMerge": { + "type": "boolean", + "x-isnullable": false, + "x-omitempty": false + }, "name": { "type": "string", "x-isnullable": false @@ -1116,11 +1146,26 @@ func init() { "repository": { "type": "object", "properties": { + "allowUpdateBranch": { + "type": "boolean", + "x-isnullable": false, + "x-omitempty": false + }, "archived": { "type": "boolean", "x-isnullable": false, "x-omitempty": false }, + "autoMergeAllowed": { + "type": "boolean", + "x-isnullable": false, + "x-omitempty": false + }, + "deleteBranchOnMerge": { + "type": "boolean", + "x-isnullable": false, + "x-omitempty": false + }, "name": { "type": "string", "x-isnullable": false @@ -1135,17 +1180,32 @@ func init() { "repositoryDetails": { "type": "object", "properties": { + "allowUpdateBranch": { + "type": "boolean", + "x-isnullable": false, + "x-omitempty": false + }, "archived": { "type": "boolean", "x-isnullable": false, "x-omitempty": false }, + "autoMergeAllowed": { + "type": "boolean", + "x-isnullable": false, + "x-omitempty": false + }, "collaborators": { "type": "array", "items": { "$ref": "#/definitions/RepositoryDetailsCollaboratorsItems0" } }, + "deleteBranchOnMerge": { + "type": "boolean", + "x-isnullable": false, + "x-omitempty": false + }, "name": { "type": "string", "x-isnullable": false