Skip to content

Commit

Permalink
[OPAL-5380] implement on_call_schedule resource and binding with group (
Browse files Browse the repository at this point in the history
  • Loading branch information
giulio-opal authored Feb 3, 2023
1 parent 08936e2 commit 3ce381e
Show file tree
Hide file tree
Showing 12 changed files with 299 additions and 12 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ jobs:
OPAL_TEST_KNOWN_OPAL_APP_ADMIN_OWNER_ID: ${{ secrets.OPAL_TEST_KNOWN_OPAL_APP_ADMIN_OWNER_ID }}
OPAL_TEST_KNOWN_GITHUB_TEST_REPO_2_RESOURCE_ID: ${{ secrets.OPAL_TEST_KNOWN_GITHUB_TEST_REPO_2_RESOURCE_ID }}
OPAL_TEST_KNOWN_OPAL_GROUP_ID: ${{ secrets.OPAL_TEST_KNOWN_OPAL_GROUP_ID }}
OPAL_TEST_KNOWN_ON_CALL_SCHEDULE_ID: ${{ secrets.OPAL_TEST_KNOWN_ON_CALL_SCHEDULE_ID }}
- name: Clean up test organization
run: make sweep
env:
Expand All @@ -58,6 +59,7 @@ jobs:
OPAL_TEST_KNOWN_OPAL_APP_ADMIN_OWNER_ID: ${{ secrets.OPAL_TEST_KNOWN_OPAL_APP_ADMIN_OWNER_ID }}
OPAL_TEST_KNOWN_GITHUB_TEST_REPO_2_RESOURCE_ID: ${{ secrets.OPAL_TEST_KNOWN_GITHUB_TEST_REPO_2_RESOURCE_ID }}
OPAL_TEST_KNOWN_OPAL_GROUP_ID: ${{ secrets.OPAL_TEST_KNOWN_OPAL_GROUP_ID }}
OPAL_TEST_KNOWN_ON_CALL_SCHEDULE_ID: ${{ secrets.OPAL_TEST_KNOWN_ON_CALL_SCHEDULE_ID }}
- name: Check for doc changes
id: changes
run: |
Expand Down
7 changes: 4 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"go.testEnvFile": "${workspaceFolder}/.envrc.local.vscode",
"go.testEnvVars": {
"TF_ACC": 1,
"TF_LOG": "DEBUG",
"TF_ACC": "1",
"TF_LOG": "DEBUG"
}
}
}
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,22 @@ BREAKING CHANGES:

NEW FEATURES:
- adds support for multi-stage approvals
- adds support for `on_call_schedules` in group resources. Example:

```terraform
resource "opal_on_call_schedule" "security_oncall_rotation" {
third_party_provider = "PAGER_DUTY"
remote_id = "PNXHVAA"
}
# Example group usage
resource "opal_group" "security" {
// ...
on_call_schedule {
id = opal_on_call_schedule.security_oncall_rotation.id
}
```

