access_monitoring_rules: Add timezone to schedules spec (#60067)

* Add timezone to AMR schedules

* Update documentation with accepted timezone values

* Address feedback

- Allow empty timezone value
- Reference IANA in error message
- Add additional timeonze test cases

* Fix test case
This commit is contained in:
Bernard Kim
2025-10-15 17:41:02 +00:00
committed by GitHub
parent 7ffa26d058
commit 10095ebfb9
7 changed files with 194 additions and 28 deletions
@@ -394,8 +394,14 @@ func (x *Schedule) GetTime() *TimeSchedule {
type TimeSchedule struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Shifts contains a set of shifts that make up the schedule.
// Shifts are configured in UTC.
Shifts []*TimeSchedule_Shift `protobuf:"bytes,1,rep,name=shifts,proto3" json:"shifts,omitempty"`
Shifts []*TimeSchedule_Shift `protobuf:"bytes,1,rep,name=shifts,proto3" json:"shifts,omitempty"`
// Timezone specifies the schedule timezone. This field is optional and defaults
// to "UTC". Accepted values use timezone locations as defined in the IANA
// Time Zone Database, such as "America/Los_Angeles", "Europe/Lisbon", or
// "Asia/Singapore".
//
// See https://data.iana.org/time-zones/tzdb/zone1970.tab for a list of supported values.
Timezone string `protobuf:"bytes,2,opt,name=timezone,proto3" json:"timezone,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -437,6 +443,13 @@ func (x *TimeSchedule) GetShifts() []*TimeSchedule_Shift {
return nil
}
func (x *TimeSchedule) GetTimezone() string {
if x != nil {
return x.Timezone
}
return ""
}
// CreateAccessMonitoringRuleRequest is the request for CreateAccessMonitoringRule.
type CreateAccessMonitoringRuleRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
@@ -1014,9 +1027,10 @@ const file_teleport_accessmonitoringrules_v1_access_monitoring_rules_proto_rawDe
"\vintegration\x18\x01 \x01(\tR\vintegration\x12\x1a\n" +
"\bdecision\x18\x02 \x01(\tR\bdecision\"O\n" +
"\bSchedule\x12C\n" +
"\x04time\x18\x01 \x01(\v2/.teleport.accessmonitoringrules.v1.TimeScheduleR\x04time\"\xa8\x01\n" +
"\x04time\x18\x01 \x01(\v2/.teleport.accessmonitoringrules.v1.TimeScheduleR\x04time\"\xc4\x01\n" +
"\fTimeSchedule\x12M\n" +
"\x06shifts\x18\x01 \x03(\v25.teleport.accessmonitoringrules.v1.TimeSchedule.ShiftR\x06shifts\x1aI\n" +
"\x06shifts\x18\x01 \x03(\v25.teleport.accessmonitoringrules.v1.TimeSchedule.ShiftR\x06shifts\x12\x1a\n" +
"\btimezone\x18\x02 \x01(\tR\btimezone\x1aI\n" +
"\x05Shift\x12\x18\n" +
"\aweekday\x18\x01 \x01(\tR\aweekday\x12\x14\n" +
"\x05start\x18\x02 \x01(\tR\x05start\x12\x10\n" +
@@ -100,9 +100,16 @@ message Schedule {
// TimeSchedule specifies an in-line schedule.
message TimeSchedule {
// Shifts contains a set of shifts that make up the schedule.
// Shifts are configured in UTC.
repeated Shift shifts = 1;
// Timezone specifies the schedule timezone. This field is optional and defaults
// to "UTC". Accepted values use timezone locations as defined in the IANA
// Time Zone Database, such as "America/Los_Angeles", "Europe/Lisbon", or
// "Asia/Singapore".
//
// See https://data.iana.org/time-zones/tzdb/zone1970.tab for a list of supported values.
string timezone = 2;
// Shift contains the weekday, start time, and end time of a shift.
message Shift {
// Weekday specifies the day of the week, e.g., "Sunday", "Monday", "Tuesday".
@@ -76,7 +76,8 @@ Optional:
Optional:
- `shifts` (Attributes List) Shifts contains a set of shifts that make up the schedule. Shifts are configured in UTC. (see [below for nested schema](#nested-schema-for-specschedulestimeshifts))
- `shifts` (Attributes List) Shifts contains a set of shifts that make up the schedule. (see [below for nested schema](#nested-schema-for-specschedulestimeshifts))
- `timezone` (String) Timezone specifies the schedule timezone. This field is optional and defaults to "UTC". Accepted values use timezone locations as defined in the IANA Time Zone Database, such as "America/Los_Angeles", "Europe/Lisbon", or "Asia/Singapore". See https://data.iana.org/time-zones/tzdb/zone1970.tab for a list of supported values.
### Nested Schema for `spec.schedules.time.shifts`
@@ -98,7 +98,8 @@ Optional:
Optional:
- `shifts` (Attributes List) Shifts contains a set of shifts that make up the schedule. Shifts are configured in UTC. (see [below for nested schema](#nested-schema-for-specschedulestimeshifts))
- `shifts` (Attributes List) Shifts contains a set of shifts that make up the schedule. (see [below for nested schema](#nested-schema-for-specschedulestimeshifts))
- `timezone` (String) Timezone specifies the schedule timezone. This field is optional and defaults to "UTC". Accepted values use timezone locations as defined in the IANA Time Zone Database, such as "America/Los_Angeles", "Europe/Lisbon", or "Asia/Singapore". See https://data.iana.org/time-zones/tzdb/zone1970.tab for a list of supported values.
### Nested Schema for `spec.schedules.time.shifts`
@@ -142,27 +142,34 @@ func GenSchemaAccessMonitoringRule(ctx context.Context) (github_com_hashicorp_te
},
"schedules": {
Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.MapNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{"time": {
Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{"shifts": {
Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.ListNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{
"end": {
Description: "End specifies the end time in the format HH:MM, e.g., \"12:30\".",
Optional: true,
Type: github_com_hashicorp_terraform_plugin_framework_types.StringType,
},
"start": {
Description: "Start specifies the start time in the format HH:MM, e.g., \"12:30\".",
Optional: true,
Type: github_com_hashicorp_terraform_plugin_framework_types.StringType,
},
"weekday": {
Description: "Weekday specifies the day of the week, e.g., \"Sunday\", \"Monday\", \"Tuesday\".",
Optional: true,
Type: github_com_hashicorp_terraform_plugin_framework_types.StringType,
},
}),
Description: "Shifts contains a set of shifts that make up the schedule. Shifts are configured in UTC.",
Optional: true,
}}),
Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{
"shifts": {
Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.ListNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{
"end": {
Description: "End specifies the end time in the format HH:MM, e.g., \"12:30\".",
Optional: true,
Type: github_com_hashicorp_terraform_plugin_framework_types.StringType,
},
"start": {
Description: "Start specifies the start time in the format HH:MM, e.g., \"12:30\".",
Optional: true,
Type: github_com_hashicorp_terraform_plugin_framework_types.StringType,
},
"weekday": {
Description: "Weekday specifies the day of the week, e.g., \"Sunday\", \"Monday\", \"Tuesday\".",
Optional: true,
Type: github_com_hashicorp_terraform_plugin_framework_types.StringType,
},
}),
Description: "Shifts contains a set of shifts that make up the schedule.",
Optional: true,
},
"timezone": {
Description: "Timezone specifies the schedule timezone. This field is optional and defaults to \"UTC\". Accepted values use timezone locations as defined in the IANA Time Zone Database, such as \"America/Los_Angeles\", \"Europe/Lisbon\", or \"Asia/Singapore\". See https://data.iana.org/time-zones/tzdb/zone1970.tab for a list of supported values.",
Optional: true,
Type: github_com_hashicorp_terraform_plugin_framework_types.StringType,
},
}),
Description: "TimeSchedule specifies an in-line schedule.",
Optional: true,
}}),
@@ -701,6 +708,23 @@ func CopyAccessMonitoringRuleFromTerraform(_ context.Context, tf github_com_hash
}
}
}
{
a, ok := tf.Attrs["timezone"]
if !ok {
diags.Append(attrReadMissingDiag{"AccessMonitoringRule.spec.schedules.time.timezone"})
} else {
v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String)
if !ok {
diags.Append(attrReadConversionFailureDiag{"AccessMonitoringRule.spec.schedules.time.timezone", "github.com/hashicorp/terraform-plugin-framework/types.String"})
} else {
var t string
if !v.Null && !v.Unknown {
t = string(v.Value)
}
obj.Timezone = t
}
}
}
}
}
}
@@ -1524,6 +1548,28 @@ func CopyAccessMonitoringRuleToTerraform(ctx context.Context, obj *github_com_gr
}
}
}
{
t, ok := tf.AttrTypes["timezone"]
if !ok {
diags.Append(attrWriteMissingDiag{"AccessMonitoringRule.spec.schedules.time.timezone"})
} else {
v, ok := tf.Attrs["timezone"].(github_com_hashicorp_terraform_plugin_framework_types.String)
if !ok {
i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil))
if err != nil {
diags.Append(attrWriteGeneralError{"AccessMonitoringRule.spec.schedules.time.timezone", err})
}
v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String)
if !ok {
diags.Append(attrWriteConversionFailureDiag{"AccessMonitoringRule.spec.schedules.time.timezone", "github.com/hashicorp/terraform-plugin-framework/types.String"})
}
v.Null = string(obj.Timezone) == ""
}
v.Value = string(obj.Timezone)
v.Unknown = false
tf.Attrs["timezone"] = v
}
}
}
v.Unknown = false
tf.Attrs["time"] = v
+5
View File
@@ -22,6 +22,7 @@ import (
"context"
"slices"
"time"
_ "time/tzdata"
"github.com/gravitational/trace"
@@ -156,6 +157,10 @@ func validateSchedules(schedules map[string]*accessmonitoringrulesv1.Schedule) e
}
func validateTimeSchedule(schedule *accessmonitoringrulesv1.TimeSchedule) error {
if _, err := time.LoadLocation(schedule.GetTimezone()); err != nil {
return trace.Wrap(err, "invalid timezone: refer to the IANA Time Zone Database for valid options")
}
if len(schedule.GetShifts()) == 0 {
return trace.BadParameter("at least one shift is required")
}
@@ -249,6 +249,98 @@ func TestValidateSchedules(t *testing.T) {
require.ErrorContains(t, err, "at least one shift is require")
},
},
{
description: "valid timezone (UTC)",
schedules: map[string]*accessmonitoringrulesv1.Schedule{
"default": {
Time: &accessmonitoringrulesv1.TimeSchedule{
Timezone: "UTC",
Shifts: []*accessmonitoringrulesv1.TimeSchedule_Shift{
{
Weekday: time.Monday.String(),
Start: "00:00",
End: "23:59",
},
},
},
},
},
assertErr: require.NoError,
},
{
description: "valid timezone (America/Los_Angeles)",
schedules: map[string]*accessmonitoringrulesv1.Schedule{
"default": {
Time: &accessmonitoringrulesv1.TimeSchedule{
Timezone: "America/Los_Angeles",
Shifts: []*accessmonitoringrulesv1.TimeSchedule_Shift{
{
Weekday: time.Monday.String(),
Start: "00:00",
End: "23:59",
},
},
},
},
},
assertErr: require.NoError,
},
{
description: "valid timezone (Europe/Lisbon)",
schedules: map[string]*accessmonitoringrulesv1.Schedule{
"default": {
Time: &accessmonitoringrulesv1.TimeSchedule{
Timezone: "Europe/Lisbon",
Shifts: []*accessmonitoringrulesv1.TimeSchedule_Shift{
{
Weekday: time.Monday.String(),
Start: "00:00",
End: "23:59",
},
},
},
},
},
assertErr: require.NoError,
},
{
description: "valid timezone (Asia/Singapore)",
schedules: map[string]*accessmonitoringrulesv1.Schedule{
"default": {
Time: &accessmonitoringrulesv1.TimeSchedule{
Timezone: "Asia/Singapore",
Shifts: []*accessmonitoringrulesv1.TimeSchedule_Shift{
{
Weekday: time.Monday.String(),
Start: "00:00",
End: "23:59",
},
},
},
},
},
assertErr: require.NoError,
},
{
description: "invalid timezone",
schedules: map[string]*accessmonitoringrulesv1.Schedule{
"default": {
Time: &accessmonitoringrulesv1.TimeSchedule{
Timezone: "invalid",
Shifts: []*accessmonitoringrulesv1.TimeSchedule_Shift{
{
Weekday: time.Monday.String(),
Start: "00:00",
End: "23:59",
},
},
},
},
},
assertErr: func(t require.TestingT, err error, _ ...interface{}) {
require.ErrorContains(t, err, "invalid timezone")
},
},
{
description: "start time is not before end time",
schedules: map[string]*accessmonitoringrulesv1.Schedule{