diff --git a/azuread/config.go b/azuread/config.go index 20646f43b9..397f6fbdbb 100644 --- a/azuread/config.go +++ b/azuread/config.go @@ -29,6 +29,7 @@ type ArmClient struct { // azure AD clients applicationsClient graphrbac.ApplicationsClient + groupsClient graphrbac.GroupsClient servicePrincipalsClient graphrbac.ServicePrincipalsClient } @@ -73,6 +74,9 @@ func (c *ArmClient) registerGraphRBACClients(endpoint, tenantID string, authoriz c.applicationsClient = graphrbac.NewApplicationsClientWithBaseURI(endpoint, tenantID) configureClient(&c.applicationsClient.Client, authorizer) + c.groupsClient = graphrbac.NewGroupsClientWithBaseURI(endpoint, tenantID) + configureClient(&c.groupsClient.Client, authorizer) + c.servicePrincipalsClient = graphrbac.NewServicePrincipalsClientWithBaseURI(endpoint, tenantID) configureClient(&c.servicePrincipalsClient.Client, authorizer) } diff --git a/azuread/data_source_group.go b/azuread/data_source_group.go new file mode 100644 index 0000000000..aa09b34b1e --- /dev/null +++ b/azuread/data_source_group.go @@ -0,0 +1,71 @@ +package azuread + +import ( + "fmt" + "log" + + "github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac" + "github.com/hashicorp/terraform/helper/schema" + "github.com/terraform-providers/terraform-provider-azuread/azuread/helpers/validate" +) + +func dataGroup() *schema.Resource { + return &schema.Resource{ + Read: dataSourceActiveDirectoryGroupRead, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings, + }, + }, + } +} + +func dataSourceActiveDirectoryGroupRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).groupsClient + ctx := meta.(*ArmClient).StopContext + + var adgroup graphrbac.ADGroup + var groupObj *graphrbac.ADGroup + + // use the name to find the Azure AD group + name := d.Get("name").(string) + filter := fmt.Sprintf("displayName eq '%s'", name) + log.Printf("[DEBUG] Using filter %q", filter) + + resp, err := client.ListComplete(ctx, filter) + if err != nil { + return fmt.Errorf("Error listing Azure AD groups: %+v", err) + } + + for _, v := range *resp.Response().Value { + if v.DisplayName == nil { + //no DisplayName returned, continue with the next iteration + continue + } else { + if *v.DisplayName == name { + log.Printf("[DEBUG] %q (API result) matches %q (given value). The group has the objectId: %q", *v.DisplayName, name, *v.ObjectID) + groupObj = &v + break + } else { + log.Printf("[DEBUG] %q (API result) does not match %q (given value)", *v.DisplayName, name) + } + } + } + + if groupObj == nil { + return fmt.Errorf("Couldn't locate a Azure AD group with a name of %q", name) + } + + adgroup = *groupObj + + d.SetId(*adgroup.ObjectID) + d.Set("object_id", adgroup.ObjectID) + + return nil +} diff --git a/azuread/data_source_group_test.go b/azuread/data_source_group_test.go new file mode 100644 index 0000000000..5edc6f9da4 --- /dev/null +++ b/azuread/data_source_group_test.go @@ -0,0 +1,47 @@ +package azuread + +import ( + "fmt" + "testing" + + "github.com/hashicorp/go-uuid" + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccDataSourceAzureADGroup_byName(t *testing.T) { + dataSourceName := "data.azuread_group.test" + id, err := uuid.GenerateUUID() + if err != nil { + t.Fatal(err) + } + config := testAccDataSourceAzureADGroup_name(id) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureADGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureADGroup(id), + }, + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureADGroupExists(dataSourceName), + resource.TestCheckResourceAttr(dataSourceName, "name", fmt.Sprintf("acctest%s", id)), + ), + }, + }, + }) +} + +func testAccDataSourceAzureADGroup_name(id string) string { + template := testAccAzureADGroup(id) + return fmt.Sprintf(` +%s + +data "azuread_group" "test" { + name = "${azuread_group.test.name}" +} +`, template) +} diff --git a/azuread/provider.go b/azuread/provider.go index d032bf543a..c48f871217 100644 --- a/azuread/provider.go +++ b/azuread/provider.go @@ -75,11 +75,13 @@ func Provider() terraform.ResourceProvider { DataSourcesMap: map[string]*schema.Resource{ "azuread_application": dataApplication(), + "azuread_group": dataGroup(), "azuread_service_principal": dataServicePrincipal(), }, ResourcesMap: map[string]*schema.Resource{ "azuread_application": resourceApplication(), + "azuread_group": resourceGroup(), "azuread_service_principal": resourceServicePrincipal(), "azuread_service_principal_password": resourceServicePrincipalPassword(), }, diff --git a/azuread/resource_group.go b/azuread/resource_group.go new file mode 100644 index 0000000000..34d158a66f --- /dev/null +++ b/azuread/resource_group.go @@ -0,0 +1,88 @@ +package azuread + +import ( + "fmt" + "log" + + "github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "github.com/terraform-providers/terraform-provider-azuread/azuread/helpers/ar" + "github.com/terraform-providers/terraform-provider-azuread/azuread/helpers/p" +) + +func resourceGroup() *schema.Resource { + return &schema.Resource{ + Create: resourceGroupCreate, + Read: resourceGroupRead, + Delete: resourceGroupDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.NoZeroValues, + }, + }, + } +} + +func resourceGroupCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).groupsClient + ctx := meta.(*ArmClient).StopContext + + name := d.Get("name").(string) + + properties := graphrbac.GroupCreateParameters{ + DisplayName: &name, + MailEnabled: p.Bool(false), //we're defaulting to false, as the API currently only supports the creation of non-mail enabled security groups. + MailNickname: &name, + SecurityEnabled: p.Bool(true), //we're defaulting to true, as the API currently only supports the creation of non-mail enabled security groups. + } + + group, err := client.Create(ctx, properties) + if err != nil { + return err + } + + d.SetId(*group.ObjectID) + + return resourceGroupRead(d, meta) +} + +func resourceGroupRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).groupsClient + ctx := meta.(*ArmClient).StopContext + + resp, err := client.Get(ctx, d.Id()) + if err != nil { + if ar.ResponseWasNotFound(resp.Response) { + log.Printf("[DEBUG] Azure AD group with id %q was not found - removing from state", d.Id()) + d.SetId("") + return nil + } + + return fmt.Errorf("Error retrieving Azure AD Group with ID %q: %+v", d.Id(), err) + } + + d.Set("name", resp.DisplayName) + + return nil +} + +func resourceGroupDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).groupsClient + ctx := meta.(*ArmClient).StopContext + + if resp, err := client.Delete(ctx, d.Id()); err != nil { + if !ar.ResponseWasNotFound(resp) { + return fmt.Errorf("Error Deleting Azure AD Group with ID %q: %+v", d.Id(), err) + } + } + + return nil +} diff --git a/azuread/resource_group_test.go b/azuread/resource_group_test.go new file mode 100644 index 0000000000..1a15009539 --- /dev/null +++ b/azuread/resource_group_test.go @@ -0,0 +1,123 @@ +package azuread + +import ( + "fmt" + "testing" + + "github.com/hashicorp/go-uuid" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/terraform-providers/terraform-provider-azuread/azuread/helpers/ar" +) + +func TestAccAzureADGroup_basic(t *testing.T) { + resourceName := "azuread_group.test" + id, err := uuid.GenerateUUID() + if err != nil { + t.Fatal(err) + } + config := testAccAzureADGroup(id) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureADGroupDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureADGroupExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("acctest%s", id)), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureADGroup_complete(t *testing.T) { + resourceName := "azuread_group.test" + id, err := uuid.GenerateUUID() + if err != nil { + t.Fatal(err) + } + config := testAccAzureADGroup(id) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureADGroupDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureADGroupExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("acctest%s", id)), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testCheckAzureADGroupExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %q", name) + } + + client := testAccProvider.Meta().(*ArmClient).groupsClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + resp, err := client.Get(ctx, rs.Primary.ID) + + if err != nil { + if ar.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: Azure AD Group %q does not exist", rs.Primary.ID) + } + return fmt.Errorf("Bad: Get on Azure AD groupsClient: %+v", err) + } + + return nil + } +} + +func testCheckAzureADGroupDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "azuread_group" { + continue + } + + client := testAccProvider.Meta().(*ArmClient).groupsClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + resp, err := client.Get(ctx, rs.Primary.ID) + + if err != nil { + if ar.ResponseWasNotFound(resp.Response) { + return nil + } + + return err + } + + return fmt.Errorf("Azure AD group still exists:\n%#v", resp) + } + + return nil +} + +func testAccAzureADGroup(id string) string { + return fmt.Sprintf(` +resource "azuread_group" "test" { + name = "acctest%s" +} +`, id) +} diff --git a/website/azuread.erb b/website/azuread.erb index 3624807520..22c5889b53 100644 --- a/website/azuread.erb +++ b/website/azuread.erb @@ -51,6 +51,10 @@ azuread_application +