Skip to content

Commit

Permalink
update godo, regenerate mocks, add list app alerts and update app ale…
Browse files Browse the repository at this point in the history
…rt destinations, and tests (#1016)
  • Loading branch information
notxarb committed Aug 11, 2021
1 parent ed070b4 commit 84fb230
Show file tree
Hide file tree
Showing 43 changed files with 612 additions and 222 deletions.
2 changes: 2 additions & 0 deletions args.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ const (
ArgAppLogTail = "tail"
// ArgAppForceRebuild forces a deployment rebuild
ArgAppForceRebuild = "force-rebuild"
// ArgAppAlertDestinations is a path to an app alert destination file.
ArgAppAlertDestinations = "app-alert-destinations"
// ArgClusterName is a cluster name argument.
ArgClusterName = "cluster-name"
// ArgClusterVersionSlug is a cluster version argument.
Expand Down
108 changes: 108 additions & 0 deletions commands/apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,29 @@ Only basic information is included with the text output format. For complete app
AddStringFlag(propose, doctl.ArgAppSpec, "", "", "Path to an app spec in JSON or YAML format. For more information about app specs, see https://www.digitalocean.com/docs/app-platform/concepts/app-spec", requiredOpt())
AddStringFlag(propose, doctl.ArgApp, "", "", "An optional existing app ID. If specified, the app spec will be treated as a proposed update to the existing app.")

CmdBuilder(
cmd,
RunAppListAlerts,
"list-alerts <app id>",
"List alerts on an app",
`List all alerts associated to an app and its components`,
Writer,
aliasOpt("la"),
displayerType(&displayers.AppAlerts{}),
)

updateAlertDestinations := CmdBuilder(
cmd,
RunAppUpdateAlertDestinations,
"update-alert-destinations <app id> <alert id>",
"Update alert destinations",
`Update alert destinations`,
Writer,
aliasOpt("uad"),
displayerType(&displayers.AppAlerts{}),
)
AddStringFlag(updateAlertDestinations, doctl.ArgAppAlertDestinations, "", "", "Path to an alert destinations file in JSON or YAML format.")

cmd.AddCommand(appsSpec())
cmd.AddCommand(appsTier())

Expand Down Expand Up @@ -837,3 +860,88 @@ func RunAppsTierInstanceSizeGet(c *CmdConfig) error {

return c.Display(displayers.AppInstanceSizes([]*godo.AppInstanceSize{instanceSize}))
}

// RunAppListAlerts gets configured alerts on an app
func RunAppListAlerts(c *CmdConfig) error {
if len(c.Args) < 1 {
return doctl.NewMissingArgsErr(c.NS)
}

appID := c.Args[0]

alerts, err := c.Apps().ListAlerts(appID)
if err != nil {
return err
}
return c.Display(displayers.AppAlerts(alerts))
}

func RunAppUpdateAlertDestinations(c *CmdConfig) error {
if len(c.Args) < 2 {
return doctl.NewMissingArgsErr(c.NS)
}

appID := c.Args[0]
alertID := c.Args[1]

alertDestinationsPath, err := c.Doit.GetString(c.NS, doctl.ArgAppAlertDestinations)
if err != nil {
return err
}
update, err := readAppAlertDestination(os.Stdin, alertDestinationsPath)
if err != nil {
return err
}

alert, err := c.Apps().UpdateAlertDestinations(appID, alertID, update)
if err != nil {
return err
}
return c.Display(displayers.AppAlerts([]*godo.AppAlert{alert}))
}

func readAppAlertDestination(stdin io.Reader, path string) (*godo.AlertDestinationUpdateRequest, error) {
var alertDestinations io.Reader
if path == "-" {
alertDestinations = stdin
} else {
alertDestinationsFile, err := os.Open(path) // guardrails-disable-line
if err != nil {
if os.IsNotExist(err) {
return nil, fmt.Errorf("opening app alert destinations: %s does not exist", path)
}
return nil, fmt.Errorf("opening app alert destinations: %w", err)
}
defer alertDestinationsFile.Close()
alertDestinations = alertDestinationsFile
}

byt, err := ioutil.ReadAll(alertDestinations)
if err != nil {
return nil, fmt.Errorf("reading app alert destinations: %w", err)
}

s, err := parseAppAlert(byt)
if err != nil {
return nil, fmt.Errorf("parsing app alert destinations: %w", err)
}

return s, nil
}

func parseAppAlert(destinations []byte) (*godo.AlertDestinationUpdateRequest, error) {
jsonAlertDestinations, err := yaml.YAMLToJSON(destinations)
if err != nil {
return nil, err
}

dec := json.NewDecoder(bytes.NewReader(jsonAlertDestinations))
dec.DisallowUnknownFields()

var alertDestinations godo.AlertDestinationUpdateRequest
if err := dec.Decode(&alertDestinations); err != nil {
return nil, err
}

return &alertDestinations, nil
}
74 changes: 74 additions & 0 deletions commands/apps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ func TestAppsCommand(t *testing.T) {
"propose",
"spec",
"tier",
"list-alerts",
"update-alert-destinations",
)
}

Expand Down Expand Up @@ -71,6 +73,46 @@ var (
TierUpgradeTo: "professional-xs",
TierDowngradeTo: "basic-xxxs",
}

testAlerts = []*godo.AppAlert{
{
ID: "c586fc0d-e8e2-4c50-9bf6-6c0a6b2ed2a7",
Spec: &godo.AppAlertSpec{
Rule: godo.AppAlertSpecRule_DeploymentFailed,
},
Emails: []string{"test@example.com", "test2@example.com"},
SlackWebhooks: []*godo.AppAlertSlackWebhook{
{
URL: "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX",
Channel: "channel name",
},
},
},
}

testAlert = godo.AppAlert{
ID: "c586fc0d-e8e2-4c50-9bf6-6c0a6b2ed2a7",
Spec: &godo.AppAlertSpec{
Rule: godo.AppAlertSpecRule_DeploymentFailed,
},
Emails: []string{"test@example.com", "test2@example.com"},
SlackWebhooks: []*godo.AppAlertSlackWebhook{
{
URL: "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX",
Channel: "channel name",
},
},
}

testAlertUpdate = godo.AlertDestinationUpdateRequest{
Emails: []string{"test@example.com", "test2@example.com"},
SlackWebhooks: []*godo.AppAlertSlackWebhook{
{
URL: "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX",
Channel: "channel name",
},
},
}
)

