From dbdd04cb0e8bc2ee7071589bd772bbee33d447fb Mon Sep 17 00:00:00 2001 From: Eron Wright Date: Thu, 6 Nov 2025 23:17:16 -0800 Subject: [PATCH 1/2] Fix dispatch_docs_build OIDC authentication MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add id-token: write permission to dispatch_docs_build job to enable OIDC token minting for Pulumi ESC authentication. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/workflows/release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 94101ab01b31..87a1b6b3b044 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -40,6 +40,9 @@ jobs: needs: publish # not a prerelease if: ${{ !contains(github.ref_name,'-') }} + permissions: + id-token: write + contents: read steps: - name: Checkout Repo uses: actions/checkout@v4 From d087bfdb121b49482c8e21b2e8eec0a5f61da37d Mon Sep 17 00:00:00 2001 From: Eron Wright Date: Fri, 7 Nov 2025 15:55:11 -0800 Subject: [PATCH 2/2] Fix panic in PimRoleEligibilitySchedule with NoExpiration type (#4351) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit fixes a panic that occurred when creating a PimRoleEligibilitySchedule resource with NoExpiration type without providing a duration field. The bug was in the inputsToSdk function at line 460, where the code unconditionally accessed exp["duration"].StringValue() even when the duration field was nil/not provided. According to Azure's PIM API schema, the expiration object supports three types: - AfterDuration: requires duration field (ISO 8601 duration) - AfterDateTime: requires endDateTime field - NoExpiration: should NOT have either field The fix adds proper checks for optional fields before accessing them, following the same pattern used elsewhere in the codebase for optional nested fields (see ticketInfo handling). Also added comprehensive test coverage for all three expiration types to prevent regression. Fixes #4351 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../customresources/custom_pim_eligibility.go | 17 ++++++- .../custom_pim_eligibility_test.go | 45 +++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/provider/pkg/resources/customresources/custom_pim_eligibility.go b/provider/pkg/resources/customresources/custom_pim_eligibility.go index 3415b7f93142..e9e232c5dc7e 100644 --- a/provider/pkg/resources/customresources/custom_pim_eligibility.go +++ b/provider/pkg/resources/customresources/custom_pim_eligibility.go @@ -457,7 +457,22 @@ func inputsToSdk(inputs resource.PropertyMap) (*armauthorization.RoleEligibility if expiration, ok := info["expiration"]; ok { exp := expiration.ObjectValue() - result.Properties.ScheduleInfo.Expiration.Duration = pulumi.StringRef(exp["duration"].StringValue()) + + // duration is optional - only present for AfterDuration type + if duration, ok := exp["duration"]; ok { + result.Properties.ScheduleInfo.Expiration.Duration = pulumi.StringRef(duration.StringValue()) + } + + // endDateTime is optional - only present for AfterDateTime type + if endDateTime, ok := exp["endDateTime"]; ok { + endTime, err := time.Parse(time.RFC3339, endDateTime.StringValue()) + if err != nil { + return nil, fmt.Errorf("invalid end time: %w", err) + } + result.Properties.ScheduleInfo.Expiration.EndDateTime = &endTime + } + + // type is required expirationType := armauthorization.Type(exp["type"].StringValue()) result.Properties.ScheduleInfo.Expiration.Type = &expirationType } diff --git a/provider/pkg/resources/customresources/custom_pim_eligibility_test.go b/provider/pkg/resources/customresources/custom_pim_eligibility_test.go index 0d679420ced6..a4a0d65cec97 100644 --- a/provider/pkg/resources/customresources/custom_pim_eligibility_test.go +++ b/provider/pkg/resources/customresources/custom_pim_eligibility_test.go @@ -382,6 +382,51 @@ func TestInputsToSdk(t *testing.T) { assert.Equal(t, armauthorization.TypeAfterDuration, *sdk.Properties.ScheduleInfo.Expiration.Type) }) + t.Run("scheduleInfo with NoExpiration", func(t *testing.T) { + t.Parallel() + + inputs := validRequiredInputs.Copy() + now := time.Now() + inputs["scheduleInfo"] = resource.NewObjectProperty(resource.PropertyMap{ + "startDateTime": resource.NewStringProperty(now.Format(time.RFC3339)), + "expiration": resource.NewObjectProperty(resource.PropertyMap{ + "type": resource.NewStringProperty("NoExpiration"), + }), + }) + + sdk, err := inputsToSdk(inputs) + require.NoError(t, err) + require.NotNil(t, sdk.Properties.ScheduleInfo) + assert.WithinDuration(t, now, *sdk.Properties.ScheduleInfo.StartDateTime, 1*time.Second) + assert.Nil(t, sdk.Properties.ScheduleInfo.Expiration.Duration) + assert.Nil(t, sdk.Properties.ScheduleInfo.Expiration.EndDateTime) + assert.Equal(t, armauthorization.TypeNoExpiration, *sdk.Properties.ScheduleInfo.Expiration.Type) + }) + + t.Run("scheduleInfo with AfterDateTime", func(t *testing.T) { + t.Parallel() + + inputs := validRequiredInputs.Copy() + now := time.Now() + endTime := now.Add(24 * time.Hour) + inputs["scheduleInfo"] = resource.NewObjectProperty(resource.PropertyMap{ + "startDateTime": resource.NewStringProperty(now.Format(time.RFC3339)), + "expiration": resource.NewObjectProperty(resource.PropertyMap{ + "endDateTime": resource.NewStringProperty(endTime.Format(time.RFC3339)), + "type": resource.NewStringProperty("AfterDateTime"), + }), + }) + + sdk, err := inputsToSdk(inputs) + require.NoError(t, err) + require.NotNil(t, sdk.Properties.ScheduleInfo) + assert.WithinDuration(t, now, *sdk.Properties.ScheduleInfo.StartDateTime, 1*time.Second) + assert.Nil(t, sdk.Properties.ScheduleInfo.Expiration.Duration) + require.NotNil(t, sdk.Properties.ScheduleInfo.Expiration.EndDateTime) + assert.WithinDuration(t, endTime, *sdk.Properties.ScheduleInfo.Expiration.EndDateTime, 1*time.Second) + assert.Equal(t, armauthorization.TypeAfterDateTime, *sdk.Properties.ScheduleInfo.Expiration.Type) + }) + t.Run("misc optional properties", func(t *testing.T) { t.Parallel()