Skip to content

Resource: Git Integration#1105

Open
AdamCoulterOz wants to merge 12 commits intomicrosoft:mainfrom
AdamCoulterOz:codex/git-integration-design
Open

Resource: Git Integration#1105
AdamCoulterOz wants to merge 12 commits intomicrosoft:mainfrom
AdamCoulterOz:codex/git-integration-design

Conversation

@AdamCoulterOz
Copy link
Copy Markdown
Contributor

Summary

This PR adds first-class Dataverse Git integration support to the provider for Azure DevOps-backed environments and solutions.

It introduces:

  • powerplatform_environment_git_integration
  • powerplatform_solution_git_branch

Related Issue

Closes #1104

What’s Included

  • Added the new Git integration service under internal/services/git_integration
  • Added powerplatform_environment_git_integration
  • Added powerplatform_solution_git_branch
  • Registered both resources in provider.go and provider_test.go
  • Added unit coverage for lifecycle, validation, import, and edge cases
  • Added examples and generated docs for both resources
  • Added import support for both resources
  • Narrowed scope to Azure DevOps only for now, since that is the path validated live

Behavior Notes

powerplatform_environment_git_integration

This resource manages the environment-level Dataverse repository binding.

  • uses environment_id
  • supports scope = "Solution" and scope = "Environment"
  • validates organization_name, project_name, and repository_name against Dataverse Git selector endpoints
  • sets SourceControlIntegrationScope before creating the parent Git configuration, matching the Maker UI sequence

powerplatform_solution_git_branch

This resource manages a solution-level Dataverse branch/folder binding.

  • uses solution_id instead of exposing partition_id
  • reads solution bindings by partitionId, matching the Maker UI / Web API behavior
  • uses the Dataverse lifecycle shape observed from the UI for update/delete
  • polls until the branch binding is actually removed from the partitionId query result

EnvironmentScope

EnvironmentScope behavior is different from SolutionScope, and this PR mirrors the UI-backed behavior validated live:

  • creates the parent sourcecontrolconfiguration
  • creates the root sourcecontrolbranchconfiguration for partitionId = 00000000-0000-0000-0000-000000000000
  • proactively enables eligible custom unmanaged solutions by setting solution.enabledforsourcecontrolintegration = true
  • does not create per-solution branch configuration rows in this mode
  • excludes built-in platform-owned default solutions from proactive enablement

Validation

Has been run locally:

  • make precommit

Has also been manually live-validated against real Dataverse environments for:

  • SolutionScope
    • create environment repo binding
    • create multiple solution branch bindings
    • remove one solution binding while keeping siblings and parent intact
  • EnvironmentScope
    • create environment repo binding
    • create root branch binding
    • proactively enable custom unmanaged solutions
    • tear down cleanly

Acceptance Tests

CI-backed acceptance tests are not included in this PR.

Reason:

  • the current upstream acceptance workflow provisions Power Platform test credentials
  • these Git integration resources additionally need Azure DevOps fixtures that Dataverse can resolve through the Git selector endpoints
  • to add true TestAcc coverage in CI, maintainers will need upstream-managed Azure DevOps org/project/repo/branch access for the acceptance-test identity

This can be added later once an upstream Azure DevOps acceptance fixture is available.

Known Separate Follow-Ups

These are not introduced by this PR, but were encountered during live validation and are tracked separately:

@AdamCoulterOz AdamCoulterOz requested a review from a team as a code owner March 26, 2026 13:45
Copilot AI review requested due to automatic review settings March 26, 2026 13:45
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 a new git_integration service to the provider to support managing Dataverse Git integration backed by Azure DevOps, including an environment-level repo binding and a solution-level branch/folder binding.

Changes:

  • Introduces powerplatform_environment_git_integration to manage Dataverse sourcecontrolconfiguration (and root branch behavior in Environment scope).
  • Introduces powerplatform_solution_git_branch to manage Dataverse sourcecontrolbranchconfiguration for a solution partition.
  • Registers both resources in the provider, and adds unit tests, examples, and generated docs.

