Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add azuredevops_group_entitlment #870

Merged
merged 8 commits into from
Sep 8, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,5 +223,6 @@ The following Environment Variables must be set in your shell prior to running a
- `AZDO_DOCKERREGISTRY_SERVICE_CONNECTION_USERNAME`
- `AZDO_GITHUB_SERVICE_CONNECTION_PAT`
- `AZDO_TEST_AAD_USER_EMAIL`
- `AZDO_TEST_AAD_GROUP_ID`
stanleyz marked this conversation as resolved.
Show resolved Hide resolved

**Note:** Acceptance tests create real resources in Azure DevOps which often cost money to run.
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
//go:build (all || resource_group_entitlement) && !exclude_resource_group_entitlement
// +build all resource_group_entitlement
// +build !exclude_resource_group_entitlement

package acceptancetests

import (
"fmt"
"os"
"strings"
"testing"

"github.com/golang/mock/gomock"
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/memberentitlementmanagement"
"github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/acceptancetests/testutils"
"github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/client"
"github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/utils"
)

func TestAccGroupEntitlement_Create(t *testing.T) {
tfNode := "azuredevops_group_entitlement.group"
displayName := "group-038c153d-c86e-443c-b6f6-3d97378025d0"
resource.ParallelTest(t, resource.TestCase{
Providers: testutils.GetProviders(),
CheckDestroy: checkGroupEntitlementDestroyed,
Steps: []resource.TestStep{
{
Config: testutils.HclGroupEntitlementResource(displayName),
Check: resource.ComposeTestCheckFunc(
checkGroupEntitlementExists(displayName),
resource.TestCheckResourceAttrSet(tfNode, "descriptor"),
),
},
},
})
}

func TestAccGroupEntitlement_AAD_Create(t *testing.T) {
tfNode := "azuredevops_group_entitlement.group_aad"
originId := os.Getenv("AZDO_TEST_AAD_GROUP_ID")
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testutils.PreCheck(t, &[]string{"AZDO_TEST_AAD_GROUP_ID"}) },
Providers: testutils.GetProviders(),
CheckDestroy: checkGroupEntitlementDestroyed,
Steps: []resource.TestStep{
{
Config: testutils.HclGroupEntitlementResourceAAD(originId),
Check: resource.ComposeTestCheckFunc(
checkGroupEntitlementAADExists(originId),
resource.TestCheckResourceAttrSet(tfNode, "descriptor"),
),
},
},
})
}

// Given the principalName of an AzDO groupEntitlement, this will return a function that will check whether
// or not the groupEntitlement (1) exists in the state and (2) exist in AzDO and (3) has the correct name
func checkGroupEntitlementExists(expectedDisplayName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
resource, ok := s.RootModule().Resources["azuredevops_group_entitlement.group"]
if !ok {
return fmt.Errorf("Did not find a GroupEntitlement in the TF state")
}

clients := testutils.GetProvider().Meta().(*client.AggregatedClient)
id, err := uuid.Parse(resource.Primary.ID)
if err != nil {
return fmt.Errorf("Error parsing GroupEntitlement ID, got %s: %v", resource.Primary.ID, err)
}

groupEntitlement, err := clients.MemberEntitleManagementClient.GetGroupEntitlement(clients.Ctx, memberentitlementmanagement.GetGroupEntitlementArgs{
GroupId: &id,
})

if err != nil {
return fmt.Errorf("GroupEntitlement with ID=%s cannot be found!. Error=%v", id, err)
}

if !strings.EqualFold(strings.ToLower(*groupEntitlement.Group.DisplayName), strings.ToLower(expectedDisplayName)) {
return fmt.Errorf("GroupEntitlement with ID=%s and principalName=%s has displayName=%s, but expected displayName=%s", resource.Primary.ID, *groupEntitlement.Group.PrincipalName, *groupEntitlement.Group.DisplayName, expectedDisplayName)
}

return nil
}
}

func checkGroupEntitlementAADExists(expectedOriginId string) resource.TestCheckFunc {
return func(s *terraform.State) error {
resource, ok := s.RootModule().Resources["azuredevops_group_entitlement.group_aad"]
if !ok {
return fmt.Errorf("Did not find a GroupEntitlement in the TF state")
}

clients := testutils.GetProvider().Meta().(*client.AggregatedClient)
id, err := uuid.Parse(resource.Primary.ID)
if err != nil {
return fmt.Errorf("Error parsing GroupEntitlement ID, got %s: %v", resource.Primary.ID, err)
}

groupEntitlement, err := clients.MemberEntitleManagementClient.GetGroupEntitlement(clients.Ctx, memberentitlementmanagement.GetGroupEntitlementArgs{
GroupId: &id,
})

if err != nil {
return fmt.Errorf("GroupEntitlement with ID=%s cannot be found!. Error=%v", id, err)
}

if !strings.EqualFold(strings.ToLower(*groupEntitlement.Group.OriginId), strings.ToLower(expectedOriginId)) {
return fmt.Errorf("GroupEntitlement with ID=%s has originId=%s, but expected originId=%s", resource.Primary.ID, *groupEntitlement.Group.OriginId, expectedOriginId)
}

return nil
}
}

