diff --git a/docs/resources/team.md b/docs/resources/team.md index 3d1eef9..8a30fd4 100644 --- a/docs/resources/team.md +++ b/docs/resources/team.md @@ -69,7 +69,6 @@ resource "gitea_team" "test_team_restricted" { - `can_create_repos` (Boolean) Flag if the Teams members should be able to create Rpositories in the Organisation - `description` (String) Description of the Team - `include_all_repositories` (Boolean) Flag if the Teams members should have access to all Repositories in the Organisation -- `members` (List of String) List of Users that should be part of this team - `permission` (String) Permissions associated with this Team Can be `none`, `read`, `write`, `admin` or `owner` - `repositories` (List of String) List of Repositories that should be part of this team diff --git a/docs/resources/team_members.md b/docs/resources/team_members.md new file mode 100644 index 0000000..35eb01d --- /dev/null +++ b/docs/resources/team_members.md @@ -0,0 +1,51 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "gitea_team_members Resource - terraform-provider-gitea" +subcategory: "" +description: |- + gitea_team_members manages all members of a single team. This resource will be recreated on member changes. +--- + +# gitea_team_members (Resource) + +`gitea_team_members` manages all members of a single team. This resource will be recreated on member changes. + +## Example Usage + +```terraform +resource "gitea_org" "example_org" { + name = "m_example_org" +} + +resource "gitea_user" "example_users" { + count = 5 + username = "m_example_user_${count.index}" + login_name = "m_example_user_${count.index}" + password = "Geheim1!" + email = "m_example_user_${count.index}@user.dev" +} + +resource "gitea_team" "example_team" { + name = "m_example_team" + organisation = gitea_org.example_org.name + description = "An example of team membership" + permission = "read" +} + +resource "gitea_team_members" "example_members" { + team_id = gitea_team.example_team.id + members = [for user in gitea_user.example_users : user.username] +} +``` + + +## Schema + +### Required + +- `members` (List of String) The user names of the members of the team. +- `team_id` (Number) The ID of the team. + +### Read-Only + +- `id` (String) The ID of this resource. diff --git a/docs/resources/team_membership.md b/docs/resources/team_membership.md new file mode 100644 index 0000000..a2f4a20 --- /dev/null +++ b/docs/resources/team_membership.md @@ -0,0 +1,52 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "gitea_team_membership Resource - terraform-provider-gitea" +subcategory: "" +description: |- + gitea_team_membership manages a single user's membership of a single team. +--- + +# gitea_team_membership (Resource) + +`gitea_team_membership` manages a single user's membership of a single team. + +## Example Usage + +```terraform +resource "gitea_org" "example_org" { + name = "m_example_org" +} + +resource "gitea_user" "example_users" { + count = 5 + username = "m_example_user_${count.index}" + login_name = "m_example_user_${count.index}" + password = "Geheim1!" + email = "m_example_user_${count.index}@user.dev" +} + +resource "gitea_team" "example_team" { + name = "m_example_team" + organisation = gitea_org.example_org.name + description = "An example team for membership testing" + permission = "read" +} + +resource "gitea_team_membership" "example_team_memberships" { + for_each = { for user in gitea_user.example_users : user.username => user } + team_id = gitea_team.example_team.id + username = each.value["username"] +} +``` + + +## Schema + +### Required + +- `team_id` (Number) The ID of the team. +- `username` (String) The username of the team member. + +### Read-Only + +- `id` (String) The ID of this resource. diff --git a/examples/resources/gitea_team_members/resource.tf b/examples/resources/gitea_team_members/resource.tf new file mode 100644 index 0000000..3a6692c --- /dev/null +++ b/examples/resources/gitea_team_members/resource.tf @@ -0,0 +1,23 @@ +resource "gitea_org" "example_org" { + name = "m_example_org" +} + +resource "gitea_user" "example_users" { + count = 5 + username = "m_example_user_${count.index}" + login_name = "m_example_user_${count.index}" + password = "Geheim1!" + email = "m_example_user_${count.index}@user.dev" +} + +resource "gitea_team" "example_team" { + name = "m_example_team" + organisation = gitea_org.example_org.name + description = "An example of team membership" + permission = "read" +} + +resource "gitea_team_members" "example_members" { + team_id = gitea_team.example_team.id + members = [for user in gitea_user.example_users : user.username] +} diff --git a/examples/resources/gitea_team_membership/resource.tf b/examples/resources/gitea_team_membership/resource.tf new file mode 100644 index 0000000..f3f5f10 --- /dev/null +++ b/examples/resources/gitea_team_membership/resource.tf @@ -0,0 +1,24 @@ +resource "gitea_org" "example_org" { + name = "m_example_org" +} + +resource "gitea_user" "example_users" { + count = 5 + username = "m_example_user_${count.index}" + login_name = "m_example_user_${count.index}" + password = "Geheim1!" + email = "m_example_user_${count.index}@user.dev" +} + +resource "gitea_team" "example_team" { + name = "m_example_team" + organisation = gitea_org.example_org.name + description = "An example team for membership testing" + permission = "read" +} + +resource "gitea_team_membership" "example_team_memberships" { + for_each = { for user in gitea_user.example_users : user.username => user } + team_id = gitea_team.example_team.id + username = each.value["username"] +} diff --git a/gitea/provider.go b/gitea/provider.go index 6a16ca6..808a14a 100644 --- a/gitea/provider.go +++ b/gitea/provider.go @@ -81,6 +81,8 @@ func Provider() *schema.Provider { "gitea_fork": resourceGiteaFork(), "gitea_public_key": resourceGiteaPublicKey(), "gitea_team": resourceGiteaTeam(), + "gitea_team_membership": resourceGiteaTeamMembership(), + "gitea_team_members": resourceGiteaTeamMembers(), "gitea_git_hook": resourceGiteaGitHook(), "gitea_token": resourceGiteaToken(), "gitea_repository_key": resourceGiteaRepositoryKey(), diff --git a/gitea/resource_gitea_team.go b/gitea/resource_gitea_team.go index e65798f..77b92f9 100644 --- a/gitea/resource_gitea_team.go +++ b/gitea/resource_gitea_team.go @@ -18,7 +18,6 @@ const ( TeamCreateRepoFlag string = "can_create_repos" TeamIncludeAllReposFlag string = "include_all_repositories" TeamUnits string = "units" - TeamMembers string = "members" TeamRepositories string = "repositories" ) @@ -94,17 +93,6 @@ func resourceTeamCreate(d *schema.ResourceData, meta interface{}) (err error) { return } - users := d.Get(TeamMembers).([]interface{}) - - for _, user := range users { - if user != "" { - _, err = client.AddTeamMember(team.ID, user.(string)) - if err != nil { - return err - } - } - } - if !includeAllRepos { err = setTeamRepositories(team, d, meta, false) if err != nil { @@ -181,17 +169,6 @@ func resourceTeamUpdate(d *schema.ResourceData, meta interface{}) (err error) { return err } - users := d.Get(TeamMembers).([]interface{}) - - for _, user := range users { - if user != "" { - _, err = client.AddTeamMember(team.ID, user.(string)) - if err != nil { - return err - } - } - } - if !includeAllRepos { err = setTeamRepositories(team, d, meta, true) if err != nil { @@ -240,8 +217,8 @@ func setTeamResourceData(team *gitea.Team, d *schema.ResourceData, meta interfac d.Set(TeamPermissions, string(team.Permission)) d.Set(TeamIncludeAllReposFlag, team.IncludesAllRepositories) d.Set(TeamUnits, d.Get(TeamUnits).(string)) - d.Set(TeamMembers, d.Get(TeamMembers)) d.Set(TeamRepositories, d.Get(TeamRepositories)) + return } @@ -304,16 +281,6 @@ func resourceGiteaTeam() *schema.Resource { Description: "List of types of Repositories that should be allowed to be created from Team members.\n" + "Can be `repo.code`, `repo.issues`, `repo.ext_issues`, `repo.wiki`, `repo.pulls`, `repo.releases`, `repo.projects` and/or `repo.ext_wiki`", }, - "members": { - Type: schema.TypeList, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - Optional: true, - Required: false, - Computed: true, - Description: "List of Users that should be part of this team", - }, "repositories": { Type: schema.TypeList, Elem: &schema.Schema{ diff --git a/gitea/resource_gitea_team_members.go b/gitea/resource_gitea_team_members.go new file mode 100644 index 0000000..455a748 --- /dev/null +++ b/gitea/resource_gitea_team_members.go @@ -0,0 +1,150 @@ +package gitea + +import ( + "fmt" + + "code.gitea.io/sdk/gitea" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +const ( + membersTeamID string = "team_id" + membersTeamMembers string = "members" +) + +func getTeamMembers(team_id int, meta interface{}) (membersNames []string, err error) { + client := meta.(*gitea.Client) + + var memberNames []string + var members []*gitea.User + + // Get all pages of users + page := 1 + for { + // Set options for current page + opts := gitea.ListTeamMembersOptions{ + ListOptions: gitea.ListOptions{Page: page, PageSize: 50}, + } + + // Get page of team members + members, _, err = client.ListTeamMembers(int64(team_id), opts) + if err != nil { + return nil, err + } + + // If no members were returned, we are done + if len(members) == 0 { + break + } + + // Update list of usernames with data from current page + for _, m := range members { + memberNames = append(memberNames, m.UserName) + } + + // Next page + page += 1 + } + + return memberNames, nil +} + +func resourceTeamMembersCreate(d *schema.ResourceData, meta interface{}) (err error) { + client := meta.(*gitea.Client) + team_id := d.Get(membersTeamID).(int) + + var memberNames []string + + // What if team already has member? + // What if user is already in the team? + // What if user does not exist? + + // Add members to the team + for _, name := range d.Get(membersTeamMembers).(*schema.Set).List() { + _ , err = client.AddTeamMember(int64(team_id), name.(string)) + if err != nil { + return err + } + // Update list of usernames of the team members + memberNames = append(memberNames, name.(string)) + } + + err = setTeamMembersData(team_id, memberNames, d) + + return +} + +func resourceTeamMembersRead(d *schema.ResourceData, meta interface{}) (err error) { + team_id := d.Get(membersTeamID).(int) + + memberNames, err := getTeamMembers(team_id, meta) + if err != nil { + return err + } + + err = setTeamMembersData(team_id, memberNames, d) + + return +} + +func resourceTeamMembersDelete(d *schema.ResourceData, meta interface{}) (err error) { + client := meta.(*gitea.Client) + team_id := d.Get(membersTeamID).(int) + + var memberNames []string + + memberNames , err = getTeamMembers(team_id, meta) + if err != nil { + return err + } + + // Delete all memberships + for _, username := range memberNames { + _, err = client.RemoveTeamMember(int64(team_id), username) + if err != nil { + return err + } + } + + return +} + +func setTeamMembersData(team_id int, memberNames []string, d *schema.ResourceData) (err error) { + d.SetId(fmt.Sprintf("%d", team_id)) + d.Set(membersTeamID, team_id) + d.Set(membersTeamMembers, memberNames) + + return +} + +func resourceGiteaTeamMembers() *schema.Resource { + return &schema.Resource{ + Read: resourceTeamMembersRead, + Create: resourceTeamMembersCreate, + Delete: resourceTeamMembersDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: map[string]*schema.Schema{ + "team_id": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + Description: "The ID of the team.", + }, + "members": { + // TypeSet is better than TypeList because + // reordering the members will not trigger recreation + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Required: true, + ForceNew: true, + Description: "The user names of the members of the team.", + }, + + }, + Description: "`gitea_team_members` manages all members of a single team. This resource will be recreated on member changes.", + } +} diff --git a/gitea/resource_gitea_team_membership.go b/gitea/resource_gitea_team_membership.go new file mode 100644 index 0000000..a7c05a1 --- /dev/null +++ b/gitea/resource_gitea_team_membership.go @@ -0,0 +1,111 @@ +package gitea + +import ( + "fmt" + + "code.gitea.io/sdk/gitea" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +const ( + membershipTeamID string = "team_id" + membershipUserName string = "username" +) + +func resourceTeamMembershipCreate(d *schema.ResourceData, meta interface{}) (err error) { + client := meta.(*gitea.Client) + + team_id := d.Get(membershipTeamID).(int) + username := d.Get(membershipUserName).(string) + + // Create the membership + _ , err = client.AddTeamMember(int64(team_id), username) + + // What if the membership exists? Consider error messages + // Does this do anything? Will err not be return in the end anyway + if err != nil { + return + } + + err = setTeamMembershipData(team_id, username, d) + + return +} + +func resourceTeamMembershipRead(d *schema.ResourceData, meta interface{}) (err error) { + client := meta.(*gitea.Client) + + var resp *gitea.Response + + team_id := d.Get(membershipTeamID).(int) + username := d.Get(membershipUserName).(string) + + // Attempt to get the user from the team. If the user is not a member of the team, this will return a 404 + _, resp, err = client.GetTeamMember(int64(team_id), username) + if err != nil { + return err + } + + // The membership does not exist in Gitea + if resp.StatusCode == 404 { + // No ID in the resource indicates that it does not exist + d.SetId("") + return nil + } + + err = setTeamMembershipData(team_id, username, d) + + return +} + +func resourceTeamMembershipDelete(d *schema.ResourceData, meta interface{}) (err error) { + client := meta.(*gitea.Client) + + team_id := d.Get(membershipTeamID).(int) + username := d.Get(membershipUserName).(string) + + // Delete the membership + _, err = client.RemoveTeamMember(int64(team_id), username) + + if err != nil { + return err + } + + return +} + +func setTeamMembershipData(team_id int, username string, d *schema.ResourceData) (err error) { + // This can't be team or usename only as that would not be unique since the + // team can have multiple members and the user can have multiple memberships. + d.SetId(fmt.Sprintf("%d_%s", team_id, username)) + d.Set(membershipTeamID, team_id) + d.Set(membershipUserName, username) + + return +} + +func resourceGiteaTeamMembership() *schema.Resource { + return &schema.Resource{ + Read: resourceTeamMembershipRead, + Create: resourceTeamMembershipCreate, + Delete: resourceTeamMembershipDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: map[string]*schema.Schema{ + "team_id": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + Description: "The ID of the team.", + }, + "username": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The username of the team member.", + }, + }, + Description: "`gitea_team_membership` manages a single user's membership of a single team.", + } +}