Skip to content

Commit

Permalink
Add validation for other field in recurring schedule
Browse files Browse the repository at this point in the history
  • Loading branch information
HuyPhanNguyen committed Dec 10, 2024
1 parent e9d3b7a commit 7b937f9
Show file tree
Hide file tree
Showing 3 changed files with 224 additions and 71 deletions.
4 changes: 2 additions & 2 deletions docs/resources/deployment_freeze.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ Required:
Optional:

- `date_of_month` (String) The date of the month for monthly schedules
- `day_number_of_month` (String) The day number of the month for monthly schedules
- `day_of_week` (String) The day of the week for monthly schedules
- `day_number_of_month` (String) Specifies which weekday position in the month. Valid values: 1 (First), 2 (Second), 3 (Third), 4 (Fourth), L (Last). Used with day_of_week
- `day_of_week` (String) The day of the week for monthly schedules when using DayOfMonth type
- `days_of_week` (List of String) List of days of the week for weekly schedules. Must follow order: Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday
- `end_after_occurrences` (Number) Number of occurrences after which the schedule should end
- `end_on_date` (String) The date when the recurring schedule should end
Expand Down
90 changes: 21 additions & 69 deletions octopusdeploy_framework/schemas/deployment_freeze.go
Original file line number Diff line number Diff line change
@@ -1,79 +1,13 @@
package schemas

import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
datasourceSchema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
resourceSchema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
)

type daysOfWeekValidator struct{}

func NewDaysOfWeekValidator() daysOfWeekValidator {
return daysOfWeekValidator{}
}

func (v daysOfWeekValidator) Description(ctx context.Context) string {
return "validates that days of the week are valid and in correct order (Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday)"
}

func (v daysOfWeekValidator) MarkdownDescription(ctx context.Context) string {
return v.Description(ctx)
}

func (v daysOfWeekValidator) ValidateList(ctx context.Context, req validator.ListRequest, resp *validator.ListResponse) {
if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() {
return
}

validDays := map[string]int{
"Sunday": 0,
"Monday": 1,
"Tuesday": 2,
"Wednesday": 3,
"Thursday": 4,
"Friday": 5,
"Saturday": 6,
}

var days []string
req.ConfigValue.ElementsAs(ctx, &days, false)

for i := 1; i < len(days); i++ {
currentDay := days[i]
previousDay := days[i-1]

currentPos, currentExists := validDays[currentDay]
previousPos, previousExists := validDays[previousDay]

if !currentExists {
resp.Diagnostics.AddError(
"Invalid day of week",
fmt.Sprintf("'%s' is not a valid day of week. Must be one of: Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday", currentDay),
)
return
}

if !previousExists {
resp.Diagnostics.AddError(
"Invalid day of week",
fmt.Sprintf("'%s' is not a valid day of week. Must be one of: Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday", previousDay),
)
return
}

if currentPos <= previousPos {
resp.Diagnostics.AddError(
"Invalid day order",
fmt.Sprintf("Days of the week must be in order (Sunday through Saturday). Found '%s' after '%s'", currentDay, previousDay),
)
return
}
}
}

type DeploymentFreezeSchema struct{}

