Skip to content

Commit

Permalink
Add support for handoffs and restrictions (#168)
Browse files Browse the repository at this point in the history
* Add support for handoffs and restrictions

* Use raw state to find effective_at value

Fix bug in restrictions being omitted

* Adds a changelog entry

* Add documentation for new effective_at attribute

* Clarify durations for shifts in custom strategy
  • Loading branch information
bobbytables authored Nov 5, 2024
1 parent 85a478a commit 69c1bfe
Show file tree
Hide file tree
Showing 5 changed files with 384 additions and 8 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 0.13.2

BUG FIXES:
* Fixes a bug where on-call schedule handoff days were not being updated in FireHydrant

EHNANCEMENTS:
* Adds a new `effective_at` attribute to the `firehydrant_on_call_schedule` resource to allow for scheduling changes to take effect at a future date

## 0.13.1

BUG FIXES:
Expand Down
3 changes: 2 additions & 1 deletion docs/resources/on_call_schedule.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,14 @@ The following arguments are supported:
* `time_zone` - (Required) The time zone that the on-call schedule is in.
* `strategy` - (Required) A block to define the strategy for the on-call schedule.
* `restrictions` - (Optional) A block to define a restriction for the on-call schedule.
* `effective_at` - (Optional) The date and time that the on-call schedule becomes effective. Must be in `YYYY-MM-DDTHH:MM:SSZ` format. Defaults to the current date and time. If set to the past, the schedule will be effective immediately. This attribute is not stored in Terraform state.

The `strategy` block supports:

* `type` - (Required) The type of strategy to use for the on-call schedule. Valid values are `weekly`, `daily`, or `custom`.
* `handoff_time` - (Required) The time of day that the on-call schedule handoff occurs. Must be in `HH:MM:SS` format.
* `handoff_day` - (Required) The day of the week that the on-call schedule handoff occurs. Valid values are `sunday`, `monday`, `tuesday`, `wednesday`, `thursday`, `friday`, and `saturday`.
* `shift_duration` - (Optional) The duration of the on-call shift in ISO8601 format. Required for `custom` strategy.
* `shift_duration` - (Optional) The duration of the on-call shift in [ISO8601 format](https://en.wikipedia.org/wiki/ISO_8601#Durations) (e.g. `PT8H`). Required for `custom` strategy.

The `restrictions` block supports:

Expand Down
11 changes: 7 additions & 4 deletions firehydrant/on_call_schedules.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,13 @@ type CreateOnCallScheduleRequest struct {
}

type UpdateOnCallScheduleRequest struct {
Name string `json:"name"`
Description string `json:"description"`
MemberIDs []string `json:"member_ids,omitempty"`
EffectiveAt string `json:"effective_at,omitempty"`
Name string `json:"name"`
Description string `json:"description"`
MemberIDs []string `json:"member_ids,omitempty"`
EffectiveAt string `json:"effective_at,omitempty"`
Color string `json:"color,omitempty"`
Strategy *OnCallScheduleStrategy `json:"strategy,omitempty"`
Restrictions []OnCallScheduleRestriction `json:"restrictions"`
}

type OnCallScheduleRestriction struct {
Expand Down
93 changes: 90 additions & 3 deletions provider/on_call_schedule_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import (
"context"
"errors"
"fmt"
"time"

"github.com/firehydrant/terraform-provider-firehydrant/firehydrant"
"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
Expand Down Expand Up @@ -116,6 +118,33 @@ func resourceOnCallSchedule() *schema.Resource {
},
},
},
"effective_at": {
Type: schema.TypeString,
Optional: true,
// Don't set computed:true since we don't want it in the state
Description: "RFC3339 timestamp for when the schedule update should take effect. If not provided or if the time is in the past, the update will take effect immediately.",
ValidateDiagFunc: schema.SchemaValidateDiagFunc(
func(v interface{}, path cty.Path) diag.Diagnostics {
timeStr := v.(string)
_, err := time.Parse(time.RFC3339, timeStr)
if err != nil {
return diag.Diagnostics{
diag.Diagnostic{
Severity: diag.Error,
Summary: "Invalid effective_at timestamp",
Detail: fmt.Sprintf("effective_at must be a valid RFC3339 timestamp (e.g. 2024-01-01T15:04:05Z), got: %s", timeStr),
AttributePath: path,
},
}
}

return nil
},
),
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
return true
},
},
},
}
}
Expand Down Expand Up @@ -260,11 +289,35 @@ func updateResourceFireHydrantOnCallSchedule(ctx context.Context, d *schema.Reso
"team_id": teamID,
})