Reviewed changes

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

Show a summary per file
File Description
internal/services/git_integration/api_git_integration.go Implements Dataverse Web API client operations for git selector endpoints + source control configuration/branch lifecycle and polling.
internal/services/git_integration/dto.go Adds DTOs for git selector endpoints, source control entities, and org/solution updates.
internal/services/git_integration/models.go Adds Terraform resource models plus helper constants and conversion helpers.
internal/services/git_integration/resource_environment_git_integration.go Implements powerplatform_environment_git_integration schema + CRUD + import.
internal/services/git_integration/resource_solution_git_branch.go Implements powerplatform_solution_git_branch schema + CRUD + import.
internal/services/git_integration/validation.go Adds remote validation helpers (selector endpoint validation, scope checks, duplicate binding checks).
internal/services/git_integration/resource_environment_git_integration_test.go Unit tests for env git integration lifecycle, scope behavior, import, and edge cases.
internal/services/git_integration/resource_solution_git_branch_test.go Unit tests for solution branch binding lifecycle, validation, and import.
internal/services/git_integration/tests/shared/get_environment_00000000-0000-0000-0000-000000000001.json Shared BAP environment fixture used by unit tests.
internal/services/git_integration/tests/resource/environment_git_integration/get_sourcecontrolconfiguration_1.json Fixture for environment git integration reads.
internal/services/git_integration/tests/resource/solution_git_branch/get_sourcecontrolbranchconfiguration_1.json Fixture for solution git branch read (initial state).
internal/services/git_integration/tests/resource/solution_git_branch/get_sourcecontrolbranchconfiguration_2.json Fixture for solution git branch read (updated state).
internal/provider/provider.go Registers the two new resources in the provider.
internal/provider/provider_test.go Verifies provider exposes the new resources.
examples/resources/powerplatform_environment_git_integration/resource.tf Example usage for environment git integration.
examples/resources/powerplatform_environment_git_integration/variables.tf Example variables for environment git integration.
examples/resources/powerplatform_environment_git_integration/outputs.tf Example outputs for environment git integration.
examples/resources/powerplatform_environment_git_integration/import.sh Example import command for environment git integration.
examples/resources/powerplatform_solution_git_branch/resource.tf Example usage wiring environment + solution + git integration + solution branch binding.
examples/resources/powerplatform_solution_git_branch/variables.tf Example variables for solution git branch.
examples/resources/powerplatform_solution_git_branch/outputs.tf Example outputs for solution git branch.
examples/resources/powerplatform_solution_git_branch/import.sh Example import command for solution git branch.
docs/resources/environment_git_integration.md Generated docs for powerplatform_environment_git_integration.
docs/resources/solution_git_branch.md Generated docs for powerplatform_solution_git_branch.

Comment thread internal/services/git_integration/models.go Outdated
Comment thread internal/services/git_integration/dto.go Outdated
Comment thread internal/services/git_integration/api_git_integration.go Outdated
Comment thread internal/services/git_integration/models.go
@AdamCoulterOz
Copy link
Copy Markdown
Contributor Author

@mawasile hopefully this looks okay. Let me know if you need any changes or clarifications.

@AdamCoulterOz AdamCoulterOz force-pushed the codex/git-integration-design branch from 6cd3e5c to a1586c1 Compare March 30, 2026 07:14
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 3 comments.

