From 70aebb03bac89e9bd0f04f71dcb46eed9d6a6936 Mon Sep 17 00:00:00 2001 From: Claire <213631+nyobe@users.noreply.github.com> Date: Mon, 9 Dec 2024 12:09:22 -0800 Subject: [PATCH] enable passed one-time scheduled actions to be rescheduled (#455) One-time schedules are [soft-deleted after running](https://github.com/pulumi/pulumi-service/blob/186e4d4249d635f8f928f416c371c739ae0b52b5/cmd/service/model/schedules.go#L457), which means updating their schedule doesn't work because the action is never actually picked up again by the scheduler. (Prior to https://github.com/pulumi/pulumi-service/pull/24133 the update would appear to succeed, but the scheduled action would just silently never run. Now at least you get an error indicating the schedule was deleted.) This PR enables rescheduling to work correctly by forcing the schedule to be replaced when its timestamp is updated. Partially addresses #449 --- CHANGELOG_PENDING.md | 2 + examples/examples_yaml_test.go | 96 +++++++++++++++++-- .../pulumi-resource-pulumiservice/schema.json | 12 ++- provider/pkg/provider/deployment_schedules.go | 1 + 4 files changed, 99 insertions(+), 12 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 5e78e668..94583d8f 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -2,4 +2,6 @@ ### Bug Fixes +- Updating the timestamp of a one-time schedule will now cause a replacement, allowing schedules that have already run to be rescheduled correctly: [#455](https://github.com/pulumi/pulumi-pulumiservice/pull/455) + ### Miscellaneous diff --git a/examples/examples_yaml_test.go b/examples/examples_yaml_test.go index bca49b45..2c5c25ea 100644 --- a/examples/examples_yaml_test.go +++ b/examples/examples_yaml_test.go @@ -9,6 +9,7 @@ import ( "path" "strings" "testing" + "time" "github.com/google/uuid" "github.com/pulumi/pulumi/pkg/v3/testing/integration" @@ -20,6 +21,7 @@ import ( type Resource struct { Type string `yaml:"type"` Properties map[string]interface{} `yaml:"properties"` + Options map[string]interface{} `yaml:"options"` } type YamlProgram struct { Name string `yaml:"name"` @@ -248,14 +250,92 @@ func TestYamlWebhookExample(t *testing.T) { } func TestYamlSchedulesExample(t *testing.T) { - cwd := getCwd(t) - digits := generateRandomFiveDigits() - integration.ProgramTest(t, &integration.ProgramTestOptions{ - Dir: path.Join(cwd, ".", "yaml-schedules"), - StackName: "test-stack-" + digits, - Config: map[string]string{ - "digits": digits, - }, + + t.Run("Yaml Schedules Example", func(t *testing.T) { + cwd := getCwd(t) + digits := generateRandomFiveDigits() + integration.ProgramTest(t, &integration.ProgramTestOptions{ + Dir: path.Join(cwd, ".", "yaml-schedules"), + StackName: "test-stack-" + digits, + Config: map[string]string{ + "digits": digits, + }, + }) + }) + + t.Run("Schedules are replaced on timestamp update", func(t *testing.T) { + writeScheduleProgram := func(timestamp time.Time) string { + return writePulumiYaml(t, YamlProgram{ + Name: "yaml-schedule-reschedule", + Runtime: "yaml", + Resources: map[string]Resource{ + "settings": { + // deployment settings are required to be setup before schedules + Type: "pulumiservice:DeploymentSettings", + Properties: map[string]any{ + "organization": ServiceProviderTestOrg, + "project": "${pulumi.project}", + "stack": "${pulumi.stack}", + }, + }, + "deployment-schedule": { + Type: "pulumiservice:DeploymentSchedule", + Properties: map[string]any{ + "organization": ServiceProviderTestOrg, + "project": "${pulumi.project}", + "stack": "${pulumi.stack}", + "timestamp": timestamp.Format(time.RFC3339), + "pulumiOperation": "refresh", + }, + Options: map[string]any{ + "dependsOn": []string{"${settings}"}, + }, + }, + "ttl-schedule": { + Type: "pulumiservice:TtlSchedule", + Properties: map[string]any{ + "organization": ServiceProviderTestOrg, + "project": "${pulumi.project}", + "stack": "${pulumi.stack}", + "timestamp": timestamp.Format(time.RFC3339), + "deleteAfterDestroy": false, + }, + Options: map[string]any{ + "dependsOn": []string{"${settings}"}, + }, + }, + }, + }) + } + + // create some initial one-time schedules + initialDir := writeScheduleProgram(time.Now().Add(1 * time.Hour)) + // and then reschedule them + rescheduleDir := writeScheduleProgram(time.Now().Add(2 * time.Hour)) + + update := &strings.Builder{} + updateOut := io.MultiWriter(os.Stdout, update) + + integration.ProgramTest(t, &integration.ProgramTestOptions{ + StackName: "test-stack-" + generateRandomFiveDigits(), + Dir: initialDir, + Quick: true, + SkipRefresh: true, + EditDirs: []integration.EditDir{ + { + Dir: rescheduleDir, + Additive: true, + Stdout: updateOut, + Stderr: updateOut, + Verbose: true, + ExpectNoChanges: false, + }, + }, + }) + + // expect the update to cause schedule replacements + assert.Contains(t, update.String(), "deployment-schedule replaced") + assert.Contains(t, update.String(), "ttl-schedule replaced") }) } diff --git a/provider/cmd/pulumi-resource-pulumiservice/schema.json b/provider/cmd/pulumi-resource-pulumiservice/schema.json index e170d37c..d228171a 100644 --- a/provider/cmd/pulumi-resource-pulumiservice/schema.json +++ b/provider/cmd/pulumi-resource-pulumiservice/schema.json @@ -1254,7 +1254,8 @@ }, "timestamp": { "description": "The time at which the schedule should run, in ISO 8601 format. Eg: 2020-01-01T00:00:00Z. If you are supplying this, do not supply scheduleCron.", - "type": "string" + "type": "string", + "willReplaceOnChanges": true }, "pulumiOperation": { "description": "Which operation to run.", @@ -1291,7 +1292,8 @@ }, "timestamp": { "description": "The time at which the schedule should run, in ISO 8601 format. Eg: 2020-01-01T00:00:00Z. If you are supplying this, do not supply scheduleCron.", - "type": "string" + "type": "string", + "willReplaceOnChanges": true }, "pulumiOperation": { "description": "Which command to run.", @@ -1388,7 +1390,8 @@ }, "timestamp": { "description": "The time at which the schedule should run, in ISO 8601 format. Eg: 2020-01-01T00:00:00Z.", - "type": "string" + "type": "string", + "willReplaceOnChanges": true }, "deleteAfterDestroy": { "description": "True if the stack and all associated history and settings should be deleted.", @@ -1422,7 +1425,8 @@ }, "timestamp": { "description": "The time at which the schedule should run, in ISO 8601 format. Eg: 2020-01-01T00:00:00Z.", - "type": "string" + "type": "string", + "willReplaceOnChanges": true }, "deleteAfterDestroy": { "description": "True if the stack and all associated history and settings should be deleted.", diff --git a/provider/pkg/provider/deployment_schedules.go b/provider/pkg/provider/deployment_schedules.go index e25464f2..926530ea 100644 --- a/provider/pkg/provider/deployment_schedules.go +++ b/provider/pkg/provider/deployment_schedules.go @@ -167,6 +167,7 @@ func ScheduleSharedDiffMaps(olds resource.PropertyMap, news resource.PropertyMap "organization": true, "project": true, "stack": true, + "timestamp": true, } for k, v := range dd { if _, ok := replaceProperties[k]; ok {