Skip to content

Resource: Unmanaged Solution#1118

Open
AdamCoulterOz wants to merge 7 commits intomicrosoft:mainfrom
AdamCoulterOz:unmanaged_solution
Open

Resource: Unmanaged Solution#1118
AdamCoulterOz wants to merge 7 commits intomicrosoft:mainfrom
AdamCoulterOz:unmanaged_solution

Conversation

@AdamCoulterOz
Copy link
Copy Markdown
Contributor

Summary

  • add powerplatform_unmanaged_solution as a first-class Dataverse CRUD resource
  • add powerplatform_unmanaged_solution data source for lookup by environment and unique name
  • add unit and acceptance coverage plus generated docs and examples

Testing

  • go test ./internal/services/solution ./internal/provider -run 'TestUnit(UnmanagedSolutionResource_Validate_Create|UnmanagedSolutionResource_Validate_Update|UnmanagedSolutionResource_Validate_Create_No_Dataverse|UnmanagedSolutionResource_Validate_Create_Eventual_Consistency|UnmanagedSolutionResource_Validate_Create_Uses_Response_ID|UnmanagedSolutionDataSource_Validate_Read|UnmanagedSolutionDataSource_Validate_No_Dataverse|UnmanagedSolutionDataSource_Validate_Not_Found|PowerPlatformProviderHasChildDataSources_Basic|PowerPlatformProviderHasChildResources_Basic)$'
  • PATH=/Users/adam/go/bin:$PATH make userdocs
  • POWER_PLATFORM_USE_CLI=true TF_ACC=1 go test ./internal/services/solution -run ^TestAccUnmanagedSolutionResource_Validate_Create_Update -timeout 90m

Closes #1117

@AdamCoulterOz AdamCoulterOz requested a review from a team as a code owner March 30, 2026 06:51
Copilot AI review requested due to automatic review settings March 30, 2026 06:51
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds first-class support for unmanaged Dataverse solutions to the solution service, enabling CRUD management as a stable Terraform resource plus lookup via a dedicated data source, along with tests, examples, docs, and changelog entry.

Changes:

  • Introduces powerplatform_unmanaged_solution resource with CRUD + import support.
  • Adds powerplatform_unmanaged_solution data source for lookup by environment_id + uniquename.
  • Extends solution API/DTOs and adds unit/acceptance tests, examples, generated docs, and changelog entry.

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
internal/services/solution/api_solution.go Adds create/update/poll logic for unmanaged solutions and parses OData-EntityId for IDs.
internal/services/solution/dto.go Extends SolutionDto (description/publisher) and adds create/update DTOs for unmanaged solution CRUD.
internal/services/solution/models.go Adds models for unmanaged solution resource and data source.
internal/services/solution/resource_unmanaged_solution.go Implements unmanaged solution Terraform resource (schema + CRUD + import).
internal/services/solution/resource_unmanaged_solution_test.go Unit + acceptance coverage for unmanaged solution resource (create/update/import + edge cases).
internal/services/solution/datasource_unmanaged_solution.go Implements unmanaged solution lookup data source.
internal/services/solution/datasource_unmanaged_solution_test.go Unit coverage for unmanaged solution data source (read/no-dataverse/not-found).
internal/services/solution/tests/resource/Validate_Unmanaged_Create/get_environment_00000000-0000-0000-0000-000000000001.json Test fixture for Dataverse-enabled environment lookup (create path).
internal/services/solution/tests/resource/Validate_Unmanaged_Create/get_solution.json Test fixture for created solution GET response.
internal/services/solution/tests/resource/Validate_Unmanaged_No_Dataverse/get_environment_00000000-0000-0000-0000-000000000001.json Test fixture for environment without Dataverse.
internal/services/solution/tests/resource/Validate_Unmanaged_Update/get_environment_00000000-0000-0000-0000-000000000001.json Test fixture for Dataverse-enabled environment lookup (update path).
internal/services/solution/tests/resource/Validate_Unmanaged_Update/get_solution_before.json Test fixture for solution state before update.
internal/services/solution/tests/resource/Validate_Unmanaged_Update/get_solution_after.json Test fixture for solution state after update.
internal/provider/provider.go Registers new unmanaged solution resource and data source with the provider.
internal/provider/provider_test.go Updates provider child resource/data source registry tests to include unmanaged solution types.
examples/resources/powerplatform_unmanaged_solution/resource.tf Adds resource example usage (creates env + unmanaged solution).
examples/resources/powerplatform_unmanaged_solution/outputs.tf Outputs example for unmanaged solution ID.
examples/resources/powerplatform_unmanaged_solution/import.sh Import example for unmanaged solution composite ID format.
examples/data-sources/powerplatform_unmanaged_solution/data-source.tf Adds unmanaged solution data source example snippet.
examples/data-sources/powerplatform_unmanaged_solution/outputs.tf Outputs example for unmanaged solution data source.
docs/resources/unmanaged_solution.md Generated docs for the unmanaged solution resource.
docs/data-sources/unmanaged_solution.md Generated docs for the unmanaged solution data source.
.changes/unreleased/added-20260330-165500-unmanaged-solution.yaml Adds changelog entry for the new resource.