// verifies that all projects referenced in the state are destroyed. This will be invoked
// *after* terraform destroys the resource but *before* the state is wiped clean.
func checkGroupEntitlementDestroyed(s *terraform.State) error {
clients := testutils.GetProvider().Meta().(*client.AggregatedClient)

//verify that every users referenced in the state does not exist in AzDO
for _, resource := range s.RootModule().Resources {
if resource.Type != "azuredevops_group_entitlement" {
continue
}

id, err := uuid.Parse(resource.Primary.ID)
if err != nil {
return fmt.Errorf("Error parsing GroupEntitlement ID, got %s: %v", resource.Primary.ID, err)
}

groupEntitlement, err := clients.MemberEntitleManagementClient.GetGroupEntitlement(clients.Ctx, memberentitlementmanagement.GetGroupEntitlementArgs{
GroupId: &id,
})

if err != nil {
if utils.ResponseWasNotFound(err) {
return nil
}
return fmt.Errorf("Bad: Get GroupEntitlment : %+v", err)
}

if groupEntitlement != nil && groupEntitlement.LicenseRule != nil && string(*groupEntitlement.LicenseRule.Status) != "none" {
return fmt.Errorf("Status should be none : %s with readGroupEntitlement error %v", string(*groupEntitlement.LicenseRule.Status), err)
}
}

return nil
}

type matchAddGroupEntitlementArgs struct {
t *testing.T
x memberentitlementmanagement.AddGroupEntitlementArgs
}

func MatchAddGroupEntitlementArgs(t *testing.T, x memberentitlementmanagement.AddGroupEntitlementArgs) gomock.Matcher {
return &matchAddGroupEntitlementArgs{t, x}
}

func (m *matchAddGroupEntitlementArgs) Matches(x interface{}) bool {
args := x.(memberentitlementmanagement.AddGroupEntitlementArgs)
m.t.Logf("MatchAddGroupEntitlementArgs:\nVALUE: account_license_type: [%s], licensing_source: [%s], origin: [%s], origin_id: [%s], display_name: [%s], principal_name: [%s]\n REF: account_license_type: [%s], licensing_source: [%s], origin: [%s], origin_id: [%s], display_name: [%s], principal_name: [%s]\n",
*args.GroupEntitlement.LicenseRule.AccountLicenseType,
*args.GroupEntitlement.LicenseRule.LicensingSource,
*args.GroupEntitlement.Group.Origin,
*args.GroupEntitlement.Group.OriginId,
*args.GroupEntitlement.Group.DisplayName,
*args.GroupEntitlement.Group.PrincipalName,
*m.x.GroupEntitlement.LicenseRule.AccountLicenseType,
*m.x.GroupEntitlement.LicenseRule.LicensingSource,
*m.x.GroupEntitlement.Group.Origin,
*m.x.GroupEntitlement.Group.OriginId,
*m.x.GroupEntitlement.Group.DisplayName,
*m.x.GroupEntitlement.Group.PrincipalName)

return *args.GroupEntitlement.LicenseRule.AccountLicenseType == *m.x.GroupEntitlement.LicenseRule.AccountLicenseType &&
*args.GroupEntitlement.Group.Origin == *m.x.GroupEntitlement.Group.Origin &&
*args.GroupEntitlement.Group.OriginId == *m.x.GroupEntitlement.Group.OriginId &&
*args.GroupEntitlement.Group.PrincipalName == *m.x.GroupEntitlement.Group.PrincipalName
}

func (m *matchAddGroupEntitlementArgs) String() string {
return fmt.Sprintf("account_license_type: [%s], licensing_source: [%s], origin: [%s], origin_id: [%s], display_name: [%s], principal_name: [%s]",
*m.x.GroupEntitlement.LicenseRule.AccountLicenseType,
*m.x.GroupEntitlement.LicenseRule.LicensingSource,
*m.x.GroupEntitlement.Group.Origin,
*m.x.GroupEntitlement.Group.OriginId,
*m.x.GroupEntitlement.Group.DisplayName,
*m.x.GroupEntitlement.Group.PrincipalName)
}
20 changes: 20 additions & 0 deletions azuredevops/internal/acceptancetests/testutils/hcl.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,26 @@ resource "azuredevops_user_entitlement" "user" {
}`, principalName)
}

// HclGroupEntitlementResource HCL describing an AzDO GroupEntitlement
func HclGroupEntitlementResource(displayName string) string {
return fmt.Sprintf(`
resource "azuredevops_group_entitlement" "group" {
display_name = "%s"
account_license_type = "express"
}`, displayName)
}

// HclGroupEntitlementResource HCL describing an AzDO GroupEntitlement linked
// with Azure AD
func HclGroupEntitlementResourceAAD(originId string) string {
return fmt.Sprintf(`
resource "azuredevops_group_entitlement" "group_aad" {
origin_id = "%s"
origin = "aad"
account_license_type = "express"
}`, originId)
}

// HclServiceEndpointGitHubResource HCL describing an AzDO service endpoint
func HclServiceEndpointGitHubResource(projectName string, serviceEndpointName string) string {
serviceEndpointResource := fmt.Sprintf(`
Expand Down
Loading