Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
## 0.3.0 (Unreleased)
## 0.4.0 (Unreleased)

ENHANCEMENTS:
- `msgraph_resource_collection`: Support resource import for collection resources (e.g. groups/{id}/members/$ref) to allow importing existing relationships into Terraform state.

## 0.3.0

FEATURES:
- **New Authentication Method**: Azure PowerShell authentication support via `use_powershell` provider attribute
Expand Down
8 changes: 8 additions & 0 deletions docs/resources/resource_collection.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,12 @@ Optional:
- `read` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). Read operations occur during any refresh or planning operation when refresh is enabled.
- `update` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours).

## Import

```shell
# MSGraph resource collection can be imported using the collection $ref URL, e.g.
terraform import msgraph_resource_collection.group_members groups/00000000-0000-0000-0000-000000000000/members/$ref

# To import using the beta API version, append the api-version query parameter:
terraform import msgraph_resource_collection.group_members 'groups/00000000-0000-0000-0000-000000000000/members/$ref?api-version=beta'
```
5 changes: 5 additions & 0 deletions examples/resources/msgraph_resource_collection/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# MSGraph resource collection can be imported using the collection $ref URL, e.g.
terraform import msgraph_resource_collection.group_members groups/00000000-0000-0000-0000-000000000000/members/$ref

# To import using the beta API version, append the api-version query parameter:
terraform import msgraph_resource_collection.group_members 'groups/00000000-0000-0000-0000-000000000000/members/$ref?api-version=beta'
1 change: 1 addition & 0 deletions internal/provider/auth_oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ func (w *OidcCredential) getAssertion(ctx context.Context) (string, error) {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", w.requestToken))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

// #nosec G704
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", fmt.Errorf("getAssertion: cannot request token: %v", err)
Expand Down
50 changes: 47 additions & 3 deletions internal/services/msgraph_resource_collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import (
"context"
"encoding/json"
"fmt"
"net/url"
"reflect"
"strings"
"time"

"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
Expand All @@ -27,9 +29,10 @@ import (
)

var (
_ resource.Resource = &MSGraphResourceCollection{}
_ resource.ResourceWithConfigure = &MSGraphResourceCollection{}
_ resource.ResourceWithModifyPlan = &MSGraphResourceCollection{}
_ resource.Resource = &MSGraphResourceCollection{}
_ resource.ResourceWithConfigure = &MSGraphResourceCollection{}
_ resource.ResourceWithModifyPlan = &MSGraphResourceCollection{}
_ resource.ResourceWithImportState = &MSGraphResourceCollection{}
)

func NewMSGraphResourceCollection() resource.Resource {
Expand Down Expand Up @@ -337,4 +340,45 @@ func flattenReferenceIds(body interface{}) ([]string, error) {
return result, nil
}

func (r *MSGraphResourceCollection) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
parsedUrl, err := url.Parse(req.ID)
if err != nil {
resp.Diagnostics.AddError("Failed to parse URL", err.Error())
return
}

urlValue := strings.TrimPrefix(parsedUrl.Path, "/")

if !strings.HasSuffix(urlValue, "/$ref") {
resp.Diagnostics.AddError(
"Invalid Import ID",
fmt.Sprintf("The import ID must be a collection URL ending with '/$ref'. For example: 'groups/{group-id}/members/$ref'. Got: %s", req.ID),
)
return
}

apiVersion := "v1.0"
if parsedUrl.Query().Get("api-version") != "" {
apiVersion = parsedUrl.Query().Get("api-version")
}

model := &MSGraphResourceCollectionModel{
Id: types.StringValue(baseCollectionUrl(urlValue)),
Url: types.StringValue(urlValue),
ApiVersion: types.StringValue(apiVersion),
ReferenceIds: types.ListNull(types.StringType),
ReadQueryParameters: types.MapNull(types.ListType{ElemType: types.StringType}),
Retry: retry.NewValueNull(),
Timeouts: timeouts.Value{
Object: types.ObjectNull(map[string]attr.Type{
"create": types.StringType,
"update": types.StringType,
"read": types.StringType,
"delete": types.StringType,
}),
},
}
resp.Diagnostics.Append(resp.State.Set(ctx, model)...)
}

func baseCollectionUrl(url string) string { return strings.TrimSuffix(url, "/$ref") }
50 changes: 50 additions & 0 deletions internal/services/msgraph_resource_collection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func TestAcc_ResourceCollectionBasic(t *testing.T) {
resource.TestCheckResourceAttr(data.ResourceName, "reference_ids.#", "1"),
),
},
data.ImportStepWithImportStateIdFunc(r.ImportIdFunc),
})
}