Comment thread internal/services/solution/resource_unmanaged_solution.go Outdated
Comment thread internal/services/solution/datasource_unmanaged_solution.go
Comment thread examples/data-sources/powerplatform_unmanaged_solution/data-source.tf Outdated
Comment thread docs/data-sources/unmanaged_solution.md
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 4 comments.

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

package solution
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unit tests in this repo generally use the external test package pattern (package <service>_test) to exercise the provider via its public surface (e.g., resource_unmanaged_solution_test.go uses package solution_test). This file uses package solution, which couples the test to unexported internals; consider moving this test into package solution_test by testing behavior through the resource/data source, or relocating the helper to a testable public/utility location if direct testing is required.

Copilot uses AI. Check for mistakes.
return
}
if !dvExists {
resp.Diagnostics.AddError(fmt.Sprintf("No Dataverse exists in environment '%s'", plan.EnvironmentId.ValueString()), "")
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This diagnostic adds an error with an empty detail string. Terraform users will only see the summary, and troubleshooting context is lost. Please provide a meaningful detail (e.g., explain that the environment is not Dataverse-enabled / missing linkedEnvironmentMetadata.instanceUrl).

Suggested change
resp.Diagnostics.AddError(fmt.Sprintf("No Dataverse exists in environment '%s'", plan.EnvironmentId.ValueString()), "")
resp.Diagnostics.AddError(
fmt.Sprintf("No Dataverse exists in environment '%s'", plan.EnvironmentId.ValueString()),
fmt.Sprintf("Unmanaged solutions can only be created in Dataverse-enabled environments. The environment '%s' does not have Dataverse provisioned, so solution creation cannot continue. Verify that the target environment is Dataverse-enabled and provision Dataverse before retrying.", plan.EnvironmentId.ValueString()),
)

Copilot uses AI. Check for mistakes.
}

if !dvExists {
resp.Diagnostics.AddError(fmt.Sprintf("No Dataverse exists in environment '%s'", state.EnvironmentId.ValueString()), "")
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This diagnostic adds an error with an empty detail string, which makes troubleshooting harder for users. Please include actionable detail (e.g., that the environment does not have Dataverse enabled / linkedEnvironmentMetadata.instanceUrl is missing).

Suggested change
resp.Diagnostics.AddError(fmt.Sprintf("No Dataverse exists in environment '%s'", state.EnvironmentId.ValueString()), "")
resp.Diagnostics.AddError(
fmt.Sprintf("No Dataverse exists in environment '%s'", state.EnvironmentId.ValueString()),
fmt.Sprintf("The environment '%s' does not have Dataverse enabled or provisioned. Unmanaged solutions require a Dataverse environment. Verify that the environment ID is correct and that Dataverse has been created for this environment.", state.EnvironmentId.ValueString()),
)

Copilot uses AI. Check for mistakes.
}

