Skip to content

Commit

Permalink
SCALRCORE-31246: Add client for run schedule rule
Browse files Browse the repository at this point in the history
  • Loading branch information
lyzohub committed Jun 20, 2024
1 parent d70eae5 commit 1e51053
Show file tree
Hide file tree
Showing 4 changed files with 395 additions and 0 deletions.
22 changes: 22 additions & 0 deletions helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -543,3 +543,25 @@ func createSlackIntegration(
}
}
}

func createRunScheduleRule(t *testing.T, client *Client, workspace *Workspace, mode ScheduleMode) (*RunScheduleRule, func()) {
ctx := context.Background()
options := RunScheduleRuleCreateOptions{
Schedule: "0 0 * * *",
ScheduleMode: mode,
Workspace: workspace,
}
rule, err := client.RunScheduleRules.Create(ctx, options)

if err != nil {
t.Fatal(err)
}

return rule, func() {
if err := client.RunScheduleRules.Delete(ctx, rule.ID); err != nil {
t.Errorf("Error destroying run schedule rule! WARNING: Dangling resources\n"+
"may exist! The full error is shown below.\n\n"+
"Run schedule rule: %s\nError: %s", rule.ID, err)
}
}
}
173 changes: 173 additions & 0 deletions run_schedule_rule.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package scalr

import (
"context"
"errors"
"fmt"
"net/url"
)

// Compile-time proof of interface implementation.
var _ RunScheduleRules = (*runScheduleRules)(nil)

// RunScheduleRules describes all the run schedule rule related methods that the Scalr API supports.
type RunScheduleRules interface {
List(ctx context.Context, options RunScheduleRuleListOptions) (*RunScheduleRulesList, error)
Create(ctx context.Context, options RunScheduleRuleCreateOptions) (*RunScheduleRule, error)
Read(ctx context.Context, ruleID string) (*RunScheduleRule, error)
Delete(ctx context.Context, ruleID string) error
Update(ctx context.Context, ruleID string, options RunScheduleRuleUpdateOptions) (*RunScheduleRule, error)
}

// runScheduleRules implements RunScheduleRules.
type runScheduleRules struct {
client *Client
}

// RunScheduleRulesList represents a list of run schedule rules.
type RunScheduleRulesList struct {
*Pagination
Items []*RunScheduleRule
}

// ScheduleMode represents the run-type that will be scheduled.
type ScheduleMode string

const (
ApplyMode ScheduleMode = "apply"
DestroyMode ScheduleMode = "destroy"
RefreshMode ScheduleMode = "refresh"
)

// RunScheduleRule represents a Scalr run schedule rule.
type RunScheduleRule struct {
ID string `jsonapi:"primary,run-schedule-rules"`
Schedule string `jsonapi:"attr,schedule"`
ScheduleMode ScheduleMode `jsonapi:"attr,schedule-mode"`

Workspace *Workspace `jsonapi:"relation,workspace,omitempty"`
}

// RunScheduleRuleListOptions represents the options for listing run schedule rules.
type RunScheduleRuleListOptions struct {
ListOptions
Workspace string `url:"filter[workspace],omitempty"`
Include string `url:"include,omitempty"`
}

// List all run schedule rules in the workspace.
func (s *runScheduleRules) List(ctx context.Context, options RunScheduleRuleListOptions) (*RunScheduleRulesList, error) {
req, err := s.client.newRequest("GET", "run-schedule-rules", &options)
if err != nil {
return nil, err
}

runScheduleRulesList := &RunScheduleRulesList{}
err = s.client.do(ctx, req, runScheduleRulesList)
if err != nil {
return nil, err
}

return runScheduleRulesList, nil
}

// RunScheduleRuleCreateOptions represents the options for creating a new run schedule rule.
type RunScheduleRuleCreateOptions struct {
ID string `jsonapi:"primary,run-schedule-rules"`
Schedule string `jsonapi:"attr,schedule"`
ScheduleMode ScheduleMode `jsonapi:"attr,schedule-mode"`

Workspace *Workspace `jsonapi:"relation,workspace,omitempty"`
}

// Create is used to create a new run schedule rule.
func (s *runScheduleRules) Create(ctx context.Context, options RunScheduleRuleCreateOptions) (*RunScheduleRule, error) {
options.ID = ""

urlPath := fmt.Sprintf("run-schedule-rules")
req, err := s.client.newRequest("POST", urlPath, &options)
if err != nil {
return nil, err
}

rule := &RunScheduleRule{}
err = s.client.do(ctx, req, rule)

if err != nil {
return nil, err
}

return rule, nil
}

