From 7ae815f26b0a50728f5fdd5546767eaec9cf37e1 Mon Sep 17 00:00:00 2001 From: Jonas Bakken Date: Mon, 31 Jul 2023 08:56:34 +0200 Subject: [PATCH 01/11] Add azuread_directory_role_eligibility_schedule_request resource --- .../services/directoryroles/client/client.go | 25 ++- ...e_eligibility_schedule_request_resource.go | 177 ++++++++++++++++++ ...gibility_schedule_request_resource_test.go | 74 ++++++++ .../services/directoryroles/registration.go | 9 +- 4 files changed, 271 insertions(+), 14 deletions(-) create mode 100644 internal/services/directoryroles/directory_role_eligibility_schedule_request_resource.go create mode 100644 internal/services/directoryroles/directory_role_eligibility_schedule_request_resource_test.go diff --git a/internal/services/directoryroles/client/client.go b/internal/services/directoryroles/client/client.go index 82b6f1f557..1f76338116 100644 --- a/internal/services/directoryroles/client/client.go +++ b/internal/services/directoryroles/client/client.go @@ -9,11 +9,12 @@ import ( ) type Client struct { - DirectoryObjectsClient *msgraph.DirectoryObjectsClient - DirectoryRolesClient *msgraph.DirectoryRolesClient - DirectoryRoleTemplatesClient *msgraph.DirectoryRoleTemplatesClient - RoleAssignmentsClient *msgraph.RoleAssignmentsClient - RoleDefinitionsClient *msgraph.RoleDefinitionsClient + DirectoryObjectsClient *msgraph.DirectoryObjectsClient + DirectoryRolesClient *msgraph.DirectoryRolesClient + DirectoryRoleTemplatesClient *msgraph.DirectoryRoleTemplatesClient + RoleAssignmentsClient *msgraph.RoleAssignmentsClient + RoleDefinitionsClient *msgraph.RoleDefinitionsClient + RoleEligibilityScheduleRequestClient *msgraph.RoleEligibilityScheduleRequestClient } func NewClient(o *common.ClientOptions) *Client { @@ -32,11 +33,15 @@ func NewClient(o *common.ClientOptions) *Client { roleDefinitionsClient := msgraph.NewRoleDefinitionsClient() o.ConfigureClient(&roleDefinitionsClient.BaseClient) + roleEligibilityScheduleRequestClient := msgraph.NewRoleEligibilityScheduleRequestClient() + o.ConfigureClient(&roleEligibilityScheduleRequestClient.BaseClient) + return &Client{ - DirectoryObjectsClient: directoryObjectsClient, - DirectoryRolesClient: directoryRolesClient, - DirectoryRoleTemplatesClient: directoryRoleTemplatesClient, - RoleAssignmentsClient: roleAssignmentsClient, - RoleDefinitionsClient: roleDefinitionsClient, + DirectoryObjectsClient: directoryObjectsClient, + DirectoryRolesClient: directoryRolesClient, + DirectoryRoleTemplatesClient: directoryRoleTemplatesClient, + RoleAssignmentsClient: roleAssignmentsClient, + RoleDefinitionsClient: roleDefinitionsClient, + RoleEligibilityScheduleRequestClient: roleEligibilityScheduleRequestClient, } } diff --git a/internal/services/directoryroles/directory_role_eligibility_schedule_request_resource.go b/internal/services/directoryroles/directory_role_eligibility_schedule_request_resource.go new file mode 100644 index 0000000000..79d9582e94 --- /dev/null +++ b/internal/services/directoryroles/directory_role_eligibility_schedule_request_resource.go @@ -0,0 +1,177 @@ +package directoryroles + +import ( + "context" + "errors" + "fmt" + "log" + "net/http" + "time" + + "github.com/hashicorp/go-azure-sdk/sdk/odata" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-azuread/internal/clients" + "github.com/hashicorp/terraform-provider-azuread/internal/tf" + "github.com/hashicorp/terraform-provider-azuread/internal/utils" + "github.com/hashicorp/terraform-provider-azuread/internal/validate" + "github.com/manicminer/hamilton/msgraph" +) + +func directoryRoleEligibilityScheduleRequestResource() *schema.Resource { + return &schema.Resource{ + CreateContext: directoryRoleEligibilityScheduleRequestResourceCreate, + ReadContext: directoryRoleEligibilityScheduleRequestResourceRead, + DeleteContext: directoryRoleEligibilityScheduleRequestResourceDelete, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(5 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(5 * time.Minute), + Delete: schema.DefaultTimeout(5 * time.Minute), + }, + + Importer: tf.ValidateResourceIDPriorToImport(func(id string) error { + if id == "" { + return errors.New("id was empty") + } + return nil + }), + + Schema: map[string]*schema.Schema{ + "role_definition_id": { + Description: "The object ID of the directory role for this role eligibility schedule request", + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateDiagFunc: validate.UUID, + }, + + "principal_id": { + Description: "The object ID of the member principal", + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateDiagFunc: validate.UUID, + }, + + "directory_scope_id": { + Description: "Identifier of the directory object representing the scope of the role eligibility schedule request", + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateDiagFunc: validate.NoEmptyStrings, + }, + + "justification": { + Description: "Justification for why the role is assigned", + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateDiagFunc: validate.NoEmptyStrings, + }, + }, + } +} + +func directoryRoleEligibilityScheduleRequestResourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*clients.Client).DirectoryRoles.RoleEligibilityScheduleRequestClient + + roleDefinitionId := d.Get("role_definition_id").(string) + principalId := d.Get("principal_id").(string) + justification := d.Get("justification").(string) + directoryScopeId := d.Get("directory_scope_id").(string) + + now := time.Now() + properties := msgraph.UnifiedRoleEligibilityScheduleRequest{ + Action: utils.String(msgraph.UnifiedRoleScheduleRequestActionAdminAssign), + RoleDefinitionId: &roleDefinitionId, + PrincipalId: &principalId, + Justification: &justification, + DirectoryScopeId: &directoryScopeId, + ScheduleInfo: &msgraph.RequestSchedule{ + StartDateTime: &now, + Expiration: &msgraph.ExpirationPattern{ + Type: utils.String(msgraph.ExpirationPatternTypeNoExpiration), + }, + }, + } + + roleEligibilityScheduleRequest, status, err := client.Create(ctx, properties) + if err != nil { + return tf.ErrorDiagF(err, "Eligibility schedule request for role %q to principal %q, received %d with error: %+v", roleDefinitionId, principalId, status, err) + } + if roleEligibilityScheduleRequest == nil || roleEligibilityScheduleRequest.ID == nil { + return tf.ErrorDiagF(errors.New("returned role roleEligibilityScheduleRequest ID was nil"), "API Error") + } + + d.SetId(*roleEligibilityScheduleRequest.ID) + + deadline, ok := ctx.Deadline() + if !ok { + return tf.ErrorDiagF(errors.New("context has no deadline"), "Waiting for directory role %q eligibility schedule request to principal %q to take effect", roleDefinitionId, principalId) + } + timeout := time.Until(deadline) + _, err = (&resource.StateChangeConf{ + Pending: []string{"Waiting"}, + Target: []string{"Done"}, + Timeout: timeout, + MinTimeout: 1 * time.Second, + ContinuousTargetOccurence: 3, + Refresh: func() (interface{}, string, error) { + _, status, err := client.Get(ctx, *roleEligibilityScheduleRequest.ID, odata.Query{}) + if err != nil { + if status == http.StatusNotFound { + return "stub", "Waiting", nil + } + return nil, "Error", fmt.Errorf("retrieving role eligibility schedule request") + } + return "stub", "Done", nil + }, + }).WaitForStateContext(ctx) + if err != nil { + return tf.ErrorDiagF(err, "Waiting for role eligibility schedule request for %q to reflect in directory role %q", principalId, roleDefinitionId) + } + + return directoryRoleEligibilityScheduleRequestResourceRead(ctx, d, meta) +} + +func directoryRoleEligibilityScheduleRequestResourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*clients.Client).DirectoryRoles.RoleEligibilityScheduleRequestClient + + id := d.Id() + roleEligibilityScheduleRequest, status, err := client.Get(ctx, id, odata.Query{}) + if err != nil { + if status == http.StatusNotFound { + log.Printf("[DEBUG] roleEligibilityScheduleRequest with ID %q was not found - removing from state", id) + d.SetId("") + return nil + } + return tf.ErrorDiagF(err, "Retrieving roleEligibilityScheduleRequest %q", id) + } + + tf.Set(d, "role_definition_id", roleEligibilityScheduleRequest.RoleDefinitionId) + tf.Set(d, "principal_id", roleEligibilityScheduleRequest.PrincipalId) + tf.Set(d, "justification", roleEligibilityScheduleRequest.Justification) + tf.Set(d, "directory_scope_id", roleEligibilityScheduleRequest.DirectoryScopeId) + + return nil +} + +func directoryRoleEligibilityScheduleRequestResourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*clients.Client).DirectoryRoles.RoleEligibilityScheduleRequestClient + + id := d.Id() + roleEligibilityScheduleRequest, _, err := client.Get(ctx, id, odata.Query{}) + if err != nil { + return tf.ErrorDiagF(err, "Retrieving roleEligibilityScheduleRequest %q", id) + } + + roleEligibilityScheduleRequest.Action = utils.String(msgraph.UnifiedRoleScheduleRequestActionAdminRemove) + + if _, _, err := client.Create(ctx, *roleEligibilityScheduleRequest); err != nil { + return tf.ErrorDiagF(err, "Deleting role eligibility schedule request %q: %+v", d.Id(), err) + } + return nil +} diff --git a/internal/services/directoryroles/directory_role_eligibility_schedule_request_resource_test.go b/internal/services/directoryroles/directory_role_eligibility_schedule_request_resource_test.go new file mode 100644 index 0000000000..10e34a91af --- /dev/null +++ b/internal/services/directoryroles/directory_role_eligibility_schedule_request_resource_test.go @@ -0,0 +1,74 @@ +package directoryroles_test + +import ( + "context" + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/go-azure-sdk/sdk/odata" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-azuread/internal/acceptance" + "github.com/hashicorp/terraform-provider-azuread/internal/acceptance/check" + "github.com/hashicorp/terraform-provider-azuread/internal/clients" + "github.com/hashicorp/terraform-provider-azuread/internal/utils" +) + +type RoleEligibilityScheduleRequestResource struct{} + +func TestAccRoleEligibilityScheduleRequest_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azuread_directory_role_eligibility_schedule_request", "test") + r := RoleEligibilityScheduleRequestResource{} + + data.ResourceTestIgnoreDangling(t, r, []resource.TestStep{ + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + }) +} + +func (r RoleEligibilityScheduleRequestResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { + client := clients.DirectoryRoles.RoleEligibilityScheduleRequestClient + + resr, status, err := client.Get(ctx, state.ID, odata.Query{}) + if err != nil { + fmt.Printf("%s, %v\n", err.Error(), status) + if status == http.StatusNotFound { + return nil, fmt.Errorf("Role Eligibility Schedule Request with ID %q does not exist", state.ID) + } + return nil, fmt.Errorf("failed to retrieve Role Eligibility Schedule Request with object ID %q: %+v", state.ID, err) + } + + return utils.Bool(resr.ID != nil && *resr.ID == state.ID), nil +} + +func (r RoleEligibilityScheduleRequestResource) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azuread" {} + +data "azuread_domains" "test" { + only_initial = true +} + +resource "azuread_user" "test" { + user_principal_name = "acctestManager.%[1]d@${data.azuread_domains.test.domains.0.domain_name}" + display_name = "acctestManager-%[1]d" + password = "%[2]s" +} + +resource "azuread_directory_role" "test" { + display_name = "Application Administrator" +} + +resource "azuread_directory_role_eligibility_schedule_request" "test" { + role_definition_id = azuread_directory_role.test.template_id + principal_id = azuread_user.test.object_id + directory_scope_id = "/" + justification = "abc" +} +`, data.RandomInteger, data.RandomPassword) +} diff --git a/internal/services/directoryroles/registration.go b/internal/services/directoryroles/registration.go index 5e27f18b62..b87bb407fd 100644 --- a/internal/services/directoryroles/registration.go +++ b/internal/services/directoryroles/registration.go @@ -32,9 +32,10 @@ func (r Registration) SupportedDataSources() map[string]*schema.Resource { // SupportedResources returns the supported Resources supported by this Service func (r Registration) SupportedResources() map[string]*schema.Resource { return map[string]*schema.Resource{ - "azuread_custom_directory_role": customDirectoryRoleResource(), - "azuread_directory_role": directoryRoleResource(), - "azuread_directory_role_assignment": directoryRoleAssignmentResource(), - "azuread_directory_role_member": directoryRoleMemberResource(), + "azuread_custom_directory_role": customDirectoryRoleResource(), + "azuread_directory_role": directoryRoleResource(), + "azuread_directory_role_assignment": directoryRoleAssignmentResource(), + "azuread_directory_role_member": directoryRoleMemberResource(), + "azuread_directory_role_eligibility_schedule_request": directoryRoleEligibilityScheduleRequestResource(), } } From d40446cc8a84a8a7392e9886b159fcf75e9edec6 Mon Sep 17 00:00:00 2001 From: Jonas Bakken Date: Mon, 31 Jul 2023 09:12:00 +0200 Subject: [PATCH 02/11] Use helpers.WaitForUpdate() in directoryRoleEligibilityScheduleRequestResourceCreate() --- ...e_eligibility_schedule_request_resource.go | 39 +++++++------------ 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/internal/services/directoryroles/directory_role_eligibility_schedule_request_resource.go b/internal/services/directoryroles/directory_role_eligibility_schedule_request_resource.go index 79d9582e94..b080691891 100644 --- a/internal/services/directoryroles/directory_role_eligibility_schedule_request_resource.go +++ b/internal/services/directoryroles/directory_role_eligibility_schedule_request_resource.go @@ -3,16 +3,15 @@ package directoryroles import ( "context" "errors" - "fmt" "log" "net/http" "time" "github.com/hashicorp/go-azure-sdk/sdk/odata" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-azuread/internal/clients" + "github.com/hashicorp/terraform-provider-azuread/internal/helpers" "github.com/hashicorp/terraform-provider-azuread/internal/tf" "github.com/hashicorp/terraform-provider-azuread/internal/utils" "github.com/hashicorp/terraform-provider-azuread/internal/validate" @@ -108,30 +107,20 @@ func directoryRoleEligibilityScheduleRequestResourceCreate(ctx context.Context, d.SetId(*roleEligibilityScheduleRequest.ID) - deadline, ok := ctx.Deadline() - if !ok { - return tf.ErrorDiagF(errors.New("context has no deadline"), "Waiting for directory role %q eligibility schedule request to principal %q to take effect", roleDefinitionId, principalId) - } - timeout := time.Until(deadline) - _, err = (&resource.StateChangeConf{ - Pending: []string{"Waiting"}, - Target: []string{"Done"}, - Timeout: timeout, - MinTimeout: 1 * time.Second, - ContinuousTargetOccurence: 3, - Refresh: func() (interface{}, string, error) { - _, status, err := client.Get(ctx, *roleEligibilityScheduleRequest.ID, odata.Query{}) - if err != nil { - if status == http.StatusNotFound { - return "stub", "Waiting", nil - } - return nil, "Error", fmt.Errorf("retrieving role eligibility schedule request") + if err := helpers.WaitForUpdate(ctx, func(ctx context.Context) (*bool, error) { + defer func() { client.BaseClient.DisableRetries = false }() + client.BaseClient.DisableRetries = true + + resr, status, err := client.Get(ctx, *roleEligibilityScheduleRequest.ID, odata.Query{}) + if err != nil { + if status == http.StatusNotFound { + return utils.Bool(false), nil } - return "stub", "Done", nil - }, - }).WaitForStateContext(ctx) - if err != nil { - return tf.ErrorDiagF(err, "Waiting for role eligibility schedule request for %q to reflect in directory role %q", principalId, roleDefinitionId) + return nil, err + } + return utils.Bool(resr != nil), nil + }); err != nil { + return tf.ErrorDiagF(err, "Waiting for role eligibility schedule request for %q to be created for directory role %q", principalId, roleDefinitionId) } return directoryRoleEligibilityScheduleRequestResourceRead(ctx, d, meta) From 15bcd3c3624327b27a5117281c5d4d94dd4e26a5 Mon Sep 17 00:00:00 2001 From: Jonas Bakken Date: Mon, 31 Jul 2023 10:18:27 +0200 Subject: [PATCH 03/11] Add docs for azuread_directory_role_eligibility_schedule_request --- ...ctory_role_eligibility_schedule_request.md | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 docs/resources/directory_role_eligibility_schedule_request.md diff --git a/docs/resources/directory_role_eligibility_schedule_request.md b/docs/resources/directory_role_eligibility_schedule_request.md new file mode 100644 index 0000000000..91b73d6bf7 --- /dev/null +++ b/docs/resources/directory_role_eligibility_schedule_request.md @@ -0,0 +1,59 @@ +--- +subcategory: "Directory Roles" +--- + +# Resource: azuread_directory_role_eligibility_schedule_request + +Manages a single directory role eligibility schedule request within Azure Active Directory. + +## API Permissions + +The following API permissions are required in order to use this resource. + +The calling principal requires one of the following application roles: `RoleEligibilitySchedule.ReadWrite.Directory` or `RoleManagement.ReadWrite.Directory`. + +The calling principal requires one of the following directory roles: `Privileged Role Administrator` or `Global Administrator`. + +## Example Usage + +```terraform +data "azuread_user" "example" { + user_principal_name = "jdoe@hashicorp.com" +} + +resource "azuread_directory_role" "example" { + display_name = "Application Administrator" +} + +resource "azuread_directory_role_eligibility_schedule_request" "example" { + role_definition_id = azuread_directory_role.example.template_id + principal_id = azuread_user.example.object_id + directory_scope_id = "/" + justification = "Example" +} +``` + +~> Note the use of the `template_id` attribute when referencing built-in roles. + +## Argument Reference + +The following arguments are supported: + +* `role_definition_id` - (Required) The template ID (in the case of built-in roles) or object ID (in the case of custom roles) of the directory role you want to assign. Changing this forces a new resource to be created. +* `principal_id` - (Required) The object ID of the principal to granted the role eligibility. Changing this forces a new resource to be created. +* `directory_scope_id` - (Required) Identifier of the directory object representing the scope of the role eligibility. Changing this forces a new resource to be created. +* `justification` - (Required) Justification for why the principal is granted the role eligibility. Changing this forces a new resource to be created. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +*No additional attributes are exported* + +## Import + +Directory role eligibility schedule requests can be imported using the ID of the assignment, e.g. + +```shell +terraform import azuread_directory_role_eligibility_schedule_request.example 822ec710-4c9f-4f71-a27a-451759cc7522 +``` From 5bb1079a763f4855d29ed3b076a6ab98616180a4 Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Thu, 21 Sep 2023 16:06:31 +0100 Subject: [PATCH 04/11] Tooling: GitHub workflow updates --- ...e-triage.yml => labeler-issue-triage.yaml} | 0 ...e.yml => labeler-pull-request-triage.yaml} | 0 .github/workflows/acctest-oidc.yaml | 45 ---------- .github/workflows/depscheck.yaml | 4 + .github/workflows/docs-lint.yaml | 4 + .github/workflows/golint.yaml | 4 + .github/workflows/increment-milestone.yaml | 23 +++++ .github/workflows/issue-comment-created.yaml | 45 +++++----- .github/workflows/issue-opened.yaml | 2 +- .github/workflows/link-milestone.yaml | 1 + .github/workflows/provider-test.yaml | 85 +++++++++++++++++++ .../workflows/pull-request-new-commit.yaml | 15 ++++ .../pull-request-reviewed-workflow.yaml | 55 ++++++++++++ .github/workflows/pull-request-reviewed.yaml | 38 +++++++++ .github/workflows/pull-request.yaml | 3 +- .github/workflows/remove-issue-label.yaml | 50 +++++++++++ .github/workflows/save-artifacts.yaml | 20 +++++ .github/workflows/tflint.yaml | 4 + .github/workflows/thirty-two-bit.yaml | 4 + .github/workflows/unit-test.yaml | 4 + .github/workflows/validate-examples.yaml | 8 +- scripts/increment-milestone.sh | 79 +++++++++++++++++ 22 files changed, 424 insertions(+), 69 deletions(-) rename .github/{labeler-issue-triage.yml => labeler-issue-triage.yaml} (100%) rename .github/{labeler-pull-request-triage.yml => labeler-pull-request-triage.yaml} (100%) delete mode 100644 .github/workflows/acctest-oidc.yaml create mode 100644 .github/workflows/increment-milestone.yaml create mode 100644 .github/workflows/provider-test.yaml create mode 100644 .github/workflows/pull-request-new-commit.yaml create mode 100644 .github/workflows/pull-request-reviewed-workflow.yaml create mode 100644 .github/workflows/pull-request-reviewed.yaml create mode 100644 .github/workflows/remove-issue-label.yaml create mode 100644 .github/workflows/save-artifacts.yaml create mode 100644 scripts/increment-milestone.sh diff --git a/.github/labeler-issue-triage.yml b/.github/labeler-issue-triage.yaml similarity index 100% rename from .github/labeler-issue-triage.yml rename to .github/labeler-issue-triage.yaml diff --git a/.github/labeler-pull-request-triage.yml b/.github/labeler-pull-request-triage.yaml similarity index 100% rename from .github/labeler-pull-request-triage.yml rename to .github/labeler-pull-request-triage.yaml diff --git a/.github/workflows/acctest-oidc.yaml b/.github/workflows/acctest-oidc.yaml deleted file mode 100644 index 0854700e43..0000000000 --- a/.github/workflows/acctest-oidc.yaml +++ /dev/null @@ -1,45 +0,0 @@ ---- -name: "Acceptance Tests: OIDC" -on: - pull_request: - types: ['opened', 'synchronize'] - paths: - - '.github/workflows/acctest-oidc.yaml' - - 'internal/provider/**' - - 'vendor/github.com/hashicorp/go-azure-sdk/sdk/auth/**' - -permissions: - contents: read - id-token: write - -jobs: - oidc-check: - runs-on: ubuntu-latest - outputs: - available: "${{ steps.check-oidc.outputs.available }}" - steps: - - id: check-oidc - run: | - if [[ "${ACTIONS_ID_TOKEN_REQUEST_URL}" == "" ]]; then - echo "available=false" | tee ${GITHUB_OUTPUT} - else - echo "available=true" | tee ${GITHUB_OUTPUT} - fi - - acctest-oidc: - runs-on: ubuntu-latest - needs: [oidc-check] - if: needs.oidc-check.outputs.available == 'true' - steps: - - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 - - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 - with: - go-version-file: .go-version - - run: bash scripts/gogetcookie.sh - - run: make tools - - run: | - echo "ARM_OIDC_TOKEN=$(curl -H "Accept: application/json; api-version=2.0" -H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" -H "Content-Type: application/json" -G --data-urlencode "audience=api://AzureADTokenExchange" "${ACTIONS_ID_TOKEN_REQUEST_URL}" | jq -r '.value')" >>${GITHUB_ENV} - - run: make testacc TEST=./internal/provider TESTARGS="-run '(?i)(TestAccProvider_.*oidc.*)'" - env: - ARM_CLIENT_ID: ${{ secrets.OIDC_CLIENT_ID }} - ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }} diff --git a/.github/workflows/depscheck.yaml b/.github/workflows/depscheck.yaml index bd835935bd..0c6d54b37b 100644 --- a/.github/workflows/depscheck.yaml +++ b/.github/workflows/depscheck.yaml @@ -19,3 +19,7 @@ jobs: - run: bash scripts/gogetcookie.sh - run: make tools - run: make depscheck + + save-artifacts-on-fail: + if: ${{ needs.depscheck.result }} == 'failure' + uses: ./.github/workflows/save-artifacts.yaml diff --git a/.github/workflows/docs-lint.yaml b/.github/workflows/docs-lint.yaml index 178a25736e..350c484b14 100644 --- a/.github/workflows/docs-lint.yaml +++ b/.github/workflows/docs-lint.yaml @@ -18,3 +18,7 @@ jobs: - run: bash scripts/gogetcookie.sh - run: make tools - run: make docs-lint + + save-artifacts-on-fail: + if: ${{ needs.docs-lint.result }} == 'failure' + uses: ./.github/workflows/save-artifacts.yaml diff --git a/.github/workflows/golint.yaml b/.github/workflows/golint.yaml index 8fc3eb2e1a..214d3a5a66 100644 --- a/.github/workflows/golint.yaml +++ b/.github/workflows/golint.yaml @@ -20,3 +20,7 @@ jobs: with: version: 'v1.50.1' args: -v + + save-artifacts-on-fail: + if: ${{ needs.golint.result }} == 'failure' + uses: ./.github/workflows/save-artifacts.yaml diff --git a/.github/workflows/increment-milestone.yaml b/.github/workflows/increment-milestone.yaml new file mode 100644 index 0000000000..7a3733a0a5 --- /dev/null +++ b/.github/workflows/increment-milestone.yaml @@ -0,0 +1,23 @@ +--- +name: Increment Milestone + +on: + push: + tags: + - 'v*.*.*' + +permissions: + issues: write + contents: read + +jobs: + increment-milestone: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + with: + fetch-depth: 0 + + - name: "Increment Milestone" + shell: bash + run: bash ./scripts/increment-milestone.sh -u https://api.github.com/repos${{ github.owner }}/${{ github.repository }}/milestones -r ${{github.ref_name}} -t ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/issue-comment-created.yaml b/.github/workflows/issue-comment-created.yaml index ee1b339cf5..7a135f0e2c 100644 --- a/.github/workflows/issue-comment-created.yaml +++ b/.github/workflows/issue-comment-created.yaml @@ -10,28 +10,33 @@ permissions: issues: write jobs: - issue_comment_triage: - runs-on: ubuntu-latest - steps: - - uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0 # v1.3.0 - with: - github_token: "${{ secrets.GITHUB_TOKEN }}" - labels: stale - - uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0 # v1.3.0 - if: ${{ !github.event.issue.pull_request }} - with: - github_token: "${{ secrets.GITHUB_TOKEN }}" - labels: waiting-response - - uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0 # v1.3.0 - if: (github.event.issue.pull_request && github.actor == github.event.issue.user.login) - with: - github_token: "${{ secrets.GITHUB_TOKEN }}" - labels: waiting-response + remove-stale: + uses: ./.github/workflows/remove-issue-label.yaml + with: + label-name: "stale" + + remove-waiting-response-from-issue: + uses: ./.github/workflows/remove-issue-label.yaml + if: ${{ !github.event.issue.pull_request }} + with: + label-name: "waiting-response" + + remove-waiting-response-from-pr: + uses: ./.github/workflows/remove-issue-label.yaml + if: (github.event.issue.pull_request && github.actor == github.event.issue.user.login) + with: + label-name: "waiting-response" pull_request_comment: runs-on: ubuntu-latest if: github.event.issue.pull_request && endsWith(github.event.comment.body, '/wr') steps: - - shell: bash - run: | - curl -X POST -H "Accept: application/vnd.github+json" -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos${{ github.owner }}/${{ github.repository }}/issues/${{ github.event.issue.number }}/labels" -d '{"labels":["waiting-response"]}' + - uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1 + with: + script: | + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ["waiting-response"] + }) diff --git a/.github/workflows/issue-opened.yaml b/.github/workflows/issue-opened.yaml index 47e02b0741..ea06246732 100644 --- a/.github/workflows/issue-opened.yaml +++ b/.github/workflows/issue-opened.yaml @@ -16,5 +16,5 @@ jobs: - uses: github/issue-labeler@98b5412841f6c4b0b3d9c29d53c13fad16bd7de2 # v3.2 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" - configuration-path: .github/labeler-issue-triage.yml + configuration-path: .github/labeler-issue-triage.yaml enable-versioned-regex: 0 diff --git a/.github/workflows/link-milestone.yaml b/.github/workflows/link-milestone.yaml index f3fb60db6b..e6dd367627 100644 --- a/.github/workflows/link-milestone.yaml +++ b/.github/workflows/link-milestone.yaml @@ -18,6 +18,7 @@ jobs: - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 with: go-version-file: .go-version + - run: | go install github.com/stephybun/link-milestone@latest link-milestone diff --git a/.github/workflows/provider-test.yaml b/.github/workflows/provider-test.yaml new file mode 100644 index 0000000000..48758d0690 --- /dev/null +++ b/.github/workflows/provider-test.yaml @@ -0,0 +1,85 @@ +--- +name: Provider Tests +on: + pull_request: + types: ["opened", "synchronize"] + paths: + - '.github/workflows/provider-test.yaml' + - 'internal/**.go' + - 'vendor/github.com/hashicorp/go-azure-sdk/sdk/auth/**' + - 'vendor/github.com/hashicorp/go-azure-sdk/sdk/environments/**' + +permissions: + contents: read + id-token: write + pull-requests: read + +jobs: + secrets-check: + runs-on: ubuntu-latest + outputs: + available: "${{ steps.check-secrets.outputs.available }}" + steps: + # we check for the ACTIONS_ID_TOKEN_REQUEST_URL variable as a proxy for other secrets + # it will be unset when running for a PR from a fork, in which case we don't run these tests + - id: check-secrets + run: | + if [[ "${ACTIONS_ID_TOKEN_REQUEST_URL}" == "" ]]; then + echo "available=false" | tee ${GITHUB_OUTPUT} + else + echo "available=true" | tee ${GITHUB_OUTPUT} + fi + + provider-tests: + runs-on: [custom, linux, large] + needs: [secrets-check] + if: needs.secrets-check.outputs.available == 'true' + steps: + - name: Checkout + uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + + - name: Install Go + uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 + with: + go-version-file: ./.go-version + + - name: Azure CLI login + run: az login --output none --username="${{ secrets.AZCLI_USERNAME }}" --password="${{ secrets.AZCLI_PASSWORD }}" + + - name: Set OIDC Token + run: | + echo "ARM_OIDC_TOKEN=$(curl -H "Accept: application/json; api-version=2.0" -H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" -H "Content-Type: application/json" -G --data-urlencode "audience=api://AzureADTokenExchange" "${ACTIONS_ID_TOKEN_REQUEST_URL}" | jq -r '.value')" >>${GITHUB_ENV} + + - name: Set OIDC Token File Path + run: echo "${ARM_OIDC_TOKEN}" >"${RUNNER_TEMP}/oidc-token.jwt" && echo "ARM_OIDC_TOKEN_FILE_PATH=${RUNNER_TEMP}/oidc-token.jwt" >>${GITHUB_ENV} + + - name: Set Client ID Path + run: echo "${{ secrets.ARM_CLIENT_ID }}" >"${RUNNER_TEMP}/client-id" && echo "ARM_CLIENT_ID_PATH=${RUNNER_TEMP}/client-id" >>${GITHUB_ENV} + + - name: Set Client Secret Path + run: echo "${{ secrets.ARM_CLIENT_SECRET }}" >"${RUNNER_TEMP}/client-secret" && echo "ARM_CLIENT_SECRET_PATH=${RUNNER_TEMP}/client-secret" >>${GITHUB_ENV} + + - name: Run provider tests + run: make testacc TEST=./internal/provider TESTARGS="-run '^TestAcc'" + env: + ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }} + ARM_CLIENT_CERTIFICATE: ${{ secrets.ARM_CLIENT_CERTIFICATE }} + ARM_CLIENT_CERTIFICATE_PASSWORD: ${{ secrets.ARM_CLIENT_CERTIFICATE_PASSWORD }} + ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }} + ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }} + + - name: Clean Up OIDC Token File Path + run: rm -f "${RUNNER_TEMP}/oidc-token.jwt" + if: always() + + - name: Clean Up Client ID Path + run: rm -f "${RUNNER_TEMP}/client-id" + if: always() + + - name: Clean Up Client Secret Path + run: rm -f "${RUNNER_TEMP}/client-secret" + if: always() + + save-artifacts-on-fail: + if: ${{ needs.secrets-check.result }} == 'failure' || ${{ needs.provider-tests.result }} == 'failure' + uses: ./.github/workflows/save-artifacts.yaml diff --git a/.github/workflows/pull-request-new-commit.yaml b/.github/workflows/pull-request-new-commit.yaml new file mode 100644 index 0000000000..a58eeb7f0d --- /dev/null +++ b/.github/workflows/pull-request-new-commit.yaml @@ -0,0 +1,15 @@ +--- +name: Pull Request New Commit + +permissions: + pull-requests: write + +on: + pull_request_target: + types: [synchronize] + +jobs: + remove-waiting-response: + uses: ./.github/workflows/remove-issue-label.yaml + with: + label-name: "waiting-response" diff --git a/.github/workflows/pull-request-reviewed-workflow.yaml b/.github/workflows/pull-request-reviewed-workflow.yaml new file mode 100644 index 0000000000..f2a7858944 --- /dev/null +++ b/.github/workflows/pull-request-reviewed-workflow.yaml @@ -0,0 +1,55 @@ +--- +name: "Pull Request Reviewed Workflow" + +on: + workflow_run: + workflows: + - "Pull Request Reviewed" + types: + - completed + +permissions: + pull-requests: write + +jobs: + add-or-remove-waiting-response: + runs-on: ubuntu-latest + outputs: + ghrepo: ${{ steps.env_vars.outputs.ghrepo }} + ghowner: ${{ steps.env_vars.outputs.ghowner }} + prnumber: ${{ steps.env_vars.outputs.prnumber }} + action: ${{ steps.env_vars.outputs.action }} + artifact_outcome: ${{ steps.env_vars.outputs.artifact_outcome }} + steps: + - name: Get Artifact + id: get_artifact + continue-on-error: true + uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0 + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: pull-request-reviewed.yaml + + - name: env_vars + id: env_vars + if: steps.get_artifact.outcome == 'success' + run: | + echo "ghrepo=$(cat artifact/ghrepo.txt)" >>${GITHUB_OUTPUT} + echo "ghowner=$(cat artifact/ghowner.txt)" >>${GITHUB_OUTPUT} + echo "prnumber=$(cat artifact/prnumber.txt)" >>${GITHUB_OUTPUT} + echo "action=$(cat artifact/action.txt)" >>${GITHUB_OUTPUT} + echo "artifact_outcome=success" >>${GITHUB_OUTPUT} + + add-waiting-reponse: + needs: add-or-remove-waiting-response + runs-on: ubuntu-latest + if: needs.add-or-remove-waiting-response.outputs.artifact_outcome == 'success' && needs.add-or-remove-waiting-response.outputs.action == 'add-waiting-response' + steps: + - run: | + curl -X POST -H "Accept: application/vnd.github+json" -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos${{ needs.add-or-remove-waiting-response.outputs.ghowner }}/${{ needs.add-or-remove-waiting-response.outputs.ghrepo }}/issues/${{ needs.add-or-remove-waiting-response.outputs.prnumber }}/labels" -d '{"labels":["waiting-response"]}' + + remove-waiting-reponse: + needs: add-or-remove-waiting-response + if: needs.add-or-remove-waiting-response.outputs.artifact_outcome == 'success' && needs.add-or-remove-waiting-response.outputs.action == 'remove-waiting-response' + uses: ./.github/workflows/remove-issue-label.yaml + with: + label-name: "waiting-response" diff --git a/.github/workflows/pull-request-reviewed.yaml b/.github/workflows/pull-request-reviewed.yaml new file mode 100644 index 0000000000..940834855b --- /dev/null +++ b/.github/workflows/pull-request-reviewed.yaml @@ -0,0 +1,38 @@ +--- +name: "Pull Request Reviewed" + +on: + pull_request_review: + types: [submitted] + +permissions: + pull-requests: read + +jobs: + add-or-remove-waiting-response: + runs-on: ubuntu-latest + steps: + - name: "Set Artifacts for add-waiting-response" + if: github.event.review.state != 'approved' && github.actor != github.event.pull_request.user.login + shell: bash + run: | + mkdir -p wr_actions + echo ${{ github.owner }} > wr_actions/ghowner.txt + echo ${{ github.repository }} > wr_actions/ghrepo.txt + echo ${{ github.event.pull_request.number }} > wr_actions/prnumber.txt + echo "add-waiting-response" > wr_actions/action.txt + + - name: "Set Artifacts for remove-waiting-response" + if: github.actor == github.event.pull_request.user.login + shell: bash + run: | + mkdir -p wr_actions + echo ${{ github.owner }} > wr_actions/ghowner.txt + echo ${{ github.repository }} > wr_actions/ghrepo.txt + echo ${{ github.event.pull_request.number }} > wr_actions/prnumber.txt + echo "remove-waiting-response" > wr_actions/action.txt + + - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + with: + name: artifact + path: wr_actions diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml index 001da138ef..3ffa51e976 100644 --- a/.github/workflows/pull-request.yaml +++ b/.github/workflows/pull-request.yaml @@ -13,8 +13,9 @@ jobs: steps: - uses: actions/labeler@ac9175f8a1f3625fd0d4fb234536d26811351594 # v4.3.0 with: - configuration-path: .github/labeler-pull-request-triage.yml + configuration-path: .github/labeler-pull-request-triage.yaml repo-token: "${{ secrets.GITHUB_TOKEN }}" + - uses: CodelyTV/pr-size-labeler@54ef36785e9f4cb5ecf1949cfc9b00dbb621d761 # v1.8.1 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/remove-issue-label.yaml b/.github/workflows/remove-issue-label.yaml new file mode 100644 index 0000000000..010e073c56 --- /dev/null +++ b/.github/workflows/remove-issue-label.yaml @@ -0,0 +1,50 @@ +name: Remove specified label from issue + +on: + # This file is reused, and called from other workflows + workflow_call: + inputs: + label-name: + required: true + type: string + + +jobs: + remove-label: + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1 + env: + REMOVE_LABEL: ${{ inputs.label-name }} + with: + script: | + const { REMOVE_LABEL } = process.env + console.log(`Attempting to remove label "${REMOVE_LABEL}"`) + + const { data } = await github.rest.issues.listLabelsOnIssue({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + }) + + // Return early if there are no labels + if (data.length == 0){ + console.log(`Issue has no labels; not attempting to remove label "${REMOVE_LABEL}"`) + return + } + + // Check if REMOVE_LABEL is present + const filteredData = data.filter(label => label.name == REMOVE_LABEL) + + // Return early if filtering didn't identify the label as present + if (filteredData.length == 0){ + console.log(`Label "${REMOVE_LABEL}" not found on issue; not attempting to remove it.`) + return + } + console.log(`Label "${REMOVE_LABEL}" found! Now deleting it from the issue...`) + await github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: REMOVE_LABEL + }) diff --git a/.github/workflows/save-artifacts.yaml b/.github/workflows/save-artifacts.yaml new file mode 100644 index 0000000000..5c95f66d74 --- /dev/null +++ b/.github/workflows/save-artifacts.yaml @@ -0,0 +1,20 @@ +name: Save Artifacts + +on: + # This file is reused, and called from other workflows + workflow_call: + +jobs: + save-artifacts: + runs-on: ubuntu-latest + steps: + - shell: bash + run: | + mkdir -p wr_actions + echo ${{ github.repository_owner }} > wr_actions/ghowner.txt + echo ${{ github.event.repository.name }} > wr_actions/ghrepo.txt + echo ${{ github.event.pull_request.number }} > wr_actions/prnumber.txt + - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + with: + name: artifact + path: wr_actions diff --git a/.github/workflows/tflint.yaml b/.github/workflows/tflint.yaml index 8f848db5c5..951a4b3fad 100644 --- a/.github/workflows/tflint.yaml +++ b/.github/workflows/tflint.yaml @@ -27,3 +27,7 @@ jobs: - run: bash scripts/gogetcookie.sh - run: make tools - run: make tflint + + save-artifacts-on-fail: + if: ${{ needs.tflint.result }} == 'failure' + uses: ./.github/workflows/save-artifacts.yaml diff --git a/.github/workflows/thirty-two-bit.yaml b/.github/workflows/thirty-two-bit.yaml index 5ad042a1e8..f9888f41fa 100644 --- a/.github/workflows/thirty-two-bit.yaml +++ b/.github/workflows/thirty-two-bit.yaml @@ -27,3 +27,7 @@ jobs: go-version-file: ./.go-version - run: bash scripts/gogetcookie.sh - run: GOARCH=386 GOOS=linux go build -o 32bitbuild . + + save-artifacts-on-fail: + if: ${{ needs.compatibility-32bit-test.result }} == 'failure' + uses: ./.github/workflows/save-artifacts.yaml diff --git a/.github/workflows/unit-test.yaml b/.github/workflows/unit-test.yaml index 82e527ffbe..ef3ec4e0d0 100644 --- a/.github/workflows/unit-test.yaml +++ b/.github/workflows/unit-test.yaml @@ -29,3 +29,7 @@ jobs: - run: make test env: GITHUB_ACTIONS_STAGE: "UNIT_TESTS" + + save-artifacts-on-fail: + if: ${{ needs.test.result }} == 'failure' + uses: ./.github/workflows/save-artifacts.yaml diff --git a/.github/workflows/validate-examples.yaml b/.github/workflows/validate-examples.yaml index 0d23a2b439..6e0806b319 100644 --- a/.github/workflows/validate-examples.yaml +++ b/.github/workflows/validate-examples.yaml @@ -17,8 +17,8 @@ concurrency: cancel-in-progress: true jobs: - website-lint: - runs-on: [custom, linux, large] + validate-examples: + runs-on: ubuntu-latest steps: - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 @@ -27,3 +27,7 @@ jobs: - run: bash scripts/gogetcookie.sh - run: make tools - run: make validate-examples + + save-artifacts-on-fail: + if: ${{ needs.validate-examples.result }} == 'failure' + uses: ./.github/workflows/save-artifacts.yaml diff --git a/scripts/increment-milestone.sh b/scripts/increment-milestone.sh new file mode 100644 index 0000000000..a0e981329b --- /dev/null +++ b/scripts/increment-milestone.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + + +while getopts u:r:t: flag +do + case "${flag}" in + r) release=${OPTARG};; + u) milestone_url=${OPTARG};; + t) token=${OPTARG};; + esac +done + +echo "Getting current milestone number..." +milestones=$(curl -L \ +-H "Accept: application/vnd.github+json" \ +-H "Authorization: Bearer $token" \ +-H "X-GitHub-Api-Version: 2022-11-28" \ +"${milestone_url}?state=open&sort=due_on&direction=desc") + +milestone_number=0 +milestones_json=$(echo "$milestones" | jq -c -r '.[]') +for milestone in ${milestones_json[@]}; do + if [[ $(echo $milestone | jq -r .title) == "$release" ]]; then + milestone_number=$(echo $milestone | jq -r .number) + break + fi +done + +if [[ $milestone_number != 0 ]]; then + + echo "Closing current milestone..." + curl -L \ + -X PATCH \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $token" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "${milestone_url}/${milestone_number}" \ + -d '{"state":"closed"}' + + major=0 + weekly=0 + patch=0 + regex="v([0-9]+).([0-9]+).([0-9]+)" + if [[ $release =~ $regex ]]; then + major="${BASH_REMATCH[1]}" + weekly="${BASH_REMATCH[2]}" + patch="${BASH_REMATCH[3]}" + fi + + if [[ $patch == 0 ]]; then + if [[ $major != 0 ]]; then + + # Get next release version + weekly=$((weekly + 1 )) + new_milestone="v$major.$weekly.0" + # Get next release due date + date=$(date -d "next Thursday" +%Y-%m-%d) + date+="T12:00:00Z" + + echo "Creating new milestone..." + curl -L \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $token" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "${milestone_url}" \ + -d "{\"title\":\"$new_milestone\", \"state\":\"open\", \"due_on\":\"$date\"}" + else + echo "Could not increment milestone" + exit 1 + fi + fi + +else + echo "Could not retrieve current milestone number to close" + exit 1 +fi From 9a2c3965ffdf9393d41a938fe2d9ab4924b64e42 Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Thu, 21 Sep 2023 21:47:31 +0100 Subject: [PATCH 05/11] provider test fixes --- .github/workflows/provider-test.yaml | 3 ++ internal/clients/builder.go | 2 +- internal/provider/provider.go | 18 ++++---- internal/provider/provider_test.go | 63 +++++++++++++++++++++++++++- 4 files changed, 76 insertions(+), 10 deletions(-) diff --git a/.github/workflows/provider-test.yaml b/.github/workflows/provider-test.yaml index 48758d0690..649e68138d 100644 --- a/.github/workflows/provider-test.yaml +++ b/.github/workflows/provider-test.yaml @@ -59,6 +59,9 @@ jobs: - name: Set Client Secret Path run: echo "${{ secrets.ARM_CLIENT_SECRET }}" >"${RUNNER_TEMP}/client-secret" && echo "ARM_CLIENT_SECRET_PATH=${RUNNER_TEMP}/client-secret" >>${GITHUB_ENV} + - name: Set Client Certificate Path + run: echo "${{ secrets.ARM_CLIENT_CERTIFICATE }}" | base64 -d >"${RUNNER_TEMP}/client-certificate.pfx" && echo "ARM_CLIENT_CERTIFICATE_PATH=${RUNNER_TEMP}/client-certificate.pfx" >>${GITHUB_ENV} + - name: Run provider tests run: make testacc TEST=./internal/provider TESTARGS="-run '^TestAcc'" env: diff --git a/internal/clients/builder.go b/internal/clients/builder.go index 44fa8192ce..41c277746f 100644 --- a/internal/clients/builder.go +++ b/internal/clients/builder.go @@ -34,7 +34,7 @@ func (b *ClientBuilder) Build(ctx context.Context) (*Client, error) { authorizer, err := auth.NewAuthorizerFromCredentials(ctx, *b.AuthConfig, b.AuthConfig.Environment.MicrosoftGraph) if err != nil { - return nil, fmt.Errorf("unable to build authorizer for Resource Manager API: %+v", err) + return nil, fmt.Errorf("unable to build authorizer: %+v", err) } client.Environment = b.AuthConfig.Environment diff --git a/internal/provider/provider.go b/internal/provider/provider.go index c755ba1854..5b5e07ad89 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -287,7 +287,7 @@ func providerConfigure(p *schema.Provider) schema.ConfigureContextFunc { ClientCertificatePassword: d.Get("client_certificate_password").(string), ClientCertificatePath: d.Get("client_certificate_path").(string), ClientSecret: *clientSecret, - OIDCAssertionToken: idToken, + OIDCAssertionToken: *idToken, GitHubOIDCTokenRequestURL: d.Get("oidc_request_url").(string), GitHubOIDCTokenRequestToken: d.Get("oidc_request_token").(string), EnableAuthenticatingUsingClientCertificate: true, @@ -344,24 +344,26 @@ func decodeCertificate(clientCertificate string) ([]byte, error) { return pfx, nil } -func oidcToken(d *schema.ResourceData) (string, error) { +func oidcToken(d *schema.ResourceData) (*string, error) { idToken := d.Get("oidc_token").(string) if path := d.Get("oidc_token_file_path").(string); path != "" { - fileToken, err := os.ReadFile(path) + fileTokenRaw, err := os.ReadFile(path) if err != nil { - return "", fmt.Errorf("reading OIDC Token from file %q: %v", path, err) + return nil, fmt.Errorf("reading OIDC Token from file %q: %v", path, err) } - if idToken != "" && idToken != string(fileToken) { - return "", fmt.Errorf("mismatch between supplied OIDC token and supplied OIDC token file contents - please either remove one or ensure they match") + fileToken := strings.TrimSpace(string(fileTokenRaw)) + + if idToken != "" && idToken != fileToken { + return nil, fmt.Errorf("mismatch between supplied OIDC token and supplied OIDC token file contents - please either remove one or ensure they match") } - idToken = string(fileToken) + idToken = fileToken } - return idToken, nil + return &idToken, nil } func getClientId(d *schema.ResourceData) (*string, error) { diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index 326b1378ef..00de46dafc 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -284,6 +284,63 @@ func testAccProvider_clientSecretAuthFromFiles(t *testing.T) { } func TestAccProvider_genericOidcAuth(t *testing.T) { + t.Run("fromEnvironment", testAccProvider_genericOidcAuthFromEnvironment) + t.Run("fromFiles", testAccProvider_genericOidcAuthFromFiles) +} + +func testAccProvider_genericOidcAuthFromEnvironment(t *testing.T) { + if os.Getenv("TF_ACC") == "" { + t.Skip("TF_ACC not set") + } + if os.Getenv("ARM_OIDC_TOKEN_FILE_PATH") == "" { + t.Skip("ARM_OIDC_TOKEN_FILE_PATH not set") + } + + // Ensure we are running using the expected env-vars + // t.SetEnv does automatic cleanup / resets the values after the test + t.Setenv("ARM_OIDC_TOKEN", "") + + provider := AzureADProvider() + ctx := context.Background() + + // Support only oidc authentication + provider.ConfigureContextFunc = func(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { + envName := d.Get("environment").(string) + env, err := environments.FromName(envName) + if err != nil { + t.Fatalf("configuring environment %q: %v", envName, err) + } + + idToken, err := oidcToken(d) + if err != nil { + return nil, diag.FromErr(err) + } + + authConfig := &auth.Credentials{ + Environment: *env, + TenantID: d.Get("tenant_id").(string), + ClientID: d.Get("client_id").(string), + + EnableAuthenticationUsingOIDC: true, + OIDCAssertionToken: *idToken, + } + + return buildClient(ctx, provider, authConfig, "") + } + + d := provider.Configure(ctx, terraform.NewResourceConfigRaw(nil)) + if d != nil && d.HasError() { + t.Fatalf("err: %+v", d) + } + + if errs := testCheckProvider(provider); len(errs) > 0 { + for _, err := range errs { + t.Error(err) + } + } +} + +func testAccProvider_genericOidcAuthFromFiles(t *testing.T) { if os.Getenv("TF_ACC") == "" { t.Skip("TF_ACC not set") } @@ -291,6 +348,10 @@ func TestAccProvider_genericOidcAuth(t *testing.T) { t.Skip("ARM_OIDC_TOKEN not set") } + // Ensure we are running using the expected env-vars + // t.SetEnv does automatic cleanup / resets the values after the test + t.Setenv("ARM_OIDC_TOKEN_FILE_PATH", "") + provider := AzureADProvider() ctx := context.Background() @@ -313,7 +374,7 @@ func TestAccProvider_genericOidcAuth(t *testing.T) { ClientID: d.Get("client_id").(string), EnableAuthenticationUsingOIDC: true, - OIDCAssertionToken: idToken, + OIDCAssertionToken: *idToken, } return buildClient(ctx, provider, authConfig, "") From b1f63320c9cc947caefbd2a01c039dadbc95b2cc Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Thu, 21 Sep 2023 23:24:18 +0100 Subject: [PATCH 06/11] milestone linker, must hardcode go version --- .github/workflows/link-milestone.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/link-milestone.yaml b/.github/workflows/link-milestone.yaml index e6dd367627..79f97d876d 100644 --- a/.github/workflows/link-milestone.yaml +++ b/.github/workflows/link-milestone.yaml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 with: - go-version-file: .go-version + go-version: '1.20.5' - run: | go install github.com/stephybun/link-milestone@latest From a1181cbf1add920ec51a0c0be33fd33545a6f125 Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Fri, 22 Sep 2023 01:38:02 +0100 Subject: [PATCH 07/11] Import validation for azuread_role_eligibility_schedule_request --- .../directory_role_eligibility_schedule_request_resource.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/services/directoryroles/directory_role_eligibility_schedule_request_resource.go b/internal/services/directoryroles/directory_role_eligibility_schedule_request_resource.go index b080691891..aad22bb1c0 100644 --- a/internal/services/directoryroles/directory_role_eligibility_schedule_request_resource.go +++ b/internal/services/directoryroles/directory_role_eligibility_schedule_request_resource.go @@ -32,8 +32,8 @@ func directoryRoleEligibilityScheduleRequestResource() *schema.Resource { }, Importer: tf.ValidateResourceIDPriorToImport(func(id string) error { - if id == "" { - return errors.New("id was empty") + if _, err := uuid.ParseUUID(id); err != nil { + return fmt.Errorf("specified ID (%q) is not valid: %s", id, err) } return nil }), From caef4c43200d3ab4ff9e3e98fd66592eb95d663e Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Fri, 22 Sep 2023 01:40:24 +0100 Subject: [PATCH 08/11] goimports --- .../directory_role_eligibility_schedule_request_resource.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/services/directoryroles/directory_role_eligibility_schedule_request_resource.go b/internal/services/directoryroles/directory_role_eligibility_schedule_request_resource.go index aad22bb1c0..eee9ee4cc4 100644 --- a/internal/services/directoryroles/directory_role_eligibility_schedule_request_resource.go +++ b/internal/services/directoryroles/directory_role_eligibility_schedule_request_resource.go @@ -3,11 +3,13 @@ package directoryroles import ( "context" "errors" + "fmt" "log" "net/http" "time" "github.com/hashicorp/go-azure-sdk/sdk/odata" + "github.com/hashicorp/go-uuid" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-azuread/internal/clients" From 14acd9b835c02c2ecec3ac8cfb42dee902e300d5 Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Fri, 22 Sep 2023 01:42:04 +0100 Subject: [PATCH 09/11] docs linting --- docs/resources/directory_role_eligibility_schedule_request.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/resources/directory_role_eligibility_schedule_request.md b/docs/resources/directory_role_eligibility_schedule_request.md index 91b73d6bf7..7d8971090e 100644 --- a/docs/resources/directory_role_eligibility_schedule_request.md +++ b/docs/resources/directory_role_eligibility_schedule_request.md @@ -39,10 +39,10 @@ resource "azuread_directory_role_eligibility_schedule_request" "example" { The following arguments are supported: -* `role_definition_id` - (Required) The template ID (in the case of built-in roles) or object ID (in the case of custom roles) of the directory role you want to assign. Changing this forces a new resource to be created. -* `principal_id` - (Required) The object ID of the principal to granted the role eligibility. Changing this forces a new resource to be created. * `directory_scope_id` - (Required) Identifier of the directory object representing the scope of the role eligibility. Changing this forces a new resource to be created. * `justification` - (Required) Justification for why the principal is granted the role eligibility. Changing this forces a new resource to be created. +* `principal_id` - (Required) The object ID of the principal to granted the role eligibility. Changing this forces a new resource to be created. +* `role_definition_id` - (Required) The template ID (in the case of built-in roles) or object ID (in the case of custom roles) of the directory role you want to assign. Changing this forces a new resource to be created. ## Attributes Reference From d36f11c594216a2ea2d85378175b695ce87c5382 Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Fri, 22 Sep 2023 01:52:17 +0100 Subject: [PATCH 10/11] Changelog for #974 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d646afdf05..6267beaaef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.43.0 (Unreleased) + +FEATURES: + +* **New Resource:** `azuread_directory_role_eligibility_schedule_request` [GH-974] + ## 2.42.0 (September 15, 2023) IMPROVEMENTS: From 5db577517513fc7c36f9076592be90bec6cd993d Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Fri, 22 Sep 2023 02:11:46 +0100 Subject: [PATCH 11/11] v2.43.0 --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6267beaaef..020d2cfae7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ -## 2.43.0 (Unreleased) +## 2.43.0 (September 22, 2023) FEATURES: -* **New Resource:** `azuread_directory_role_eligibility_schedule_request` [GH-974] +* **New Resource:** `azuread_directory_role_eligibility_schedule_request` ([#974](https://github.com/hashicorp/terraform-provider-azuread/issues/974)) ## 2.42.0 (September 15, 2023)