## v0.0.4
- Fixes a bug for owner user parsing
Expand Down
11 changes: 10 additions & 1 deletion docs/resources/group.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,12 @@ resource "opal_group" "google_group_example" {

### Optional

- `audit_message_channel` (Block List) An audit message channel for this group. (see [below for nested schema](#nestedblock--audit_message_channel))
- `audit_message_channel` (Block Set) An audit message channel for this group. (see [below for nested schema](#nestedblock--audit_message_channel))
- `auto_approval` (Boolean) Automatically approve all requests for this group without review.
- `description` (String) The description of the group.
- `is_requestable` (Boolean) Allow users to create an access request for this group. By default, any group is requestable.
- `max_duration` (Number) The maximum duration for which this group can be requested (in minutes). By default, the max duration is indefinite access.
- `on_call_schedule` (Block Set) An on call schedule for this group. (see [below for nested schema](#nestedblock--on_call_schedule))
- `recommended_duration` (Number) The recommended duration for which the group should be requested (in minutes). Will be the default value in a request. Use -1 to set to indefinite.
- `remote_info` (Block List, Max: 1) Remote info that is required for the creation of remote groups. (see [below for nested schema](#nestedblock--remote_info))
- `request_template_id` (String) The ID of a request template for this group. You can get this ID from the URL in the Opal web app.
Expand All @@ -165,6 +166,14 @@ Required:
- `id` (String) The ID of the message channel for this group.


<a id="nestedblock--on_call_schedule"></a>
### Nested Schema for `on_call_schedule`

Required:

- `id` (String) The UUID of the on call schedule for this group.


<a id="nestedblock--remote_info"></a>
### Nested Schema for `remote_info`

Expand Down
47 changes: 47 additions & 0 deletions docs/resources/on_call_schedule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
page_title: "opal_on_call_schedule Resource - terraform-provider-opal"
subcategory: ""
description: |-
An Opal OnCallSchedule resource.
---

# opal_on_call_schedule (Resource)

An Opal OnCallSchedule resource.

## Example Usage

```terraform
resource "opal_on_call_schedule" "security_oncall_rotation" {
third_party_provider = "PAGER_DUTY"
remote_id = "PNXHVAA"
}
# Example group usage
resource "opal_group" "security" {
// ...
on_call_schedule {
id = opal_on_call_schedule.security_oncall_rotation.id
}
// or if an UUID is already present in Opal
on_call_schedule {
id = "878ba05b-33f0-4dd5-a199-09efc06abcf7"
}
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `remote_id` (String) The remote ID of the on call schedule.
- `third_party_provider` (String) The provider of the on call schedule (i.e. PAGER_DUTY, OPSGENIE).

### Read-Only

- `id` (String) The ID of the on call schedule.

Please [file a ticket](https://github.com/opalsecurity/terraform-provider-opal/issues) to discuss use cases that are not yet supported in the provider.
18 changes: 18 additions & 0 deletions examples/resources/on_call_schedule.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
resource "opal_on_call_schedule" "security_oncall_rotation" {
third_party_provider = "PAGER_DUTY"
remote_id = "PNXHVAA"
}

# Example group usage
resource "opal_group" "security" {
// ...

on_call_schedule {
id = opal_on_call_schedule.security_oncall_rotation.id
}

// or if an UUID is already present in Opal
on_call_schedule {
id = "878ba05b-33f0-4dd5-a199-09efc06abcf7"
}
}
60 changes: 58 additions & 2 deletions opal/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ func resourceGroup() *schema.Resource {
},
"audit_message_channel": {
Description: "An audit message channel for this group.",
Type: schema.TypeList,
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
Expand Down Expand Up @@ -200,6 +200,20 @@ func resourceGroup() *schema.Resource {
Optional: true,
Default: true,
},
"on_call_schedule": {
Description: "An on call schedule for this group.",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"id": {
Description: "The UUID of the on call schedule for this group.",
Type: schema.TypeString,
Required: true,
},
},
},
},
},
}
}
Expand Down Expand Up @@ -320,6 +334,12 @@ func resourceGroupCreate(ctx context.Context, d *schema.ResourceData, m any) dia
}
}

if _, ok := d.GetOk("on_call_schedule"); ok {
if diag := resourceGroupUpdateOnCallSchedules(ctx, d, client); diag != nil {
return diag
}
}

return resourceGroupRead(ctx, d, m)
}

Expand Down Expand Up @@ -348,7 +368,7 @@ func resourceGroupUpdateVisibility(ctx context.Context, d *schema.ResourceData,
func resourceGroupUpdateAuditMessageChannels(ctx context.Context, d *schema.ResourceData, client *opal.APIClient) diag.Diagnostics {
var channelIDs []string
if auditMessageChannelsI, ok := d.GetOk("audit_message_channel"); ok {
rawChannels := auditMessageChannelsI.([]any)
rawChannels := auditMessageChannelsI.(*schema.Set).List()
for _, rawChannel := range rawChannels {
channel := rawChannel.(map[string]any)
channelIDs = append(channelIDs, channel["id"].(string))
Expand All @@ -363,6 +383,24 @@ func resourceGroupUpdateAuditMessageChannels(ctx context.Context, d *schema.Reso
return nil
}

func resourceGroupUpdateOnCallSchedules(ctx context.Context, d *schema.ResourceData, client *opal.APIClient) diag.Diagnostics {
var onCallScheduleIDs []string
if onCallSchedulesI, ok := d.GetOk("on_call_schedule"); ok {
rawOnCallSchedules := onCallSchedulesI.(*schema.Set).List()
for _, rawOnCallSchedule := range rawOnCallSchedules {
onCallSchedule := rawOnCallSchedule.(map[string]any)
onCallScheduleIDs = append(onCallScheduleIDs, onCallSchedule["id"].(string))
}
}

onCallScheduleList := *opal.NewOnCallScheduleIDList(onCallScheduleIDs)
if _, _, err := client.GroupsApi.SetGroupOnCallSchedules(ctx, d.Id()).OnCallScheduleIDList(onCallScheduleList).Execute(); err != nil {
return diagFromErr(ctx, err)
}

return nil
}

func resourceGroupUpdateResources(ctx context.Context, d *schema.ResourceData, client *opal.APIClient) diag.Diagnostics {
var rawResources []any
if resourceI, ok := d.GetOk("resource"); ok {
Expand Down Expand Up @@ -501,6 +539,18 @@ func resourceGroupRead(ctx context.Context, d *schema.ResourceData, m any) diag.
}
d.Set("audit_message_channel", auditChannels)

onCallSchedulesResponse, _, err := client.GroupsApi.GetGroupOnCallSchedules(ctx, group.GroupId).Execute()
if err != nil {
return diagFromErr(ctx, err)
}
onCallSchedules := make([]any, 0, len(onCallSchedulesResponse.OnCallSchedules))
for _, onCallSchedule := range onCallSchedulesResponse.OnCallSchedules {
onCallSchedules = append(onCallSchedules, map[string]any{
"id": onCallSchedule.OnCallScheduleId,
})
}
d.Set("on_call_schedule", onCallSchedules)

reviewerStages, _, err := client.GroupsApi.GetGroupReviewerStages(ctx, group.GroupId).Execute()
if err != nil {
return diagFromErr(ctx, err)
Expand Down Expand Up @@ -598,6 +648,12 @@ func resourceGroupUpdate(ctx context.Context, d *schema.ResourceData, m any) dia
}
}

if d.HasChange("on_call_schedule") {
if diag := resourceGroupUpdateOnCallSchedules(ctx, d, client); diag != nil {
return diag
}
}

if d.HasChange("reviewer_stage") {
reviewerStages := any([]any{})
if reviewersStagesBlock, ok := d.GetOk("reviewer_stage"); ok {
Expand Down
25 changes: 23 additions & 2 deletions opal/group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,21 @@ package opal
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"github.com/opalsecurity/opal-go"
"os"
"strings"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"github.com/opalsecurity/opal-go"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

var knownOpalAppID = os.Getenv("OPAL_TEST_KNOWN_OPAL_APP_ID")
var knownOpalAppAdminOwnerID = os.Getenv("OPAL_TEST_KNOWN_OPAL_APP_ADMIN_OWNER_ID")
var knownGithubRepoResourceID = os.Getenv("OPAL_TEST_KNOWN_GITHUB_TEST_REPO_2_RESOURCE_ID")
var knownOnCallScheduleID = os.Getenv("OPAL_TEST_KNOWN_ON_CALL_SCHEDULE_ID")

func TestAccGroup_Import(t *testing.T) {
baseName := "tf_acc_group_test_" + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
Expand Down Expand Up @@ -367,6 +369,25 @@ func TestAccGroup_Remote(t *testing.T) {
})
}

func TestAccGroup_OnCallSchedule(t *testing.T) {
baseName := "tf_acc_group_test_" + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
resourceName := "opal_group." + baseName
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckGroupDestroy,
Steps: []resource.TestStep{
{
Config: testAccGroupResourceWithReviewer(baseName, baseName, fmt.Sprintf(`on_call_schedule { id = "%s" }`, knownOnCallScheduleID)),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "on_call_schedule.#", "1"),
resource.TestCheckTypeSetElemNestedAttrs(resourceName, "on_call_schedule.*", map[string]string{"id": knownOnCallScheduleID}),
),
},
},
})
}

func testAccGroupResourceWithAccessLevel(resourceID, accessLevelRemoteID string) string {
return fmt.Sprintf(`
resource {
Expand Down
Loading

0 comments on commit 3ce381e

Please sign in to comment.