func TestRunAppsCreate(t *testing.T) {
Expand Down Expand Up @@ -762,3 +804,35 @@ func TestRunAppsTierInstanceSizeGet(t *testing.T) {
require.NoError(t, err)
})
}

func TestRunAppsListAlerts(t *testing.T) {
withTestClient(t, func(config *CmdConfig, tm *tcMocks) {
appID := uuid.New().String()
tm.apps.EXPECT().ListAlerts(appID).Times(1).Return(testAlerts, nil)

config.Args = append(config.Args, appID)
err := RunAppListAlerts(config)
require.NoError(t, err)
})
}

func TestRunAppsUpdateAlertDestinations(t *testing.T) {
withTestClient(t, func(config *CmdConfig, tm *tcMocks) {
destinationsFile, err := ioutil.TempFile("", "dest")
require.NoError(t, err)
defer func() {
os.Remove(destinationsFile.Name())
destinationsFile.Close()
}()

err = json.NewEncoder(destinationsFile).Encode(&testAlertUpdate)
require.NoError(t, err)
appID := uuid.New().String()
tm.apps.EXPECT().UpdateAlertDestinations(appID, testAlert.ID, &testAlertUpdate).Times(1).Return(&testAlert, nil)

config.Args = append(config.Args, appID, testAlert.ID)
config.Doit.Set(config.NS, doctl.ArgAppAlertDestinations, destinationsFile.Name())
err = RunAppUpdateAlertDestinations(config)
require.NoError(t, err)
})
}
84 changes: 84 additions & 0 deletions commands/displayers/apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,3 +369,87 @@ func (r AppProposeResponse) JSON(w io.Writer) error {
e.SetIndent("", " ")
return e.Encode(r.Res)
}

