Skip to content

Commit 09175a4

Browse files
authored
Merge branch 'main' into copilot/fix-839
2 parents 8524434 + e454a26 commit 09175a4

File tree

63 files changed

+526
-379
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+526
-379
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
kind: changed
2+
body: Improved error message formatting and consistency for better user experience across environment templates, tenant isolation policy, and tenant settings resources
3+
custom:
4+
Issue: 823
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: fixed
2+
body: Fixed duplicated constants and literals issues across codebase to improve maintainability
3+
time: 2025-06-03T16:46:44.448551164Z
4+
custom:
5+
Issue: "798"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: fixed
2+
body: Fixed recursive retry loops and missing return statements in environment services that could lead to infinite loops and data corruption. Added proper error wrapping for better debugging context.
3+
time: 2025-06-04T13:34:04.220409865Z
4+
custom:
5+
Issue: "808"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: fixed
2+
body: 'fix: add proper API error wrapping and data conversion context across multiple services'
3+
time: 2025-06-04T17:05:39.915274906Z
4+
custom:
5+
Issue: "810"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: fixed
2+
body: Fixed missing return statements after error handling in three datasources to prevent potential panics and undefined behavior
3+
time: 2025-06-04T17:50:56.587490569Z
4+
custom:
5+
Issue: "814"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: fixed
2+
body: 'Fixed validation and modifiers issues: improved error handling in validators and plan modifiers, corrected error messages for SHA256 checksums, and enhanced diagnostic context'
3+
time: 2025-06-05T10:25:19.227038423Z
4+
custom:
5+
Issue: "821"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: fixed
2+
body: 'Fix configuration and constants issues: CAE challenge detection strings, RX PowerApps Advisor API domain constants, and AuxiliaryTenantIDs type safety conversion'
3+
time: 2025-06-05T12:17:43.646455328Z
4+
custom:
5+
Issue: "824"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: fixed
2+
body: Fix recursive retry loops and missing return statements after AddError in environment services
3+
time: 2025-06-10T15:47:40.435353291Z
4+
custom:
5+
Issue: "808"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
kind: fixed
2+
body: Fixed type safety and formatting issues including missing string validation, interface assertions, and escaped newline formatting
3+
custom:
4+
Issue: 825

