diff --git a/CHANGELOG.md b/CHANGELOG.md index 53bbce4..60a11b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/docs/resources/resource_collection.md b/docs/resources/resource_collection.md index e64c78d..248674f 100644 --- a/docs/resources/resource_collection.md +++ b/docs/resources/resource_collection.md @@ -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' + ``` diff --git a/examples/resources/msgraph_resource_collection/import.sh b/examples/resources/msgraph_resource_collection/import.sh new file mode 100644 index 0000000..cbe8db9 --- /dev/null +++ b/examples/resources/msgraph_resource_collection/import.sh @@ -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' diff --git a/internal/provider/auth_oidc.go b/internal/provider/auth_oidc.go index 1398cd3..56809a3 100644 --- a/internal/provider/auth_oidc.go +++ b/internal/provider/auth_oidc.go @@ -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) diff --git a/internal/services/msgraph_resource_collection.go b/internal/services/msgraph_resource_collection.go index 1e97d9c..fcf13f6 100644 --- a/internal/services/msgraph_resource_collection.go +++ b/internal/services/msgraph_resource_collection.go @@ -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" @@ -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 { @@ -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") } diff --git a/internal/services/msgraph_resource_collection_test.go b/internal/services/msgraph_resource_collection_test.go index dd45fef..ea21517 100644 --- a/internal/services/msgraph_resource_collection_test.go +++ b/internal/services/msgraph_resource_collection_test.go @@ -30,6 +30,7 @@ func TestAcc_ResourceCollectionBasic(t *testing.T) { resource.TestCheckResourceAttr(data.ResourceName, "reference_ids.#", "1"), ), }, + data.ImportStepWithImportStateIdFunc(r.ImportIdFunc), }) } @@ -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(), @@ -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(), @@ -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{} @@ -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 `