Comment on lines +156 to +170
func normalizeSolutionID(environmentID, configuredValue string) (string, error) {
if configuredValue == "" {
return "", errors.New("the `solution_id` attribute must not be empty")
}

environmentPrefix, solutionID, found := strings.Cut(configuredValue, "_")
if !found {
return "", fmt.Errorf("the `solution_id` value `%s` must be the `id` exported by `powerplatform_solution` for environment `%s`", configuredValue, environmentID)
}

if environmentPrefix != environmentID {
return "", fmt.Errorf("the `solution_id` value `%s` uses environment `%s`, but this resource is targeting environment `%s`", configuredValue, environmentPrefix, environmentID)
}

return solutionID, nil
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.

normalizeSolutionID only checks for the <environmentId>_<solutionGuid> delimiter, but it never validates that either segment is actually a GUID. Since the extracted solutionID is later interpolated into OData filters and composite-key URLs, non-GUID input can lead to malformed requests or injection-style issues. Consider validating both environmentPrefix and solutionID against the repo’s GUID regex (and returning a clear Invalid solution_id error) before returning solutionID.

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

Choose a reason for hiding this comment

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

normalizeSolutionID now rejects non-GUID environment and solution segments before they ever reach OData URLs.

Comment on lines +417 to +452
func (c *client) DeleteSolutionGitBranch(ctx context.Context, environmentID, branchID, configurationID, partitionID string) error {
existingBranch, err := c.lookupAnySolutionGitBranchByPartition(ctx, environmentID, configurationID, partitionID)
if err != nil {
if errors.Is(err, customerrors.ErrObjectNotFound) {
return nil
}
return err
}

if existingBranch.StatusCode == sourceControlBranchConfigurationStatusInactive {
return c.waitForSolutionGitBranchRemoval(ctx, environmentID, branchID, configurationID, partitionID)
}

environmentHost, err := c.EnvironmentClient.GetEnvironmentHostById(ctx, environmentID)
if err != nil {
return err
}

apiURL := helpers.BuildDataverseApiUrl(environmentHost, buildSourceControlBranchConfigurationCompositeKeyPath(branchID, partitionID), nil)
headers := http.Header{}
headers.Set("If-Match", "*")
resp, err := c.Api.Execute(ctx, nil, http.MethodPatch, apiURL, headers, disableSourceControlBranchConfigurationDto{
StatusCode: sourceControlBranchConfigurationStatusInactive,
}, []int{http.StatusNoContent, http.StatusNotFound, http.StatusForbidden}, nil)
if err != nil {
return err
}
if err := c.Api.HandleForbiddenResponse(resp); err != nil {
return err
}
if resp.HttpResponse.StatusCode == http.StatusNotFound {
return nil
}

return c.waitForSolutionGitBranchRemoval(ctx, environmentID, branchID, configurationID, partitionID)
}
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.

DeleteSolutionGitBranch looks up the existing branch by (configurationID, partitionID), but then performs the disable PATCH using the branchID argument (typically state ID). If the remote row was recreated and the ID drifted, the PATCH can 404, and waitForSolutionGitBranchRemoval will continue to see the existing row and potentially loop until timeout. Use existingBranch.ID (from the lookup) for the PATCH and subsequent removal wait, or explicitly handle ID mismatches by retrying with the discovered ID.

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

Choose a reason for hiding this comment

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

DeleteSolutionGitBranch now patches and waits on the branch row Dataverse actually returns from the lookup, instead of trusting a possibly stale state ID.

Comment on lines +196 to +199
if err := r.GitIntegrationClient.EnableEnvironmentScopeSolutions(ctx, plan.EnvironmentID.ValueString()); err != nil {
resp.Diagnostics.AddError(fmt.Sprintf("Client error when enabling environment-scoped solutions for %s", r.FullTypeName()), err.Error())
return
}
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.

In Environment scope, the resource proactively enables solutions via EnableEnvironmentScopeSolutions, but the resource does not record which solutions it changed. As a result, Delete cannot reliably revert solution.enabledforsourcecontrolintegration to its prior value, which contradicts the PR description and leaves lasting side-effects after destroy. Consider tracking the set of solution IDs that were enabled by this resource in state (or another durable mechanism) and reverting them on Delete (and potentially when switching away from Environment scope).

Suggested change
if err := r.GitIntegrationClient.EnableEnvironmentScopeSolutions(ctx, plan.EnvironmentID.ValueString()); err != nil {
resp.Diagnostics.AddError(fmt.Sprintf("Client error when enabling environment-scoped solutions for %s", r.FullTypeName()), err.Error())
return
}
tflog.Debug(ctx, "Skipping automatic enablement of environment-scoped solutions because the resource does not durably track which solutions it changed and therefore cannot safely revert them on destroy", map[string]interface{}{
"environment_id": plan.EnvironmentID.ValueString(),
"resource_type": r.FullTypeName(),
})

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

Choose a reason for hiding this comment

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

This is by design, because this is how the make portal works. The intended behaviour is that once "environment" scoped source control is enabled, all unmanaged solutions in that environment lazy bind on first navigation to the git page for them (as an automatic side effect). We don't need to keep track of which ones we've activated, because they all automatically get deactivated when the single environment binding is removed again. They cannot be connected mutually exclusively from each other.

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 1 comment.

Comment on lines +417 to +440
func (c *client) DeleteSolutionGitBranch(ctx context.Context, environmentID, branchID, configurationID, partitionID string) error {
existingBranch, err := c.lookupAnySolutionGitBranchByPartition(ctx, environmentID, configurationID, partitionID)
if err != nil {
if errors.Is(err, customerrors.ErrObjectNotFound) {
return nil
}
return err
}

if existingBranch.StatusCode == sourceControlBranchConfigurationStatusInactive {
return c.waitForSolutionGitBranchRemoval(ctx, environmentID, branchID, configurationID, partitionID)
}

environmentHost, err := c.EnvironmentClient.GetEnvironmentHostById(ctx, environmentID)
if err != nil {
return err
}

apiURL := helpers.BuildDataverseApiUrl(environmentHost, buildSourceControlBranchConfigurationCompositeKeyPath(branchID, partitionID), nil)
headers := http.Header{}
headers.Set("If-Match", "*")
resp, err := c.Api.Execute(ctx, nil, http.MethodPatch, apiURL, headers, disableSourceControlBranchConfigurationDto{
StatusCode: sourceControlBranchConfigurationStatusInactive,
}, []int{http.StatusNoContent, http.StatusNotFound, http.StatusForbidden}, nil)
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.

DeleteSolutionGitBranch looks up the current branch row (existingBranch), but the disable PATCH still uses the branchID argument (state ID) to build the composite-key URL. If the row was recreated and the state ID is stale, the PATCH can 404 while the row still exists, and waitForSolutionGitBranchRemoval can loop until timeout. Use existingBranch.ID (and pass that same discovered ID into the subsequent wait) once the lookup succeeds.

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

Choose a reason for hiding this comment

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

delete now uses the discovered live branch ID, not stale state

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 28 out of 28 changed files in this pull request and generated 2 comments.

Comment on lines +175 to +177
if environmentPrefix != environmentID {
return "", fmt.Errorf("the `solution_id` value `%s` uses environment `%s`, but this resource is targeting environment `%s`", configuredValue, environmentPrefix, environmentID)
}
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.

In normalizeSolutionID, the environment prefix and the configured environment_id are compared using a raw string inequality. Since GUIDs are case-insensitive and can appear in different canonical forms, this can reject otherwise valid solution_id values (e.g., mixed-case environment IDs). Consider normalizing both values (e.g., parse both as UUIDs and compare their canonical .String() values, or use strings.EqualFold) before returning an error.

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

Choose a reason for hiding this comment

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

solution_id now compares parsed UUIDs canonically, so mixed-case GUIDs are accepted and invalid environment_id fails loudly.

case "SolutionScope":
return scopeSolution
default:
return ""
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.

GetSourceControlIntegrationScope can return an empty string when the org setting is missing or unrecognized (sourceControlIntegrationScopeFromOrgDbValue default case). That empty value is then written into Terraform state as scope, even though the schema/validators only support Environment or Solution, which can cause perpetual diffs or confusing behavior. Consider treating an unknown/missing value as Solution (Dataverse default) or returning an explicit error diagnostic when the scope cannot be determined.

Suggested change
return ""
return scopeSolution

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

Choose a reason for hiding this comment

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

GetSourceControlIntegrationScope now errors if Dataverse returns an unknown or missing scope instead of writing "" into state.

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 28 out of 28 changed files in this pull request and generated 2 comments.

Comment on lines +78 to +89
projectName := types.StringNull()
if dto.ProjectName != "" {
projectName = types.StringValue(dto.ProjectName)
}

return EnvironmentGitIntegrationResourceModel{
ID: types.StringValue(dto.ID),
EnvironmentID: types.StringValue(environmentID),
GitProvider: types.StringValue(gitProviderFromInt(dto.GitProvider)),
Scope: types.StringValue(scope),
OrganizationName: types.StringValue(dto.OrganizationName),
ProjectName: projectName,
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.

convertSourceControlConfigurationDtoToModel sets ProjectName to types.StringNull() when the API returns an empty projectname. Since project_name is a required schema attribute, writing null into state can lead to invalid state and/or perpetual diffs after Read/Import. Prefer always setting a concrete string value in state (even if empty), or treat an empty API value as an error and surface a diagnostic so required attributes are never stored as null.

Suggested change
projectName := types.StringNull()
if dto.ProjectName != "" {
projectName = types.StringValue(dto.ProjectName)
}
return EnvironmentGitIntegrationResourceModel{
ID: types.StringValue(dto.ID),
EnvironmentID: types.StringValue(environmentID),
GitProvider: types.StringValue(gitProviderFromInt(dto.GitProvider)),
Scope: types.StringValue(scope),
OrganizationName: types.StringValue(dto.OrganizationName),
ProjectName: projectName,
return EnvironmentGitIntegrationResourceModel{
ID: types.StringValue(dto.ID),
EnvironmentID: types.StringValue(environmentID),
GitProvider: types.StringValue(gitProviderFromInt(dto.GitProvider)),
Scope: types.StringValue(scope),
OrganizationName: types.StringValue(dto.OrganizationName),
ProjectName: types.StringValue(dto.ProjectName),

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

Choose a reason for hiding this comment

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

convertSourceControlConfigurationDtoToModel now always writes a concrete project_name string to state, never null.

Comment on lines +139 to +146
func (c *client) EnsureSolutionScopeRootBranch(ctx context.Context, environmentID, configurationID, organizationName, projectName, repositoryName string) error {
defaultBranch, err := c.GetGitRepositoryDefaultBranch(ctx, environmentID, organizationName, projectName, repositoryName)
if err != nil {
return err
}

existingBranch, err := c.lookupAnySolutionGitBranchByPartition(ctx, environmentID, configurationID, rootPartitionID)
if err == nil {
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.

EnsureSolutionScopeRootBranch is invoked for both Solution-scope and Environment-scope flows (e.g., EnvironmentGitIntegrationResource calls it when scope == "Environment"). The current name suggests it is only relevant to Solution scope, which makes the code harder to follow. Consider renaming it to something scope-agnostic (e.g., EnsureRootBranchConfiguration) to reflect actual usage.

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

Choose a reason for hiding this comment

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

renamed EnsureSolutionScopeRootBranch to EnsureRootBranchConfiguration so the helper name matches actual use in both Solution and Environment scope flows.

@AdamCoulterOz AdamCoulterOz requested a review from Copilot April 3, 2026 11:35
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 29 out of 29 changed files in this pull request and generated 1 comment.

Comment on lines +68 to +75
func gitProviderFromInt(value int) string {
switch value {
case 0:
return gitProviderAzureDevOps
default:
return ""
}
}
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.

gitProviderFromInt returns an empty string for any Dataverse gitprovider value other than 0. Because git_provider is a required attribute and the resource only supports Azure DevOps, writing "" into state on Read/Import will lead to confusing diffs and hides the real problem (an unsupported remote configuration). Consider surfacing an explicit diagnostic/error when the API returns an unknown provider value, rather than mapping it to an empty string.

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

@mawasile mawasile left a comment

Choose a reason for hiding this comment

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

Don't we need data sources to read existing integrations?


resource "powerplatform_environment_git_integration" "example" {
environment_id = powerplatform_environment.example.id
git_provider = var.git_provider
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.

because .tf files are build directly into docs and therefore ends up as terraform registry documentation, it is better when examples do not have var. values and values are explicit in the .tf files

@@ -0,0 +1,53 @@
variable "environment_display_name" {
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 above, lets move those values inline into .tf file

use_cli = true
}

# Known limitation: Dataverse Git integration currently works only with delegated
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.

additionally those new resources that don't support non-interactive should be mentioned in preview section of the README.md file

MarkdownDescription: "Git provider for the repository binding. Supported value is `AzureDevOps`.",
Required: true,
Validators: []validator.String{
stringvalidator.OneOf(gitProviderAzureDevOps),
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 the only supported value if Azure Dev Ops, then there is not need to expose that attribute to the user, we can expose it later if more endpoints would be added

tflog.Debug(ctx, fmt.Sprintf("METADATA: %s", resp.TypeName))
}

func (r *EnvironmentGitIntegrationResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
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.

in environment scope UI allows to pick branch and git folder, in solution scope root git folder. Why we don't expose those values as attributes?

defer exitContext()

resp.Schema = schema.Schema{
MarkdownDescription: "Manages a solution-level Dataverse Git branch binding. This maps to the documented `sourcecontrolbranchconfiguration` Dataverse table and links a solution partition to a branch and folder beneath an environment Git integration.\n\nKnown limitation: the underlying Power Platform Git integration bootstrap currently requires delegated user principal authentication with Azure DevOps access. Service principal, app-only, and OIDC pipeline identities are not currently supported by the backing Dataverse Git integration flow.",
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.

resource should be added to README.md file, to preview section

@@ -0,0 +1,3 @@
# Solution Git branch resources can be imported using environment_id/git_integration_id/solution_id
# The final segment can be either the raw Dataverse solution id or the provider-formatted powerplatform_solution.id
terraform import powerplatform_solution_git_branch.example 00000000-0000-0000-0000-000000000000/11111111-1111-1111-1111-111111111111/00000000-0000-0000-0000-000000000000_22222222-2222-2222-2222-222222222222
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.

Shouldn't it look like that?

Suggested change
terraform import powerplatform_solution_git_branch.example 00000000-0000-0000-0000-000000000000/11111111-1111-1111-1111-111111111111/00000000-0000-0000-0000-000000000000_22222222-2222-2222-2222-222222222222
terraform import powerplatform_solution_git_branch.example 00000000-0000-0000-0000-000000000000/11111111-1111-1111-1111-111111111111/22222222-2222-2222-2222-222222222222

# user principal authentication that also has Azure DevOps repository access.
# Service principal, app-only, and OIDC pipeline identities are not supported.

resource "local_file" "solution_settings_file" {
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.

I think we can simplify this .tf example and skip solution import without solution_settings_file


environment_id = powerplatform_environment.example.id
git_provider = var.git_provider
scope = var.scope
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.

like in other .tf file, we can do the variable inline, so that the example is more readable in docs


httpmock.RegisterResponder("POST", "https://00000000-0000-0000-0000-000000000001.crm4.dynamics.com/api/data/v9.0/sourcecontrolbranchconfigurations",
func(req *http.Request) (*http.Response, error) {
bodyBytes, err := io.ReadAll(req.Body)
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.

returning .json files will be simpler to manage

@AdamCoulterOz AdamCoulterOz changed the title Add Dataverse git integration resources Resource: Git Integration Apr 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Dataverse Git Integration Resources

3 participants