Skip to content

Commit

Permalink
[droplets]: add droplet backup policies (#749)
Browse files Browse the repository at this point in the history
* [droplets]: add droplet backup policies

* add droplet backup policy to droplet create request

* updates in code: correct types, omitempty, returns; add tests

* Update droplets.go

Co-authored-by: Andrew Starr-Bochicchio <andrewsomething@users.noreply.github.com>

* Update droplets.go

Co-authored-by: Andrew Starr-Bochicchio <andrewsomething@users.noreply.github.com>

* Update droplets.go

Co-authored-by: Andrew Starr-Bochicchio <andrewsomething@users.noreply.github.com>

* rename BackupPolicyCreateRequest to DropletBackupPolicyRequest

* fix test for listing all policies after changes in a code

* rename BackupPolicy to DropletBackupPolicyConfig

* add EnableBackupsWithPolicy to droplet actions

* update EnableBackupsWithPolicy: use change_backup_policy type

* remove omitempty for backup policy hour in DropletBackupPolicyRequest to be able to use zero as a value when encoding in client.NewRequest

* Update droplet_actions.go

Co-authored-by: Andrew Starr-Bochicchio <andrewsomething@users.noreply.github.com>

* add ChangeBackupPolicy; use DropletBackupPolicyRequest instead of map in request; update and add tests for changes made

---------

Co-authored-by: Andrew Starr-Bochicchio <andrewsomething@users.noreply.github.com>
  • Loading branch information
loosla and andrewsomething authored Nov 5, 2024
1 parent 29a46b0 commit a11b76f
Show file tree
Hide file tree
Showing 4 changed files with 414 additions and 30 deletions.
38 changes: 38 additions & 0 deletions droplet_actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ type DropletActionsService interface {
SnapshotByTag(context.Context, string, string) ([]Action, *Response, error)
EnableBackups(context.Context, int) (*Action, *Response, error)
EnableBackupsByTag(context.Context, string) ([]Action, *Response, error)
EnableBackupsWithPolicy(context.Context, int, *DropletBackupPolicyRequest) (*Action, *Response, error)
ChangeBackupPolicy(context.Context, int, *DropletBackupPolicyRequest) (*Action, *Response, error)
DisableBackups(context.Context, int) (*Action, *Response, error)
DisableBackupsByTag(context.Context, string) ([]Action, *Response, error)
PasswordReset(context.Context, int) (*Action, *Response, error)
Expand Down Expand Up @@ -169,6 +171,42 @@ func (s *DropletActionsServiceOp) EnableBackupsByTag(ctx context.Context, tag st
return s.doActionByTag(ctx, tag, request)
}

// EnableBackupsWithPolicy enables droplet's backup with a backup policy applied.
func (s *DropletActionsServiceOp) EnableBackupsWithPolicy(ctx context.Context, id int, policy *DropletBackupPolicyRequest) (*Action, *Response, error) {
if policy == nil {
return nil, nil, NewArgError("policy", "policy can't be nil")
}

policyMap := map[string]interface{}{
"plan": policy.Plan,
"weekday": policy.Weekday,
}
if policy.Hour != nil {
policyMap["hour"] = policy.Hour
}

request := &ActionRequest{"type": "enable_backups", "backup_policy": policyMap}
return s.doAction(ctx, id, request)
}

// ChangeBackupPolicy updates a backup policy when backups are enabled.
func (s *DropletActionsServiceOp) ChangeBackupPolicy(ctx context.Context, id int, policy *DropletBackupPolicyRequest) (*Action, *Response, error) {
if policy == nil {
return nil, nil, NewArgError("policy", "policy can't be nil")
}

policyMap := map[string]interface{}{
"plan": policy.Plan,
"weekday": policy.Weekday,
}
if policy.Hour != nil {
policyMap["hour"] = policy.Hour
}

request := &ActionRequest{"type": "change_backup_policy", "backup_policy": policyMap}
return s.doAction(ctx, id, request)
}

// DisableBackups disables backups for a Droplet.
func (s *DropletActionsServiceOp) DisableBackups(ctx context.Context, id int) (*Action, *Response, error) {
request := &ActionRequest{"type": "disable_backups"}
Expand Down
96 changes: 96 additions & 0 deletions droplet_actions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,102 @@ func TestDropletAction_EnableBackupsByTag(t *testing.T) {
}
}

func TestDropletAction_EnableBackupsWithPolicy(t *testing.T) {
setup()
defer teardown()

policyRequest := &DropletBackupPolicyRequest{
Plan: "weekly",
Weekday: "TUE",
Hour: PtrTo(20),
}

policy := map[string]interface{}{
"hour": float64(20),
"plan": "weekly",
"weekday": "TUE",
}

request := &ActionRequest{
"type": "enable_backups",
"backup_policy": policy,
}

mux.HandleFunc("/v2/droplets/1/actions", func(w http.ResponseWriter, r *http.Request) {
v := new(ActionRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatalf("decode json: %v", err)
}

testMethod(t, r, http.MethodPost)

if !reflect.DeepEqual(v, request) {
t.Errorf("Request body = %+v, expected %+v", v, request)
}

fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})

action, _, err := client.DropletActions.EnableBackupsWithPolicy(ctx, 1, policyRequest)
if err != nil {
t.Errorf("DropletActions.EnableBackups returned error: %v", err)
}

expected := &Action{Status: "in-progress"}
if !reflect.DeepEqual(action, expected) {
t.Errorf("DropletActions.EnableBackups returned %+v, expected %+v", action, expected)
}
}

func TestDropletAction_ChangeBackupPolicy(t *testing.T) {
setup()
defer teardown()

policyRequest := &DropletBackupPolicyRequest{
Plan: "weekly",
Weekday: "SUN",
Hour: PtrTo(0),
}

policy := map[string]interface{}{
"hour": float64(0),
"plan": "weekly",
"weekday": "SUN",
}

request := &ActionRequest{
"type": "change_backup_policy",
"backup_policy": policy,
}

mux.HandleFunc("/v2/droplets/1/actions", func(w http.ResponseWriter, r *http.Request) {
v := new(ActionRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatalf("decode json: %v", err)
}

testMethod(t, r, http.MethodPost)

if !reflect.DeepEqual(v, request) {
t.Errorf("Request body = %+v, expected %+v", v, request)
}

fmt.Fprintf(w, `{"action":{"status":"in-progress"}}`)
})

action, _, err := client.DropletActions.ChangeBackupPolicy(ctx, 1, policyRequest)
if err != nil {
t.Errorf("DropletActions.EnableBackups returned error: %v", err)
}

expected := &Action{Status: "in-progress"}
if !reflect.DeepEqual(action, expected) {
t.Errorf("DropletActions.EnableBackups returned %+v, expected %+v", action, expected)
}
}

func TestDropletAction_DisableBackups(t *testing.T) {
setup()
defer teardown()
Expand Down
172 changes: 145 additions & 27 deletions droplets.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ type DropletsService interface {
Backups(context.Context, int, *ListOptions) ([]Image, *Response, error)
Actions(context.Context, int, *ListOptions) ([]Action, *Response, error)
Neighbors(context.Context, int) ([]Droplet, *Response, error)
GetBackupPolicy(context.Context, int) (*DropletBackupPolicy, *Response, error)
ListBackupPolicies(context.Context, *ListOptions) (map[int]*DropletBackupPolicy, *Response, error)
ListSupportedBackupPolicies(context.Context) ([]*SupportedBackupPolicy, *Response, error)
}

// DropletsServiceOp handles communication with the Droplet related methods of the
Expand Down Expand Up @@ -218,37 +221,46 @@ func (d DropletCreateSSHKey) MarshalJSON() ([]byte, error) {

// DropletCreateRequest represents a request to create a Droplet.
type DropletCreateRequest struct {
Name string `json:"name"`
Region string `json:"region"`
Size string `json:"size"`
Image DropletCreateImage `json:"image"`
SSHKeys []DropletCreateSSHKey `json:"ssh_keys"`
Backups bool `json:"backups"`
IPv6 bool `json:"ipv6"`
PrivateNetworking bool `json:"private_networking"`
Monitoring bool `json:"monitoring"`
UserData string `json:"user_data,omitempty"`
Volumes []DropletCreateVolume `json:"volumes,omitempty"`
Tags []string `json:"tags"`
VPCUUID string `json:"vpc_uuid,omitempty"`
WithDropletAgent *bool `json:"with_droplet_agent,omitempty"`
Name string `json:"name"`
Region string `json:"region"`
Size string `json:"size"`
Image DropletCreateImage `json:"image"`
SSHKeys []DropletCreateSSHKey `json:"ssh_keys"`
Backups bool `json:"backups"`
IPv6 bool `json:"ipv6"`
PrivateNetworking bool `json:"private_networking"`
Monitoring bool `json:"monitoring"`
UserData string `json:"user_data,omitempty"`
Volumes []DropletCreateVolume `json:"volumes,omitempty"`
Tags []string `json:"tags"`
VPCUUID string `json:"vpc_uuid,omitempty"`
WithDropletAgent *bool `json:"with_droplet_agent,omitempty"`
BackupPolicy *DropletBackupPolicyRequest `json:"backup_policy,omitempty"`
}

// DropletMultiCreateRequest is a request to create multiple Droplets.
type DropletMultiCreateRequest struct {
Names []string `json:"names"`
Region string `json:"region"`
Size string `json:"size"`
Image DropletCreateImage `json:"image"`
SSHKeys []DropletCreateSSHKey `json:"ssh_keys"`
Backups bool `json:"backups"`
IPv6 bool `json:"ipv6"`
PrivateNetworking bool `json:"private_networking"`
Monitoring bool `json:"monitoring"`
UserData string `json:"user_data,omitempty"`
Tags []string `json:"tags"`
VPCUUID string `json:"vpc_uuid,omitempty"`
WithDropletAgent *bool `json:"with_droplet_agent,omitempty"`
Names []string `json:"names"`
Region string `json:"region"`
Size string `json:"size"`
Image DropletCreateImage `json:"image"`
SSHKeys []DropletCreateSSHKey `json:"ssh_keys"`
Backups bool `json:"backups"`
IPv6 bool `json:"ipv6"`
PrivateNetworking bool `json:"private_networking"`
Monitoring bool `json:"monitoring"`
UserData string `json:"user_data,omitempty"`
Tags []string `json:"tags"`
VPCUUID string `json:"vpc_uuid,omitempty"`
WithDropletAgent *bool `json:"with_droplet_agent,omitempty"`
BackupPolicy *DropletBackupPolicyRequest `json:"backup_policy,omitempty"`
}

// DropletBackupPolicyRequest defines the backup policy when creating a Droplet.
type DropletBackupPolicyRequest struct {
Plan string `json:"plan,omitempty"`
Weekday string `json:"weekday,omitempty"`
Hour *int `json:"hour,omitempty"`
}

func (d DropletCreateRequest) String() string {
Expand Down Expand Up @@ -618,3 +630,109 @@ func (s *DropletsServiceOp) dropletActionStatus(ctx context.Context, uri string)

return action.Status, nil
}

// DropletBackupPolicy defines the information about a droplet's backup policy.
type DropletBackupPolicy struct {
DropletID int `json:"droplet_id,omitempty"`
BackupEnabled bool `json:"backup_enabled,omitempty"`
BackupPolicy *DropletBackupPolicyConfig `json:"backup_policy,omitempty"`
NextBackupWindow *BackupWindow `json:"next_backup_window,omitempty"`
}

// DropletBackupPolicyConfig defines the backup policy for a Droplet.
type DropletBackupPolicyConfig struct {
Plan string `json:"plan,omitempty"`
Weekday string `json:"weekday,omitempty"`
Hour int `json:"hour,omitempty"`
WindowLengthHours int `json:"window_length_hours,omitempty"`
RetentionPeriodDays int `json:"retention_period_days,omitempty"`
}

// dropletBackupPolicyRoot represents a DropletBackupPolicy root
type dropletBackupPolicyRoot struct {
DropletBackupPolicy *DropletBackupPolicy `json:"policy,omitempty"`
}

type dropletBackupPoliciesRoot struct {
DropletBackupPolicies map[int]*DropletBackupPolicy `json:"policies,omitempty"`
Links *Links `json:"links,omitempty"`
Meta *Meta `json:"meta"`
}

// Get individual droplet backup policy.
func (s *DropletsServiceOp) GetBackupPolicy(ctx context.Context, dropletID int) (*DropletBackupPolicy, *Response, error) {
if dropletID < 1 {
return nil, nil, NewArgError("dropletID", "cannot be less than 1")
}

path := fmt.Sprintf("%s/%d/backups/policy", dropletBasePath, dropletID)

req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}

root := new(dropletBackupPolicyRoot)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}

return root.DropletBackupPolicy, resp, err
}

// List all droplet backup policies.
func (s *DropletsServiceOp) ListBackupPolicies(ctx context.Context, opt *ListOptions) (map[int]*DropletBackupPolicy, *Response, error) {
path := fmt.Sprintf("%s/backups/policies", dropletBasePath)
path, err := addOptions(path, opt)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}

root := new(dropletBackupPoliciesRoot)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
if l := root.Links; l != nil {
resp.Links = l
}
if m := root.Meta; m != nil {
resp.Meta = m
}

return root.DropletBackupPolicies, resp, nil
}

type SupportedBackupPolicy struct {
Name string `json:"name,omitempty"`
PossibleWindowStarts []int `json:"possible_window_starts,omitempty"`
WindowLengthHours int `json:"window_length_hours,omitempty"`
RetentionPeriodDays int `json:"retention_period_days,omitempty"`
PossibleDays []string `json:"possible_days,omitempty"`
}

type dropletSupportedBackupPoliciesRoot struct {
SupportedBackupPolicies []*SupportedBackupPolicy `json:"supported_policies,omitempty"`
}

// List supported droplet backup policies.
func (s *DropletsServiceOp) ListSupportedBackupPolicies(ctx context.Context) ([]*SupportedBackupPolicy, *Response, error) {
path := fmt.Sprintf("%s/backups/supported_policies", dropletBasePath)
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}

root := new(dropletSupportedBackupPoliciesRoot)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}

return root.SupportedBackupPolicies, resp, nil
}
Loading

0 comments on commit a11b76f

Please sign in to comment.