Expand All @@ -46,6 +47,7 @@ func TestAcc_ResourceCollectionUpdate(t *testing.T) {
resource.TestCheckResourceAttr(data.ResourceName, "reference_ids.#", "1"),
),
},
data.ImportStepWithImportStateIdFunc(r.ImportIdFunc),
{
// add second member
Config: r.updateTwoMembers(),
Expand All @@ -54,6 +56,7 @@ func TestAcc_ResourceCollectionUpdate(t *testing.T) {
resource.TestCheckResourceAttr(data.ResourceName, "reference_ids.#", "2"),
),
},
data.ImportStepWithImportStateIdFunc(r.ImportIdFunc, "reference_ids.#", "reference_ids.0", "reference_ids.1"),
{
// remove second member again
Config: r.updateOneMember(),
Expand All @@ -65,6 +68,38 @@ func TestAcc_ResourceCollectionUpdate(t *testing.T) {
})
}

func TestAcc_ResourceCollectionImport(t *testing.T) {
data := acceptance.BuildTestData(t, "msgraph_resource_collection", "test")
r := MSGraphTestResourceCollection{}

data.ResourceTest(t, r, []resource.TestStep{
{
Config: r.basic(),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).Exists(r),
resource.TestCheckResourceAttr(data.ResourceName, "reference_ids.#", "1"),
),
},
data.ImportStepWithImportStateIdFunc(r.ImportIdFunc),
})
}

func TestAcc_ResourceCollectionImportWithApiVersion(t *testing.T) {
data := acceptance.BuildTestData(t, "msgraph_resource_collection", "test")
r := MSGraphTestResourceCollection{}

data.ResourceTest(t, r, []resource.TestStep{
{
Config: r.basic(),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).Exists(r),
resource.TestCheckResourceAttr(data.ResourceName, "reference_ids.#", "1"),
),
},
data.ImportStepWithImportStateIdFunc(r.ImportIdFuncWithBetaApiVersion),
})
}

func TestAcc_ResourceCollectionRetry(t *testing.T) {
data := acceptance.BuildTestData(t, "msgraph_resource_collection", "test")
r := MSGraphTestResourceCollection{}
Expand Down Expand Up @@ -156,6 +191,21 @@ func (r MSGraphTestResourceCollection) Exists(ctx context.Context, client *clien
return nil, fmt.Errorf("checking for presence of existing collection %s(api_version=%s): %w", id, apiVersion, err)
}

func (r MSGraphTestResourceCollection) ImportIdFunc(tfState *terraform.State) (string, error) {
state := tfState.RootModule().Resources["msgraph_resource_collection.test"].Primary
url := state.Attributes["url"]
apiVersion := state.Attributes["api_version"]
if apiVersion != "" && apiVersion != "v1.0" {
return url + "?api-version=" + apiVersion, nil
}
return url, nil
}

func (r MSGraphTestResourceCollection) ImportIdFuncWithBetaApiVersion(tfState *terraform.State) (string, error) {
state := tfState.RootModule().Resources["msgraph_resource_collection.test"].Primary
return state.Attributes["url"] + "?api-version=beta", nil
}

// configuration helpers
func (r MSGraphTestResourceCollection) basic() string {
return `
Expand Down
Loading