1/* 2Copyright 2018 The Doctl Authors All rights reserved. 3Licensed under the Apache License, Version 2.0 (the "License"); 4you may not use this file except in compliance with the License. 5You may obtain a copy of the License at 6 http://www.apache.org/licenses/LICENSE-2.0 7Unless required by applicable law or agreed to in writing, software 8distributed under the License is distributed on an "AS IS" BASIS, 9WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10See the License for the specific language governing permissions and 11limitations under the License. 12*/ 13 14package commands 15 16import ( 17 "errors" 18 "fmt" 19 "strings" 20 21 "github.com/digitalocean/doctl" 22 "github.com/digitalocean/doctl/commands/displayers" 23 "github.com/digitalocean/doctl/do" 24 "github.com/digitalocean/godo" 25 "github.com/spf13/cobra" 26) 27 28// Monitoring creates the monitoring commands hierarchy. 29func Monitoring() *Command { 30 cmd := &Command{ 31 Command: &cobra.Command{ 32 Use: "monitoring", 33 Short: "[Beta] Display commands to manage monitoring", 34 Long: `The sub-commands of ` + "`" + `doctl monitoring` + "`" + ` manage the monitoring on your account. 35 36An alert policy can be applied to resource(s) (currently Droplets) 37in order to alert on resource consumption.`, 38 }, 39 } 40 41 cmd.AddCommand(alertPolicies()) 42 return cmd 43} 44 45func alertPolicies() *Command { 46 cmd := &Command{ 47 Command: &cobra.Command{ 48 Use: "alert", 49 Aliases: []string{"alerts", "a"}, 50 Short: "Display commands for managing alert policies", 51 Long: "The commands under `doctl monitoring alert` are for the management of alert policies.", 52 }, 53 } 54 55 cmdAlertPolicyCreate := CmdBuilder(cmd, RunCmdAlertPolicyCreate, "create", "Create an alert policy", `Use this command to create a new alert policy.`, Writer) 56 AddStringFlag(cmdAlertPolicyCreate, doctl.ArgAlertPolicyDescription, "", "", "A description of the alert policy.") 57 AddStringFlag(cmdAlertPolicyCreate, doctl.ArgAlertPolicyType, "", "", "The type of alert policy.") 58 AddStringFlag(cmdAlertPolicyCreate, doctl.ArgAlertPolicyCompare, "", "", "The comparator of the alert policy. Either `GreaterThan` or `LessThan`") 59 AddStringFlag(cmdAlertPolicyCreate, doctl.ArgAlertPolicyWindow, "", "5m", "The window to apply the alert policy conditions against.") 60 AddIntFlag(cmdAlertPolicyCreate, doctl.ArgAlertPolicyValue, "", 0, "The value of the alert policy to compare against.") 61 AddBoolFlag(cmdAlertPolicyCreate, doctl.ArgAlertPolicyEnabled, "", true, "Whether the alert policy is enabled.") 62 AddStringSliceFlag(cmdAlertPolicyCreate, doctl.ArgAlertPolicyEmails, "", nil, "Emails to send alerts to.") 63 AddStringSliceFlag(cmdAlertPolicyCreate, doctl.ArgAlertPolicyTags, "", nil, "Tags to apply the alert against.") 64 AddStringSliceFlag(cmdAlertPolicyCreate, doctl.ArgAlertPolicyEntities, "", nil, "Entities to apply the alert against. (e.g. a droplet ID for a droplet alert policy)") 65 AddStringSliceFlag(cmdAlertPolicyCreate, doctl.ArgAlertPolicySlackChannels, "", nil, "Slack channels to send alerts to.") 66 AddStringSliceFlag(cmdAlertPolicyCreate, doctl.ArgAlertPolicySlackURLs, "", nil, "Slack URLs to send alerts to.") 67 68 cmdAlertPolicyUpdate := CmdBuilder(cmd, RunCmdAlertPolicyUpdate, "update <alert-policy-uuid>...", "Update an alert policy", `Use this command to update an existing alert policy.`, Writer) 69 AddStringFlag(cmdAlertPolicyUpdate, doctl.ArgAlertPolicyDescription, "", "", "A description of the alert policy.") 70 AddStringFlag(cmdAlertPolicyUpdate, doctl.ArgAlertPolicyType, "", "", "The type of alert policy.") 71 AddStringFlag(cmdAlertPolicyUpdate, doctl.ArgAlertPolicyCompare, "", "", "The comparator of the alert policy. Either `GreaterThan` or `LessThan`") 72 AddStringFlag(cmdAlertPolicyUpdate, doctl.ArgAlertPolicyWindow, "", "5m", "The window to apply the alert policy conditions against.") 73 AddIntFlag(cmdAlertPolicyUpdate, doctl.ArgAlertPolicyValue, "", 0, "The value of the alert policy to compare against.") 74 AddBoolFlag(cmdAlertPolicyUpdate, doctl.ArgAlertPolicyEnabled, "", true, "Whether the alert policy is enabled.") 75 AddStringSliceFlag(cmdAlertPolicyUpdate, doctl.ArgAlertPolicyEmails, "", nil, "Emails to send alerts to.") 76 AddStringSliceFlag(cmdAlertPolicyUpdate, doctl.ArgAlertPolicyTags, "", nil, "Tags to apply the alert against.") 77 AddStringSliceFlag(cmdAlertPolicyUpdate, doctl.ArgAlertPolicyEntities, "", nil, "Entities to apply the alert against. (e.g. a droplet ID for a droplet alert policy)") 78 AddStringSliceFlag(cmdAlertPolicyUpdate, doctl.ArgAlertPolicySlackChannels, "", nil, "Slack channels to send alerts to.") 79 AddStringSliceFlag(cmdAlertPolicyUpdate, doctl.ArgAlertPolicySlackURLs, "", nil, "Slack URLs to send alerts to.") 80 81 CmdBuilder(cmd, RunCmdAlertPolicyGet, "get <alert-policy-uuid>", "Retrieve information about an alert policy", `Use this command to retrieve an alert policy and see its configuration.`, Writer, 82 displayerType(&displayers.AlertPolicy{})) 83 84 CmdBuilder(cmd, RunCmdAlertPolicyList, "list", "List all alert policies", `Use this command to retrieve a list of all the alert policies in your account.`, Writer, 85 aliasOpt("ls"), displayerType(&displayers.AlertPolicy{})) 86 87 cmdRunAlertPolicyDelete := CmdBuilder(cmd, RunCmdAlertPolicyDelete, "delete <alert-policy-uuid>...", "Delete an alert policy", `Use this command to delete an alert policy.`, Writer) 88 AddBoolFlag(cmdRunAlertPolicyDelete, doctl.ArgForce, doctl.ArgShortForce, false, "Delete an alert policy without confirmation prompt") 89 90 return cmd 91} 92 93// RunCmdAlertPolicyCreate runs alert policy create. 94func RunCmdAlertPolicyCreate(c *CmdConfig) error { 95 ms := c.Monitoring() 96 97 desc, err := c.Doit.GetString(c.NS, doctl.ArgAlertPolicyDescription) 98 if err != nil { 99 return err 100 } 101 102 alertType, err := c.Doit.GetString(c.NS, doctl.ArgAlertPolicyType) 103 if err != nil { 104 return err 105 } 106 err = validateAlertPolicyType(alertType) 107 if err != nil { 108 return err 109 } 110 111 value, err := c.Doit.GetInt(c.NS, doctl.ArgAlertPolicyValue) 112 if err != nil { 113 return err 114 } 115 116 window, err := c.Doit.GetString(c.NS, doctl.ArgAlertPolicyWindow) 117 if err != nil { 118 return err 119 } 120 err = validateAlertPolicyWindow(window) 121 if err != nil { 122 return err 123 } 124 125 entities, err := c.Doit.GetStringSlice(c.NS, doctl.ArgAlertPolicyEntities) 126 if err != nil { 127 return err 128 } 129 130 tags, err := c.Doit.GetStringSlice(c.NS, doctl.ArgAlertPolicyTags) 131 if err != nil { 132 return err 133 } 134 135 enabled, err := c.Doit.GetBool(c.NS, doctl.ArgAlertPolicyEnabled) 136 if err != nil { 137 return err 138 } 139 140 compareStr, err := c.Doit.GetString(c.NS, doctl.ArgAlertPolicyCompare) 141 if err != nil { 142 return err 143 } 144 145 compare, err := getComparator(compareStr) 146 if err != nil { 147 return err 148 } 149 150 emails, err := c.Doit.GetStringSlice(c.NS, doctl.ArgAlertPolicyEmails) 151 if err != nil { 152 return err 153 } 154 155 slackChannels, err := c.Doit.GetStringSlice(c.NS, doctl.ArgAlertPolicySlackChannels) 156 if err != nil { 157 return err 158 } 159 160 slackURLs, err := c.Doit.GetStringSlice(c.NS, doctl.ArgAlertPolicySlackURLs) 161 if err != nil { 162 return err 163 } 164 165 if len(slackURLs) != len(slackChannels) { 166 return errors.New("must provide the same number of slack channels as slack URLs") 167 } 168 169 if len(emails) == 0 && len(slackURLs) == 0 { 170 return errors.New("must provide either emails or slack details to send the alert to") 171 } 172 173 slacks := make([]godo.SlackDetails, len(slackChannels)) 174 for i, channel := range slackChannels { 175 slacks[i] = godo.SlackDetails{Channel: channel, URL: slackURLs[i]} 176 } 177 178 apcr := &godo.AlertPolicyCreateRequest{ 179 Type: alertType, 180 Description: desc, 181 Compare: compare, 182 Value: float32(value), 183 Window: window, 184 Entities: entities, 185 Tags: tags, 186 Alerts: godo.Alerts{ 187 Slack: slacks, 188 Email: emails, 189 }, 190 Enabled: &enabled, 191 } 192 p, err := ms.CreateAlertPolicy(apcr) 193 if err != nil { 194 return err 195 } 196 197 return c.Display(&displayers.AlertPolicy{AlertPolicies: do.AlertPolicies{*p}}) 198} 199 200// RunCmdAlertPolicyUpdate runs alert policy update. 201func RunCmdAlertPolicyUpdate(c *CmdConfig) error { 202 err := ensureOneArg(c) 203 if err != nil { 204 return err 205 } 206 207 uuid := c.Args[0] 208 209 ms := c.Monitoring() 210 211 desc, err := c.Doit.GetString(c.NS, doctl.ArgAlertPolicyDescription) 212 if err != nil { 213 return err 214 } 215 216 alertType, err := c.Doit.GetString(c.NS, doctl.ArgAlertPolicyType) 217 if err != nil { 218 return err 219 } 220 err = validateAlertPolicyType(alertType) 221 if err != nil { 222 return err 223 } 224 225 value, err := c.Doit.GetInt(c.NS, doctl.ArgAlertPolicyValue) 226 if err != nil { 227 return err 228 } 229 230 window, err := c.Doit.GetString(c.NS, doctl.ArgAlertPolicyWindow) 231 if err != nil { 232 return err 233 } 234 err = validateAlertPolicyWindow(window) 235 if err != nil { 236 return err 237 } 238 239 entities, err := c.Doit.GetStringSlice(c.NS, doctl.ArgAlertPolicyEntities) 240 if err != nil { 241 return err 242 } 243 244 tags, err := c.Doit.GetStringSlice(c.NS, doctl.ArgAlertPolicyTags) 245 if err != nil { 246 return err 247 } 248 249 enabled, err := c.Doit.GetBool(c.NS, doctl.ArgAlertPolicyEnabled) 250 if err != nil { 251 return err 252 } 253 254 compareStr, err := c.Doit.GetString(c.NS, doctl.ArgAlertPolicyCompare) 255 if err != nil { 256 return err 257 } 258 259 compare, err := getComparator(compareStr) 260 if err != nil { 261 return err 262 } 263 264 emails, err := c.Doit.GetStringSlice(c.NS, doctl.ArgAlertPolicyEmails) 265 if err != nil { 266 return err 267 } 268 269 slackChannels, err := c.Doit.GetStringSlice(c.NS, doctl.ArgAlertPolicySlackChannels) 270 if err != nil { 271 return err 272 } 273 274 slackURLs, err := c.Doit.GetStringSlice(c.NS, doctl.ArgAlertPolicySlackURLs) 275 if err != nil { 276 return err 277 } 278 279 if len(slackURLs) != len(slackChannels) { 280 return errors.New("must provide the same number of slack channels as slack URLs") 281 } 282 283 if len(emails) == 0 && len(slackURLs) == 0 { 284 return errors.New("must provide either emails or slack details to send the alert to") 285 } 286 287 slacks := make([]godo.SlackDetails, len(slackChannels)) 288 for i, channel := range slackChannels { 289 slacks[i] = godo.SlackDetails{Channel: channel, URL: slackURLs[i]} 290 } 291 292 apcr := &godo.AlertPolicyUpdateRequest{ 293 Type: alertType, 294 Description: desc, 295 Compare: compare, 296 Value: float32(value), 297 Window: window, 298 Entities: entities, 299 Tags: tags, 300 Alerts: godo.Alerts{ 301 Slack: slacks, 302 Email: emails, 303 }, 304 Enabled: &enabled, 305 } 306 p, err := ms.UpdateAlertPolicy(uuid, apcr) 307 if err != nil { 308 return err 309 } 310 311 return c.Display(&displayers.AlertPolicy{AlertPolicies: do.AlertPolicies{*p}}) 312} 313 314func getComparator(compareStr string) (godo.AlertPolicyComp, error) { 315 var compare godo.AlertPolicyComp 316 if strings.EqualFold("LessThan", compareStr) { 317 compare = godo.LessThan 318 } else if strings.EqualFold("GreaterThan", compareStr) { 319 compare = godo.GreaterThan 320 } else { 321 return "", errors.New("comparator must be GreaterThan or LessThan") 322 } 323 return compare, nil 324} 325 326func validateAlertPolicyType(t string) error { 327 switch t { 328 case godo.DropletCPUUtilizationPercent: 329 fallthrough 330 case godo.DropletMemoryUtilizationPercent: 331 fallthrough 332 case godo.DropletDiskUtilizationPercent: 333 fallthrough 334 case godo.DropletDiskReadRate: 335 fallthrough 336 case godo.DropletDiskWriteRate: 337 fallthrough 338 case godo.DropletOneMinuteLoadAverage: 339 fallthrough 340 case godo.DropletFiveMinuteLoadAverage: 341 fallthrough 342 case godo.DropletFifteenMinuteLoadAverage: 343 fallthrough 344 case godo.DropletPublicOutboundBandwidthRate: 345 return nil 346 default: 347 return errors.New(fmt.Sprintf("'%s' is not a valid alert policy type", t)) 348 } 349} 350 351func validateAlertPolicyWindow(w string) error { 352 switch w { 353 case "5m": 354 fallthrough 355 case "10m": 356 fallthrough 357 case "30m": 358 fallthrough 359 case "1h": 360 return nil 361 default: 362 return errors.New(fmt.Sprintf("'%s' is not a valid alert policy window. Must be one of '5m', '10m', '30m', or '1h'", w)) 363 } 364} 365 366// RunCmdAlertPolicyGet runs alert policy get. 367func RunCmdAlertPolicyGet(c *CmdConfig) error { 368 err := ensureOneArg(c) 369 if err != nil { 370 return err 371 } 372 373 uuid := c.Args[0] 374 ms := c.Monitoring() 375 p, err := ms.GetAlertPolicy(uuid) 376 if err != nil { 377 return err 378 } 379 380 return c.Display(&displayers.AlertPolicy{AlertPolicies: do.AlertPolicies{*p}}) 381} 382 383// RunCmdAlertPolicyList runs alert policy list. 384func RunCmdAlertPolicyList(c *CmdConfig) error { 385 ms := c.Monitoring() 386 policies, err := ms.ListAlertPolicies() 387 if err != nil { 388 return err 389 } 390 391 return c.Display(&displayers.AlertPolicy{AlertPolicies: policies}) 392} 393 394// RunCmdAlertPolicyDelete runs alert policy delete. 395func RunCmdAlertPolicyDelete(c *CmdConfig) error { 396 if len(c.Args) < 1 { 397 return doctl.NewMissingArgsErr(c.NS) 398 } 399 400 force, err := c.Doit.GetBool(c.NS, doctl.ArgForce) 401 if err != nil { 402 return err 403 } 404 405 if force || AskForConfirmDelete("alert policy", len(c.Args)) == nil { 406 for id := range c.Args { 407 uuid := c.Args[id] 408 ms := c.Monitoring() 409 if err := ms.DeleteAlertPolicy(uuid); err != nil { 410 return err 411 } 412 } 413 } else { 414 return errOperationAborted 415 } 416 417 return nil 418} 419