diff --git a/integration_tests/teamcity_data.tar.gz b/integration_tests/teamcity_data.tar.gz index b220efb..919f6dc 100644 Binary files a/integration_tests/teamcity_data.tar.gz and b/integration_tests/teamcity_data.tar.gz differ diff --git a/teamcity/role_assignment.go b/teamcity/role_assignment.go new file mode 100644 index 0000000..3cb4c9e --- /dev/null +++ b/teamcity/role_assignment.go @@ -0,0 +1,105 @@ +package teamcity + +import ( + "fmt" + "net/http" + + "github.com/dghubble/sling" +) + +// GroupRoleAssignment is the model for role assignment for groups in TeamCity +type GroupRoleAssignment struct { + GroupKey string //`json:"groupkey,omitempty" xml:"groupkey"` + RoleID string //`json:"roleid,omitempty" xml:"roleid"` + Scope string //`json:"scope,omitempty" xml:"scope"` +} + +// RoleAssignmentReference represents a response of a request to assign role to a group or a user +type RoleAssignmentReference struct { + RoleID string `json:"roleId,omitempty" xml:"roleId"` + Scope string `json:"scope,omitempty" xml:"scope"` + Href string `json:"href,omitempty" xml:"href"` +} + +type roleAssignmentsJSON struct { + Items []RoleAssignmentReference `json:"role"` +} + +// NewGroupRoleAssignment returns an instance of a GroupRoleAssignment. A non-empty groupKey, roleId, and scope is required. +func NewGroupRoleAssignment(groupKey string, roleID string, scope string) (*GroupRoleAssignment, error) { + if groupKey == "" { + return nil, fmt.Errorf("GroupKey is required") + } + + if roleID == "" { + return nil, fmt.Errorf("RoleId is required") + } + + if scope == "" { + return nil, fmt.Errorf("scope is required. Use \"g\" at the global level for System Administrators, otherwise for other roles, use \"p:_Root\" for the root project, or \"p:\" for other projects") + } + + return &GroupRoleAssignment{ + GroupKey: groupKey, + RoleID: roleID, + Scope: scope, + }, nil +} + +// RoleAssignmentService has operations for handling role assignments for groups or users +type RoleAssignmentService struct { + groupSling *sling.Sling + httpClient *http.Client + groupHelper *restHelper +} + +func newRoleAssignmentService(base *sling.Sling, httpClient *http.Client) *RoleAssignmentService { + groupSling := base.New().Path(fmt.Sprintf("userGroups/")) + return &RoleAssignmentService{ + httpClient: httpClient, + groupSling: groupSling, + groupHelper: newRestHelperWithSling(httpClient, groupSling), + } +} + +// AssignToGroup adds a role assignment to a group +func (s *RoleAssignmentService) AssignToGroup(assignment *GroupRoleAssignment) (*RoleAssignmentReference, error) { + var out RoleAssignmentReference + + // URL for assigning role is /app/rest/userGroups/{groupLocator}/roles/{roleId}/{scope} + err := s.groupHelper.post(fmt.Sprintf("%s/roles/%s/%s", assignment.GroupKey, assignment.RoleID, assignment.Scope), nil, &out, "AssignToGroup role to group") + if err != nil { + return nil, err + } + return &out, nil +} + +// GetForGroup get a specific role assignment for a group +func (s *RoleAssignmentService) GetForGroup(assignment *GroupRoleAssignment) (*RoleAssignmentReference, error) { + var out RoleAssignmentReference + + // URL for getting a specific role assignments is /app/rest/userGroups/{groupLocator}/roles/{roleId}/{scope} + err := s.groupHelper.get(fmt.Sprintf("%s/roles/%s/%s", assignment.GroupKey, assignment.RoleID, assignment.Scope), &out, "GetForGroup role assignmens for group") + if err != nil { + return nil, err + } + return &out, nil +} + +// GetAllForGroup gets all the role assignments for a group +func (s *RoleAssignmentService) GetAllForGroup(group *Group) ([]RoleAssignmentReference, error) { + var aux roleAssignmentsJSON + + // URL for getting role assignments is /app/rest/userGroups/{groupLocator}/roles + err := s.groupHelper.get(fmt.Sprintf("%s/roles", group.Key), &aux, "GetForGroup role assignments for group") + if err != nil { + return nil, err + } + return aux.Items, nil +} + +// UnassignFromGroup removes the role assignment from a group +func (s *RoleAssignmentService) UnassignFromGroup(assignment *GroupRoleAssignment) error { + // URL for unassigning role is /app/rest/userGroups/{groupLocator}/roles/{roleId}/{scope} + return s.groupHelper.delete(fmt.Sprintf("%s/roles/%s/%s", assignment.GroupKey, assignment.RoleID, assignment.Scope), "UnassignFromGroup role from group") +} diff --git a/teamcity/role_assignment_test.go b/teamcity/role_assignment_test.go new file mode 100644 index 0000000..abadb62 --- /dev/null +++ b/teamcity/role_assignment_test.go @@ -0,0 +1,130 @@ +package teamcity_test + +import ( + "testing" + + "github.com/cvbarros/go-teamcity/teamcity" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRoleAssignment_AssignGlobalSysAdminToGroup(t *testing.T) { + client := setup() + + newGroup, _ := teamcity.NewGroup("TESTGROUPKEY", "Test Group Name", "Test Group Description") + newGroupRoleAssignment, _ := teamcity.NewGroupRoleAssignment("TESTGROUPKEY", "SYSTEM_ADMIN", "g") // "g" is for sys admins at the global level + + actualGroup, err := client.Groups.Create(newGroup) + require.NoError(t, err) + require.NotNil(t, actualGroup) + + createdGroupRoleAssignment, err := client.RoleAssignments.AssignToGroup(newGroupRoleAssignment) + require.NoError(t, err) + require.NotNil(t, createdGroupRoleAssignment) + assert.NotEmpty(t, createdGroupRoleAssignment.RoleID) + assert.NotEmpty(t, createdGroupRoleAssignment.Scope) + assert.NotEmpty(t, createdGroupRoleAssignment.Href) + + groupRoleAssignments, err := client.RoleAssignments.GetAllForGroup(newGroup) + require.NoError(t, err) + assert.Equal(t, 1, len(groupRoleAssignments)) + actualGroupRoleAssignmentReference := groupRoleAssignments[0] + assert.Equal(t, createdGroupRoleAssignment.RoleID, actualGroupRoleAssignmentReference.RoleID) + assert.Equal(t, createdGroupRoleAssignment.Scope, actualGroupRoleAssignmentReference.Scope) + assert.Equal(t, createdGroupRoleAssignment.Href, actualGroupRoleAssignmentReference.Href) + + actualGroupRoleAssignmentReference2, err := client.RoleAssignments.GetForGroup(newGroupRoleAssignment) + require.NoError(t, err) + assert.Equal(t, createdGroupRoleAssignment.RoleID, actualGroupRoleAssignmentReference2.RoleID) + assert.Equal(t, createdGroupRoleAssignment.Scope, actualGroupRoleAssignmentReference2.Scope) + assert.Equal(t, createdGroupRoleAssignment.Href, actualGroupRoleAssignmentReference2.Href) + + // Clean up group after test + cleanUpGroup(t, client, actualGroup.Key) +} + +func TestRoleAssignment_AssignToGroup(t *testing.T) { + client := setup() + + parent, _ := teamcity.NewProject("ParentProject", "Parent Project", "") + child, _ := teamcity.NewProject("ChildProject", "Child Project", "ParentProject") + + _, err := client.Projects.Create(parent) + require.NoError(t, err) + created, err := client.Projects.Create(child) + require.NoError(t, err) + + newGroup, _ := teamcity.NewGroup("TESTGROUPKEY", "Test Group Name", "Test Group Description") + newGroupRoleAssignment, _ := teamcity.NewGroupRoleAssignment("TESTGROUPKEY", "PROJECT_DEVELOPER", "p:"+created.ID) + + actualGroup, err := client.Groups.Create(newGroup) + require.NoError(t, err) + require.NotNil(t, actualGroup) + + createdGroupRoleAssignment, err := client.RoleAssignments.AssignToGroup(newGroupRoleAssignment) + require.NoError(t, err) + require.NotNil(t, createdGroupRoleAssignment) + assert.NotEmpty(t, createdGroupRoleAssignment.RoleID) + assert.NotEmpty(t, createdGroupRoleAssignment.Scope) + assert.NotEmpty(t, createdGroupRoleAssignment.Href) + + groupRoleAssignments, err := client.RoleAssignments.GetAllForGroup(newGroup) + require.NoError(t, err) + assert.Equal(t, 1, len(groupRoleAssignments)) + actualGroupRoleAssignmentReference := groupRoleAssignments[0] + assert.Equal(t, createdGroupRoleAssignment.RoleID, actualGroupRoleAssignmentReference.RoleID) + assert.Equal(t, createdGroupRoleAssignment.Scope, actualGroupRoleAssignmentReference.Scope) + assert.Equal(t, createdGroupRoleAssignment.Href, actualGroupRoleAssignmentReference.Href) + + actualGroupRoleAssignmentReference2, err := client.RoleAssignments.GetForGroup(newGroupRoleAssignment) + require.NoError(t, err) + assert.Equal(t, createdGroupRoleAssignment.RoleID, actualGroupRoleAssignmentReference2.RoleID) + assert.Equal(t, createdGroupRoleAssignment.Scope, actualGroupRoleAssignmentReference2.Scope) + assert.Equal(t, createdGroupRoleAssignment.Href, actualGroupRoleAssignmentReference2.Href) + + // Clean up group and projects after test + cleanUpGroup(t, client, actualGroup.Key) + cleanUpProject(t, client, "ParentProject") +} + +func TestRoleAssignment_UnassignFromGroup(t *testing.T) { + client := setup() + + parent, _ := teamcity.NewProject("ParentProject", "Parent Project", "") + child, _ := teamcity.NewProject("ChildProject", "Child Project", "ParentProject") + + _, err := client.Projects.Create(parent) + require.NoError(t, err) + created, err := client.Projects.Create(child) + require.NoError(t, err) + + newGroup, _ := teamcity.NewGroup("TESTGROUPKEY", "Test Group Name", "Test Group Description") + newGroupRoleAssignment, _ := teamcity.NewGroupRoleAssignment("TESTGROUPKEY", "PROJECT_DEVELOPER", "p:"+created.ID) + + actualGroup, err := client.Groups.Create(newGroup) + require.NoError(t, err) + require.NotNil(t, actualGroup) + + createdGroupRoleAssignment, err := client.RoleAssignments.AssignToGroup(newGroupRoleAssignment) + require.NoError(t, err) + require.NotNil(t, createdGroupRoleAssignment) + assert.NotEmpty(t, createdGroupRoleAssignment.RoleID) + assert.NotEmpty(t, createdGroupRoleAssignment.Scope) + assert.NotEmpty(t, createdGroupRoleAssignment.Href) + + err = client.RoleAssignments.UnassignFromGroup(newGroupRoleAssignment) + require.NoError(t, err) + + groupRoleAssignments, err := client.RoleAssignments.GetAllForGroup(newGroup) + require.NoError(t, err) + assert.Equal(t, 0, len(groupRoleAssignments)) + + // The Role has been unassigneded, so expect error, and message to contain 404 (NOT FOUND) + _, err = client.RoleAssignments.GetForGroup(newGroupRoleAssignment) + require.Error(t, err) + assert.Contains(t, err.Error(), "404") + + // Clean up group and projects after test + cleanUpGroup(t, client, actualGroup.Key) + cleanUpProject(t, client, "ParentProject") +} diff --git a/teamcity/teamcity.go b/teamcity/teamcity.go index 6a1c93b..b6c1430 100644 --- a/teamcity/teamcity.go +++ b/teamcity/teamcity.go @@ -61,11 +61,12 @@ type Client struct { commonBase *sling.Sling - Projects *ProjectService - BuildTypes *BuildTypeService - Server *ServerService - VcsRoots *VcsRootService - Groups *GroupService + Projects *ProjectService + BuildTypes *BuildTypeService + Server *ServerService + VcsRoots *VcsRootService + Groups *GroupService + RoleAssignments *RoleAssignmentService } func NewClient(auth Auth, httpClient *http.Client) (*Client, error) { @@ -105,14 +106,15 @@ func newClientInstance(auth Auth, address string, httpClient *http.Client) (*Cli } return &Client{ - address: address, - HTTPClient: httpClient, - commonBase: sharedClient, - Projects: newProjectService(sharedClient.New(), httpClient), - BuildTypes: newBuildTypeService(sharedClient.New(), httpClient), - Server: newServerService(sharedClient.New()), - VcsRoots: newVcsRootService(sharedClient.New(), httpClient), - Groups: newGroupService(sharedClient.New(), httpClient), + address: address, + HTTPClient: httpClient, + commonBase: sharedClient, + Projects: newProjectService(sharedClient.New(), httpClient), + BuildTypes: newBuildTypeService(sharedClient.New(), httpClient), + Server: newServerService(sharedClient.New()), + VcsRoots: newVcsRootService(sharedClient.New(), httpClient), + Groups: newGroupService(sharedClient.New(), httpClient), + RoleAssignments: newRoleAssignmentService(sharedClient.New(), httpClient), }, nil }