func (d DeploymentFreezeSchema) GetResourceSchema() resourceSchema.Schema {
Expand All @@ -85,10 +19,16 @@ func (d DeploymentFreezeSchema) GetResourceSchema() resourceSchema.Schema {
"end": GetDateTimeResourceSchema("The end time of the freeze, must be RFC3339 format", true),
"recurring_schedule": resourceSchema.SingleNestedAttribute{
Optional: true,
Validators: []validator.Object{
NewRecurringScheduleValidator(),
},
Attributes: map[string]resourceSchema.Attribute{
"type": resourceSchema.StringAttribute{
Description: "Type of recurring schedule (OnceDaily, DaysPerWeek, DaysPerMonth, Annually)",
Required: true,
Validators: []validator.String{
stringvalidator.OneOf("OnceDaily", "DaysPerWeek", "DaysPerMonth", "Annually"),
},
},
"unit": resourceSchema.Int64Attribute{
Description: "The unit value for the schedule",
Expand All @@ -97,6 +37,9 @@ func (d DeploymentFreezeSchema) GetResourceSchema() resourceSchema.Schema {
"end_type": resourceSchema.StringAttribute{
Description: "When the recurring schedule should end (Never, OnDate, AfterOccurrences)",
Required: true,
Validators: []validator.String{
stringvalidator.OneOf("Never", "OnDate", "AfterOccurrences"),
},
},
"end_on_date": GetDateTimeResourceSchema("The date when the recurring schedule should end", false),
"end_after_occurrences": resourceSchema.Int64Attribute{
Expand All @@ -106,14 +49,20 @@ func (d DeploymentFreezeSchema) GetResourceSchema() resourceSchema.Schema {
"monthly_schedule_type": resourceSchema.StringAttribute{
Description: "Type of monthly schedule (DayOfMonth, DateOfMonth)",
Optional: true,
Validators: []validator.String{
stringvalidator.OneOf("DayOfMonth", "DateOfMonth"),
},
},
"date_of_month": resourceSchema.StringAttribute{
Description: "The date of the month for monthly schedules",
Optional: true,
},
"day_number_of_month": resourceSchema.StringAttribute{
Description: "The day number of the month for monthly schedules",
Description: "Specifies which weekday position in the month. Valid values: 1 (First), 2 (Second), 3 (Third), 4 (Fourth), L (Last). Used with day_of_week",
Optional: true,
Validators: []validator.String{
stringvalidator.OneOf("1", "2", "3", "4", "L"),
},
},
"days_of_week": resourceSchema.ListAttribute{
Description: "List of days of the week for weekly schedules. Must follow order: Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday",
Expand All @@ -124,8 +73,11 @@ func (d DeploymentFreezeSchema) GetResourceSchema() resourceSchema.Schema {
},
},
"day_of_week": resourceSchema.StringAttribute{
Description: "The day of the week for monthly schedules",
Description: "The day of the week for monthly schedules when using DayOfMonth type",
Optional: true,
Validators: []validator.String{
stringvalidator.OneOf("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"),
},
},
},
},
Expand Down
201 changes: 201 additions & 0 deletions octopusdeploy_framework/schemas/deployment_freeze_validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package schemas

import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)

type daysOfWeekValidator struct{}

func NewDaysOfWeekValidator() daysOfWeekValidator {
return daysOfWeekValidator{}
}

func (v daysOfWeekValidator) Description(ctx context.Context) string {
return "validates that days of the week are valid and in correct order (Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday)"
}

func (v daysOfWeekValidator) MarkdownDescription(ctx context.Context) string {
return v.Description(ctx)
}

func (v daysOfWeekValidator) ValidateList(ctx context.Context, req validator.ListRequest, resp *validator.ListResponse) {
if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() {
return
}

validDays := map[string]int{
"Sunday": 0,
"Monday": 1,
"Tuesday": 2,
"Wednesday": 3,
"Thursday": 4,
"Friday": 5,
"Saturday": 6,
}

var days []string
req.ConfigValue.ElementsAs(ctx, &days, false)

for i := 1; i < len(days); i++ {
currentDay := days[i]
previousDay := days[i-1]

currentPos, currentExists := validDays[currentDay]
previousPos, previousExists := validDays[previousDay]

if !currentExists {
resp.Diagnostics.AddError(
"Invalid day of week",
fmt.Sprintf("'%s' is not a valid day of week. Must be one of: Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday", currentDay),
)
return
}

if !previousExists {
resp.Diagnostics.AddError(
"Invalid day of week",
fmt.Sprintf("'%s' is not a valid day of week. Must be one of: Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday", previousDay),
)
return
}

if currentPos <= previousPos {
resp.Diagnostics.AddError(
"Invalid day order",
fmt.Sprintf("Days of the week must be in order (Sunday through Saturday). Found '%s' after '%s'", currentDay, previousDay),
)
return
}
}
}

type recurringScheduleValidator struct{}

func NewRecurringScheduleValidator() recurringScheduleValidator {
return recurringScheduleValidator{}
}

func (v recurringScheduleValidator) Description(_ context.Context) string {
return "validates that required fields are set based on the schedule type"
}

func (v recurringScheduleValidator) MarkdownDescription(ctx context.Context) string {
return v.Description(ctx)
}

func (v recurringScheduleValidator) ValidateObject(ctx context.Context, req validator.ObjectRequest, resp *validator.ObjectResponse) {
if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() {
return
}

var schedule struct {
Type types.String `tfsdk:"type"`
Unit types.Int64 `tfsdk:"unit"`
EndType types.String `tfsdk:"end_type"`
EndOnDate timetypes.RFC3339 `tfsdk:"end_on_date"`
EndAfterOccurrences types.Int64 `tfsdk:"end_after_occurrences"`
MonthlyScheduleType types.String `tfsdk:"monthly_schedule_type"`
DateOfMonth types.String `tfsdk:"date_of_month"`
DayNumberOfMonth types.String `tfsdk:"day_number_of_month"`
DaysOfWeek types.List `tfsdk:"days_of_week"`
DayOfWeek types.String `tfsdk:"day_of_week"`
}

resp.Diagnostics.Append(req.ConfigValue.As(ctx, &schedule, basetypes.ObjectAsOptions{})...)
if resp.Diagnostics.HasError() {
return
}

scheduleType := schedule.Type.ValueString()

switch scheduleType {
case "OnceDaily":
// OnceDaily only requires type and unit which are already marked as required

case "DaysPerWeek":
if schedule.DaysOfWeek.IsNull() {
resp.Diagnostics.AddAttributeError(
path.Root("days_of_week"),
"Missing Required Field",
"days_of_week must be set when schedule type is DaysPerWeek",
)
}

case "DaysPerMonth":
if schedule.MonthlyScheduleType.IsNull() {
resp.Diagnostics.AddAttributeError(
path.Root("monthly_schedule_type"),
"Missing Required Field",
"monthly_schedule_type must be set when schedule type is DaysPerMonth",
)
return
}

monthlyType := schedule.MonthlyScheduleType.ValueString()
switch monthlyType {
case "DateOfMonth":
if schedule.DateOfMonth.IsNull() {
resp.Diagnostics.AddAttributeError(
path.Root("date_of_month"),
"Missing Required Field",
"date_of_month must be set when monthly_schedule_type is DateOfMonth",
)
}

case "DayOfMonth":
if schedule.DayNumberOfMonth.IsNull() {
resp.Diagnostics.AddAttributeError(
path.Root("day_number_of_month"),
"Missing Required Field",
"day_number_of_month must be set when monthly_schedule_type is DayOfMonth",
)
} else {
dayNum := schedule.DayNumberOfMonth.ValueString()
validDayNums := map[string]bool{
"1": true,
"2": true,
"3": true,
"4": true,
"L": true,
}
if !validDayNums[dayNum] {
resp.Diagnostics.AddAttributeError(
path.Root("day_number_of_month"),
"Invalid Day Number",
fmt.Sprintf("day_number_of_month must be one of: 1, 2, 3, 4, L, got: %s", dayNum),
)
}
}
if schedule.DayOfWeek.IsNull() {
resp.Diagnostics.AddAttributeError(
path.Root("day_of_week"),
"Missing Required Field",
"day_of_week must be set when monthly_schedule_type is DayOfMonth",
)
}

default:
resp.Diagnostics.AddAttributeError(
path.Root("monthly_schedule_type"),
"Invalid Monthly Schedule Type",
fmt.Sprintf("monthly_schedule_type must be either DateOfMonth or DayOfMonth, got: %s", monthlyType),
)
}

case "Annually":
// Annually only requires type and unit which are already marked as required

default:
resp.Diagnostics.AddAttributeError(
path.Root("type"),
"Invalid Schedule Type",
fmt.Sprintf("type must be one of: OnceDaily, DaysPerWeek, DaysPerMonth, Annually, got: %s", scheduleType),
)
}
}

0 comments on commit 7b937f9

Please sign in to comment.