examples/resources/powerplatform_billing_policy/terraform.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ terraform {
55
}
66
azurerm = {
77
source = "hashicorp/azurerm"
8-
version = "4.32.0"
8+
version = "4.33.0"
99
}
1010
azurecaf = {
1111
source = "aztfmod/azurecaf"

internal/api/client.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ func IsCaeChallengeResponse(resp *http.Response) bool {
8383
if resp.StatusCode == http.StatusUnauthorized {
8484
wwwAuthenticate := resp.Header.Get("WWW-Authenticate")
8585
if wwwAuthenticate != "" {
86-
return strings.Contains(wwwAuthenticate, "claims=") &&
87-
strings.Contains(wwwAuthenticate, "insufficient_claims")
86+
return strings.Contains(wwwAuthenticate, constants.CAE_CHALLENGE_CLAIMS_INDICATOR) &&
87+
strings.Contains(wwwAuthenticate, constants.CAE_CHALLENGE_INSUFFICIENT_CLAIMS_INDICATOR)
8888
}
8989
}
9090

internal/api/client_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ func TestUnitApiClient_GetConfig(t *testing.T) {
3939
}
4040

4141
switch err.(type) {
42-
case customerrors.UrlFormatError:
42+
case *customerrors.UrlFormatError:
4343
return
4444
default:
45-
t.Errorf("Expected error type %s but got %s", reflect.TypeOf(customerrors.UrlFormatError{}), reflect.TypeOf(err))
45+
t.Errorf("Expected error type %s but got %s", reflect.TypeOf(&customerrors.UrlFormatError{}), reflect.TypeOf(err))
4646
}
4747
}
4848

internal/constants/constants.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ const (
141141

142142
const (
143143
DEFAULT_RESOURCE_OPERATION_TIMEOUT_IN_MINUTES = 20 * time.Minute
144+
MAX_RETRY_COUNT = 10
144145
)
145146

146147
const (
@@ -180,6 +181,18 @@ const (
180181
AZAPI_PROVIDER_VERSION_CONSTRAINT = ">= 1.15.0"
181182
)
182183

184+
const (
185+
ADMIN_MANAGEMENT_APP_API_VERSION = "2020-10-01"
186+
ENTERPRISE_POLICY_API_VERSION = "2019-10-01"
187+
BAP_API_VERSION = "2023-06-01"
188+
BAP_2021_API_VERSION = "2021-04-01"
189+
BAP_2022_API_VERSION = "2022-05-01"
190+
APPLICATION_API_VERSION = "2022-03-01-preview"
191+
ENVIRONMENT_GROUP_API_VERSION = "2021-10-01-preview"
192+
CONNECTORS_API_VERSION = "2019-05-01"
193+
TENANT_SETTINGS_API_VERSION = "2020-08-01"
194+
)
195+
183196
const (
184197
SOLUTION_CHECKER_RULESET_ID = "0ad12346-e108-40b8-a956-9a8f95ea18c9"
185198
)
@@ -188,6 +201,10 @@ const (
188201
NO_MANAGEMENT_APPLICATION_ERROR_MSG = "authorization has been denied for this request. Make sure that your service principal is registered as an admin management application: https://learn.microsoft.com/en-us/power-platform/admin/powerplatform-api-create-service-principal#registering-an-admin-management-application"
189202
)
190203

204+
const (
205+
CAE_CHALLENGE_CLAIMS_INDICATOR = "claims="
206+
CAE_CHALLENGE_INSUFFICIENT_CLAIMS_INDICATOR = "insufficient_claims"
207+
)
191208
// Error codes for provider errors.
192209
const (
193210
ERROR_OBJECT_NOT_FOUND = "OBJECT_NOT_FOUND"

internal/customerrors/url_format_error.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,21 @@ import (
77
"fmt"
88
)
99

10-
var _ error = UrlFormatError{}
10+
var _ error = (*UrlFormatError)(nil)
1111

1212
type UrlFormatError struct {
1313
Url string
1414
Err error
1515
}
1616

1717
func NewUrlFormatError(url string, err error) error {
18-
return UrlFormatError{
18+
return &UrlFormatError{
1919
Err: err,
2020
Url: url,
2121
}
2222
}
2323

24-
func (e UrlFormatError) Error() string {
24+
func (e *UrlFormatError) Error() string {
2525
errorMsg := ""
2626
if e.Err != nil {
2727
errorMsg = e.Err.Error()
@@ -30,6 +30,6 @@ func (e UrlFormatError) Error() string {
3030
return fmt.Sprintf("Request url must be an absolute url: '%s' : '%s'", e.Url, errorMsg)
3131
}
3232

33-
func (e UrlFormatError) Unwrap() error {
33+
func (e *UrlFormatError) Unwrap() error {
3434
return e.Err
3535
}

internal/customtypes/uuid_value.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818

1919
const (
2020
UUIDTypeErrorInvalidStringHeader = "Invalid UUID String Value"
21-
UUIDTypeErrorInvalidStringDetails = `A string value was provided that is not valid UUID string format.\n\nGiven Value: %s\n`
21+
UUIDTypeErrorInvalidStringDetails = "A string value was provided that is not valid UUID string format.\n\nGiven Value: %s\n"
2222
)
2323

2424
var (

internal/helpers/cert.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ func GetCertificateRawFromCertOrFilePath(certificate, certificateFilePath string
2222
if certificateFilePath != "" {
2323
pfx, err := os.ReadFile(certificateFilePath)
2424
if err != nil {
25-
return "", err
25+
return "", fmt.Errorf("failed to read certificate file '%s': %w", certificateFilePath, err)
2626
}
2727
certAsBase64 := base64.StdEncoding.EncodeToString(pfx)
2828
return strings.TrimSpace(certAsBase64), nil
@@ -38,7 +38,7 @@ func ConvertBase64ToCert(b64, password string) ([]*x509.Certificate, crypto.Priv
3838

3939
certs, key, err := convertByteToCert(pfx, password)
4040
if err != nil {
41-
return nil, nil, err
41+
return nil, nil, fmt.Errorf("failed to convert certificate bytes: %w", err)
4242
}
4343

4444
return certs, key, nil
@@ -62,7 +62,7 @@ func convertByteToCert(certData []byte, password string) ([]*x509.Certificate, c
6262

6363
key, cert, _, err := pkcs12.DecodeChain(certData, password)
6464
if err != nil {
65-
return nil, nil, err
65+
return nil, nil, fmt.Errorf("failed to decode PKCS12 certificate chain: %w", err)
6666
}
6767

6868
if cert == nil {

internal/helpers/hash_test.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,29 @@ import (
1313
func TestUnitCalculateSHA256(t *testing.T) {
1414
t.Parallel()
1515

16+
const (
17+
sameContent = "same"
18+
differentContent = "different"
19+
)
20+
1621
tdir := t.TempDir()
1722
file1 := tdir + "/test.txt"
1823
file2 := tdir + "/test2.txt"
1924
file3 := tdir + "/test3.txt"
2025
file4 := tdir + "/test4.txt"
2126
file5 := tdir + "/test5"
2227

23-
err := os.WriteFile(file1, []byte("same"), 0644)
28+
err := os.WriteFile(file1, []byte(sameContent), 0644)
2429
if err != nil {
2530
t.Fatal(err)
2631
}
2732

28-
err = os.WriteFile(file2, []byte("same"), 0644)
33+
err = os.WriteFile(file2, []byte(sameContent), 0644)
2934
if err != nil {
3035
t.Fatal(err)
3136
}
3237

33-
err = os.WriteFile(file3, []byte("different"), 0644)
38+
err = os.WriteFile(file3, []byte(differentContent), 0644)
3439
if err != nil {
3540
t.Fatal(err)
3641
}

internal/helpers/uri.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ package helpers
55

66
import (
77
"fmt"
8+
"net/url"
89
"strings"
10+
11+
"github.com/microsoft/terraform-provider-power-platform/internal/constants"
912
)
1013

1114
// BuildEnvironmentHostUri builds the host uri for the environmentid
@@ -29,3 +32,26 @@ func BuildTenantHostUri(tenantId, powerPlatformUrl string) string {
2932

3033
return fmt.Sprintf("%s.%s.tenant.%s", envId, realm, powerPlatformUrl)
3134
}
35+
36+
// BuildApiUrl builds a URL for API endpoints with a given host, path, and query parameters.
37+
func BuildApiUrl(host, path string, query url.Values) string {
38+
apiUrl := &url.URL{
39+
Scheme: constants.HTTPS,
40+
Host: host,
41+
Path: path,
42+
}
43+
if query != nil {
44+
apiUrl.RawQuery = query.Encode()
45+
}
46+
return apiUrl.String()
47+
}
48+
49+
// BuildDataverseApiUrl builds a URL for Dataverse API endpoints.
50+
func BuildDataverseApiUrl(environmentHost, path string, query url.Values) string {
51+
return BuildApiUrl(environmentHost, path, query)
52+
}
53+
54+
// BuildBapiUrl builds a URL for BAPI endpoints.
55+
func BuildBapiUrl(bapiHost, path string, query url.Values) string {
56+
return BuildApiUrl(bapiHost, path, query)
57+
}

internal/modifiers/set_bool_value_unknown_if_checksum_change_modifier.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func (d *setBoolValueToUnknownIfChecksumsChangeModifier) hasChecksumChanged(ctx
6161

6262
value, err := helpers.CalculateSHA256(attribute.ValueString())
6363
if err != nil {
64-
resp.Diagnostics.AddError(fmt.Sprintf("Error calculating MD5 checksum for %s", attribute), err.Error())
64+
resp.Diagnostics.AddError(fmt.Sprintf("Error calculating SHA256 checksum for %q", attribute), err.Error())
6565
}
6666

6767
return value != attributeChecksum.ValueString()

internal/modifiers/set_string_value_unknown_if_checksum_change_modifier.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,20 @@ func (d *setStringValueToUnknownIfChecksumsChangeModifier) hasChecksumChanged(ct
5454
var attribute types.String
5555
diags := req.Plan.GetAttribute(ctx, path.Root(attributeName), &attribute)
5656
resp.Diagnostics.Append(diags...)
57+
if diags.HasError() {
58+
return false
59+
}
5760

5861
var attributeChecksum types.String
5962
diags = req.State.GetAttribute(ctx, path.Root(checksumAttributeName), &attributeChecksum)
6063
resp.Diagnostics.Append(diags...)
64+
if diags.HasError() {
65+
return false
66+
}
6167

6268
value, err := helpers.CalculateSHA256(attribute.ValueString())
6369
if err != nil {
64-
resp.Diagnostics.AddError(fmt.Sprintf("Error calculating MD5 checksum for %s", attribute), err.Error())
70+
resp.Diagnostics.AddError(fmt.Sprintf("Error calculating SHA256 checksum for attribute %q", attributeName), err.Error())
6571
}
6672

6773
return value != attributeChecksum.ValueString()

internal/modifiers/sync_attribute_plan_modifier.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,17 @@ func (d *syncAttributePlanModifier) PlanModifyString(ctx context.Context, req pl
4040
return
4141
}
4242

43-
if settingsFile.IsNull() {
44-
resp.PlanValue = types.StringNull()
45-
} else if settingsFile.IsUnknown() {
43+
if settingsFile.IsNull() || settingsFile.IsUnknown() {
4644
resp.PlanValue = types.StringNull()
4745
} else {
4846
value, err := helpers.CalculateSHA256(settingsFile.ValueString())
4947
if err != nil {
50-
resp.Diagnostics.AddError(fmt.Sprintf("Error calculating MD5 checksum for %s", d.syncAttribute), err.Error())
48+
resp.Diagnostics.AddError(fmt.Sprintf("Error calculating SHA256 checksum for %s", d.syncAttribute), err.Error())
5149
return
5250
}
5351

5452
if value == "" {
53+
resp.Diagnostics.AddError(fmt.Sprintf("Checksum is empty for %s", d.syncAttribute), "Calculated SHA256 checksum resulted in an empty value, which is unexpected.")
5554
resp.PlanValue = types.StringUnknown()
5655
} else {
5756
resp.PlanValue = types.StringValue(value)

internal/provider/provider.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,11 @@ func configureUseMsi(ctx context.Context, p *PowerPlatformProvider, clientId str
355355
// Convert the slice to an array
356356
auxiliaryTenantIDsList := make([]string, len(auxiliaryTenantIDs.Elements()))
357357
for i, v := range auxiliaryTenantIDs.Elements() {
358-
auxiliaryTenantIDsList[i] = v.String()
358+
if str, ok := v.(types.String); ok {
359+
auxiliaryTenantIDsList[i] = str.ValueString()
360+
} else {
361+
tflog.Warn(ctx, fmt.Sprintf("Element at index %d in auxiliaryTenantIDs is not of type types.String. Skipping.", i))
362+
}
359363
}
360364
p.Config.AuxiliaryTenantIDs = auxiliaryTenantIDsList
361365
p.Config.UseMsi = true

internal/provider/provider_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,9 @@ func TestUnitPowerPlatformProviderHasChildDataSources_Basic(t *testing.T) {
7676
}
7777
datasources := provider.NewPowerPlatformProvider(context.Background())().(*provider.PowerPlatformProvider).DataSources(context.Background())
7878

79-
require.Equal(t, len(expectedDataSources), len(datasources), "There are an unexpected number of registered data sources")
79+
require.Equalf(t, len(expectedDataSources), len(datasources), "Expected %d data sources, got %d", len(expectedDataSources), len(datasources))
8080
for _, d := range datasources {
81-
require.Contains(t, expectedDataSources, d(), "An unexpected data source was registered")
81+
require.Containsf(t, expectedDataSources, d(), "Data source %+v was not expected", d())
8282
}
8383
}
8484

@@ -109,9 +109,9 @@ func TestUnitPowerPlatformProviderHasChildResources_Basic(t *testing.T) {
109109
}
110110
resources := provider.NewPowerPlatformProvider(context.Background())().(*provider.PowerPlatformProvider).Resources(context.Background())
111111

112-
require.Equal(t, len(expectedResources), len(resources), "There are an unexpected number of registered resources")
112+
require.Equalf(t, len(expectedResources), len(resources), "Expected %d resources, got %d", len(expectedResources), len(resources))
113113
for _, r := range resources {
114-
require.Contains(t, expectedResources, r(), "An unexpected resource was registered")
114+
require.Containsf(t, expectedResources, r(), "Resource %+v was not expected", r())
115115
}
116116
}
117117

0 commit comments

Comments
 (0)