// Read a run schedule rule by ID.
func (s *runScheduleRules) Read(ctx context.Context, ruleID string) (*RunScheduleRule, error) {
if !validStringID(&ruleID) {
return nil, errors.New("invalid value for run schedule rule ID")
}

urlPath := fmt.Sprintf("run-schedule-rules/%s", url.QueryEscape(ruleID))

options := struct {
Include string `url:"include"`
}{
Include: "workspace",
}

req, err := s.client.newRequest("GET", urlPath, options)
if err != nil {
return nil, err
}

rule := &RunScheduleRule{}
err = s.client.do(ctx, req, rule)
if err != nil {
return nil, err
}

return rule, nil
}

// RunScheduleRuleUpdateOptions represents the options for updating a run schedule rule.
type RunScheduleRuleUpdateOptions struct {
ID string `jsonapi:"primary,run-schedule-rules"`
Schedule string `jsonapi:"attr,schedule"`
ScheduleMode ScheduleMode `jsonapi:"attr,schedule-mode"`
}

// Update an existing run schedule rule.
func (s *runScheduleRules) Update(ctx context.Context, ruleID string, options RunScheduleRuleUpdateOptions) (*RunScheduleRule, error) {
if !validStringID(&ruleID) {
return nil, errors.New("invalid value for run schedule rule ID")
}

urlPath := fmt.Sprintf("run-schedule-rules/%s", url.QueryEscape(ruleID))

req, err := s.client.newRequest("PATCH", urlPath, &options)
if err != nil {
return nil, err
}

rule := &RunScheduleRule{}
err = s.client.do(ctx, req, rule)
if err != nil {
return nil, err
}

return rule, nil
}

// Delete deletes a run schedule rule by its ID.
func (s *runScheduleRules) Delete(ctx context.Context, ruleID string) error {
if !validStringID(&ruleID) {
return errors.New("invalid value for run schedule rule ID")
}

urlPath := fmt.Sprintf("run-schedule-rules/%s", url.QueryEscape(ruleID))
req, err := s.client.newRequest("DELETE", urlPath, nil)
if err != nil {
return err
}

return s.client.do(ctx, req, nil)
}
198 changes: 198 additions & 0 deletions run_schedule_rule_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package scalr

import (
"context"
"fmt"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)

func TestRunScheduleRulesList(t *testing.T) {
client := testClient(t)
ctx := context.Background()
environment, environmentCleanup := createEnvironment(t, client)
defer environmentCleanup()
workspace, workspaceCleanup := createWorkspace(t, client, environment)
defer workspaceCleanup()

scheduleRuleA, scheduleRuleACleanup := createRunScheduleRule(t, client, workspace, ApplyMode)
defer scheduleRuleACleanup()
scheduleRuleB, scheduleRuleBCleanup := createRunScheduleRule(t, client, workspace, DestroyMode)
defer scheduleRuleBCleanup()

t.Run("without include", func(t *testing.T) {
ruleList, err := client.RunScheduleRules.List(ctx, RunScheduleRuleListOptions{
Workspace: workspace.ID,
})
require.NoError(t, err)
assert.Equal(t, 2, ruleList.TotalCount)

ruleIDs := make([]string, len(ruleList.Items))
for i, rule := range ruleList.Items {
ruleIDs[i] = rule.ID
}
assert.Contains(t, ruleIDs, scheduleRuleA.ID)
assert.Contains(t, ruleIDs, scheduleRuleB.ID)
})

t.Run("with include", func(t *testing.T) {
ruleList, err := client.RunScheduleRules.List(ctx, RunScheduleRuleListOptions{
Workspace: workspace.ID,
Include: "workspace",
})
require.NoError(t, err)
assert.Equal(t, 2, ruleList.TotalCount)
for _, rule := range ruleList.Items {
assert.NotEqual(t, rule.Workspace, nil)
}
})
}