type AppAlerts []*godo.AppAlert

var _ Displayable = (*AppAlerts)(nil)

func (a AppAlerts) Cols() []string {
return []string{
"ID",
"Spec.Rule",
"Trigger",
"ComponentName",
"Emails",
"SlackWebhooks",
"Spec.Disabled",
}
}

func (a AppAlerts) ColMap() map[string]string {
return map[string]string{
"ID": "ID",
"Spec.Rule": "Alert Rule",
"Trigger": "Alert Trigger",
"ComponentName": "Component Name",
"Emails": "Number Of Emails",
"SlackWebhooks": "Number Of Slack Webhooks",
"Spec.Disabled": "Alert Disabled?",
}
}

func (a AppAlerts) KV() []map[string]interface{} {
out := make([]map[string]interface{}, len(a))

for i, alert := range a {
var trigger string
switch alert.Spec.Rule {
case godo.AppAlertSpecRule_UnspecifiedRule:
trigger = "Unknown"
case godo.AppAlertSpecRule_CPUUtilization, godo.AppAlertSpecRule_MemUtilization, godo.AppAlertSpecRule_RestartCount:
var operator, window string
switch alert.Spec.Operator {
case godo.AppAlertSpecOperator_GreaterThan:
operator = ">"
case godo.AppAlertSpecOperator_LessThan:
operator = "<"
default:
operator = "Unknown"
}
switch alert.Spec.Window {
case godo.AppAlertSpecWindow_FiveMinutes:
window = "5m"
case godo.AppAlertSpecWindow_TenMinutes:
window = "10m"
case godo.AppAlertSpecWindow_ThirtyMinutes:
window = "30M"
case godo.AppAlertSpecWindow_OneHour:
window = "1h"
default:
window = "Unknown"
}
trigger = fmt.Sprintf("%s %.2f for %s", operator, alert.Spec.Value, window)
case godo.AppAlertSpecRule_DeploymentFailed, godo.AppAlertSpecRule_DeploymentLive, godo.AppAlertSpecRule_DomainFailed, godo.AppAlertSpecRule_DomainLive:
trigger = "Event"
default:
trigger = "Unknown"
}

out[i] = map[string]interface{}{
"ID": alert.ID,
"Spec.Rule": alert.Spec.Rule,
"Trigger": trigger,
"ComponentName": alert.ComponentName,
"Emails": len(alert.Emails),
"SlackWebhooks": len(alert.SlackWebhooks),
"Spec.Disabled": alert.Spec.Disabled,
}
}
return out
}

func (a AppAlerts) JSON(w io.Writer) error {
e := json.NewEncoder(w)
e.SetIndent("", " ")
return e.Encode(a)
}
19 changes: 19 additions & 0 deletions do/apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ type AppsService interface {

ListInstanceSizes() ([]*godo.AppInstanceSize, error)
GetInstanceSize(slug string) (*godo.AppInstanceSize, error)

ListAlerts(appID string) ([]*godo.AppAlert, error)
UpdateAlertDestinations(appID, alertID string, update *godo.AlertDestinationUpdateRequest) (*godo.AppAlert, error)
}

type appsService struct {
Expand Down Expand Up @@ -218,3 +221,19 @@ func (s *appsService) GetInstanceSize(slug string) (*godo.AppInstanceSize, error
}
return instanceSize, nil
}

func (s *appsService) ListAlerts(appID string) ([]*godo.AppAlert, error) {
alerts, _, err := s.client.Apps.ListAlerts(s.ctx, appID)
if err != nil {
return nil, err
}
return alerts, nil
}

func (s *appsService) UpdateAlertDestinations(appID, alertID string, update *godo.AlertDestinationUpdateRequest) (*godo.AppAlert, error) {
alert, _, err := s.client.Apps.UpdateAlertDestinations(s.ctx, appID, alertID, update)
if err != nil {
return nil, err
}
return alert, nil
}
30 changes: 30 additions & 0 deletions do/mocks/AppsService.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 84fb230

Please sign in to comment.