func splitSolutionCompositeID(id string) []string {
return strings.Split(id, "_")
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

splitSolutionCompositeID uses strings.Split, which will split on every underscore. Import IDs are expected to have exactly two parts (environment_id and solution_id), so using a 2-way split (e.g., SplitN with 2) would make parsing more robust and avoid surprising failures if an underscore ever appears in an ID string.

Suggested change
return strings.Split(id, "_")
return strings.SplitN(id, "_", 2)

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated 1 comment.

Comment thread internal/services/solution/api_solution.go
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated no new comments.

@@ -0,0 +1,5 @@
kind: added
body: Added the `powerplatform_unmanaged_solution` resource for direct Dataverse solution CRUD without ZIP import lifecycle coupling.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, lets rename this resource and data source to powerplatform_solution.

The PR with rename of an exising solution into _import is here. I will get it merge into main asap.

return client.GetSolutionById(ctx, environmentId, solutionId)
}

func (client *Client) WaitForUnmanagedSolution(ctx context.Context, environmentId, solutionId, uniqueName string) (*SolutionDto, error) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function is used only in this file?

Suggested change
func (client *Client) WaitForUnmanagedSolution(ctx context.Context, environmentId, solutionId, uniqueName string) (*SolutionDto, error) {
func (client *Client) waitForUnmanagedSolution(ctx context.Context, environmentId, solutionId, uniqueName string) (*SolutionDto, error) {

}
}

return client.WaitForUnmanagedSolution(ctx, environmentId, solutionID, uniqueName)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return client.WaitForUnmanagedSolution(ctx, environmentId, solutionID, uniqueName)
return client.waitForUnmanagedSolution(ctx, environmentId, solutionID, uniqueName)

@@ -0,0 +1,159 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need datasource since we already have powerplatform_solutions in the provider doing the same thing?

defer exitContext()

resp.Schema = schema.Schema{
MarkdownDescription: "Resource for managing unmanaged Dataverse solutions as first-class solution records without coupling lifecycle to solution ZIP import operations.",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
MarkdownDescription: "Resource for managing unmanaged Dataverse solutions as first-class solution records without coupling lifecycle to solution ZIP import operations.",
MarkdownDescription: "Resource for managing Power Platform solutions as first-class solution records inside Dataverse",

@@ -0,0 +1,2 @@
# Unmanaged solution resources can be imported using the provider id format {environment_id}_{solution_id}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to be consistent with the rest of the provider:

Suggested change
# Unmanaged solution resources can be imported using the provider id format {environment_id}_{solution_id}
# Unmanaged solution resources can be imported using the provider id format {environment_id}/{solution_id}

@@ -0,0 +1,2 @@
# Unmanaged solution resources can be imported using the provider id format {environment_id}_{solution_id}
terraform import powerplatform_unmanaged_solution.solution 00000000-0000-0000-0000-000000000000_00000000-0000-0000-0000-000000000001
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to be consistent with the rest of the provider:

Suggested change
terraform import powerplatform_unmanaged_solution.solution 00000000-0000-0000-0000-000000000000_00000000-0000-0000-0000-000000000001
terraform import powerplatform_unmanaged_solution.solution 00000000-0000-0000-0000-000000000000/00000000-0000-0000-0000-000000000001

unmanagedPublisherID = "aa47dc6c-bf13-490b-a007-1da95a0d1e3f"
)

func TestUnitUnmanagedSolutionResource_Validate_Create(t *testing.T) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each of this unit test should have Acceptance Test version too


const (
unmanagedSolutionEnvironmentID = "00000000-0000-0000-0000-000000000001"
unmanagedSolutionID = "86928ed8-df37-4ce2-add5-47030a833bff"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can solutionid and publisherId be something like 00000000-0000-0000-0000-00000000000X so its clear that its a mockup value?


httpmock.RegisterResponder("GET", "https://00000000-0000-0000-0000-000000000001.crm4.dynamics.com/api/data/v9.2/solutions?%24expand=publisherid&%24filter=uniquename+eq+%27TerraformTestSolution%27",
func(req *http.Request) (*http.Response, error) {
readAttempts++
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if possible http mocked responders should work wihout any explicit logic. They should just respond with new .json files

@AdamCoulterOz AdamCoulterOz changed the title Add unmanaged solution resource and data source Resource: Unmanaged Solution Apr 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add unmanaged solution resource

3 participants