func TestRunScheduleRulesCreate(t *testing.T) {
client := testClient(t)
ctx := context.Background()
environment, environmentCleanup := createEnvironment(t, client)
defer environmentCleanup()
workspace, workspaceCleanup := createWorkspace(t, client, environment)
defer workspaceCleanup()

t.Run("with valid options", func(t *testing.T) {
options := RunScheduleRuleCreateOptions{
Schedule: "0 0 * * *",
ScheduleMode: ApplyMode,
Workspace: workspace,
}
rule, err := client.RunScheduleRules.Create(ctx, options)
require.NoError(t, err)

// Get a refreshed view from the API.
refreshed, err := client.RunScheduleRules.Read(ctx, rule.ID)
require.NoError(t, err)

for _, item := range []*RunScheduleRule{
rule,
refreshed,
} {
assert.NotEmpty(t, item.ID)
}
err = client.RunScheduleRules.Delete(ctx, rule.ID)
require.NoError(t, err)
})

t.Run("when rule type collision", func(t *testing.T) {
options := RunScheduleRuleCreateOptions{
Schedule: "0 0 * * *",
ScheduleMode: ApplyMode,
Workspace: workspace,
}
rule, err := client.RunScheduleRules.Create(ctx, options)
require.NoError(t, err)

_, err = client.RunScheduleRules.Create(ctx, options)
require.Error(t, err)

err = client.RunScheduleRules.Delete(ctx, rule.ID)
require.NoError(t, err)
})
}

func TestRunScheduleRulesRead(t *testing.T) {
client := testClient(t)
ctx := context.Background()
environment, environmentCleanup := createEnvironment(t, client)
defer environmentCleanup()
workspace, workspaceCleanup := createWorkspace(t, client, environment)
defer workspaceCleanup()

ruleTest, ruleTestCleanup := createRunScheduleRule(t, client, workspace, ApplyMode)
defer ruleTestCleanup()

t.Run("by ID when the rule exists", func(t *testing.T) {
rule, err := client.RunScheduleRules.Read(ctx, ruleTest.ID)
require.NoError(t, err)
assert.Equal(t, ruleTest.ID, rule.ID)
})

t.Run("by ID when the rule does not exist", func(t *testing.T) {
rule, err := client.RunScheduleRules.Read(ctx, "rule-nonexisting")
assert.Nil(t, rule)
assert.Error(t, err)
})

t.Run("by ID without a valid rule ID", func(t *testing.T) {
rule, err := client.RunScheduleRules.Read(ctx, badIdentifier)
assert.Nil(t, rule)
assert.EqualError(t, err, "invalid value for run schedule rule ID")
})
}

func TestRunScheduleRulesUpdate(t *testing.T) {
client := testClient(t)
ctx := context.Background()
environment, environmentCleanup := createEnvironment(t, client)
defer environmentCleanup()
workspace, workspaceCleanup := createWorkspace(t, client, environment)
defer workspaceCleanup()

scheduleRuleA, scheduleRuleACleanup := createRunScheduleRule(t, client, workspace, ApplyMode)
defer scheduleRuleACleanup()
_, scheduleRuleBCleanup := createRunScheduleRule(t, client, workspace, RefreshMode)
defer scheduleRuleBCleanup()

t.Run("with valid options", func(t *testing.T) {
options := RunScheduleRuleUpdateOptions{
Schedule: "* * * * *",
ScheduleMode: DestroyMode,
}

rule, err := client.RunScheduleRules.Update(ctx, scheduleRuleA.ID, options)
require.NoError(t, err)

// Get a refreshed view from the API.
refreshed, err := client.RunScheduleRules.Read(ctx, rule.ID)
require.NoError(t, err)

for _, item := range []*RunScheduleRule{
rule,
refreshed,
} {
assert.Equal(t, options.Schedule, item.Schedule)
assert.Equal(t, options.ScheduleMode, item.ScheduleMode)
}
})

t.Run("with mode collision", func(t *testing.T) {
rule, err := client.RunScheduleRules.Update(ctx, scheduleRuleA.ID, RunScheduleRuleUpdateOptions{
Schedule: "* * * * *",
ScheduleMode: RefreshMode,
})
assert.Nil(t, rule)
assert.Error(t, err)
})
}

func TestRunScheduleRulesDelete(t *testing.T) {
client := testClient(t)
ctx := context.Background()
environment, environmentCleanup := createEnvironment(t, client)
defer environmentCleanup()
workspace, workspaceCleanup := createWorkspace(t, client, environment)
defer workspaceCleanup()

scheduleRule, _ := createRunScheduleRule(t, client, workspace, ApplyMode)

t.Run("with valid options", func(t *testing.T) {
err := client.RunScheduleRules.Delete(ctx, scheduleRule.ID)
require.NoError(t, err)

_, err = client.RunScheduleRules.Read(ctx, scheduleRule.ID)
assert.Equal(
t,
ResourceNotFoundError{
Message: fmt.Sprintf("RunScheduleRule with ID '%s' not found or user unauthorized.", scheduleRule.ID),
}.Error(),
err.Error(),
)
})
}
Loading

0 comments on commit 1e51053

Please sign in to comment.