Skip to content

Commit

Permalink
uptime checks: add crud methods
Browse files Browse the repository at this point in the history
Tried to see how far I could get adding support for uptime checks and
alerts in a day, and this is what I could do.

This is still missing:

  * Integration tests,
  * More in-depth tests for the `create` check operation, since I added
    a bit of extra functionality over the default API interaction,
  * The `get state` method for checks. It has a different displayable
    type, so it was too much work for today,
  * All of the uptime alerts, since it's a separate set of endpoints.

Not sure if we want to merge as-is, but I figured it's probably better
than not having the functionality. I'll try to complete it on the next
free Friday.
  • Loading branch information
bentranter committed Jun 30, 2023
1 parent da6bcfa commit e846085
Show file tree
Hide file tree
Showing 10 changed files with 657 additions and 0 deletions.
11 changes: 11 additions & 0 deletions args.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,17 @@ const (
// ArgOutput is an output type argument.
ArgOutput = "output"

// ArgUptimeCheckName is the name of an uptime check.
ArgUptimeCheckName = "name"
// ArgUptimeCheckType is the type of an uptime check.
ArgUptimeCheckType = "type"
// ArgUptimeCheckTarget is the target of an uptime check.
ArgUptimeCheckTarget = "target"
// ArgUptimeCheckRegions are the regions of an uptime check.
ArgUptimeCheckRegions = "regions"
// ArgUptimeCheckEnabled is whether or not an uptime check is enabled.
ArgUptimeCheckEnabled = "enabled"

// ArgVolumeSize is the size of a volume.
ArgVolumeSize = "size"
// ArgVolumeDesc is the description of a volume.
Expand Down
2 changes: 2 additions & 0 deletions commands/command_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type CmdConfig struct {
BillingHistory func() do.BillingHistoryService
Invoices func() do.InvoicesService
Tags func() do.TagsService
UptimeChecks func() do.UptimeChecksService
Volumes func() do.VolumesService
VolumeActions func() do.VolumeActionsService
Snapshots func() do.SnapshotsService
Expand Down Expand Up @@ -105,6 +106,7 @@ func NewCmdConfig(ns string, dc doctl.Config, out io.Writer, args []string, init
c.BillingHistory = func() do.BillingHistoryService { return do.NewBillingHistoryService(godoClient) }
c.Invoices = func() do.InvoicesService { return do.NewInvoicesService(godoClient) }
c.Tags = func() do.TagsService { return do.NewTagsService(godoClient) }
c.UptimeChecks = func() do.UptimeChecksService { return do.NewUptimeChecksService(godoClient) }
c.Volumes = func() do.VolumesService { return do.NewVolumesService(godoClient) }
c.VolumeActions = func() do.VolumeActionsService { return do.NewVolumeActionsService(godoClient) }
c.Snapshots = func() do.SnapshotsService { return do.NewSnapshotsService(godoClient) }
Expand Down
3 changes: 3 additions & 0 deletions commands/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ type tcMocks struct {
reservedIPs *domocks.MockReservedIPsService
reservedIPActions *domocks.MockReservedIPActionsService
domains *domocks.MockDomainsService
uptimeChecks *domocks.MockUptimeChecksService
volumes *domocks.MockVolumesService
volumeActions *domocks.MockVolumeActionsService
tags *domocks.MockTagsService
Expand Down Expand Up @@ -210,6 +211,7 @@ func withTestClient(t *testing.T, tFn testFn) {
dropletActions: domocks.NewMockDropletActionsService(ctrl),
domains: domocks.NewMockDomainsService(ctrl),
tags: domocks.NewMockTagsService(ctrl),
uptimeChecks: domocks.NewMockUptimeChecksService(ctrl),
volumes: domocks.NewMockVolumesService(ctrl),
volumeActions: domocks.NewMockVolumeActionsService(ctrl),
snapshots: domocks.NewMockSnapshotsService(ctrl),
Expand Down Expand Up @@ -268,6 +270,7 @@ func withTestClient(t *testing.T, tFn testFn) {
BillingHistory: func() do.BillingHistoryService { return tm.billingHistory },
Invoices: func() do.InvoicesService { return tm.invoices },
Tags: func() do.TagsService { return tm.tags },
UptimeChecks: func() do.UptimeChecksService { return tm.uptimeChecks },
Volumes: func() do.VolumesService { return tm.volumes },
VolumeActions: func() do.VolumeActionsService { return tm.volumeActions },
Snapshots: func() do.SnapshotsService { return tm.snapshots },
Expand Down
68 changes: 68 additions & 0 deletions commands/displayers/uptime_check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
Copyright 2023 The Doctl Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package displayers

import (
"fmt"
"io"

"github.com/digitalocean/doctl/do"
)

type UptimeCheck struct {
UptimeChecks []do.UptimeCheck
}

var _ Displayable = &UptimeCheck{}

func (uc *UptimeCheck) JSON(out io.Writer) error {
return writeJSON(uc.UptimeChecks, out)

}

func (uc *UptimeCheck) Cols() []string {
return []string{
"ID", "Name", "Type", "Target", "Regions", "Enabled",
}
}

func (uc *UptimeCheck) ColMap() map[string]string {
return map[string]string{
"ID": "ID",
"Name": "Name",
"Type": "Type",
"Target": "Target",
"Regions": "Regions",
"Enabled": "Enabled",
}
}

func (uc *UptimeCheck) KV() []map[string]interface{} {
out := make([]map[string]interface{}, 0, len(uc.UptimeChecks))
for _, uptimeCheck := range uc.UptimeChecks {
m := map[string]interface{}{
"ID": uptimeCheck.ID,
"Name": uptimeCheck.Name,
"Type": uptimeCheck.Type,
"Target": uptimeCheck.Target,
"Enabled": uptimeCheck.Enabled,
}
m["Regions"] = ""
if len(uptimeCheck.Regions) > 0 {
m["Regions"] = fmt.Sprintf("%v", uptimeCheck.Regions)
}
out = append(out, m)
}
return out
}
1 change: 1 addition & 0 deletions commands/doit.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ func addCommands() {
DoitCmd.AddCommand(OneClicks())
DoitCmd.AddCommand(Monitoring())
DoitCmd.AddCommand(Serverless())
DoitCmd.AddCommand(UptimeCheck())
}

func computeCmd() *Command {
Expand Down
205 changes: 205 additions & 0 deletions commands/uptime_checks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
/*
Copyright 2023 The Doctl Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package commands

import (
"fmt"
"net/url"

"github.com/digitalocean/doctl"
"github.com/digitalocean/doctl/commands/displayers"
"github.com/digitalocean/doctl/do"
"github.com/digitalocean/godo"
"github.com/spf13/cobra"
)

// UptimeCheck creates the UptimeCheck command
func UptimeCheck() *Command {
cmd := &Command{
Command: &cobra.Command{
Use: "uptime",
Short: "Display commands to manage uptime checks",
Long: `The sub-commands of ` + "`" + `doctl uptime` + "`" + ` manage your uptime checks.
DigitalOcean Uptime Checks provide the ability to monitor your endpoints from around the world,
and alert you when they're slow, unavailable, or SSL certificates are expiring.`,
},
}

cmdUptimeChecksCreate := CmdBuilder(cmd, RunUptimeChecksCreate, "create <uptime-check-name>", "Create an uptime check", `Use this command to create an uptime check on your account.
You can use flags to specify the uptime check, target, type, regions, and whether or not the check is enabled.`, Writer,
aliasOpt("c"), displayerType(&displayers.UptimeCheck{}))
AddStringFlag(cmdUptimeChecksCreate, doctl.ArgUptimeCheckTarget, "", "", "Uptime check target, must be a valid URL", requiredOpt())
AddStringFlag(cmdUptimeChecksCreate, doctl.ArgUptimeCheckType, "", "", "Uptime check type, must be one of ping, http, or https. Defaults to either http or https, depending on the URL target provided")
AddStringSliceFlag(cmdUptimeChecksCreate, doctl.ArgUptimeCheckRegions, "", []string{"us_east"}, "Uptime check regions, must be a comma-separated list from any of us_east, us_west, eu_west, or se_asia. Defaults to [us_east]")
AddBoolFlag(cmdUptimeChecksCreate, doctl.ArgUptimeCheckEnabled, "", true, "Whether or not the uptime check is enabled, defaults to true")

CmdBuilder(cmd, RunUptimeChecksGet, "get <uptime-check-id>", "Get an uptime check", `Use this command to get an uptime check on your account by ID.`, Writer,
aliasOpt("g"), displayerType(&displayers.UptimeCheck{}))

CmdBuilder(cmd, RunUptimeChecksList, "list", "List uptime checks", `Use this command to list all of the uptime checks on your account.`, Writer,
aliasOpt("ls"), displayerType(&displayers.UptimeCheck{}))

cmdUptimeCheckUpdate := CmdBuilder(cmd, RunUptimeChecksUpdate, "update <uptime-check-id>", "Update an uptime check", `Use this command to update an uptime check on your account.
You can use flags to specify an updated uptime check name, target, type, and regions. All of these flags are required. An uptime check cannot be disabled via doctl, you must do so through the Cloud control panel or through the public API directly`, Writer,
aliasOpt("u"), displayerType(&displayers.UptimeCheck{}))
AddStringFlag(cmdUptimeCheckUpdate, doctl.ArgUptimeCheckName, "", "", "Uptime check name", requiredOpt())
AddStringFlag(cmdUptimeCheckUpdate, doctl.ArgUptimeCheckTarget, "", "", "Uptime check target, must be a valid URL", requiredOpt())
AddStringFlag(cmdUptimeCheckUpdate, doctl.ArgUptimeCheckType, "", "", "Uptime check type, must be one of ping, http, or https", requiredOpt())
AddStringSliceFlag(cmdUptimeCheckUpdate, doctl.ArgUptimeCheckRegions, "", []string{"us_east"}, "Uptime check regions, must be a comma-separated list from any of us_east, us_west, eu_west, or se_asia", requiredOpt())

CmdBuilder(cmd, RunUptimeChecksDelete, "delete <uptime-check-id>", "Delete an uptime check", `Use this command to delete an uptime check on your account by ID.`, Writer,
aliasOpt("d", "del", "rm"))

return cmd
}

// RunUptimeChecksCreate creates an uptime check.
func RunUptimeChecksCreate(c *CmdConfig) error {
if len(c.Args) == 0 {
return doctl.NewMissingArgsErr(c.NS)
}

checkName := c.Args[0]

checkTarget, err := c.Doit.GetString(c.NS, doctl.ArgUptimeCheckTarget)
if err != nil {
return err
}
checkURL, err := url.Parse(checkTarget)
if err != nil {
return fmt.Errorf("the uptime check target %s is not a valid URL: %w", checkTarget, err)
}

checkType := checkURL.Scheme
checkTypeArg, err := c.Doit.GetString(c.NS, doctl.ArgUptimeCheckType)
if err != nil {
return err
}
if checkTypeArg != "" {
checkType = checkTypeArg
}
if checkType != "ping" && checkType != "http" && checkType != "https" {
return fmt.Errorf("the uptime check type must be one of ping, http, or https, got %s", checkType)
}

checkRegions := []string{"us_east"}
checkRegionsArg, err := c.Doit.GetStringSlice(c.NS, doctl.ArgUptimeCheckRegions)
if err != nil {
return err
}
if len(checkRegionsArg) > 0 {
checkRegions = checkRegionsArg
}

checkEnabled, err := c.Doit.GetBool(c.NS, doctl.ArgUptimeCheckEnabled)
if err != nil {
return err
}

uptimeCheck, err := c.UptimeChecks().Create(&godo.CreateUptimeCheckRequest{
Name: checkName,
Type: checkType,
Target: checkTarget,
Regions: checkRegions,
Enabled: checkEnabled,
})
if err != nil {
return err
}

item := &displayers.UptimeCheck{UptimeChecks: []do.UptimeCheck{*uptimeCheck}}
return c.Display(item)
}

// RunUptimeChecksGet gets an uptime check by ID.
func RunUptimeChecksGet(c *CmdConfig) error {
if len(c.Args) == 0 {
return doctl.NewMissingArgsErr(c.NS)
}

uptimeCheck, err := c.UptimeChecks().Get(c.Args[0])
if err != nil {
return nil
}

item := &displayers.UptimeCheck{UptimeChecks: []do.UptimeCheck{*uptimeCheck}}
return c.Display(item)
}

// RunUptimeChecksList returns a list of uptime checks.
func RunUptimeChecksList(c *CmdConfig) error {
uptimeChecks, err := c.UptimeChecks().List()
if err != nil {
return err
}
item := &displayers.UptimeCheck{UptimeChecks: uptimeChecks}
return c.Display(item)
}

// RunUptimeChecksUpdate updates an uptime check by ID.
func RunUptimeChecksUpdate(c *CmdConfig) error {
if len(c.Args) == 0 {
return doctl.NewMissingArgsErr(c.NS)
}

checkID := c.Args[0]

checkName, err := c.Doit.GetString(c.NS, doctl.ArgUptimeCheckName)
if err != nil {
return err
}

checkTarget, err := c.Doit.GetString(c.NS, doctl.ArgUptimeCheckTarget)
if err != nil {
return err
}

checkType, err := c.Doit.GetString(c.NS, doctl.ArgUptimeCheckType)
if err != nil {
return err
}
if checkType != "ping" && checkType != "http" && checkType != "https" {
return fmt.Errorf("the uptime check type must be one of ping, http, or https, got %s", checkType)
}

checkRegions, err := c.Doit.GetStringSlice(c.NS, doctl.ArgUptimeCheckRegions)
if err != nil {
return err
}

uptimeCheck, err := c.UptimeChecks().Update(checkID, &godo.UpdateUptimeCheckRequest{
Name: checkName,
Type: checkType,
Target: checkTarget,
Regions: checkRegions,
})
if err != nil {
return err
}

item := &displayers.UptimeCheck{UptimeChecks: []do.UptimeCheck{*uptimeCheck}}
return c.Display(item)
}

// RunUptimeChecksDelete deletes an uptime check by ID.
func RunUptimeChecksDelete(c *CmdConfig) error {
if len(c.Args) == 0 {
return doctl.NewMissingArgsErr(c.NS)
}

return c.UptimeChecks().Delete(c.Args[0])
}
Loading

0 comments on commit e846085

Please sign in to comment.