onCallSchedule := firehydrant.UpdateOnCallScheduleRequest{
// Initialize updateRequest with basic fields
updateRequest := firehydrant.UpdateOnCallScheduleRequest{
Name: d.Get("name").(string),
Description: d.Get("description").(string),
}

// Check if effective_at exists in raw config rather than state
if raw := d.GetRawConfig().GetAttr("effective_at"); !raw.IsNull() {
effectiveAtStr := raw.AsString()
effectiveAt, err := time.Parse(time.RFC3339, effectiveAtStr)
if err != nil {
return diag.FromErr(err)
}

// Only set effective_at if it's in the future
if effectiveAt.After(time.Now()) {
updateRequest.EffectiveAt = effectiveAt.Format(time.RFC3339)
tflog.Debug(ctx, "Schedule update will take effect at: "+updateRequest.EffectiveAt, map[string]interface{}{
"effective_at": updateRequest.EffectiveAt,
})
} else {
tflog.Debug(ctx, "Provided effective_at is in the past, update will take effect immediately", map[string]interface{}{
"effective_at": effectiveAtStr,
"now": time.Now().Format(time.RFC3339),
})
}
}

// Get member IDs
inputMemberIDs := d.Get("member_ids").([]interface{})
if len(inputMemberIDs) == 0 {
inputMemberIDs = d.Get("members").([]interface{})
Expand All @@ -275,10 +328,44 @@ func updateResourceFireHydrantOnCallSchedule(ctx context.Context, d *schema.Reso
memberIDs = append(memberIDs, v)
}
}
onCallSchedule.MemberIDs = memberIDs
updateRequest.MemberIDs = memberIDs

// Get strategy configuration
if v, ok := d.GetOk("strategy"); ok {
if strategies := v.([]interface{}); len(strategies) > 0 {
strategy := strategies[0].(map[string]interface{})
updateRequest.Strategy = &firehydrant.OnCallScheduleStrategy{
Type: strategy["type"].(string),
HandoffTime: strategy["handoff_time"].(string),
HandoffDay: strategy["handoff_day"].(string),
}

// Set shift duration for custom strategy
if strategy["type"].(string) == "custom" {
updateRequest.Strategy.ShiftDuration = strategy["shift_duration"].(string)
}
}
}

// Get color if set
if v, ok := d.GetOk("color"); ok {
updateRequest.Color = v.(string)
}

// Get restrictions
restrictions := d.Get("restrictions").([]interface{})
for _, r := range restrictions {
restriction := r.(map[string]interface{})
updateRequest.Restrictions = append(updateRequest.Restrictions, firehydrant.OnCallScheduleRestriction{
StartDay: restriction["start_day"].(string),
StartTime: restriction["start_time"].(string),
EndDay: restriction["end_day"].(string),
EndTime: restriction["end_time"].(string),
})
}

// Update the on-call schedule
_, err := firehydrantAPIClient.OnCallSchedules().Update(ctx, teamID, id, onCallSchedule)
_, err := firehydrantAPIClient.OnCallSchedules().Update(ctx, teamID, id, updateRequest)
if err != nil {
return diag.Errorf("Error updating on-call schedule %s: %v", id, err)
}
Expand Down
Loading

0 comments on commit 69c1bfe

Please # to comment.