provider/datadog: Vendor go-datadog-api
This commit is contained in:
parent
eb2407fccf
commit
237e257f37
|
@ -396,6 +396,10 @@
|
||||||
"ImportPath": "github.com/bgentry/speakeasy",
|
"ImportPath": "github.com/bgentry/speakeasy",
|
||||||
"Rev": "36e9cfdd690967f4f690c6edcc9ffacd006014a0"
|
"Rev": "36e9cfdd690967f4f690c6edcc9ffacd006014a0"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/cenkalti/backoff",
|
||||||
|
"Rev": "4dc77674aceaabba2c7e3da25d4c823edfb73f99"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/coreos/etcd/Godeps/_workspace/src/github.com/ugorji/go/codec",
|
"ImportPath": "github.com/coreos/etcd/Godeps/_workspace/src/github.com/ugorji/go/codec",
|
||||||
"Comment": "v2.3.0-alpha.0-652-ge552791",
|
"Comment": "v2.3.0-alpha.0-652-ge552791",
|
||||||
|
@ -717,6 +721,10 @@
|
||||||
"Comment": "v1.5.4-13-g75ce5fb",
|
"Comment": "v1.5.4-13-g75ce5fb",
|
||||||
"Rev": "75ce5fbba34b1912a3641adbd58cf317d7315821"
|
"Rev": "75ce5fbba34b1912a3641adbd58cf317d7315821"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/zorkian/go-datadog-api",
|
||||||
|
"Rev": "632146c79714fe4232b496087802f922c1daf96f"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "golang.org/x/crypto/curve25519",
|
"ImportPath": "golang.org/x/crypto/curve25519",
|
||||||
"Rev": "1f22c0103821b9390939b6776727195525381532"
|
"Rev": "1f22c0103821b9390939b6776727195525381532"
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
|
@ -0,0 +1,2 @@
|
||||||
|
language: go
|
||||||
|
go: 1.3.3
|
|
@ -0,0 +1,20 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Cenk Altı
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,116 @@
|
||||||
|
# Exponential Backoff [![GoDoc][godoc image]][godoc] [![Build Status][travis image]][travis]
|
||||||
|
|
||||||
|
This is a Go port of the exponential backoff algorithm from [Google's HTTP Client Library for Java][google-http-java-client].
|
||||||
|
|
||||||
|
[Exponential backoff][exponential backoff wiki]
|
||||||
|
is an algorithm that uses feedback to multiplicatively decrease the rate of some process,
|
||||||
|
in order to gradually find an acceptable rate.
|
||||||
|
The retries exponentially increase and stop increasing when a certain threshold is met.
|
||||||
|
|
||||||
|
## How To
|
||||||
|
|
||||||
|
We define two functions, `Retry()` and `RetryNotify()`.
|
||||||
|
They receive an `Operation` to execute, a `BackOff` algorithm,
|
||||||
|
and an optional `Notify` error handler.
|
||||||
|
|
||||||
|
The operation will be executed, and will be retried on failure with delay
|
||||||
|
as given by the backoff algorithm. The backoff algorithm can also decide when to stop
|
||||||
|
retrying.
|
||||||
|
In addition, the notify error handler will be called after each failed attempt,
|
||||||
|
except for the last time, whose error should be handled by the caller.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// An Operation is executing by Retry() or RetryNotify().
|
||||||
|
// The operation will be retried using a backoff policy if it returns an error.
|
||||||
|
type Operation func() error
|
||||||
|
|
||||||
|
// Notify is a notify-on-error function. It receives an operation error and
|
||||||
|
// backoff delay if the operation failed (with an error).
|
||||||
|
//
|
||||||
|
// NOTE that if the backoff policy stated to stop retrying,
|
||||||
|
// the notify function isn't called.
|
||||||
|
type Notify func(error, time.Duration)
|
||||||
|
|
||||||
|
func Retry(Operation, BackOff) error
|
||||||
|
func RetryNotify(Operation, BackOff, Notify)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
See more advanced examples in the [godoc][advanced example].
|
||||||
|
|
||||||
|
### Retry
|
||||||
|
|
||||||
|
Simple retry helper that uses the default exponential backoff algorithm:
|
||||||
|
|
||||||
|
```go
|
||||||
|
operation := func() error {
|
||||||
|
// An operation that might fail.
|
||||||
|
return nil // or return errors.New("some error")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := Retry(operation, NewExponentialBackOff())
|
||||||
|
if err != nil {
|
||||||
|
// Handle error.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Operation is successful.
|
||||||
|
return nil
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ticker
|
||||||
|
|
||||||
|
```go
|
||||||
|
operation := func() error {
|
||||||
|
// An operation that might fail
|
||||||
|
return nil // or return errors.New("some error")
|
||||||
|
}
|
||||||
|
|
||||||
|
b := NewExponentialBackOff()
|
||||||
|
ticker := NewTicker(b)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Ticks will continue to arrive when the previous operation is still running,
|
||||||
|
// so operations that take a while to fail could run in quick succession.
|
||||||
|
for range ticker.C {
|
||||||
|
if err = operation(); err != nil {
|
||||||
|
log.Println(err, "will retry...")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ticker.Stop()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// Operation has failed.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Operation is successful.
|
||||||
|
return nil
|
||||||
|
```
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# install
|
||||||
|
$ go get github.com/cenkalti/backoff
|
||||||
|
|
||||||
|
# test
|
||||||
|
$ cd $GOPATH/src/github.com/cenkalti/backoff
|
||||||
|
$ go get -t ./...
|
||||||
|
$ go test -v -cover
|
||||||
|
```
|
||||||
|
|
||||||
|
[godoc]: https://godoc.org/github.com/cenkalti/backoff
|
||||||
|
[godoc image]: https://godoc.org/github.com/cenkalti/backoff?status.png
|
||||||
|
[travis]: https://travis-ci.org/cenkalti/backoff
|
||||||
|
[travis image]: https://travis-ci.org/cenkalti/backoff.png
|
||||||
|
|
||||||
|
[google-http-java-client]: https://github.com/google/google-http-java-client
|
||||||
|
[exponential backoff wiki]: http://en.wikipedia.org/wiki/Exponential_backoff
|
||||||
|
|
||||||
|
[advanced example]: https://godoc.org/github.com/cenkalti/backoff#example_
|
|
@ -0,0 +1,117 @@
|
||||||
|
package backoff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is an example that demonstrates how this package could be used
|
||||||
|
// to perform various advanced operations.
|
||||||
|
//
|
||||||
|
// It executes an HTTP GET request with exponential backoff,
|
||||||
|
// while errors are logged and failed responses are closed, as required by net/http package.
|
||||||
|
//
|
||||||
|
// Note we define a condition function which is used inside the operation to
|
||||||
|
// determine whether the operation succeeded or failed.
|
||||||
|
func Example() error {
|
||||||
|
res, err := GetWithRetry(
|
||||||
|
"http://localhost:9999",
|
||||||
|
ErrorIfStatusCodeIsNot(http.StatusOK),
|
||||||
|
NewExponentialBackOff())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// Close response body of last (failed) attempt.
|
||||||
|
// The Last attempt isn't handled by the notify-on-error function,
|
||||||
|
// which closes the body of all the previous attempts.
|
||||||
|
if e := res.Body.Close(); e != nil {
|
||||||
|
log.Printf("error closing last attempt's response body: %s", e)
|
||||||
|
}
|
||||||
|
log.Printf("too many failed request attempts: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer res.Body.Close() // The response's Body must be closed.
|
||||||
|
|
||||||
|
// Read body
|
||||||
|
_, _ = ioutil.ReadAll(res.Body)
|
||||||
|
|
||||||
|
// Do more stuff
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWithRetry is a helper function that performs an HTTP GET request
|
||||||
|
// to the given URL, and retries with the given backoff using the given condition function.
|
||||||
|
//
|
||||||
|
// It also uses a notify-on-error function which logs
|
||||||
|
// and closes the response body of the failed request.
|
||||||
|
func GetWithRetry(url string, condition Condition, bck BackOff) (*http.Response, error) {
|
||||||
|
var res *http.Response
|
||||||
|
err := RetryNotify(
|
||||||
|
func() error {
|
||||||
|
var err error
|
||||||
|
res, err = http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return condition(res)
|
||||||
|
},
|
||||||
|
bck,
|
||||||
|
LogAndClose())
|
||||||
|
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Condition is a retry condition function.
|
||||||
|
// It receives a response, and returns an error
|
||||||
|
// if the response failed the condition.
|
||||||
|
type Condition func(*http.Response) error
|
||||||
|
|
||||||
|
// ErrorIfStatusCodeIsNot returns a retry condition function.
|
||||||
|
// The condition returns an error
|
||||||
|
// if the given response's status code is not the given HTTP status code.
|
||||||
|
func ErrorIfStatusCodeIsNot(status int) Condition {
|
||||||
|
return func(res *http.Response) error {
|
||||||
|
if res.StatusCode != status {
|
||||||
|
return NewError(res)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error is returned on ErrorIfX() condition functions throughout this package.
|
||||||
|
type Error struct {
|
||||||
|
Response *http.Response
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewError(res *http.Response) *Error {
|
||||||
|
// Sanity check
|
||||||
|
if res == nil {
|
||||||
|
panic("response object is nil")
|
||||||
|
}
|
||||||
|
return &Error{Response: res}
|
||||||
|
}
|
||||||
|
func (err *Error) Error() string { return "request failed" }
|
||||||
|
|
||||||
|
// LogAndClose is a notify-on-error function.
|
||||||
|
// It logs the error and closes the response body.
|
||||||
|
func LogAndClose() Notify {
|
||||||
|
return func(err error, wait time.Duration) {
|
||||||
|
switch e := err.(type) {
|
||||||
|
case *Error:
|
||||||
|
defer e.Response.Body.Close()
|
||||||
|
|
||||||
|
b, err := ioutil.ReadAll(e.Response.Body)
|
||||||
|
var body string
|
||||||
|
if err != nil {
|
||||||
|
body = "can't read body"
|
||||||
|
} else {
|
||||||
|
body = string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("%s: %s", e.Response.Status, body)
|
||||||
|
default:
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
// Package backoff implements backoff algorithms for retrying operations.
|
||||||
|
//
|
||||||
|
// Also has a Retry() helper for retrying operations that may fail.
|
||||||
|
package backoff
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// BackOff is a backoff policy for retrying an operation.
|
||||||
|
type BackOff interface {
|
||||||
|
// NextBackOff returns the duration to wait before retrying the operation,
|
||||||
|
// or backoff.Stop to indicate that no more retries should be made.
|
||||||
|
//
|
||||||
|
// Example usage:
|
||||||
|
//
|
||||||
|
// duration := backoff.NextBackOff();
|
||||||
|
// if (duration == backoff.Stop) {
|
||||||
|
// // Do not retry operation.
|
||||||
|
// } else {
|
||||||
|
// // Sleep for duration and retry operation.
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
NextBackOff() time.Duration
|
||||||
|
|
||||||
|
// Reset to initial state.
|
||||||
|
Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indicates that no more retries should be made for use in NextBackOff().
|
||||||
|
const Stop time.Duration = -1
|
||||||
|
|
||||||
|
// ZeroBackOff is a fixed backoff policy whose backoff time is always zero,
|
||||||
|
// meaning that the operation is retried immediately without waiting, indefinitely.
|
||||||
|
type ZeroBackOff struct{}
|
||||||
|
|
||||||
|
func (b *ZeroBackOff) Reset() {}
|
||||||
|
|
||||||
|
func (b *ZeroBackOff) NextBackOff() time.Duration { return 0 }
|
||||||
|
|
||||||
|
// StopBackOff is a fixed backoff policy that always returns backoff.Stop for
|
||||||
|
// NextBackOff(), meaning that the operation should never be retried.
|
||||||
|
type StopBackOff struct{}
|
||||||
|
|
||||||
|
func (b *StopBackOff) Reset() {}
|
||||||
|
|
||||||
|
func (b *StopBackOff) NextBackOff() time.Duration { return Stop }
|
||||||
|
|
||||||
|
// ConstantBackOff is a backoff policy that always returns the same backoff delay.
|
||||||
|
// This is in contrast to an exponential backoff policy,
|
||||||
|
// which returns a delay that grows longer as you call NextBackOff() over and over again.
|
||||||
|
type ConstantBackOff struct {
|
||||||
|
Interval time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *ConstantBackOff) Reset() {}
|
||||||
|
func (b *ConstantBackOff) NextBackOff() time.Duration { return b.Interval }
|
||||||
|
|
||||||
|
func NewConstantBackOff(d time.Duration) *ConstantBackOff {
|
||||||
|
return &ConstantBackOff{Interval: d}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package backoff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNextBackOffMillis(t *testing.T) {
|
||||||
|
subtestNextBackOff(t, 0, new(ZeroBackOff))
|
||||||
|
subtestNextBackOff(t, Stop, new(StopBackOff))
|
||||||
|
}
|
||||||
|
|
||||||
|
func subtestNextBackOff(t *testing.T, expectedValue time.Duration, backOffPolicy BackOff) {
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
next := backOffPolicy.NextBackOff()
|
||||||
|
if next != expectedValue {
|
||||||
|
t.Errorf("got: %d expected: %d", next, expectedValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConstantBackOff(t *testing.T) {
|
||||||
|
backoff := NewConstantBackOff(time.Second)
|
||||||
|
if backoff.NextBackOff() != time.Second {
|
||||||
|
t.Error("invalid interval")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package backoff
|
||||||
|
|
||||||
|
import "log"
|
||||||
|
|
||||||
|
func ExampleRetry() error {
|
||||||
|
operation := func() error {
|
||||||
|
// An operation that might fail.
|
||||||
|
return nil // or return errors.New("some error")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := Retry(operation, NewExponentialBackOff())
|
||||||
|
if err != nil {
|
||||||
|
// Handle error.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Operation is successful.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleTicker() error {
|
||||||
|
operation := func() error {
|
||||||
|
// An operation that might fail
|
||||||
|
return nil // or return errors.New("some error")
|
||||||
|
}
|
||||||
|
|
||||||
|
b := NewExponentialBackOff()
|
||||||
|
ticker := NewTicker(b)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Ticks will continue to arrive when the previous operation is still running,
|
||||||
|
// so operations that take a while to fail could run in quick succession.
|
||||||
|
for _ = range ticker.C {
|
||||||
|
if err = operation(); err != nil {
|
||||||
|
log.Println(err, "will retry...")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ticker.Stop()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// Operation has failed.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Operation is successful.
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,151 @@
|
||||||
|
package backoff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
ExponentialBackOff is a backoff implementation that increases the backoff
|
||||||
|
period for each retry attempt using a randomization function that grows exponentially.
|
||||||
|
|
||||||
|
NextBackOff() is calculated using the following formula:
|
||||||
|
|
||||||
|
randomized interval =
|
||||||
|
RetryInterval * (random value in range [1 - RandomizationFactor, 1 + RandomizationFactor])
|
||||||
|
|
||||||
|
In other words NextBackOff() will range between the randomization factor
|
||||||
|
percentage below and above the retry interval.
|
||||||
|
|
||||||
|
For example, given the following parameters:
|
||||||
|
|
||||||
|
RetryInterval = 2
|
||||||
|
RandomizationFactor = 0.5
|
||||||
|
Multiplier = 2
|
||||||
|
|
||||||
|
the actual backoff period used in the next retry attempt will range between 1 and 3 seconds,
|
||||||
|
multiplied by the exponential, that is, between 2 and 6 seconds.
|
||||||
|
|
||||||
|
Note: MaxInterval caps the RetryInterval and not the randomized interval.
|
||||||
|
|
||||||
|
If the time elapsed since an ExponentialBackOff instance is created goes past the
|
||||||
|
MaxElapsedTime, then the method NextBackOff() starts returning backoff.Stop.
|
||||||
|
|
||||||
|
The elapsed time can be reset by calling Reset().
|
||||||
|
|
||||||
|
Example: Given the following default arguments, for 10 tries the sequence will be,
|
||||||
|
and assuming we go over the MaxElapsedTime on the 10th try:
|
||||||
|
|
||||||
|
Request # RetryInterval (seconds) Randomized Interval (seconds)
|
||||||
|
|
||||||
|
1 0.5 [0.25, 0.75]
|
||||||
|
2 0.75 [0.375, 1.125]
|
||||||
|
3 1.125 [0.562, 1.687]
|
||||||
|
4 1.687 [0.8435, 2.53]
|
||||||
|
5 2.53 [1.265, 3.795]
|
||||||
|
6 3.795 [1.897, 5.692]
|
||||||
|
7 5.692 [2.846, 8.538]
|
||||||
|
8 8.538 [4.269, 12.807]
|
||||||
|
9 12.807 [6.403, 19.210]
|
||||||
|
10 19.210 backoff.Stop
|
||||||
|
|
||||||
|
Note: Implementation is not thread-safe.
|
||||||
|
*/
|
||||||
|
type ExponentialBackOff struct {
|
||||||
|
InitialInterval time.Duration
|
||||||
|
RandomizationFactor float64
|
||||||
|
Multiplier float64
|
||||||
|
MaxInterval time.Duration
|
||||||
|
// After MaxElapsedTime the ExponentialBackOff stops.
|
||||||
|
// It never stops if MaxElapsedTime == 0.
|
||||||
|
MaxElapsedTime time.Duration
|
||||||
|
Clock Clock
|
||||||
|
|
||||||
|
currentInterval time.Duration
|
||||||
|
startTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clock is an interface that returns current time for BackOff.
|
||||||
|
type Clock interface {
|
||||||
|
Now() time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default values for ExponentialBackOff.
|
||||||
|
const (
|
||||||
|
DefaultInitialInterval = 500 * time.Millisecond
|
||||||
|
DefaultRandomizationFactor = 0.5
|
||||||
|
DefaultMultiplier = 1.5
|
||||||
|
DefaultMaxInterval = 60 * time.Second
|
||||||
|
DefaultMaxElapsedTime = 15 * time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewExponentialBackOff creates an instance of ExponentialBackOff using default values.
|
||||||
|
func NewExponentialBackOff() *ExponentialBackOff {
|
||||||
|
b := &ExponentialBackOff{
|
||||||
|
InitialInterval: DefaultInitialInterval,
|
||||||
|
RandomizationFactor: DefaultRandomizationFactor,
|
||||||
|
Multiplier: DefaultMultiplier,
|
||||||
|
MaxInterval: DefaultMaxInterval,
|
||||||
|
MaxElapsedTime: DefaultMaxElapsedTime,
|
||||||
|
Clock: SystemClock,
|
||||||
|
}
|
||||||
|
b.Reset()
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
type systemClock struct{}
|
||||||
|
|
||||||
|
func (t systemClock) Now() time.Time {
|
||||||
|
return time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemClock implements Clock interface that uses time.Now().
|
||||||
|
var SystemClock = systemClock{}
|
||||||
|
|
||||||
|
// Reset the interval back to the initial retry interval and restarts the timer.
|
||||||
|
func (b *ExponentialBackOff) Reset() {
|
||||||
|
b.currentInterval = b.InitialInterval
|
||||||
|
b.startTime = b.Clock.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextBackOff calculates the next backoff interval using the formula:
|
||||||
|
// Randomized interval = RetryInterval +/- (RandomizationFactor * RetryInterval)
|
||||||
|
func (b *ExponentialBackOff) NextBackOff() time.Duration {
|
||||||
|
// Make sure we have not gone over the maximum elapsed time.
|
||||||
|
if b.MaxElapsedTime != 0 && b.GetElapsedTime() > b.MaxElapsedTime {
|
||||||
|
return Stop
|
||||||
|
}
|
||||||
|
defer b.incrementCurrentInterval()
|
||||||
|
return getRandomValueFromInterval(b.RandomizationFactor, rand.Float64(), b.currentInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetElapsedTime returns the elapsed time since an ExponentialBackOff instance
|
||||||
|
// is created and is reset when Reset() is called.
|
||||||
|
//
|
||||||
|
// The elapsed time is computed using time.Now().UnixNano().
|
||||||
|
func (b *ExponentialBackOff) GetElapsedTime() time.Duration {
|
||||||
|
return b.Clock.Now().Sub(b.startTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increments the current interval by multiplying it with the multiplier.
|
||||||
|
func (b *ExponentialBackOff) incrementCurrentInterval() {
|
||||||
|
// Check for overflow, if overflow is detected set the current interval to the max interval.
|
||||||
|
if float64(b.currentInterval) >= float64(b.MaxInterval)/b.Multiplier {
|
||||||
|
b.currentInterval = b.MaxInterval
|
||||||
|
} else {
|
||||||
|
b.currentInterval = time.Duration(float64(b.currentInterval) * b.Multiplier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a random value from the following interval:
|
||||||
|
// [randomizationFactor * currentInterval, randomizationFactor * currentInterval].
|
||||||
|
func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration {
|
||||||
|
var delta = randomizationFactor * float64(currentInterval)
|
||||||
|
var minInterval = float64(currentInterval) - delta
|
||||||
|
var maxInterval = float64(currentInterval) + delta
|
||||||
|
|
||||||
|
// Get a random value from the range [minInterval, maxInterval].
|
||||||
|
// The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then
|
||||||
|
// we want a 33% chance for selecting either 1, 2 or 3.
|
||||||
|
return time.Duration(minInterval + (random * (maxInterval - minInterval + 1)))
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
package backoff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBackOff(t *testing.T) {
|
||||||
|
var (
|
||||||
|
testInitialInterval = 500 * time.Millisecond
|
||||||
|
testRandomizationFactor = 0.1
|
||||||
|
testMultiplier = 2.0
|
||||||
|
testMaxInterval = 5 * time.Second
|
||||||
|
testMaxElapsedTime = 15 * time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
|
exp := NewExponentialBackOff()
|
||||||
|
exp.InitialInterval = testInitialInterval
|
||||||
|
exp.RandomizationFactor = testRandomizationFactor
|
||||||
|
exp.Multiplier = testMultiplier
|
||||||
|
exp.MaxInterval = testMaxInterval
|
||||||
|
exp.MaxElapsedTime = testMaxElapsedTime
|
||||||
|
exp.Reset()
|
||||||
|
|
||||||
|
var expectedResults = []time.Duration{500, 1000, 2000, 4000, 5000, 5000, 5000, 5000, 5000, 5000}
|
||||||
|
for i, d := range expectedResults {
|
||||||
|
expectedResults[i] = d * time.Millisecond
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, expected := range expectedResults {
|
||||||
|
assertEquals(t, expected, exp.currentInterval)
|
||||||
|
// Assert that the next backoff falls in the expected range.
|
||||||
|
var minInterval = expected - time.Duration(testRandomizationFactor*float64(expected))
|
||||||
|
var maxInterval = expected + time.Duration(testRandomizationFactor*float64(expected))
|
||||||
|
var actualInterval = exp.NextBackOff()
|
||||||
|
if !(minInterval <= actualInterval && actualInterval <= maxInterval) {
|
||||||
|
t.Error("error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetRandomizedInterval(t *testing.T) {
|
||||||
|
// 33% chance of being 1.
|
||||||
|
assertEquals(t, 1, getRandomValueFromInterval(0.5, 0, 2))
|
||||||
|
assertEquals(t, 1, getRandomValueFromInterval(0.5, 0.33, 2))
|
||||||
|
// 33% chance of being 2.
|
||||||
|
assertEquals(t, 2, getRandomValueFromInterval(0.5, 0.34, 2))
|
||||||
|
assertEquals(t, 2, getRandomValueFromInterval(0.5, 0.66, 2))
|
||||||
|
// 33% chance of being 3.
|
||||||
|
assertEquals(t, 3, getRandomValueFromInterval(0.5, 0.67, 2))
|
||||||
|
assertEquals(t, 3, getRandomValueFromInterval(0.5, 0.99, 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestClock struct {
|
||||||
|
i time.Duration
|
||||||
|
start time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TestClock) Now() time.Time {
|
||||||
|
t := c.start.Add(c.i)
|
||||||
|
c.i += time.Second
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetElapsedTime(t *testing.T) {
|
||||||
|
var exp = NewExponentialBackOff()
|
||||||
|
exp.Clock = &TestClock{}
|
||||||
|
exp.Reset()
|
||||||
|
|
||||||
|
var elapsedTime = exp.GetElapsedTime()
|
||||||
|
if elapsedTime != time.Second {
|
||||||
|
t.Errorf("elapsedTime=%d", elapsedTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMaxElapsedTime(t *testing.T) {
|
||||||
|
var exp = NewExponentialBackOff()
|
||||||
|
exp.Clock = &TestClock{start: time.Time{}.Add(10000 * time.Second)}
|
||||||
|
// Change the currentElapsedTime to be 0 ensuring that the elapsed time will be greater
|
||||||
|
// than the max elapsed time.
|
||||||
|
exp.startTime = time.Time{}
|
||||||
|
assertEquals(t, Stop, exp.NextBackOff())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackOffOverflow(t *testing.T) {
|
||||||
|
var (
|
||||||
|
testInitialInterval time.Duration = math.MaxInt64 / 2
|
||||||
|
testMaxInterval time.Duration = math.MaxInt64
|
||||||
|
testMultiplier = 2.1
|
||||||
|
)
|
||||||
|
|
||||||
|
exp := NewExponentialBackOff()
|
||||||
|
exp.InitialInterval = testInitialInterval
|
||||||
|
exp.Multiplier = testMultiplier
|
||||||
|
exp.MaxInterval = testMaxInterval
|
||||||
|
exp.Reset()
|
||||||
|
|
||||||
|
exp.NextBackOff()
|
||||||
|
// Assert that when an overflow is possible the current varerval time.Duration is set to the max varerval time.Duration .
|
||||||
|
assertEquals(t, testMaxInterval, exp.currentInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertEquals(t *testing.T, expected, value time.Duration) {
|
||||||
|
if expected != value {
|
||||||
|
t.Errorf("got: %d, expected: %d", value, expected)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package backoff
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// An Operation is executing by Retry() or RetryNotify().
|
||||||
|
// The operation will be retried using a backoff policy if it returns an error.
|
||||||
|
type Operation func() error
|
||||||
|
|
||||||
|
// Notify is a notify-on-error function. It receives an operation error and
|
||||||
|
// backoff delay if the operation failed (with an error).
|
||||||
|
//
|
||||||
|
// NOTE that if the backoff policy stated to stop retrying,
|
||||||
|
// the notify function isn't called.
|
||||||
|
type Notify func(error, time.Duration)
|
||||||
|
|
||||||
|
// Retry the function f until it does not return error or BackOff stops.
|
||||||
|
// f is guaranteed to be run at least once.
|
||||||
|
// It is the caller's responsibility to reset b after Retry returns.
|
||||||
|
//
|
||||||
|
// Retry sleeps the goroutine for the duration returned by BackOff after a
|
||||||
|
// failed operation returns.
|
||||||
|
func Retry(o Operation, b BackOff) error { return RetryNotify(o, b, nil) }
|
||||||
|
|
||||||
|
// RetryNotify calls notify function with the error and wait duration
|
||||||
|
// for each failed attempt before sleep.
|
||||||
|
func RetryNotify(operation Operation, b BackOff, notify Notify) error {
|
||||||
|
var err error
|
||||||
|
var next time.Duration
|
||||||
|
|
||||||
|
b.Reset()
|
||||||
|
for {
|
||||||
|
if err = operation(); err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if next = b.NextBackOff(); next == Stop {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if notify != nil {
|
||||||
|
notify(err, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(next)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package backoff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRetry(t *testing.T) {
|
||||||
|
const successOn = 3
|
||||||
|
var i = 0
|
||||||
|
|
||||||
|
// This function is successfull on "successOn" calls.
|
||||||
|
f := func() error {
|
||||||
|
i++
|
||||||
|
log.Printf("function is called %d. time\n", i)
|
||||||
|
|
||||||
|
if i == successOn {
|
||||||
|
log.Println("OK")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("error")
|
||||||
|
return errors.New("error")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := Retry(f, NewExponentialBackOff())
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %s", err.Error())
|
||||||
|
}
|
||||||
|
if i != successOn {
|
||||||
|
t.Errorf("invalid number of retries: %d", i)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
package backoff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ticker holds a channel that delivers `ticks' of a clock at times reported by a BackOff.
|
||||||
|
//
|
||||||
|
// Ticks will continue to arrive when the previous operation is still running,
|
||||||
|
// so operations that take a while to fail could run in quick succession.
|
||||||
|
type Ticker struct {
|
||||||
|
C <-chan time.Time
|
||||||
|
c chan time.Time
|
||||||
|
b BackOff
|
||||||
|
stop chan struct{}
|
||||||
|
stopOnce sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTicker returns a new Ticker containing a channel that will send the time at times
|
||||||
|
// specified by the BackOff argument. Ticker is guaranteed to tick at least once.
|
||||||
|
// The channel is closed when Stop method is called or BackOff stops.
|
||||||
|
func NewTicker(b BackOff) *Ticker {
|
||||||
|
c := make(chan time.Time)
|
||||||
|
t := &Ticker{
|
||||||
|
C: c,
|
||||||
|
c: c,
|
||||||
|
b: b,
|
||||||
|
stop: make(chan struct{}),
|
||||||
|
}
|
||||||
|
go t.run()
|
||||||
|
runtime.SetFinalizer(t, (*Ticker).Stop)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop turns off a ticker. After Stop, no more ticks will be sent.
|
||||||
|
func (t *Ticker) Stop() {
|
||||||
|
t.stopOnce.Do(func() { close(t.stop) })
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Ticker) run() {
|
||||||
|
c := t.c
|
||||||
|
defer close(c)
|
||||||
|
t.b.Reset()
|
||||||
|
|
||||||
|
// Ticker is guaranteed to tick at least once.
|
||||||
|
afterC := t.send(time.Now())
|
||||||
|
|
||||||
|
for {
|
||||||
|
if afterC == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case tick := <-afterC:
|
||||||
|
afterC = t.send(tick)
|
||||||
|
case <-t.stop:
|
||||||
|
t.c = nil // Prevent future ticks from being sent to the channel.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Ticker) send(tick time.Time) <-chan time.Time {
|
||||||
|
select {
|
||||||
|
case t.c <- tick:
|
||||||
|
case <-t.stop:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
next := t.b.NextBackOff()
|
||||||
|
if next == Stop {
|
||||||
|
t.Stop()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.After(next)
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package backoff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTicker(t *testing.T) {
|
||||||
|
const successOn = 3
|
||||||
|
var i = 0
|
||||||
|
|
||||||
|
// This function is successfull on "successOn" calls.
|
||||||
|
f := func() error {
|
||||||
|
i++
|
||||||
|
log.Printf("function is called %d. time\n", i)
|
||||||
|
|
||||||
|
if i == successOn {
|
||||||
|
log.Println("OK")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("error")
|
||||||
|
return errors.New("error")
|
||||||
|
}
|
||||||
|
|
||||||
|
b := NewExponentialBackOff()
|
||||||
|
ticker := NewTicker(b)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
for _ = range ticker.C {
|
||||||
|
if err = f(); err != nil {
|
||||||
|
t.Log(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %s", err.Error())
|
||||||
|
}
|
||||||
|
if i != successOn {
|
||||||
|
t.Errorf("invalid number of retries: %d", i)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
Copyright (c) 2013 by authors and contributors.
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of the <organization> nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER>
|
||||||
|
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||||
|
THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,42 @@
|
||||||
|
TEST?=.
|
||||||
|
VETARGS?=-asmdecl -atomic -bool -buildtags -copylocks -methods -nilfunc -printf -rangeloops -shift -structtags -unsafeptr
|
||||||
|
|
||||||
|
default: test
|
||||||
|
|
||||||
|
# get dependencies
|
||||||
|
updatedeps:
|
||||||
|
go list ./... \
|
||||||
|
| xargs go list -f '{{join .Deps "\n"}}' \
|
||||||
|
| grep -v go-datadog-api\
|
||||||
|
| grep -v '/internal/' \
|
||||||
|
| sort -u \
|
||||||
|
| xargs go get -f -u -v
|
||||||
|
|
||||||
|
# test runs the unit tests and vets the code
|
||||||
|
test:
|
||||||
|
go test . $(TESTARGS) -v -timeout=30s -parallel=4
|
||||||
|
@$(MAKE) vet
|
||||||
|
|
||||||
|
# testacc runs acceptance tests
|
||||||
|
testacc:
|
||||||
|
go test integration/* -v $(TESTARGS) -timeout 90m
|
||||||
|
|
||||||
|
# testrace runs the race checker
|
||||||
|
testrace:
|
||||||
|
go test -race $(TEST) $(TESTARGS)
|
||||||
|
|
||||||
|
# vet runs the Go source code static analysis tool `vet` to find
|
||||||
|
# any common errors.
|
||||||
|
vet:
|
||||||
|
@go tool vet 2>/dev/null ; if [ $$? -eq 3 ]; then \
|
||||||
|
go get golang.org/x/tools/cmd/vet; \
|
||||||
|
fi
|
||||||
|
@echo "go tool vet $(VETARGS) $(TEST) "
|
||||||
|
@go tool vet $(VETARGS) $(TEST) ; if [ $$? -eq 1 ]; then \
|
||||||
|
echo ""; \
|
||||||
|
echo "Vet found suspicious constructs. Please check the reported constructs"; \
|
||||||
|
echo "and fix them if necessary before submitting the code for review."; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
.PHONY: default test testacc updatedeps vet
|
|
@ -0,0 +1,69 @@
|
||||||
|
[![GoDoc](http://img.shields.io/badge/godoc-reference-blue.svg)](http://godoc.org/github.com/zorkian/go-datadog-api)
|
||||||
|
[![Build
|
||||||
|
status](https://travis-ci.org/zorkian/go-datadog-api.svg)](https://travis-ci.org/zorkian/go-datadog-api)
|
||||||
|
|
||||||
|
# Datadog API in Go
|
||||||
|
|
||||||
|
Hi!
|
||||||
|
|
||||||
|
This is a Go wrapper for the Datadog API. You should use this library if you need to interact
|
||||||
|
with the Datadog system. You can post metrics with it if you want, but this library is probably
|
||||||
|
mostly used for automating dashboards/alerting and retrieving data (events, etc).
|
||||||
|
|
||||||
|
The source API documentation is here: <http://docs.datadoghq.com/api/>
|
||||||
|
|
||||||
|
## USAGE
|
||||||
|
|
||||||
|
To use this project, include it in your code like:
|
||||||
|
|
||||||
|
``` go
|
||||||
|
import "github.com/zorkian/go-datadog-api"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, you can work with it:
|
||||||
|
|
||||||
|
``` go
|
||||||
|
client := datadog.NewClient("api key", "application key")
|
||||||
|
|
||||||
|
dash, err := client.GetDashboard(10880)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("fatal: %s\n", err)
|
||||||
|
}
|
||||||
|
log.Printf("dashboard %d: %s\n", dash.Id, dash.Title)
|
||||||
|
```
|
||||||
|
|
||||||
|
That's all; it's pretty easy to use. Check out the Godoc link for the
|
||||||
|
available API methods and, if you can't find the one you need,
|
||||||
|
let us know (or patches welcome)!
|
||||||
|
|
||||||
|
## DOCUMENTATION
|
||||||
|
|
||||||
|
Please see: <http://godoc.org/github.com/zorkian/go-datadog-api>
|
||||||
|
|
||||||
|
## BUGS/PROBLEMS/CONTRIBUTING
|
||||||
|
|
||||||
|
There are certainly some, but presently no known major bugs. If you do
|
||||||
|
find something that doesn't work as expected, please file an issue on
|
||||||
|
Github:
|
||||||
|
|
||||||
|
<https://github.com/zorkian/go-datadog-api/issues>
|
||||||
|
|
||||||
|
Thanks in advance! And, as always, patches welcome!
|
||||||
|
|
||||||
|
## DEVELOPMENT
|
||||||
|
|
||||||
|
* Get dependencies with `make updatedeps`.
|
||||||
|
* Run tests tests with `make test`.
|
||||||
|
* Integration tests can be run with `make testacc`.
|
||||||
|
|
||||||
|
The acceptance tests require _DATADOG_API_KEY_ and _DATADOG_APP_KEY_ to be available
|
||||||
|
in your environment variables.
|
||||||
|
|
||||||
|
*Warning: the integrations tests will create and remove real resources in your Datadog
|
||||||
|
account*
|
||||||
|
|
||||||
|
## COPYRIGHT AND LICENSE
|
||||||
|
|
||||||
|
Please see the LICENSE file for the included license information.
|
||||||
|
|
||||||
|
Copyright 2013 by authors and contributors.
|
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
* Datadog API for Go
|
||||||
|
*
|
||||||
|
* Please see the included LICENSE file for licensing information.
|
||||||
|
*
|
||||||
|
* Copyright 2013 by authors and contributors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package datadog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Alert represents the data of an alert: a query that can fire and send a
|
||||||
|
// message to the users.
|
||||||
|
type Alert struct {
|
||||||
|
Id int `json:"id,omitempty"`
|
||||||
|
Creator int `json:"creator,omitempty"`
|
||||||
|
Query string `json:"query,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
Silenced bool `json:"silenced,omitempty"`
|
||||||
|
NotifyNoData bool `json:"notify_no_data,omitempty"`
|
||||||
|
State string `json:"state,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// reqAlerts receives a slice of all alerts.
|
||||||
|
type reqAlerts struct {
|
||||||
|
Alerts []Alert `json:"alerts,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAlert adds a new alert to the system. This returns a pointer to an
|
||||||
|
// Alert so you can pass that to UpdateAlert later if needed.
|
||||||
|
func (self *Client) CreateAlert(alert *Alert) (*Alert, error) {
|
||||||
|
var out Alert
|
||||||
|
err := self.doJsonRequest("POST", "/v1/alert", alert, &out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAlert takes an alert that was previously retrieved through some method
|
||||||
|
// and sends it back to the server.
|
||||||
|
func (self *Client) UpdateAlert(alert *Alert) error {
|
||||||
|
return self.doJsonRequest("PUT", fmt.Sprintf("/v1/alert/%d", alert.Id),
|
||||||
|
alert, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAlert retrieves an alert by identifier.
|
||||||
|
func (self *Client) GetAlert(id int) (*Alert, error) {
|
||||||
|
var out Alert
|
||||||
|
err := self.doJsonRequest("GET", fmt.Sprintf("/v1/alert/%d", id), nil, &out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAlert removes an alert from the system.
|
||||||
|
func (self *Client) DeleteAlert(id int) error {
|
||||||
|
return self.doJsonRequest("DELETE", fmt.Sprintf("/v1/alert/%d", id),
|
||||||
|
nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAlerts returns a slice of all alerts.
|
||||||
|
func (self *Client) GetAlerts() ([]Alert, error) {
|
||||||
|
var out reqAlerts
|
||||||
|
err := self.doJsonRequest("GET", "/v1/alert", nil, &out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out.Alerts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MuteAlerts turns off alerting notifications.
|
||||||
|
func (self *Client) MuteAlerts() error {
|
||||||
|
return self.doJsonRequest("POST", "/v1/mute_alerts", nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmuteAlerts turns on alerting notifications.
|
||||||
|
func (self *Client) UnmuteAlerts() error {
|
||||||
|
return self.doJsonRequest("POST", "/v1/unmute_alerts", nil, nil)
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* Datadog API for Go
|
||||||
|
*
|
||||||
|
* Please see the included LICENSE file for licensing information.
|
||||||
|
*
|
||||||
|
* Copyright 2013 by authors and contributors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package datadog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Comment is a special form of event that appears in a stream.
|
||||||
|
type Comment struct {
|
||||||
|
Id int `json:"id"`
|
||||||
|
RelatedId int `json:"related_event_id"`
|
||||||
|
Handle string `json:"handle"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Resource string `json:"resource"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// reqComment is the container for receiving commenst.
|
||||||
|
type reqComment struct {
|
||||||
|
Comment Comment `json:"comment"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateComment adds a new comment to the system.
|
||||||
|
func (self *Client) CreateComment(handle, message string) (*Comment, error) {
|
||||||
|
var out reqComment
|
||||||
|
comment := Comment{Handle: handle, Message: message}
|
||||||
|
err := self.doJsonRequest("POST", "/v1/comments", &comment, &out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &out.Comment, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateRelatedComment adds a new comment, but lets you specify the related
|
||||||
|
// identifier for the comment.
|
||||||
|
func (self *Client) CreateRelatedComment(handle, message string,
|
||||||
|
relid int) (*Comment, error) {
|
||||||
|
var out reqComment
|
||||||
|
comment := Comment{Handle: handle, Message: message, RelatedId: relid}
|
||||||
|
err := self.doJsonRequest("POST", "/v1/comments", &comment, &out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &out.Comment, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EditComment changes the message and possibly handle of a particular comment.
|
||||||
|
func (self *Client) EditComment(id int, handle, message string) error {
|
||||||
|
comment := Comment{Handle: handle, Message: message}
|
||||||
|
return self.doJsonRequest("PUT", fmt.Sprintf("/v1/comments/%d", id),
|
||||||
|
&comment, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteComment does exactly what you expect.
|
||||||
|
func (self *Client) DeleteComment(id int) error {
|
||||||
|
return self.doJsonRequest("DELETE", fmt.Sprintf("/v1/comments/%d", id),
|
||||||
|
nil, nil)
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
/*
|
||||||
|
* Datadog API for Go
|
||||||
|
*
|
||||||
|
* Please see the included LICENSE file for licensing information.
|
||||||
|
*
|
||||||
|
* Copyright 2013 by authors and contributors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package datadog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Graph represents a graph that might exist on a dashboard.
|
||||||
|
type Graph struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Events []struct{} `json:"events"`
|
||||||
|
Definition struct {
|
||||||
|
Viz string `json:"viz"`
|
||||||
|
Requests []struct {
|
||||||
|
Query string `json:"q"`
|
||||||
|
Stacked bool `json:"stacked"`
|
||||||
|
} `json:"requests"`
|
||||||
|
} `json:"definition"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template variable represents a template variable that might exist on a dashboard
|
||||||
|
type TemplateVariable struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Prefix string `json:"prefix"`
|
||||||
|
Default string `json:"default"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dashboard represents a user created dashboard. This is the full dashboard
|
||||||
|
// struct when we load a dashboard in detail.
|
||||||
|
type Dashboard struct {
|
||||||
|
Id int `json:"id"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Graphs []Graph `json:"graphs"`
|
||||||
|
TemplateVariables []TemplateVariable `json:"template_variables,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DashboardLite represents a user created dashboard. This is the mini
|
||||||
|
// struct when we load the summaries.
|
||||||
|
type DashboardLite struct {
|
||||||
|
Id int `json:"id,string"` // TODO: Remove ',string'.
|
||||||
|
Resource string `json:"resource"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// reqGetDashboards from /api/v1/dash
|
||||||
|
type reqGetDashboards struct {
|
||||||
|
Dashboards []DashboardLite `json:"dashes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// reqGetDashboard from /api/v1/dash/:dashboard_id
|
||||||
|
type reqGetDashboard struct {
|
||||||
|
Resource string `json:"resource"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
Dashboard Dashboard `json:"dash"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDashboard returns a single dashboard created on this account.
|
||||||
|
func (self *Client) GetDashboard(id int) (*Dashboard, error) {
|
||||||
|
var out reqGetDashboard
|
||||||
|
err := self.doJsonRequest("GET", fmt.Sprintf("/v1/dash/%d", id), nil, &out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &out.Dashboard, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDashboards returns a list of all dashboards created on this account.
|
||||||
|
func (self *Client) GetDashboards() ([]DashboardLite, error) {
|
||||||
|
var out reqGetDashboards
|
||||||
|
err := self.doJsonRequest("GET", "/v1/dash", nil, &out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out.Dashboards, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteDashboard deletes a dashboard by the identifier.
|
||||||
|
func (self *Client) DeleteDashboard(id int) error {
|
||||||
|
return self.doJsonRequest("DELETE", fmt.Sprintf("/v1/dash/%d", id), nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDashboard creates a new dashboard when given a Dashboard struct. Note
|
||||||
|
// that the Id, Resource, Url and similar elements are not used in creation.
|
||||||
|
func (self *Client) CreateDashboard(dash *Dashboard) (*Dashboard, error) {
|
||||||
|
var out reqGetDashboard
|
||||||
|
err := self.doJsonRequest("POST", "/v1/dash", dash, &out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &out.Dashboard, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDashboard in essence takes a Dashboard struct and persists it back to
|
||||||
|
// the server. Use this if you've updated your local and need to push it back.
|
||||||
|
func (self *Client) UpdateDashboard(dash *Dashboard) error {
|
||||||
|
return self.doJsonRequest("PUT", fmt.Sprintf("/v1/dash/%d", dash.Id),
|
||||||
|
dash, nil)
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* Datadog API for Go
|
||||||
|
*
|
||||||
|
* Please see the included LICENSE file for licensing information.
|
||||||
|
*
|
||||||
|
* Copyright 2013 by authors and contributors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package datadog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Recurrence struct {
|
||||||
|
Period int `json:"period,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
UntilDate int `json:"until_date,omitempty"`
|
||||||
|
UntilOccurrences int `json:"until_occurrences,omitempty"`
|
||||||
|
WeekDays []string `json:"week_days,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Downtime struct {
|
||||||
|
Active bool `json:"active,omitempty"`
|
||||||
|
Canceled int `json:"canceled,omitempty"`
|
||||||
|
Disabled bool `json:"disabled,omitempty"`
|
||||||
|
End int `json:"end,omitempty"`
|
||||||
|
Id int `json:"id,omitempty"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
Recurrence *Recurrence `json:"recurrence,omitempty"`
|
||||||
|
Scope []string `json:"scope,omitempty"`
|
||||||
|
Start int `json:"start,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// reqDowntimes retrieves a slice of all Downtimes.
|
||||||
|
type reqDowntimes struct {
|
||||||
|
Downtimes []Downtime `json:"downtimes,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDowntime adds a new downtme to the system. This returns a pointer
|
||||||
|
// to a Downtime so you can pass that to UpdateDowntime or CancelDowntime
|
||||||
|
// later if needed.
|
||||||
|
func (self *Client) CreateDowntime(downtime *Downtime) (*Downtime, error) {
|
||||||
|
var out Downtime
|
||||||
|
err := self.doJsonRequest("POST", "/v1/downtime", downtime, &out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDowntime takes a downtime that was previously retrieved through some method
|
||||||
|
// and sends it back to the server.
|
||||||
|
func (self *Client) UpdateDowntime(downtime *Downtime) error {
|
||||||
|
return self.doJsonRequest("PUT", fmt.Sprintf("/v1/downtime/%d", downtime.Id),
|
||||||
|
downtime, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getdowntime retrieves an downtime by identifier.
|
||||||
|
func (self *Client) GetDowntime(id int) (*Downtime, error) {
|
||||||
|
var out Downtime
|
||||||
|
err := self.doJsonRequest("GET", fmt.Sprintf("/v1/downtime/%d", id), nil, &out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteDowntime removes an downtime from the system.
|
||||||
|
func (self *Client) DeleteDowntime(id int) error {
|
||||||
|
return self.doJsonRequest("DELETE", fmt.Sprintf("/v1/downtime/%d", id),
|
||||||
|
nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDowntimes returns a slice of all downtimes.
|
||||||
|
func (self *Client) GetDowntimes() ([]Downtime, error) {
|
||||||
|
var out reqDowntimes
|
||||||
|
err := self.doJsonRequest("GET", "/v1/downtime", nil, &out.Downtimes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out.Downtimes, nil
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
* Datadog API for Go
|
||||||
|
*
|
||||||
|
* Please see the included LICENSE file for licensing information.
|
||||||
|
*
|
||||||
|
* Copyright 2013 by authors and contributors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package datadog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Event is a single event. If this is being used to post an event, then not
|
||||||
|
// all fields will be filled out.
|
||||||
|
type Event struct {
|
||||||
|
Id int `json:"id,omitempty"`
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
Text string `json:"text,omitempty"`
|
||||||
|
Time int `json:"date_happened,omitempty"` // UNIX time.
|
||||||
|
Priority string `json:"priority,omitempty"`
|
||||||
|
AlertType string `json:"alert_type,omitempty"`
|
||||||
|
Host string `json:"host,omitempty"`
|
||||||
|
Aggregation string `json:"aggregation_key,omitempty"`
|
||||||
|
SourceType string `json:"source_type,omitempty"`
|
||||||
|
Tags []string `json:"tags,omitempty"`
|
||||||
|
Url string `json:"url,omitempty"`
|
||||||
|
Resource string `json:"resource,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// reqGetEvent is the container for receiving a single event.
|
||||||
|
type reqGetEvent struct {
|
||||||
|
Event Event `json:"event,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// reqGetEvents is for returning many events.
|
||||||
|
type reqGetEvents struct {
|
||||||
|
Events []Event `json:"events,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostEvent takes as input an event and then posts it to the server.
|
||||||
|
func (self *Client) PostEvent(event *Event) (*Event, error) {
|
||||||
|
var out reqGetEvent
|
||||||
|
err := self.doJsonRequest("POST", "/v1/events", event, &out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &out.Event, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEvent gets a single event given an identifier.
|
||||||
|
func (self *Client) GetEvent(id int) (*Event, error) {
|
||||||
|
var out reqGetEvent
|
||||||
|
err := self.doJsonRequest("GET", fmt.Sprintf("/v1/events/%d", id), nil, &out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &out.Event, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryEvents returns a slice of events from the query stream.
|
||||||
|
func (self *Client) GetEvents(start, end int,
|
||||||
|
priority, sources, tags string) ([]Event, error) {
|
||||||
|
// Since this is a GET request, we need to build a query string.
|
||||||
|
vals := url.Values{}
|
||||||
|
vals.Add("start", strconv.Itoa(start))
|
||||||
|
vals.Add("end", strconv.Itoa(end))
|
||||||
|
if priority != "" {
|
||||||
|
vals.Add("priority", priority)
|
||||||
|
}
|
||||||
|
if sources != "" {
|
||||||
|
vals.Add("sources", sources)
|
||||||
|
}
|
||||||
|
if tags != "" {
|
||||||
|
vals.Add("tags", tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now the request and response.
|
||||||
|
var out reqGetEvents
|
||||||
|
err := self.doJsonRequest("GET",
|
||||||
|
fmt.Sprintf("/v1/events?%s", vals.Encode()), nil, &out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out.Events, nil
|
||||||
|
}
|
138
vendor/github.com/zorkian/go-datadog-api/integration/dashboards_test.go
generated
vendored
Normal file
138
vendor/github.com/zorkian/go-datadog-api/integration/dashboards_test.go
generated
vendored
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/zorkian/go-datadog-api"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
client = initTest()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateAndDeleteDashboard(t *testing.T) {
|
||||||
|
expected := getTestDashboard()
|
||||||
|
// create the dashboard and compare it
|
||||||
|
actual, err := client.CreateDashboard(expected)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Creating a dashboard failed when it shouldn't. (%s)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer cleanUpDashboard(t, actual.Id)
|
||||||
|
|
||||||
|
assertDashboardEquals(t, actual, expected)
|
||||||
|
|
||||||
|
// now try to fetch it freshly and compare it again
|
||||||
|
actual, err = client.GetDashboard(actual.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving a dashboard failed when it shouldn't. (%s)", err)
|
||||||
|
}
|
||||||
|
assertDashboardEquals(t, actual, expected)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateDashboard(t *testing.T) {
|
||||||
|
expected := getTestDashboard()
|
||||||
|
board, err := client.CreateDashboard(expected)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Creating a dashboard failed when it shouldn't. (%s)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer cleanUpDashboard(t, board.Id)
|
||||||
|
board.Title = "___New-Test-Board___"
|
||||||
|
|
||||||
|
if err := client.UpdateDashboard(board); err != nil {
|
||||||
|
t.Fatalf("Updating a dashboard failed when it shouldn't: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := client.GetDashboard(board.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving a dashboard failed when it shouldn't: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertDashboardEquals(t, actual, board)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetDashboards(t *testing.T) {
|
||||||
|
boards, err := client.GetDashboards()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving dashboards failed when it shouldn't: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
num := len(boards)
|
||||||
|
board := createTestDashboard(t)
|
||||||
|
defer cleanUpDashboard(t, board.Id)
|
||||||
|
|
||||||
|
boards, err = client.GetDashboards()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving dashboards failed when it shouldn't: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if num+1 != len(boards) {
|
||||||
|
t.Fatalf("Number of dashboards didn't match expected: %d != %d", len(boards), num+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTestDashboard() *datadog.Dashboard {
|
||||||
|
return &datadog.Dashboard{
|
||||||
|
Title: "___Test-Board___",
|
||||||
|
Description: "Testboard description",
|
||||||
|
TemplateVariables: []datadog.TemplateVariable{},
|
||||||
|
Graphs: createGraph(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTestDashboard(t *testing.T) *datadog.Dashboard {
|
||||||
|
board := getTestDashboard()
|
||||||
|
board, err := client.CreateDashboard(board)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Creating a dashboard failed when it shouldn't: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return board
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanUpDashboard(t *testing.T, id int) {
|
||||||
|
if err := client.DeleteDashboard(id); err != nil {
|
||||||
|
t.Fatalf("Deleting a dashboard failed when it shouldn't. Manual cleanup needed. (%s)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
deletedBoard, err := client.GetDashboard(id)
|
||||||
|
if deletedBoard != nil {
|
||||||
|
t.Fatal("Dashboard hasn't been deleted when it should have been. Manual cleanup needed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Fetching deleted dashboard didn't lead to an error. Manual cleanup needed.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestGraphDefintionRequests struct {
|
||||||
|
Query string `json:"q"`
|
||||||
|
Stacked bool `json:"stacked"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func createGraph() []datadog.Graph {
|
||||||
|
graphDefinition := datadog.Graph{}.Definition
|
||||||
|
graphDefinition.Viz = "timeseries"
|
||||||
|
r := datadog.Graph{}.Definition.Requests
|
||||||
|
graphDefinition.Requests = append(r, TestGraphDefintionRequests{Query: "avg:system.mem.free{*}", Stacked: false})
|
||||||
|
graph := datadog.Graph{Title: "Mandatory graph", Definition: graphDefinition}
|
||||||
|
graphs := []datadog.Graph{}
|
||||||
|
graphs = append(graphs, graph)
|
||||||
|
return graphs
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertDashboardEquals(t *testing.T, actual, expected *datadog.Dashboard) {
|
||||||
|
if actual.Title != expected.Title {
|
||||||
|
t.Errorf("Dashboard title does not match: %s != %s", actual.Title, expected.Title)
|
||||||
|
}
|
||||||
|
if actual.Description != expected.Description {
|
||||||
|
t.Errorf("Dashboard description does not match: %s != %s", actual.Description, expected.Description)
|
||||||
|
}
|
||||||
|
if len(actual.Graphs) != len(expected.Graphs) {
|
||||||
|
t.Errorf("Number of Dashboard graphs does not match: %d != %d", len(actual.Graphs), len(expected.Graphs))
|
||||||
|
}
|
||||||
|
if len(actual.TemplateVariables) != len(expected.TemplateVariables) {
|
||||||
|
t.Errorf("Number of Dashboard template variables does not match: %d != %d", len(actual.TemplateVariables), len(expected.TemplateVariables))
|
||||||
|
}
|
||||||
|
}
|
110
vendor/github.com/zorkian/go-datadog-api/integration/downtime_test.go
generated
vendored
Normal file
110
vendor/github.com/zorkian/go-datadog-api/integration/downtime_test.go
generated
vendored
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/zorkian/go-datadog-api"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
client = initTest()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateAndDeleteDowntime(t *testing.T) {
|
||||||
|
expected := getTestDowntime()
|
||||||
|
// create the downtime and compare it
|
||||||
|
actual := createTestDowntime(t)
|
||||||
|
defer cleanUpDowntime(t, actual.Id)
|
||||||
|
|
||||||
|
// Set ID of our original struct to zero we we can easily compare the results
|
||||||
|
expected.Id = actual.Id
|
||||||
|
assert.Equal(t, expected, actual)
|
||||||
|
|
||||||
|
actual, err := client.GetDowntime(actual.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving a downtime failed when it shouldn't: (%s)", err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateDowntime(t *testing.T) {
|
||||||
|
|
||||||
|
downtime := createTestDowntime(t)
|
||||||
|
|
||||||
|
downtime.Scope = []string{"env:downtime_test", "env:downtime_test2"}
|
||||||
|
defer cleanUpDowntime(t, downtime.Id)
|
||||||
|
|
||||||
|
if err := client.UpdateDowntime(downtime); err != nil {
|
||||||
|
t.Fatalf("Updating a downtime failed when it shouldn't: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := client.GetDowntime(downtime.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving a downtime failed when it shouldn't: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, downtime, actual)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetDowntime(t *testing.T) {
|
||||||
|
downtimes, err := client.GetDowntimes()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving downtimes failed when it shouldn't: %s", err)
|
||||||
|
}
|
||||||
|
num := len(downtimes)
|
||||||
|
|
||||||
|
downtime := createTestDowntime(t)
|
||||||
|
defer cleanUpDowntime(t, downtime.Id)
|
||||||
|
|
||||||
|
downtimes, err = client.GetDowntimes()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving downtimes failed when it shouldn't: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if num+1 != len(downtimes) {
|
||||||
|
t.Fatalf("Number of downtimes didn't match expected: %d != %d", len(downtimes), num+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTestDowntime() *datadog.Downtime {
|
||||||
|
|
||||||
|
r := &datadog.Recurrence{
|
||||||
|
Type: "weeks",
|
||||||
|
Period: 1,
|
||||||
|
WeekDays: []string{"Mon", "Tue", "Wed", "Thu", "Fri"},
|
||||||
|
}
|
||||||
|
|
||||||
|
return &datadog.Downtime{
|
||||||
|
Message: "Test downtime message",
|
||||||
|
Scope: []string{"env:downtime_test"},
|
||||||
|
Start: 1577836800,
|
||||||
|
End: 1577840400,
|
||||||
|
Recurrence: r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTestDowntime(t *testing.T) *datadog.Downtime {
|
||||||
|
downtime := getTestDowntime()
|
||||||
|
downtime, err := client.CreateDowntime(downtime)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Creating a downtime failed when it shouldn't: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return downtime
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanUpDowntime(t *testing.T, id int) {
|
||||||
|
if err := client.DeleteDowntime(id); err != nil {
|
||||||
|
t.Fatalf("Deleting a downtime failed when it shouldn't. Manual cleanup needed. (%s)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
deletedDowntime, err := client.GetDowntime(id)
|
||||||
|
if deletedDowntime != nil && deletedDowntime.Canceled == 0 {
|
||||||
|
t.Fatal("Downtime hasn't been deleted when it should have been. Manual cleanup needed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil && deletedDowntime.Canceled == 0 {
|
||||||
|
t.Fatal("Fetching deleted downtime didn't lead to an error and downtime Canceled not set.")
|
||||||
|
}
|
||||||
|
}
|
152
vendor/github.com/zorkian/go-datadog-api/integration/monitors_test.go
generated
vendored
Normal file
152
vendor/github.com/zorkian/go-datadog-api/integration/monitors_test.go
generated
vendored
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/zorkian/go-datadog-api"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
client = initTest()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateAndDeleteMonitor(t *testing.T) {
|
||||||
|
expected := getTestMonitor()
|
||||||
|
// create the monitor and compare it
|
||||||
|
actual := createTestMonitor(t)
|
||||||
|
defer cleanUpMonitor(t, actual.Id)
|
||||||
|
|
||||||
|
// Set ID of our original struct to zero we we can easily compare the results
|
||||||
|
expected.Id = actual.Id
|
||||||
|
assert.Equal(t, expected, actual)
|
||||||
|
|
||||||
|
actual, err := client.GetMonitor(actual.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving a monitor failed when it shouldn't: (%s)", err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateMonitor(t *testing.T) {
|
||||||
|
|
||||||
|
monitor := createTestMonitor(t)
|
||||||
|
defer cleanUpMonitor(t, monitor.Id)
|
||||||
|
|
||||||
|
monitor.Name = "___New-Test-Monitor___"
|
||||||
|
if err := client.UpdateMonitor(monitor); err != nil {
|
||||||
|
t.Fatalf("Updating a monitor failed when it shouldn't: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := client.GetMonitor(monitor.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving a monitor failed when it shouldn't: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, monitor, actual)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetMonitor(t *testing.T) {
|
||||||
|
monitors, err := client.GetMonitors()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving monitors failed when it shouldn't: %s", err)
|
||||||
|
}
|
||||||
|
num := len(monitors)
|
||||||
|
|
||||||
|
monitor := createTestMonitor(t)
|
||||||
|
defer cleanUpMonitor(t, monitor.Id)
|
||||||
|
|
||||||
|
monitors, err = client.GetMonitors()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving monitors failed when it shouldn't: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if num+1 != len(monitors) {
|
||||||
|
t.Fatalf("Number of monitors didn't match expected: %d != %d", len(monitors), num+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMuteUnmuteMonitor(t *testing.T) {
|
||||||
|
monitor := createTestMonitor(t)
|
||||||
|
defer cleanUpMonitor(t, monitor.Id)
|
||||||
|
|
||||||
|
// Mute
|
||||||
|
err := client.MuteMonitor(monitor.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to mute monitor")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
monitor, err = client.GetMonitor(monitor.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving monitors failed when it shouldn't: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mute without options will result in monitor.Options.Silenced
|
||||||
|
// to have a key of "*" with value 0
|
||||||
|
assert.Equal(t, 0, monitor.Options.Silenced["*"])
|
||||||
|
|
||||||
|
// Unmute
|
||||||
|
err = client.UnmuteMonitor(monitor.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to unmute monitor")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update remote state
|
||||||
|
monitor, err = client.GetMonitor(monitor.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving monitors failed when it shouldn't: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert this map is empty
|
||||||
|
assert.Equal(t, 0, len(monitor.Options.Silenced))
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Testing of global mute and unmuting has not been added for following reasons:
|
||||||
|
* Disabling and enabling of global monitoring does an @all mention which is noisy
|
||||||
|
* It exposes risk to users that run integration tests in their main account
|
||||||
|
* There is no endpoint to verify success
|
||||||
|
*/
|
||||||
|
|
||||||
|
func getTestMonitor() *datadog.Monitor {
|
||||||
|
|
||||||
|
o := datadog.Options{
|
||||||
|
NotifyNoData: true,
|
||||||
|
NoDataTimeframe: 60,
|
||||||
|
Silenced: map[string]int{},
|
||||||
|
}
|
||||||
|
|
||||||
|
return &datadog.Monitor{
|
||||||
|
Message: "Test message",
|
||||||
|
Query: "avg(last_15m):avg:system.disk.in_use{*} by {host,device} > 0.8",
|
||||||
|
Name: "Test monitor",
|
||||||
|
Options: o,
|
||||||
|
Type: "metric alert",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTestMonitor(t *testing.T) *datadog.Monitor {
|
||||||
|
monitor := getTestMonitor()
|
||||||
|
monitor, err := client.CreateMonitor(monitor)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Creating a monitor failed when it shouldn't: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return monitor
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanUpMonitor(t *testing.T, id int) {
|
||||||
|
if err := client.DeleteMonitor(id); err != nil {
|
||||||
|
t.Fatalf("Deleting a monitor failed when it shouldn't. Manual cleanup needed. (%s)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
deletedMonitor, err := client.GetMonitor(id)
|
||||||
|
if deletedMonitor != nil {
|
||||||
|
t.Fatal("Monitor hasn't been deleted when it should have been. Manual cleanup needed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Fetching deleted monitor didn't lead to an error.")
|
||||||
|
}
|
||||||
|
}
|
766
vendor/github.com/zorkian/go-datadog-api/integration/screen_widgets_test.go
generated
vendored
Normal file
766
vendor/github.com/zorkian/go-datadog-api/integration/screen_widgets_test.go
generated
vendored
Normal file
|
@ -0,0 +1,766 @@
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/zorkian/go-datadog-api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAlertValueWidget(t *testing.T) {
|
||||||
|
board := createTestScreenboard(t)
|
||||||
|
defer cleanUpScreenboard(t, board.Id)
|
||||||
|
|
||||||
|
expected := datadog.Widget{}.AlertValueWidget
|
||||||
|
|
||||||
|
expected.X = 1
|
||||||
|
expected.Y = 1
|
||||||
|
expected.Width = 5
|
||||||
|
expected.Height = 5
|
||||||
|
expected.TitleText = "foo"
|
||||||
|
expected.TitleAlign = "center"
|
||||||
|
expected.TitleSize = 1
|
||||||
|
expected.Title = true
|
||||||
|
expected.TextSize = "auto"
|
||||||
|
expected.Precision = 2
|
||||||
|
expected.AlertId = 1
|
||||||
|
expected.Type = "alert_value"
|
||||||
|
expected.Unit = "auto"
|
||||||
|
expected.AddTimeframe = false
|
||||||
|
|
||||||
|
w := datadog.Widget{AlertValueWidget: expected}
|
||||||
|
|
||||||
|
board.Widgets = append(board.Widgets, w)
|
||||||
|
|
||||||
|
if err := client.UpdateScreenboard(board); err != nil {
|
||||||
|
t.Fatalf("Updating a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := client.GetScreenboard(board.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actualWidget := actual.Widgets[0].AlertValueWidget
|
||||||
|
|
||||||
|
assertEquals(t, "x", actualWidget.X, expected.X)
|
||||||
|
assertEquals(t, "y", actualWidget.Y, expected.Y)
|
||||||
|
assertEquals(t, "height", actualWidget.Height, expected.Height)
|
||||||
|
assertEquals(t, "width", actualWidget.Width, expected.Width)
|
||||||
|
assertEquals(t, "title_text", actualWidget.TitleText, expected.TitleText)
|
||||||
|
assertEquals(t, "title_size", actualWidget.TitleSize, expected.TitleSize)
|
||||||
|
assertEquals(t, "title_align", actualWidget.TitleAlign, expected.TitleAlign)
|
||||||
|
assertEquals(t, "title", actualWidget.Title, expected.Title)
|
||||||
|
assertEquals(t, "text_size", actualWidget.TextSize, expected.TextSize)
|
||||||
|
assertEquals(t, "precision", actualWidget.Precision, expected.Precision)
|
||||||
|
assertEquals(t, "alert_id", actualWidget.AlertId, expected.AlertId)
|
||||||
|
assertEquals(t, "type", actualWidget.Type, expected.Type)
|
||||||
|
assertEquals(t, "unit", actualWidget.Unit, expected.Unit)
|
||||||
|
assertEquals(t, "add_timeframe", actualWidget.AddTimeframe, expected.AddTimeframe)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChangeWidget(t *testing.T) {
|
||||||
|
board := createTestScreenboard(t)
|
||||||
|
defer cleanUpScreenboard(t, board.Id)
|
||||||
|
|
||||||
|
expected := datadog.Widget{}.ChangeWidget
|
||||||
|
|
||||||
|
expected.X = 1
|
||||||
|
expected.Y = 1
|
||||||
|
expected.Width = 5
|
||||||
|
expected.Height = 5
|
||||||
|
expected.TitleText = "foo"
|
||||||
|
expected.TitleAlign = "center"
|
||||||
|
expected.TitleSize = 1
|
||||||
|
expected.Title = true
|
||||||
|
expected.Aggregator = "min"
|
||||||
|
expected.TileDef = datadog.TileDef{}
|
||||||
|
|
||||||
|
w := datadog.Widget{ChangeWidget: expected}
|
||||||
|
|
||||||
|
board.Widgets = append(board.Widgets, w)
|
||||||
|
|
||||||
|
if err := client.UpdateScreenboard(board); err != nil {
|
||||||
|
t.Fatalf("Updating a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := client.GetScreenboard(board.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actualWidget := actual.Widgets[0].ChangeWidget
|
||||||
|
|
||||||
|
assertEquals(t, "x", actualWidget.X, expected.X)
|
||||||
|
assertEquals(t, "y", actualWidget.Y, expected.Y)
|
||||||
|
assertEquals(t, "height", actualWidget.Height, expected.Height)
|
||||||
|
assertEquals(t, "width", actualWidget.Width, expected.Width)
|
||||||
|
assertEquals(t, "title_text", actualWidget.TitleText, expected.TitleText)
|
||||||
|
assertEquals(t, "title_size", actualWidget.TitleSize, expected.TitleSize)
|
||||||
|
assertEquals(t, "title_align", actualWidget.TitleAlign, expected.TitleAlign)
|
||||||
|
assertEquals(t, "title", actualWidget.Title, expected.Title)
|
||||||
|
assertEquals(t, "aggregator", actualWidget.Aggregator, expected.Aggregator)
|
||||||
|
assertTileDefEquals(t, actualWidget.TileDef, expected.TileDef)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGraphWidget(t *testing.T) {
|
||||||
|
board := createTestScreenboard(t)
|
||||||
|
defer cleanUpScreenboard(t, board.Id)
|
||||||
|
|
||||||
|
expected := datadog.Widget{}.GraphWidget
|
||||||
|
|
||||||
|
expected.X = 1
|
||||||
|
expected.Y = 1
|
||||||
|
expected.Width = 5
|
||||||
|
expected.Height = 5
|
||||||
|
expected.TitleText = "foo"
|
||||||
|
expected.TitleAlign = "center"
|
||||||
|
expected.TitleSize = 1
|
||||||
|
expected.Title = true
|
||||||
|
expected.Timeframe = "1d"
|
||||||
|
expected.Type = "alert_graph"
|
||||||
|
expected.Legend = true
|
||||||
|
expected.LegendSize = 5
|
||||||
|
expected.TileDef = datadog.TileDef{}
|
||||||
|
|
||||||
|
w := datadog.Widget{GraphWidget: expected}
|
||||||
|
|
||||||
|
board.Widgets = append(board.Widgets, w)
|
||||||
|
|
||||||
|
if err := client.UpdateScreenboard(board); err != nil {
|
||||||
|
t.Fatalf("Updating a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := client.GetScreenboard(board.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actualWidget := actual.Widgets[0].GraphWidget
|
||||||
|
|
||||||
|
assertEquals(t, "x", actualWidget.X, expected.X)
|
||||||
|
assertEquals(t, "y", actualWidget.Y, expected.Y)
|
||||||
|
assertEquals(t, "height", actualWidget.Height, expected.Height)
|
||||||
|
assertEquals(t, "width", actualWidget.Width, expected.Width)
|
||||||
|
assertEquals(t, "title_text", actualWidget.TitleText, expected.TitleText)
|
||||||
|
assertEquals(t, "title_size", actualWidget.TitleSize, expected.TitleSize)
|
||||||
|
assertEquals(t, "title_align", actualWidget.TitleAlign, expected.TitleAlign)
|
||||||
|
assertEquals(t, "title", actualWidget.Title, expected.Title)
|
||||||
|
assertEquals(t, "type", actualWidget.Type, expected.Type)
|
||||||
|
assertEquals(t, "timeframe", actualWidget.Timeframe, expected.Timeframe)
|
||||||
|
assertEquals(t, "legend", actualWidget.Legend, expected.Legend)
|
||||||
|
assertEquals(t, "legend_size", actualWidget.LegendSize, expected.LegendSize)
|
||||||
|
assertTileDefEquals(t, actualWidget.TileDef, expected.TileDef)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEventTimelineWidget(t *testing.T) {
|
||||||
|
board := createTestScreenboard(t)
|
||||||
|
defer cleanUpScreenboard(t, board.Id)
|
||||||
|
|
||||||
|
expected := datadog.Widget{}.EventTimelineWidget
|
||||||
|
|
||||||
|
expected.X = 1
|
||||||
|
expected.Y = 1
|
||||||
|
expected.Width = 5
|
||||||
|
expected.Height = 5
|
||||||
|
expected.TitleText = "foo"
|
||||||
|
expected.TitleAlign = "center"
|
||||||
|
expected.TitleSize = 1
|
||||||
|
expected.Title = true
|
||||||
|
expected.Query = "avg:system.load.1{foo} by {bar}"
|
||||||
|
expected.Timeframe = "1d"
|
||||||
|
expected.Type = "alert_graph"
|
||||||
|
|
||||||
|
w := datadog.Widget{EventTimelineWidget: expected}
|
||||||
|
|
||||||
|
board.Widgets = append(board.Widgets, w)
|
||||||
|
|
||||||
|
if err := client.UpdateScreenboard(board); err != nil {
|
||||||
|
t.Fatalf("Updating a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := client.GetScreenboard(board.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actualWidget := actual.Widgets[0].EventTimelineWidget
|
||||||
|
|
||||||
|
assertEquals(t, "x", actualWidget.X, expected.X)
|
||||||
|
assertEquals(t, "y", actualWidget.Y, expected.Y)
|
||||||
|
assertEquals(t, "height", actualWidget.Height, expected.Height)
|
||||||
|
assertEquals(t, "width", actualWidget.Width, expected.Width)
|
||||||
|
assertEquals(t, "title_text", actualWidget.TitleText, expected.TitleText)
|
||||||
|
assertEquals(t, "title_size", actualWidget.TitleSize, expected.TitleSize)
|
||||||
|
assertEquals(t, "title_align", actualWidget.TitleAlign, expected.TitleAlign)
|
||||||
|
assertEquals(t, "title", actualWidget.Title, expected.Title)
|
||||||
|
assertEquals(t, "type", actualWidget.Type, expected.Type)
|
||||||
|
assertEquals(t, "query", actualWidget.Query, expected.Query)
|
||||||
|
assertEquals(t, "timeframe", actualWidget.Timeframe, expected.Timeframe)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAlertGraphWidget(t *testing.T) {
|
||||||
|
board := createTestScreenboard(t)
|
||||||
|
defer cleanUpScreenboard(t, board.Id)
|
||||||
|
|
||||||
|
expected := datadog.Widget{}.AlertGraphWidget
|
||||||
|
|
||||||
|
expected.X = 1
|
||||||
|
expected.Y = 1
|
||||||
|
expected.Width = 5
|
||||||
|
expected.Height = 5
|
||||||
|
expected.TitleText = "foo"
|
||||||
|
expected.TitleAlign = "center"
|
||||||
|
expected.TitleSize = 1
|
||||||
|
expected.Title = true
|
||||||
|
expected.VizType = ""
|
||||||
|
expected.Timeframe = "1d"
|
||||||
|
expected.AddTimeframe = false
|
||||||
|
expected.AlertId = 1
|
||||||
|
expected.Type = "alert_graph"
|
||||||
|
|
||||||
|
w := datadog.Widget{AlertGraphWidget: expected}
|
||||||
|
|
||||||
|
board.Widgets = append(board.Widgets, w)
|
||||||
|
|
||||||
|
if err := client.UpdateScreenboard(board); err != nil {
|
||||||
|
t.Fatalf("Updating a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := client.GetScreenboard(board.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actualWidget := actual.Widgets[0].AlertGraphWidget
|
||||||
|
|
||||||
|
assertEquals(t, "x", actualWidget.X, expected.X)
|
||||||
|
assertEquals(t, "y", actualWidget.Y, expected.Y)
|
||||||
|
assertEquals(t, "height", actualWidget.Height, expected.Height)
|
||||||
|
assertEquals(t, "width", actualWidget.Width, expected.Width)
|
||||||
|
assertEquals(t, "title_text", actualWidget.TitleText, expected.TitleText)
|
||||||
|
assertEquals(t, "title_size", actualWidget.TitleSize, expected.TitleSize)
|
||||||
|
assertEquals(t, "title_align", actualWidget.TitleAlign, expected.TitleAlign)
|
||||||
|
assertEquals(t, "title", actualWidget.Title, expected.Title)
|
||||||
|
assertEquals(t, "type", actualWidget.Type, expected.Type)
|
||||||
|
assertEquals(t, "viz_type", actualWidget.VizType, expected.VizType)
|
||||||
|
assertEquals(t, "timeframe", actualWidget.Timeframe, expected.Timeframe)
|
||||||
|
assertEquals(t, "add_timeframe", actualWidget.AddTimeframe, expected.AddTimeframe)
|
||||||
|
assertEquals(t, "alert_id", actualWidget.AlertId, expected.AlertId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHostMapWidget(t *testing.T) {
|
||||||
|
board := createTestScreenboard(t)
|
||||||
|
defer cleanUpScreenboard(t, board.Id)
|
||||||
|
|
||||||
|
expected := datadog.Widget{}.HostMapWidget
|
||||||
|
|
||||||
|
expected.X = 1
|
||||||
|
expected.Y = 1
|
||||||
|
expected.Width = 5
|
||||||
|
expected.Height = 5
|
||||||
|
expected.TitleText = "foo"
|
||||||
|
expected.TitleAlign = "center"
|
||||||
|
expected.TitleSize = 1
|
||||||
|
expected.Title = true
|
||||||
|
expected.Type = "check_status"
|
||||||
|
expected.Query = "avg:system.load.1{foo} by {bar}"
|
||||||
|
expected.Timeframe = "1d"
|
||||||
|
expected.Legend = true
|
||||||
|
expected.LegendSize = 5
|
||||||
|
expected.TileDef = datadog.TileDef{}
|
||||||
|
|
||||||
|
w := datadog.Widget{HostMapWidget: expected}
|
||||||
|
|
||||||
|
board.Widgets = append(board.Widgets, w)
|
||||||
|
|
||||||
|
if err := client.UpdateScreenboard(board); err != nil {
|
||||||
|
t.Fatalf("Updating a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := client.GetScreenboard(board.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actualWidget := actual.Widgets[0].HostMapWidget
|
||||||
|
|
||||||
|
assertEquals(t, "x", actualWidget.X, expected.X)
|
||||||
|
assertEquals(t, "y", actualWidget.Y, expected.Y)
|
||||||
|
assertEquals(t, "height", actualWidget.Height, expected.Height)
|
||||||
|
assertEquals(t, "width", actualWidget.Width, expected.Width)
|
||||||
|
assertEquals(t, "title_text", actualWidget.TitleText, expected.TitleText)
|
||||||
|
assertEquals(t, "title_size", actualWidget.TitleSize, expected.TitleSize)
|
||||||
|
assertEquals(t, "title_align", actualWidget.TitleAlign, expected.TitleAlign)
|
||||||
|
assertEquals(t, "title", actualWidget.Title, expected.Title)
|
||||||
|
assertEquals(t, "type", actualWidget.Type, expected.Type)
|
||||||
|
assertEquals(t, "query", actualWidget.Query, expected.Query)
|
||||||
|
assertEquals(t, "timeframe", actualWidget.Timeframe, expected.Timeframe)
|
||||||
|
assertEquals(t, "query", actualWidget.Query, expected.Query)
|
||||||
|
assertEquals(t, "legend", actualWidget.Legend, expected.Legend)
|
||||||
|
assertEquals(t, "legend_size", actualWidget.LegendSize, expected.LegendSize)
|
||||||
|
assertTileDefEquals(t, actualWidget.TileDef, expected.TileDef)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckStatusWidget(t *testing.T) {
|
||||||
|
board := createTestScreenboard(t)
|
||||||
|
defer cleanUpScreenboard(t, board.Id)
|
||||||
|
|
||||||
|
expected := datadog.Widget{}.CheckStatusWidget
|
||||||
|
|
||||||
|
expected.X = 1
|
||||||
|
expected.Y = 1
|
||||||
|
expected.Width = 5
|
||||||
|
expected.Height = 5
|
||||||
|
expected.TitleText = "foo"
|
||||||
|
expected.TitleAlign = "center"
|
||||||
|
expected.TitleSize = 1
|
||||||
|
expected.Title = true
|
||||||
|
expected.Type = "check_status"
|
||||||
|
expected.Tags = "foo"
|
||||||
|
expected.Timeframe = "1d"
|
||||||
|
expected.Timeframe = "1d"
|
||||||
|
expected.Check = "datadog.agent.up"
|
||||||
|
expected.Group = "foo"
|
||||||
|
expected.Grouping = "check"
|
||||||
|
|
||||||
|
w := datadog.Widget{CheckStatusWidget: expected}
|
||||||
|
|
||||||
|
board.Widgets = append(board.Widgets, w)
|
||||||
|
|
||||||
|
if err := client.UpdateScreenboard(board); err != nil {
|
||||||
|
t.Fatalf("Updating a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := client.GetScreenboard(board.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actualWidget := actual.Widgets[0].CheckStatusWidget
|
||||||
|
|
||||||
|
assertEquals(t, "x", actualWidget.X, expected.X)
|
||||||
|
assertEquals(t, "y", actualWidget.Y, expected.Y)
|
||||||
|
assertEquals(t, "height", actualWidget.Height, expected.Height)
|
||||||
|
assertEquals(t, "width", actualWidget.Width, expected.Width)
|
||||||
|
assertEquals(t, "title_text", actualWidget.TitleText, expected.TitleText)
|
||||||
|
assertEquals(t, "title_size", actualWidget.TitleSize, expected.TitleSize)
|
||||||
|
assertEquals(t, "title_align", actualWidget.TitleAlign, expected.TitleAlign)
|
||||||
|
assertEquals(t, "title", actualWidget.Title, expected.Title)
|
||||||
|
assertEquals(t, "type", actualWidget.Type, expected.Type)
|
||||||
|
assertEquals(t, "tags", actualWidget.Tags, expected.Tags)
|
||||||
|
assertEquals(t, "timeframe", actualWidget.Timeframe, expected.Timeframe)
|
||||||
|
assertEquals(t, "check", actualWidget.Check, expected.Check)
|
||||||
|
assertEquals(t, "group", actualWidget.Group, expected.Group)
|
||||||
|
assertEquals(t, "grouping", actualWidget.Grouping, expected.Grouping)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIFrameWidget(t *testing.T) {
|
||||||
|
board := createTestScreenboard(t)
|
||||||
|
defer cleanUpScreenboard(t, board.Id)
|
||||||
|
|
||||||
|
expected := datadog.Widget{}.IFrameWidget
|
||||||
|
|
||||||
|
expected.X = 1
|
||||||
|
expected.Y = 1
|
||||||
|
expected.Width = 5
|
||||||
|
expected.Height = 5
|
||||||
|
expected.TitleText = "foo"
|
||||||
|
expected.TitleAlign = "center"
|
||||||
|
expected.TitleSize = 1
|
||||||
|
expected.Title = true
|
||||||
|
expected.Url = "http://www.example.com"
|
||||||
|
expected.Type = "iframe"
|
||||||
|
|
||||||
|
w := datadog.Widget{IFrameWidget: expected}
|
||||||
|
|
||||||
|
board.Widgets = append(board.Widgets, w)
|
||||||
|
|
||||||
|
if err := client.UpdateScreenboard(board); err != nil {
|
||||||
|
t.Fatalf("Updating a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := client.GetScreenboard(board.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actualWidget := actual.Widgets[0].IFrameWidget
|
||||||
|
|
||||||
|
assertEquals(t, "x", actualWidget.X, expected.X)
|
||||||
|
assertEquals(t, "y", actualWidget.Y, expected.Y)
|
||||||
|
assertEquals(t, "height", actualWidget.Height, expected.Height)
|
||||||
|
assertEquals(t, "width", actualWidget.Width, expected.Width)
|
||||||
|
assertEquals(t, "title_text", actualWidget.TitleText, expected.TitleText)
|
||||||
|
assertEquals(t, "title_size", actualWidget.TitleSize, expected.TitleSize)
|
||||||
|
assertEquals(t, "title_align", actualWidget.TitleAlign, expected.TitleAlign)
|
||||||
|
assertEquals(t, "title", actualWidget.Title, expected.Title)
|
||||||
|
assertEquals(t, "url", actualWidget.Url, expected.Url)
|
||||||
|
assertEquals(t, "type", actualWidget.Type, expected.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoteWidget(t *testing.T) {
|
||||||
|
board := createTestScreenboard(t)
|
||||||
|
defer cleanUpScreenboard(t, board.Id)
|
||||||
|
|
||||||
|
expected := datadog.Widget{}.NoteWidget
|
||||||
|
|
||||||
|
expected.X = 1
|
||||||
|
expected.Y = 1
|
||||||
|
expected.Width = 5
|
||||||
|
expected.Height = 5
|
||||||
|
expected.TitleText = "foo"
|
||||||
|
expected.TitleAlign = "center"
|
||||||
|
expected.TitleSize = 1
|
||||||
|
expected.Title = true
|
||||||
|
expected.Color = "green"
|
||||||
|
expected.FontSize = 5
|
||||||
|
expected.RefreshEvery = 60
|
||||||
|
expected.TickPos = "foo"
|
||||||
|
expected.TickEdge = "bar"
|
||||||
|
expected.Html = "<strong>baz</strong>"
|
||||||
|
expected.Tick = false
|
||||||
|
expected.Note = "quz"
|
||||||
|
expected.AutoRefresh = false
|
||||||
|
|
||||||
|
w := datadog.Widget{NoteWidget: expected}
|
||||||
|
|
||||||
|
board.Widgets = append(board.Widgets, w)
|
||||||
|
|
||||||
|
if err := client.UpdateScreenboard(board); err != nil {
|
||||||
|
t.Fatalf("Updating a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := client.GetScreenboard(board.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actualWidget := actual.Widgets[0].NoteWidget
|
||||||
|
|
||||||
|
assertEquals(t, "x", actualWidget.X, expected.X)
|
||||||
|
assertEquals(t, "y", actualWidget.Y, expected.Y)
|
||||||
|
assertEquals(t, "height", actualWidget.Height, expected.Height)
|
||||||
|
assertEquals(t, "width", actualWidget.Width, expected.Width)
|
||||||
|
assertEquals(t, "title_text", actualWidget.TitleText, expected.TitleText)
|
||||||
|
assertEquals(t, "title_size", actualWidget.TitleSize, expected.TitleSize)
|
||||||
|
assertEquals(t, "title_align", actualWidget.TitleAlign, expected.TitleAlign)
|
||||||
|
assertEquals(t, "title", actualWidget.Title, expected.Title)
|
||||||
|
assertEquals(t, "color", actualWidget.Color, expected.Color)
|
||||||
|
assertEquals(t, "front_size", actualWidget.FontSize, expected.FontSize)
|
||||||
|
assertEquals(t, "refresh_every", actualWidget.RefreshEvery, expected.RefreshEvery)
|
||||||
|
assertEquals(t, "tick_pos", actualWidget.TickPos, expected.TickPos)
|
||||||
|
assertEquals(t, "tick_edge", actualWidget.TickEdge, expected.TickEdge)
|
||||||
|
assertEquals(t, "tick", actualWidget.Tick, expected.Tick)
|
||||||
|
assertEquals(t, "html", actualWidget.Html, expected.Html)
|
||||||
|
assertEquals(t, "note", actualWidget.Note, expected.Note)
|
||||||
|
assertEquals(t, "auto_refresh", actualWidget.AutoRefresh, expected.AutoRefresh)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToplistWidget(t *testing.T) {
|
||||||
|
board := createTestScreenboard(t)
|
||||||
|
defer cleanUpScreenboard(t, board.Id)
|
||||||
|
|
||||||
|
expected := datadog.Widget{}.ToplistWidget
|
||||||
|
expected.X = 1
|
||||||
|
expected.Y = 1
|
||||||
|
expected.Width = 5
|
||||||
|
expected.Height = 5
|
||||||
|
expected.Type = "toplist"
|
||||||
|
expected.TitleText = "foo"
|
||||||
|
expected.TitleSize.Auto = false
|
||||||
|
expected.TitleSize.Size = 5
|
||||||
|
expected.TitleAlign = "center"
|
||||||
|
expected.Title = false
|
||||||
|
expected.Timeframe = "5m"
|
||||||
|
expected.Legend = false
|
||||||
|
expected.LegendSize = 5
|
||||||
|
|
||||||
|
w := datadog.Widget{ToplistWidget: expected}
|
||||||
|
|
||||||
|
board.Widgets = append(board.Widgets, w)
|
||||||
|
|
||||||
|
if err := client.UpdateScreenboard(board); err != nil {
|
||||||
|
t.Fatalf("Updating a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := client.GetScreenboard(board.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actualWidget := actual.Widgets[0].ToplistWidget
|
||||||
|
|
||||||
|
assertEquals(t, "x", actualWidget.X, expected.X)
|
||||||
|
assertEquals(t, "y", actualWidget.Y, expected.Y)
|
||||||
|
assertEquals(t, "height", actualWidget.Height, expected.Height)
|
||||||
|
assertEquals(t, "width", actualWidget.Width, expected.Width)
|
||||||
|
assertEquals(t, "title_text", actualWidget.TitleText, expected.TitleText)
|
||||||
|
assertEquals(t, "title_size", actualWidget.TitleSize, expected.TitleSize)
|
||||||
|
assertEquals(t, "title_align", actualWidget.TitleAlign, expected.TitleAlign)
|
||||||
|
assertEquals(t, "legend", actualWidget.Legend, expected.Legend)
|
||||||
|
assertEquals(t, "legend_size", actualWidget.LegendSize, expected.LegendSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEventSteamWidget(t *testing.T) {
|
||||||
|
board := createTestScreenboard(t)
|
||||||
|
defer cleanUpScreenboard(t, board.Id)
|
||||||
|
|
||||||
|
expected := datadog.Widget{}.EventStreamWidget
|
||||||
|
expected.EventSize = "1"
|
||||||
|
expected.Width = 1
|
||||||
|
expected.Height = 1
|
||||||
|
expected.X = 1
|
||||||
|
expected.Y = 1
|
||||||
|
expected.Query = "foo"
|
||||||
|
expected.Timeframe = "5w"
|
||||||
|
expected.Title = false
|
||||||
|
expected.TitleAlign = "center"
|
||||||
|
expected.TitleSize.Auto = false
|
||||||
|
expected.TitleSize.Size = 5
|
||||||
|
expected.TitleText = "bar"
|
||||||
|
expected.Type = "baz"
|
||||||
|
|
||||||
|
w := datadog.Widget{EventStreamWidget: expected}
|
||||||
|
|
||||||
|
board.Widgets = append(board.Widgets, w)
|
||||||
|
|
||||||
|
if err := client.UpdateScreenboard(board); err != nil {
|
||||||
|
t.Fatalf("Updating a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := client.GetScreenboard(board.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actualWidget := actual.Widgets[0].EventStreamWidget
|
||||||
|
|
||||||
|
assertEquals(t, "event_size", actualWidget.EventSize, expected.EventSize)
|
||||||
|
assertEquals(t, "width", actualWidget.Width, expected.Width)
|
||||||
|
assertEquals(t, "height", actualWidget.Height, expected.Height)
|
||||||
|
assertEquals(t, "x", actualWidget.X, expected.X)
|
||||||
|
assertEquals(t, "y", actualWidget.Y, expected.Y)
|
||||||
|
assertEquals(t, "query", actualWidget.Query, expected.Query)
|
||||||
|
assertEquals(t, "timeframe", actualWidget.Timeframe, expected.Timeframe)
|
||||||
|
assertEquals(t, "title", actualWidget.Title, expected.Title)
|
||||||
|
assertEquals(t, "title_align", actualWidget.TitleAlign, expected.TitleAlign)
|
||||||
|
assertEquals(t, "title_size", actualWidget.TitleSize, expected.TitleSize)
|
||||||
|
assertEquals(t, "title_text", actualWidget.TitleText, expected.TitleText)
|
||||||
|
assertEquals(t, "type", actualWidget.Type, expected.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImageWidget(t *testing.T) {
|
||||||
|
board := createTestScreenboard(t)
|
||||||
|
defer cleanUpScreenboard(t, board.Id)
|
||||||
|
|
||||||
|
expected := datadog.Widget{}.ImageWidget
|
||||||
|
|
||||||
|
expected.Width = 1
|
||||||
|
expected.Height = 1
|
||||||
|
expected.X = 1
|
||||||
|
expected.Y = 1
|
||||||
|
expected.Title = false
|
||||||
|
expected.TitleAlign = "center"
|
||||||
|
expected.TitleSize.Auto = false
|
||||||
|
expected.TitleSize.Size = 5
|
||||||
|
expected.TitleText = "bar"
|
||||||
|
expected.Type = "baz"
|
||||||
|
expected.Url = "qux"
|
||||||
|
expected.Sizing = "quuz"
|
||||||
|
|
||||||
|
w := datadog.Widget{ImageWidget: expected}
|
||||||
|
|
||||||
|
board.Widgets = append(board.Widgets, w)
|
||||||
|
|
||||||
|
if err := client.UpdateScreenboard(board); err != nil {
|
||||||
|
t.Fatalf("Updating a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := client.GetScreenboard(board.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actualWidget := actual.Widgets[0].ImageWidget
|
||||||
|
|
||||||
|
assertEquals(t, "width", actualWidget.Width, expected.Width)
|
||||||
|
assertEquals(t, "height", actualWidget.Height, expected.Height)
|
||||||
|
assertEquals(t, "x", actualWidget.X, expected.X)
|
||||||
|
assertEquals(t, "y", actualWidget.Y, expected.Y)
|
||||||
|
assertEquals(t, "title", actualWidget.Title, expected.Title)
|
||||||
|
assertEquals(t, "title_align", actualWidget.TitleAlign, expected.TitleAlign)
|
||||||
|
assertEquals(t, "title_size", actualWidget.TitleSize, expected.TitleSize)
|
||||||
|
assertEquals(t, "title_text", actualWidget.TitleText, expected.TitleText)
|
||||||
|
assertEquals(t, "type", actualWidget.Type, expected.Type)
|
||||||
|
assertEquals(t, "url", actualWidget.Url, expected.Url)
|
||||||
|
assertEquals(t, "sizing", actualWidget.Sizing, expected.Sizing)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFreeTextWidget(t *testing.T) {
|
||||||
|
board := createTestScreenboard(t)
|
||||||
|
defer cleanUpScreenboard(t, board.Id)
|
||||||
|
|
||||||
|
expected := datadog.Widget{}.FreeTextWidget
|
||||||
|
|
||||||
|
expected.X = 1
|
||||||
|
expected.Y = 1
|
||||||
|
expected.Height = 10
|
||||||
|
expected.Width = 10
|
||||||
|
expected.Text = "Test"
|
||||||
|
expected.FontSize = "16"
|
||||||
|
expected.TextAlign = "center"
|
||||||
|
|
||||||
|
w := datadog.Widget{FreeTextWidget: expected}
|
||||||
|
|
||||||
|
board.Widgets = append(board.Widgets, w)
|
||||||
|
|
||||||
|
if err := client.UpdateScreenboard(board); err != nil {
|
||||||
|
t.Fatalf("Updating a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := client.GetScreenboard(board.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actualWidget := actual.Widgets[0].FreeTextWidget
|
||||||
|
|
||||||
|
assertEquals(t, "font-size", actualWidget.FontSize, expected.FontSize)
|
||||||
|
assertEquals(t, "height", actualWidget.Height, expected.Height)
|
||||||
|
assertEquals(t, "width", actualWidget.Width, expected.Width)
|
||||||
|
assertEquals(t, "x", actualWidget.X, expected.X)
|
||||||
|
assertEquals(t, "y", actualWidget.Y, expected.Y)
|
||||||
|
assertEquals(t, "text", actualWidget.Text, expected.Text)
|
||||||
|
assertEquals(t, "text-align", actualWidget.TextAlign, expected.TextAlign)
|
||||||
|
assertEquals(t, "type", actualWidget.Type, expected.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimeseriesWidget(t *testing.T) {
|
||||||
|
board := createTestScreenboard(t)
|
||||||
|
defer cleanUpScreenboard(t, board.Id)
|
||||||
|
|
||||||
|
expected := datadog.Widget{}.TimeseriesWidget
|
||||||
|
expected.X = 1
|
||||||
|
expected.Y = 1
|
||||||
|
expected.Width = 20
|
||||||
|
expected.Height = 30
|
||||||
|
expected.Title = true
|
||||||
|
expected.TitleAlign = "centre"
|
||||||
|
expected.TitleSize = datadog.TextSize{Size: 16}
|
||||||
|
expected.TitleText = "Test"
|
||||||
|
expected.Timeframe = "1m"
|
||||||
|
|
||||||
|
w := datadog.Widget{TimeseriesWidget: expected}
|
||||||
|
|
||||||
|
board.Widgets = append(board.Widgets, w)
|
||||||
|
if err := client.UpdateScreenboard(board); err != nil {
|
||||||
|
t.Fatalf("Updating a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := client.GetScreenboard(board.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actualWidget := actual.Widgets[0].TimeseriesWidget
|
||||||
|
|
||||||
|
assertEquals(t, "height", actualWidget.Height, expected.Height)
|
||||||
|
assertEquals(t, "width", actualWidget.Width, expected.Width)
|
||||||
|
assertEquals(t, "x", actualWidget.X, expected.X)
|
||||||
|
assertEquals(t, "y", actualWidget.Y, expected.Y)
|
||||||
|
assertEquals(t, "title", actualWidget.Title, expected.Title)
|
||||||
|
assertEquals(t, "title-align", actualWidget.TitleAlign, expected.TitleAlign)
|
||||||
|
assertEquals(t, "title-size.size", actualWidget.TitleSize.Size, expected.TitleSize.Size)
|
||||||
|
assertEquals(t, "title-size.auto", actualWidget.TitleSize.Auto, expected.TitleSize.Auto)
|
||||||
|
assertEquals(t, "title-text", actualWidget.TitleText, expected.TitleText)
|
||||||
|
assertEquals(t, "type", actualWidget.Type, expected.Type)
|
||||||
|
assertEquals(t, "timeframe", actualWidget.Timeframe, expected.Timeframe)
|
||||||
|
assertEquals(t, "legend", actualWidget.Legend, expected.Legend)
|
||||||
|
assertTileDefEquals(t, actualWidget.TileDef, expected.TileDef)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryValueWidget(t *testing.T) {
|
||||||
|
board := createTestScreenboard(t)
|
||||||
|
defer cleanUpScreenboard(t, board.Id)
|
||||||
|
|
||||||
|
expected := datadog.Widget{}.QueryValueWidget
|
||||||
|
expected.X = 1
|
||||||
|
expected.Y = 1
|
||||||
|
expected.Width = 20
|
||||||
|
expected.Height = 30
|
||||||
|
expected.Title = true
|
||||||
|
expected.TitleAlign = "centre"
|
||||||
|
expected.TitleSize = datadog.TextSize{Size: 16}
|
||||||
|
expected.TitleText = "Test"
|
||||||
|
expected.Timeframe = "1m"
|
||||||
|
expected.TimeframeAggregator = "sum"
|
||||||
|
expected.Aggregator = "min"
|
||||||
|
expected.Query = "docker.containers.running"
|
||||||
|
expected.MetricType = "standard"
|
||||||
|
/* TODO: add test for conditional formats
|
||||||
|
"conditional_formats": [{
|
||||||
|
"comparator": ">",
|
||||||
|
"color": "white_on_red",
|
||||||
|
"custom_bg_color": null,
|
||||||
|
"value": 1,
|
||||||
|
"invert": false,
|
||||||
|
"custom_fg_color": null}],
|
||||||
|
*/
|
||||||
|
expected.IsValidQuery = true
|
||||||
|
expected.ResultCalcFunc = "raw"
|
||||||
|
expected.Aggregator = "avg"
|
||||||
|
expected.CalcFunc = "raw"
|
||||||
|
|
||||||
|
w := datadog.Widget{QueryValueWidget: expected}
|
||||||
|
|
||||||
|
board.Widgets = append(board.Widgets, w)
|
||||||
|
if err := client.UpdateScreenboard(board); err != nil {
|
||||||
|
t.Fatalf("Updating a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := client.GetScreenboard(board.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving a screenboard failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actualWidget := actual.Widgets[0].QueryValueWidget
|
||||||
|
|
||||||
|
assertEquals(t, "height", actualWidget.Height, expected.Height)
|
||||||
|
assertEquals(t, "width", actualWidget.Width, expected.Width)
|
||||||
|
assertEquals(t, "x", actualWidget.X, expected.X)
|
||||||
|
assertEquals(t, "y", actualWidget.Y, expected.Y)
|
||||||
|
assertEquals(t, "title", actualWidget.Title, expected.Title)
|
||||||
|
assertEquals(t, "title-align", actualWidget.TitleAlign, expected.TitleAlign)
|
||||||
|
assertEquals(t, "title-size.size", actualWidget.TitleSize.Size, expected.TitleSize.Size)
|
||||||
|
assertEquals(t, "title-size.auto", actualWidget.TitleSize.Auto, expected.TitleSize.Auto)
|
||||||
|
assertEquals(t, "title-text", actualWidget.TitleText, expected.TitleText)
|
||||||
|
assertEquals(t, "type", actualWidget.Type, expected.Type)
|
||||||
|
assertEquals(t, "timeframe", actualWidget.Timeframe, expected.Timeframe)
|
||||||
|
assertEquals(t, "timeframe-aggregator", actualWidget.TimeframeAggregator, expected.TimeframeAggregator)
|
||||||
|
assertEquals(t, "aggregator", actualWidget.Aggregator, expected.Aggregator)
|
||||||
|
assertEquals(t, "query", actualWidget.Query, expected.Query)
|
||||||
|
assertEquals(t, "is_valid_query", actualWidget.IsValidQuery, expected.IsValidQuery)
|
||||||
|
assertEquals(t, "res_calc_func", actualWidget.ResultCalcFunc, expected.ResultCalcFunc)
|
||||||
|
assertEquals(t, "aggr", actualWidget.Aggregator, expected.Aggregator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertTileDefEquals(t *testing.T, actual datadog.TileDef, expected datadog.TileDef) {
|
||||||
|
assertEquals(t, "num-events", len(actual.Events), len(expected.Events))
|
||||||
|
assertEquals(t, "num-requests", len(actual.Requests), len(expected.Requests))
|
||||||
|
assertEquals(t, "viz", actual.Viz, expected.Viz)
|
||||||
|
|
||||||
|
for i, event := range actual.Events {
|
||||||
|
assertEquals(t, "event-query", event.Query, expected.Events[i].Query)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, request := range actual.Requests {
|
||||||
|
assertEquals(t, "request-query", request.Query, expected.Requests[i].Query)
|
||||||
|
assertEquals(t, "request-type", request.Type, expected.Requests[i].Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertEquals(t *testing.T, attribute string, a, b interface{}) {
|
||||||
|
if a != b {
|
||||||
|
t.Errorf("The two %s values '%v' and '%v' are not equal", attribute, a, b)
|
||||||
|
}
|
||||||
|
}
|
143
vendor/github.com/zorkian/go-datadog-api/integration/screenboards_test.go
generated
vendored
Normal file
143
vendor/github.com/zorkian/go-datadog-api/integration/screenboards_test.go
generated
vendored
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/zorkian/go-datadog-api"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
client = initTest()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateAndDeleteScreenboard(t *testing.T) {
|
||||||
|
expected := getTestScreenboard()
|
||||||
|
// create the screenboard and compare it
|
||||||
|
actual, err := client.CreateScreenboard(expected)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Creating a screenboard failed when it shouldn't. (%s)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer cleanUpScreenboard(t, actual.Id)
|
||||||
|
|
||||||
|
assertScreenboardEquals(t, actual, expected)
|
||||||
|
|
||||||
|
// now try to fetch it freshly and compare it again
|
||||||
|
actual, err = client.GetScreenboard(actual.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving a screenboard failed when it shouldn't. (%s)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertScreenboardEquals(t, actual, expected)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShareAndRevokeScreenboard(t *testing.T) {
|
||||||
|
expected := getTestScreenboard()
|
||||||
|
// create the screenboard
|
||||||
|
actual, err := client.CreateScreenboard(expected)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Creating a screenboard failed when it shouldn't: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer cleanUpScreenboard(t, actual.Id)
|
||||||
|
|
||||||
|
// share screenboard and verify it was shared
|
||||||
|
var response datadog.ScreenShareResponse
|
||||||
|
err = client.ShareScreenboard(actual.Id, &response)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to share screenboard: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// revoke screenboard
|
||||||
|
err = client.RevokeScreenboard(actual.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to revoke sharing of screenboard: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateScreenboard(t *testing.T) {
|
||||||
|
board := createTestScreenboard(t)
|
||||||
|
defer cleanUpScreenboard(t, board.Id)
|
||||||
|
|
||||||
|
board.Title = "___New-Test-Board___"
|
||||||
|
if err := client.UpdateScreenboard(board); err != nil {
|
||||||
|
t.Fatalf("Updating a screenboard failed when it shouldn't: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := client.GetScreenboard(board.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving a screenboard failed when it shouldn't: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertScreenboardEquals(t, actual, board)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetScreenboards(t *testing.T) {
|
||||||
|
boards, err := client.GetScreenboards()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving screenboards failed when it shouldn't: %s", err)
|
||||||
|
}
|
||||||
|
num := len(boards)
|
||||||
|
|
||||||
|
board := createTestScreenboard(t)
|
||||||
|
defer cleanUpScreenboard(t, board.Id)
|
||||||
|
|
||||||
|
boards, err = client.GetScreenboards()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving screenboards failed when it shouldn't: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if num+1 != len(boards) {
|
||||||
|
t.Fatalf("Number of screenboards didn't match expected: %d != %d", len(boards), num+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTestScreenboard() *datadog.Screenboard {
|
||||||
|
return &datadog.Screenboard{
|
||||||
|
Title: "___Test-Board___",
|
||||||
|
Height: "600",
|
||||||
|
Width: "800",
|
||||||
|
Widgets: []datadog.Widget{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTestScreenboard(t *testing.T) *datadog.Screenboard {
|
||||||
|
board := getTestScreenboard()
|
||||||
|
board, err := client.CreateScreenboard(board)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Creating a screenboard failed when it shouldn't: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return board
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanUpScreenboard(t *testing.T, id int) {
|
||||||
|
if err := client.DeleteScreenboard(id); err != nil {
|
||||||
|
t.Fatalf("Deleting a screenboard failed when it shouldn't. Manual cleanup needed. (%s)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
deletedBoard, err := client.GetScreenboard(id)
|
||||||
|
if deletedBoard != nil {
|
||||||
|
t.Fatal("Screenboard hasn't been deleted when it should have been. Manual cleanup needed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Fetching deleted screenboard didn't lead to an error. Manual cleanup needed.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertScreenboardEquals(t *testing.T, actual, expected *datadog.Screenboard) {
|
||||||
|
if actual.Title != expected.Title {
|
||||||
|
t.Errorf("Screenboard title does not match: %s != %s", actual.Title, expected.Title)
|
||||||
|
}
|
||||||
|
if actual.Width != expected.Width {
|
||||||
|
t.Errorf("Screenboard width does not match: %s != %s", actual.Width, expected.Width)
|
||||||
|
}
|
||||||
|
if actual.Height != expected.Height {
|
||||||
|
t.Errorf("Screenboard width does not match: %s != %s", actual.Height, expected.Height)
|
||||||
|
}
|
||||||
|
if len(actual.Widgets) != len(expected.Widgets) {
|
||||||
|
t.Errorf("Number of Screenboard widgets does not match: %d != %d", len(actual.Widgets), len(expected.Widgets))
|
||||||
|
}
|
||||||
|
}
|
24
vendor/github.com/zorkian/go-datadog-api/integration/test_helpers.go
generated
vendored
Normal file
24
vendor/github.com/zorkian/go-datadog-api/integration/test_helpers.go
generated
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/zorkian/go-datadog-api"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
apiKey string
|
||||||
|
appKey string
|
||||||
|
client *datadog.Client
|
||||||
|
)
|
||||||
|
|
||||||
|
func initTest() *datadog.Client {
|
||||||
|
apiKey = os.Getenv("DATADOG_API_KEY")
|
||||||
|
appKey = os.Getenv("DATADOG_APP_KEY")
|
||||||
|
|
||||||
|
if apiKey == "" || appKey == "" {
|
||||||
|
log.Fatal("Please make sure to set the env variables 'DATADOG_API_KEY' and 'DATADOG_APP_KEY' before running this test")
|
||||||
|
}
|
||||||
|
|
||||||
|
return datadog.NewClient(apiKey, appKey)
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Datadog API for Go
|
||||||
|
*
|
||||||
|
* Please see the included LICENSE file for licensing information.
|
||||||
|
*
|
||||||
|
* Copyright 2013 by authors and contributors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package datadog
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// Client is the object that handles talking to the Datadog API. This maintains
|
||||||
|
// state information for a particular application connection.
|
||||||
|
type Client struct {
|
||||||
|
apiKey, appKey string
|
||||||
|
|
||||||
|
//The Http Client that is used to make requests
|
||||||
|
HttpClient *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient returns a new datadog.Client which can be used to access the API
|
||||||
|
// methods. The expected argument is the API key.
|
||||||
|
func NewClient(apiKey, appKey string) *Client {
|
||||||
|
return &Client{
|
||||||
|
apiKey: apiKey,
|
||||||
|
appKey: appKey,
|
||||||
|
HttpClient: http.DefaultClient,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
/*
|
||||||
|
* Datadog API for Go
|
||||||
|
*
|
||||||
|
* Please see the included LICENSE file for licensing information.
|
||||||
|
*
|
||||||
|
* Copyright 2013 by authors and contributors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package datadog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ThresholdCount struct {
|
||||||
|
Ok json.Number `json:"ok,omitempty"`
|
||||||
|
Critical json.Number `json:"critical,omitempty"`
|
||||||
|
Warning json.Number `json:"warning,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
NoDataTimeframe int `json:"no_data_timeframe,omitempty"`
|
||||||
|
NotifyAudit bool `json:"notify_audit,omitempty"`
|
||||||
|
NotifyNoData bool `json:"notify_no_data,omitempty"`
|
||||||
|
RenotifyInterval int `json:"renotify_interval,omitempty"`
|
||||||
|
Silenced map[string]int `json:"silenced,omitempty"`
|
||||||
|
TimeoutH int `json:"timeout_h,omitempty"`
|
||||||
|
EscalationMessage string `json:"escalation_message,omitempty"`
|
||||||
|
Thresholds ThresholdCount `json:"thresholds,omitempty"`
|
||||||
|
IncludeTags bool `json:"include_tags,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//Monitors allow you to watch a metric or check that you care about,
|
||||||
|
//notifying your team when some defined threshold is exceeded.
|
||||||
|
type Monitor struct {
|
||||||
|
Id int `json:"id,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Query string `json:"query,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
Tags []string `json:"tags,omitempty"`
|
||||||
|
Options Options `json:"options,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// reqMonitors receives a slice of all monitors
|
||||||
|
type reqMonitors struct {
|
||||||
|
Monitors []Monitor `json:"monitors,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Createmonitor adds a new monitor to the system. This returns a pointer to an
|
||||||
|
// monitor so you can pass that to Updatemonitor later if needed.
|
||||||
|
func (self *Client) CreateMonitor(monitor *Monitor) (*Monitor, error) {
|
||||||
|
var out Monitor
|
||||||
|
err := self.doJsonRequest("POST", "/v1/monitor", monitor, &out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updatemonitor takes an monitor that was previously retrieved through some method
|
||||||
|
// and sends it back to the server.
|
||||||
|
func (self *Client) UpdateMonitor(monitor *Monitor) error {
|
||||||
|
return self.doJsonRequest("PUT", fmt.Sprintf("/v1/monitor/%d", monitor.Id),
|
||||||
|
monitor, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getmonitor retrieves an monitor by identifier.
|
||||||
|
func (self *Client) GetMonitor(id int) (*Monitor, error) {
|
||||||
|
var out Monitor
|
||||||
|
err := self.doJsonRequest("GET", fmt.Sprintf("/v1/monitor/%d", id), nil, &out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deletemonitor removes an monitor from the system.
|
||||||
|
func (self *Client) DeleteMonitor(id int) error {
|
||||||
|
return self.doJsonRequest("DELETE", fmt.Sprintf("/v1/monitor/%d", id),
|
||||||
|
nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMonitors returns a slice of all monitors.
|
||||||
|
func (self *Client) GetMonitors() ([]Monitor, error) {
|
||||||
|
var out reqMonitors
|
||||||
|
err := self.doJsonRequest("GET", "/v1/monitor", nil, &out.Monitors)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out.Monitors, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MuteMonitors turns off monitoring notifications.
|
||||||
|
func (self *Client) MuteMonitors() error {
|
||||||
|
return self.doJsonRequest("POST", "/v1/monitor/mute_all", nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmuteMonitors turns on monitoring notifications.
|
||||||
|
func (self *Client) UnmuteMonitors() error {
|
||||||
|
return self.doJsonRequest("POST", "/v1/monitor/unmute_all", nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MuteMonitor turns off monitoring notifications for a monitor.
|
||||||
|
func (self *Client) MuteMonitor(id int) error {
|
||||||
|
return self.doJsonRequest("POST", fmt.Sprintf("/v1/monitor/%d/mute", id), nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmuteMonitor turns on monitoring notifications for a monitor.
|
||||||
|
func (self *Client) UnmuteMonitor(id int) error {
|
||||||
|
return self.doJsonRequest("POST", fmt.Sprintf("/v1/monitor/%d/unmute", id), nil, nil)
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
/*
|
||||||
|
* Datadog API for Go
|
||||||
|
*
|
||||||
|
* Please see the included LICENSE file for licensing information.
|
||||||
|
*
|
||||||
|
* Copyright 2013 by authors and contributors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package datadog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cenkalti/backoff"
|
||||||
|
)
|
||||||
|
|
||||||
|
// uriForAPI is to be called with something like "/v1/events" and it will give
|
||||||
|
// the proper request URI to be posted to.
|
||||||
|
func (self *Client) uriForAPI(api string) string {
|
||||||
|
url := os.Getenv("DATADOG_HOST")
|
||||||
|
if url == "" {
|
||||||
|
url = "https://app.datadoghq.com"
|
||||||
|
}
|
||||||
|
if strings.Index(api, "?") > -1 {
|
||||||
|
return url + "/api" + api + "&api_key=" +
|
||||||
|
self.apiKey + "&application_key=" + self.appKey
|
||||||
|
} else {
|
||||||
|
return url + "/api" + api + "?api_key=" +
|
||||||
|
self.apiKey + "&application_key=" + self.appKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// doJsonRequest is the simplest type of request: a method on a URI that returns
|
||||||
|
// some JSON result which we unmarshal into the passed interface.
|
||||||
|
func (self *Client) doJsonRequest(method, api string,
|
||||||
|
reqbody, out interface{}) error {
|
||||||
|
// Handle the body if they gave us one.
|
||||||
|
var bodyreader io.Reader
|
||||||
|
if method != "GET" && reqbody != nil {
|
||||||
|
bjson, err := json.Marshal(reqbody)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bodyreader = bytes.NewReader(bjson)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(method, self.uriForAPI(api), bodyreader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if bodyreader != nil {
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the request and retry it if it's not a POST request
|
||||||
|
var resp *http.Response
|
||||||
|
if method == "POST" {
|
||||||
|
resp, err = self.HttpClient.Do(req)
|
||||||
|
} else {
|
||||||
|
resp, err = self.doRequestWithRetries(req, 60*time.Second)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fmt.Errorf("API error %s: %s", resp.Status, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If they don't care about the body, then we don't care to give them one,
|
||||||
|
// so bail out because we're done.
|
||||||
|
if out == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we got no body, by default let's just make an empty JSON dict. This
|
||||||
|
// saves us some work in other parts of the code.
|
||||||
|
if len(body) == 0 {
|
||||||
|
body = []byte{'{', '}'}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(body, &out)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// doRequestWithRetries performs an HTTP request repeatedly for maxTime or until
|
||||||
|
// no error and no HTTP response code higher than 299 is returned.
|
||||||
|
func (self *Client) doRequestWithRetries(req *http.Request, maxTime time.Duration) (*http.Response, error) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
resp *http.Response
|
||||||
|
bo = backoff.NewExponentialBackOff()
|
||||||
|
)
|
||||||
|
bo.MaxElapsedTime = maxTime
|
||||||
|
|
||||||
|
err = backoff.Retry(func() error {
|
||||||
|
resp, err = self.HttpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||||
|
return errors.New("API error: " + resp.Status)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}, bo)
|
||||||
|
|
||||||
|
return resp, err
|
||||||
|
}
|
|
@ -0,0 +1,287 @@
|
||||||
|
package datadog
|
||||||
|
|
||||||
|
type TextSize struct {
|
||||||
|
Size int
|
||||||
|
Auto bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type TileDef struct {
|
||||||
|
Events []TileDefEvent `json:"events,omitempty"`
|
||||||
|
Markers []TimeseriesMarker `json:"markers,omitempty"`
|
||||||
|
Requests []TimeseriesRequest `json:"requests,omitempty"`
|
||||||
|
Viz string `json:"viz,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TimeseriesRequest struct {
|
||||||
|
Query string `json:"q,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
ConditionalFormats []ConditionalFormat `json:"conditional_formats,omitempty"`
|
||||||
|
Style TimeseriesRequestStyle `json:"style,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TimeseriesRequestStyle struct {
|
||||||
|
Palette string `json:"palette,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TimeseriesMarker struct {
|
||||||
|
Label string `json:"label,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Value string `json:"value,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TileDefEvent struct {
|
||||||
|
Query string `json:"q"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AlertValueWidget struct {
|
||||||
|
TitleSize int `json:"title_size,omitempty"`
|
||||||
|
Title bool `json:"title,omitempty"`
|
||||||
|
TitleAlign string `json:"title_align,omitempty"`
|
||||||
|
TextAlign string `json:"text_align,omitempty"`
|
||||||
|
TitleText string `json:"title_text,omitempty"`
|
||||||
|
Precision int `json:"precision,omitempty"`
|
||||||
|
AlertId int `json:"alert_id,omitempty"`
|
||||||
|
Timeframe string `json:"timeframe,omitempty"`
|
||||||
|
AddTimeframe bool `json:"add_timeframe,omitempty"`
|
||||||
|
Y int `json:"y,omitempty"`
|
||||||
|
X int `json:"x,omitempty"`
|
||||||
|
TextSize string `json:"text_size,omitempty"`
|
||||||
|
Height int `json:"height,omitempty"`
|
||||||
|
Width int `json:"width,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Unit string `json:"unit,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChangeWidget struct {
|
||||||
|
TitleSize int `json:"title_size,omitempty"`
|
||||||
|
Title bool `json:"title,omitempty"`
|
||||||
|
TitleAlign string `json:"title_align,omitempty"`
|
||||||
|
TitleText string `json:"title_text,omitempty"`
|
||||||
|
Height int `json:"height,omitempty"`
|
||||||
|
Width int `json:"width,omitempty"`
|
||||||
|
X int `json:"y,omitempty"`
|
||||||
|
Y int `json:"x,omitempty"`
|
||||||
|
Aggregator string `json:"aggregator,omitempty"`
|
||||||
|
TileDef TileDef `json:"tile_def,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GraphWidget struct {
|
||||||
|
TitleSize int `json:"title_size,omitempty"`
|
||||||
|
Title bool `json:"title,omitempty"`
|
||||||
|
TitleAlign string `json:"title_align,omitempty"`
|
||||||
|
TitleText string `json:"title_text,omitempty"`
|
||||||
|
Height int `json:"height,omitempty"`
|
||||||
|
Width int `json:"width,omitempty"`
|
||||||
|
X int `json:"y,omitempty"`
|
||||||
|
Y int `json:"x,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Timeframe string `json:"timeframe,omitempty"`
|
||||||
|
LegendSize int `json:"legend_size,omitempty"`
|
||||||
|
Legend bool `json:"legend,omitempty"`
|
||||||
|
TileDef TileDef `json:"tile_def,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventTimelineWidget struct {
|
||||||
|
TitleSize int `json:"title_size,omitempty"`
|
||||||
|
Title bool `json:"title,omitempty"`
|
||||||
|
TitleAlign string `json:"title_align,omitempty"`
|
||||||
|
TitleText string `json:"title_text,omitempty"`
|
||||||
|
Height int `json:"height,omitempty"`
|
||||||
|
Width int `json:"width,omitempty"`
|
||||||
|
X int `json:"y,omitempty"`
|
||||||
|
Y int `json:"x,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Timeframe string `json:"timeframe,omitempty"`
|
||||||
|
Query string `json:"query,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AlertGraphWidget struct {
|
||||||
|
TitleSize int `json:"title_size,omitempty"`
|
||||||
|
VizType string `json:"timeseries,omitempty"`
|
||||||
|
Title bool `json:"title,omitempty"`
|
||||||
|
TitleAlign string `json:"title_align,omitempty"`
|
||||||
|
TitleText string `json:"title_text,omitempty"`
|
||||||
|
Height int `json:"height,omitempty"`
|
||||||
|
Width int `json:"width,omitempty"`
|
||||||
|
X int `json:"y,omitempty"`
|
||||||
|
Y int `json:"x,omitempty"`
|
||||||
|
AlertId int `json:"alert_id,omitempty"`
|
||||||
|
Timeframe string `json:"timeframe,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
AddTimeframe bool `json:"add_timeframe,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HostMapWidget struct {
|
||||||
|
TitleSize int `json:"title_size,omitempty"`
|
||||||
|
Title bool `json:"title,omitempty"`
|
||||||
|
TitleAlign string `json:"title_align,omitempty"`
|
||||||
|
TitleText string `json:"title_text,omitempty"`
|
||||||
|
Height int `json:"height,omitempty"`
|
||||||
|
Width int `json:"width,omitempty"`
|
||||||
|
X int `json:"y,omitempty"`
|
||||||
|
Y int `json:"x,omitempty"`
|
||||||
|
Query string `json:"query,omitempty"`
|
||||||
|
Timeframe string `json:"timeframe,omitempty"`
|
||||||
|
LegendSize int `json:"legend_size,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Legend bool `json:"legend,omitempty"`
|
||||||
|
TileDef TileDef `json:"tile_def,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CheckStatusWidget struct {
|
||||||
|
TitleSize int `json:"title_size,omitempty"`
|
||||||
|
Title bool `json:"title,omitempty"`
|
||||||
|
TitleAlign string `json:"title_align,omitempty"`
|
||||||
|
TextAlign string `json:"text_align,omitempty"`
|
||||||
|
TitleText string `json:"title_text,omitempty"`
|
||||||
|
Height int `json:"height,omitempty"`
|
||||||
|
Width int `json:"width,omitempty"`
|
||||||
|
X int `json:"y,omitempty"`
|
||||||
|
Y int `json:"x,omitempty"`
|
||||||
|
Tags string `json:"tags,omitempty"`
|
||||||
|
Timeframe string `json:"timeframe,omitempty"`
|
||||||
|
TextSize string `json:"text_size,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Check string `json:"check,omitempty"`
|
||||||
|
Group string `json:"group,omitempty"`
|
||||||
|
Grouping string `json:"grouping,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IFrameWidget struct {
|
||||||
|
TitleSize int `json:"title_size,omitempty"`
|
||||||
|
Title bool `json:"title,omitempty"`
|
||||||
|
Url string `json:"url,omitempty"`
|
||||||
|
TitleAlign string `json:"title_align,omitempty"`
|
||||||
|
TitleText string `json:"title_text,omitempty"`
|
||||||
|
Height int `json:"height,omitempty"`
|
||||||
|
Width int `json:"width,omitempty"`
|
||||||
|
X int `json:"y,omitempty"`
|
||||||
|
Y int `json:"x,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NoteWidget struct {
|
||||||
|
TitleSize int `json:"title_size,omitempty"`
|
||||||
|
Title bool `json:"title,omitempty"`
|
||||||
|
RefreshEvery int `json:"refresh_every,omitempty"`
|
||||||
|
TickPos string `json:"tick_pos,omitempty"`
|
||||||
|
TitleAlign string `json:"title_align,omitempty"`
|
||||||
|
TickEdge string `json:"tick_edge,omitempty"`
|
||||||
|
TextAlign string `json:"text_align,omitempty"`
|
||||||
|
TitleText string `json:"title_text,omitempty"`
|
||||||
|
Height int `json:"height,omitempty"`
|
||||||
|
Color string `json:"bgcolor,omitempty"`
|
||||||
|
Html string `json:"html,omitempty"`
|
||||||
|
Y int `json:"y,omitempty"`
|
||||||
|
X int `json:"x,omitempty"`
|
||||||
|
FontSize int `json:"font_size,omitempty"`
|
||||||
|
Tick bool `json:"tick,omitempty"`
|
||||||
|
Note string `json:"type,omitempty"`
|
||||||
|
Width int `json:"width,omitempty"`
|
||||||
|
AutoRefresh bool `json:"auto_refresh,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TimeseriesWidget struct {
|
||||||
|
Height int `json:"height,omitempty"`
|
||||||
|
Legend bool `json:"legend,omitempty"`
|
||||||
|
TileDef TileDef `json:"tile_def,omitempty"`
|
||||||
|
Timeframe string `json:"timeframe,omitempty"`
|
||||||
|
Title bool `json:"title,omitempty"`
|
||||||
|
TitleAlign string `json:"title_align,omitempty"`
|
||||||
|
TitleSize TextSize `json:"title_size,omitempty"`
|
||||||
|
TitleText string `json:"title_text,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Width int `json:"width,omitempty"`
|
||||||
|
X int `json:"x,omitempty"`
|
||||||
|
Y int `json:"y,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueryValueWidget struct {
|
||||||
|
Timeframe string `json:"timeframe,omitempty"`
|
||||||
|
TimeframeAggregator string `json:"aggr,omitempty"`
|
||||||
|
Aggregator string `json:"aggregator,omitempty"`
|
||||||
|
CalcFunc string `json:"calc_func,omitempty"`
|
||||||
|
ConditionalFormats []ConditionalFormat `json:"conditional_formats,omitempty"`
|
||||||
|
Height int `json:"height,omitempty"`
|
||||||
|
IsValidQuery bool `json:"is_valid_query,omitempty,omitempty"`
|
||||||
|
Metric string `json:"metric,omitempty"`
|
||||||
|
MetricType string `json:"metric_type,omitempty"`
|
||||||
|
Precision int `json:"precision,omitempty"`
|
||||||
|
Query string `json:"query,omitempty"`
|
||||||
|
ResultCalcFunc string `json:"res_calc_func,omitempty"`
|
||||||
|
Tags []string `json:"tags,omitempty"`
|
||||||
|
TextAlign string `json:"text_align,omitempty"`
|
||||||
|
TextSize TextSize `json:"text_size,omitempty"`
|
||||||
|
Title bool `json:"title,omitempty"`
|
||||||
|
TitleAlign string `json:"title_align,omitempty"`
|
||||||
|
TitleSize TextSize `json:"title_size,omitempty"`
|
||||||
|
TitleText string `json:"title_text,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Unit string `json:"auto,omitempty"`
|
||||||
|
Width int `json:"width,omitempty"`
|
||||||
|
X int `json:"x,omitempty"`
|
||||||
|
Y int `json:"y,omitempty"`
|
||||||
|
}
|
||||||
|
type ConditionalFormat struct {
|
||||||
|
Color string `json:"color,omitempty"`
|
||||||
|
Comparator string `json:"comparator,omitempty"`
|
||||||
|
Inverted bool `json:"invert,omitempty"`
|
||||||
|
Value int `json:"value,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ToplistWidget struct {
|
||||||
|
Height int `json:"height,omitempty"`
|
||||||
|
Legend bool `json:"legend,omitempty"`
|
||||||
|
LegendSize int `json:"legend_size,omitempty"`
|
||||||
|
TileDef TileDef `json:"tile_def,omitempty"`
|
||||||
|
Timeframe string `json:"timeframe,omitempty"`
|
||||||
|
Title bool `json:"title,omitempty"`
|
||||||
|
TitleAlign string `json:"title_align,omitempty"`
|
||||||
|
TitleSize TextSize `json:"title_size,omitempty"`
|
||||||
|
TitleText string `json:"title_text,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Width int `json:"width,omitempty"`
|
||||||
|
X int `json:"x,omitempty"`
|
||||||
|
Y int `json:"y,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventStreamWidget struct {
|
||||||
|
EventSize string `json:"event_size,omitempty"`
|
||||||
|
Height int `json:"height,omitempty"`
|
||||||
|
Query string `json:"query,omitempty"`
|
||||||
|
Timeframe string `json:"timeframe,omitempty"`
|
||||||
|
Title bool `json:"title,omitempty"`
|
||||||
|
TitleAlign string `json:"title_align,omitempty"`
|
||||||
|
TitleSize TextSize `json:"title_size,omitempty"`
|
||||||
|
TitleText string `json:"title_text,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Width int `json:"width,omitempty"`
|
||||||
|
X int `json:"x,omitempty"`
|
||||||
|
Y int `json:"y,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FreeTextWidget struct {
|
||||||
|
Color string `json:"color,omitempty"`
|
||||||
|
FontSize string `json:"font_size,omitempty"`
|
||||||
|
Height int `json:"height,omitempty"`
|
||||||
|
Text string `json:"text,omitempty"`
|
||||||
|
TextAlign string `json:"text_align,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Width int `json:"width,omitempty"`
|
||||||
|
X int `json:"x,omitempty"`
|
||||||
|
Y int `json:"y,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImageWidget struct {
|
||||||
|
Height int `json:"height,omitempty"`
|
||||||
|
Sizing string `json:"sizing,omitempty"`
|
||||||
|
Title bool `json:"title,omitempty"`
|
||||||
|
TitleAlign string `json:"title_align,omitempty"`
|
||||||
|
TitleSize TextSize `json:"title_size,omitempty"`
|
||||||
|
TitleText string `json:"title_text,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Url string `json:"url,omitempty"`
|
||||||
|
Width int `json:"width,omitempty"`
|
||||||
|
X int `json:"x,omitempty"`
|
||||||
|
Y int `json:"y,omitempty"`
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
/*
|
||||||
|
* Datadog API for Go
|
||||||
|
*
|
||||||
|
* Please see the included LICENSE file for licensing information.
|
||||||
|
*
|
||||||
|
* Copyright 2013 by authors and contributors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package datadog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Screenboard represents a user created screenboard. This is the full screenboard
|
||||||
|
// struct when we load a screenboard in detail.
|
||||||
|
type Screenboard struct {
|
||||||
|
Id int `json:"id,omitempty"`
|
||||||
|
Title string `json:"board_title,omitempty"`
|
||||||
|
Height string `json:"height,omitempty"`
|
||||||
|
Width string `json:"width,omitempty"`
|
||||||
|
Shared bool `json:"shared,omitempty"`
|
||||||
|
Templated bool `json:"templated,omitempty"`
|
||||||
|
TemplateVariables []TemplateVariable `json:"template_variables,omitempty"`
|
||||||
|
Widgets []Widget `json:"widgets,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//type Widget struct {
|
||||||
|
type Widget struct {
|
||||||
|
Default string `json:"default,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Prefix string `json:"prefix,omitempty"`
|
||||||
|
TimeseriesWidget TimeseriesWidget `json:"timeseries,omitempty"`
|
||||||
|
QueryValueWidget QueryValueWidget `json:"query_value,omitempty"`
|
||||||
|
EventStreamWidget EventStreamWidget `json:"event_stream,omitempty"`
|
||||||
|
FreeTextWidget FreeTextWidget `json:"free_text,omitempty"`
|
||||||
|
ToplistWidget ToplistWidget `json:"toplist,omitempty"`
|
||||||
|
ImageWidget ImageWidget `json:"image,omitempty"`
|
||||||
|
ChangeWidget ChangeWidget `json:"change,omitempty"`
|
||||||
|
GraphWidget GraphWidget `json:"graph,omitempty"`
|
||||||
|
EventTimelineWidget EventTimelineWidget `json:"event_timeline,omitempty"`
|
||||||
|
AlertValueWidget AlertValueWidget `json:"alert_value,omitempty"`
|
||||||
|
AlertGraphWidget AlertGraphWidget `json:"alert_graph,omitempty"`
|
||||||
|
HostMapWidget HostMapWidget `json:"hostmap,omitempty"`
|
||||||
|
CheckStatusWidget CheckStatusWidget `json:"check_status,omitempty"`
|
||||||
|
IFrameWidget IFrameWidget `json:"iframe,omitempty"`
|
||||||
|
NoteWidget NoteWidget `json:"frame,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScreenboardLite represents a user created screenboard. This is the mini
|
||||||
|
// struct when we load the summaries.
|
||||||
|
type ScreenboardLite struct {
|
||||||
|
Id int `json:"id,omitempty"`
|
||||||
|
Resource string `json:"resource,omitempty"`
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// reqGetScreenboards from /api/v1/screen
|
||||||
|
type reqGetScreenboards struct {
|
||||||
|
Screenboards []*ScreenboardLite `json:"screenboards"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetScreenboard returns a single screenboard created on this account.
|
||||||
|
func (self *Client) GetScreenboard(id int) (*Screenboard, error) {
|
||||||
|
out := &Screenboard{}
|
||||||
|
err := self.doJsonRequest("GET", fmt.Sprintf("/v1/screen/%d", id), nil, out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetScreenboards returns a list of all screenboards created on this account.
|
||||||
|
func (self *Client) GetScreenboards() ([]*ScreenboardLite, error) {
|
||||||
|
var out reqGetScreenboards
|
||||||
|
err := self.doJsonRequest("GET", "/v1/screen", nil, &out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out.Screenboards, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteScreenboard deletes a screenboard by the identifier.
|
||||||
|
func (self *Client) DeleteScreenboard(id int) error {
|
||||||
|
return self.doJsonRequest("DELETE", fmt.Sprintf("/v1/screen/%d", id), nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateScreenboard creates a new screenboard when given a Screenboard struct. Note
|
||||||
|
// that the Id, Resource, Url and similar elements are not used in creation.
|
||||||
|
func (self *Client) CreateScreenboard(board *Screenboard) (*Screenboard, error) {
|
||||||
|
out := &Screenboard{}
|
||||||
|
if err := self.doJsonRequest("POST", "/v1/screen", board, out); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateScreenboard in essence takes a Screenboard struct and persists it back to
|
||||||
|
// the server. Use this if you've updated your local and need to push it back.
|
||||||
|
func (self *Client) UpdateScreenboard(board *Screenboard) error {
|
||||||
|
return self.doJsonRequest("PUT", fmt.Sprintf("/v1/screen/%d", board.Id), board, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ScreenShareResponse struct {
|
||||||
|
BoardId int `json:"board_id"`
|
||||||
|
PublicUrl string `json:"public_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShareScreenboard shares an existing screenboard, it takes and updates ScreenShareResponse
|
||||||
|
func (self *Client) ShareScreenboard(id int, response *ScreenShareResponse) error {
|
||||||
|
return self.doJsonRequest("GET", fmt.Sprintf("/v1/screen/share/%d", id), nil, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevokeScreenboard revokes a currently shared screenboard
|
||||||
|
func (self *Client) RevokeScreenboard(id int) error {
|
||||||
|
return self.doJsonRequest("DELETE", fmt.Sprintf("/v1/screen/share/%d", id), nil, nil)
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Datadog API for Go
|
||||||
|
*
|
||||||
|
* Please see the included LICENSE file for licensing information.
|
||||||
|
*
|
||||||
|
* Copyright 2013 by authors and contributors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package datadog
|
||||||
|
|
||||||
|
// reqSearch is the container for receiving search results.
|
||||||
|
type reqSearch struct {
|
||||||
|
Results struct {
|
||||||
|
Hosts []string `json:"hosts,omitempty"`
|
||||||
|
Metrics []string `json:"metrics,omitempty"`
|
||||||
|
} `json:"results"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchHosts searches through the hosts facet, returning matching hostnames.
|
||||||
|
func (self *Client) SearchHosts(search string) ([]string, error) {
|
||||||
|
var out reqSearch
|
||||||
|
err := self.doJsonRequest("GET", "/v1/search?q=hosts:"+search, nil, &out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out.Results.Hosts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchMetrics searches through the metrics facet, returning matching ones.
|
||||||
|
func (self *Client) SearchMetrics(search string) ([]string, error) {
|
||||||
|
var out reqSearch
|
||||||
|
err := self.doJsonRequest("GET", "/v1/search?q=metrics:"+search, nil, &out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out.Results.Metrics, nil
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* Datadog API for Go
|
||||||
|
*
|
||||||
|
* Please see the included LICENSE file for licensing information.
|
||||||
|
*
|
||||||
|
* Copyright 2013 by authors and contributors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package datadog
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
// DataPoint is a tuple of [UNIX timestamp, value]. This has to use floats
|
||||||
|
// because the value could be non-integer.
|
||||||
|
type DataPoint [2]float64
|
||||||
|
|
||||||
|
// Metric represents a collection of data points that we might send or receive
|
||||||
|
// on one single metric line.
|
||||||
|
type Metric struct {
|
||||||
|
Metric string `json:"metric,omitempty"`
|
||||||
|
Points []DataPoint `json:"points,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Host string `json:"host,omitempty"`
|
||||||
|
Tags []string `json:"tags,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Series represents a collection of data points we get when we query for timeseries data
|
||||||
|
type Series struct {
|
||||||
|
Metric string `json:"metric,omitempty"`
|
||||||
|
DisplayName string `json:"display_name,omitempty"`
|
||||||
|
Points []DataPoint `json:"pointlist,omitempty"`
|
||||||
|
Start float64 `json:"start,omitempty"`
|
||||||
|
End float64 `json:"end,omitempty"`
|
||||||
|
Interval int `json:"interval,omitempty"`
|
||||||
|
Aggr string `json:"aggr,omitempty"`
|
||||||
|
Length int `json:"length,omitempty"`
|
||||||
|
Scope string `json:"scope,omitempty"`
|
||||||
|
Expression string `json:"expression,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// reqPostSeries from /api/v1/series
|
||||||
|
type reqPostSeries struct {
|
||||||
|
Series []Metric `json:"series,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// reqMetrics is the container for receiving metric results.
|
||||||
|
type reqMetrics struct {
|
||||||
|
Series []Series `json:"series,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostMetrics takes as input a slice of metrics and then posts them up to the
|
||||||
|
// server for posting data.
|
||||||
|
func (client *Client) PostMetrics(series []Metric) error {
|
||||||
|
return client.doJsonRequest("POST", "/v1/series",
|
||||||
|
reqPostSeries{Series: series}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryMetrics takes as input from, to (seconds from Unix Epoch) and query string and then requests
|
||||||
|
// timeseries data for that time peried
|
||||||
|
func (client *Client) QueryMetrics(from, to int64, query string) ([]Series, error) {
|
||||||
|
var out reqMetrics
|
||||||
|
err := client.doJsonRequest("GET", "/v1/query?from="+strconv.FormatInt(from, 10)+"&to="+strconv.FormatInt(to, 10)+"&query="+query,
|
||||||
|
nil, &out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out.Series, nil
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Datadog API for Go
|
||||||
|
*
|
||||||
|
* Please see the included LICENSE file for licensing information.
|
||||||
|
*
|
||||||
|
* Copyright 2016 by authors and contributors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package datadog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Snapshot creates an image from a graph and returns the URL of the image.
|
||||||
|
func (self *Client) Snapshot(query string, start, end time.Time, eventQuery string) (string, error) {
|
||||||
|
v := url.Values{}
|
||||||
|
v.Add("start", fmt.Sprintf("%d", start.Unix()))
|
||||||
|
v.Add("end", fmt.Sprintf("%d", end.Unix()))
|
||||||
|
v.Add("metric_query", query)
|
||||||
|
v.Add("event_query", eventQuery)
|
||||||
|
|
||||||
|
out := struct {
|
||||||
|
SnapshotURL string `json:"snapshot_url"`
|
||||||
|
}{}
|
||||||
|
err := self.doJsonRequest("GET", "/v1/graph/snapshot?"+v.Encode(), nil, &out)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return out.SnapshotURL, nil
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
/*
|
||||||
|
* Datadog API for Go
|
||||||
|
*
|
||||||
|
* Please see the included LICENSE file for licensing information.
|
||||||
|
*
|
||||||
|
* Copyright 2013 by authors and contributors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package datadog
|
||||||
|
|
||||||
|
// TagMap is used to receive the format given to us by the API.
|
||||||
|
type TagMap map[string][]string
|
||||||
|
|
||||||
|
// reqGetTags is the container for receiving tags.
|
||||||
|
type reqGetTags struct {
|
||||||
|
Tags TagMap `json:"tags,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// regGetHostTags is for receiving a slice of tags.
|
||||||
|
type reqGetHostTags struct {
|
||||||
|
Tags []string `json:"tags,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTags returns a map of tags.
|
||||||
|
func (self *Client) GetTags(source string) (TagMap, error) {
|
||||||
|
var out reqGetTags
|
||||||
|
uri := "/v1/tags/hosts"
|
||||||
|
if source != "" {
|
||||||
|
uri += "?source=" + source
|
||||||
|
}
|
||||||
|
err := self.doJsonRequest("GET", uri, nil, &out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out.Tags, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHostTags returns a slice of tags for a given host and source.
|
||||||
|
func (self *Client) GetHostTags(host, source string) ([]string, error) {
|
||||||
|
var out reqGetHostTags
|
||||||
|
uri := "/v1/tags/hosts/" + host
|
||||||
|
if source != "" {
|
||||||
|
uri += "?source=" + source
|
||||||
|
}
|
||||||
|
err := self.doJsonRequest("GET", uri, nil, &out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out.Tags, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHostTagsBySource is a different way of viewing the tags. It returns a map
|
||||||
|
// of source:[tag,tag].
|
||||||
|
func (self *Client) GetHostTagsBySource(host, source string) (TagMap, error) {
|
||||||
|
var out reqGetTags
|
||||||
|
uri := "/v1/tags/hosts/" + host + "?by_source=true"
|
||||||
|
if source != "" {
|
||||||
|
uri += "&source=" + source
|
||||||
|
}
|
||||||
|
err := self.doJsonRequest("GET", uri, nil, &out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out.Tags, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTagsToHost does exactly what it says on the tin. Given a list of tags,
|
||||||
|
// add them to the host. The source is optionally specificed, and defaults to
|
||||||
|
// "users" as per the API documentation.
|
||||||
|
func (self *Client) AddTagsToHost(host, source string, tags []string) error {
|
||||||
|
uri := "/v1/tags/hosts/" + host
|
||||||
|
if source != "" {
|
||||||
|
uri += "?source=" + source
|
||||||
|
}
|
||||||
|
return self.doJsonRequest("POST", uri, reqGetHostTags{Tags: tags}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateHostTags overwrites existing tags for a host, allowing you to specify
|
||||||
|
// a new set of tags for the given source. This defaults to "users".
|
||||||
|
func (self *Client) UpdateHostTags(host, source string, tags []string) error {
|
||||||
|
uri := "/v1/tags/hosts/" + host
|
||||||
|
if source != "" {
|
||||||
|
uri += "?source=" + source
|
||||||
|
}
|
||||||
|
return self.doJsonRequest("PUT", uri, reqGetHostTags{Tags: tags}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveHostTags removes all tags from a host for the given source. If none is
|
||||||
|
// given, the API defaults to "users".
|
||||||
|
func (self *Client) RemoveHostTags(host, source string) error {
|
||||||
|
uri := "/v1/tags/hosts/" + host
|
||||||
|
if source != "" {
|
||||||
|
uri += "?source=" + source
|
||||||
|
}
|
||||||
|
return self.doJsonRequest("DELETE", uri, nil, nil)
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* Datadog API for Go
|
||||||
|
*
|
||||||
|
* Please see the included LICENSE file for licensing information.
|
||||||
|
*
|
||||||
|
* Copyright 2013 by authors and contributors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package datadog
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Handle string `json:"handle,omitempty"`
|
||||||
|
Email string `json:"email,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Role string `json:"role,omitempty"`
|
||||||
|
IsAdmin bool `json:"is_admin,omitempty"`
|
||||||
|
Verified bool `json:"verified,omitempty"`
|
||||||
|
Disabled bool `json:"disabled,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// reqInviteUsers contains email addresses to send invitations to.
|
||||||
|
type reqInviteUsers struct {
|
||||||
|
Emails []string `json:"emails,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// InviteUsers takes a slice of email addresses and sends invitations to them.
|
||||||
|
func (self *Client) InviteUsers(emails []string) error {
|
||||||
|
return self.doJsonRequest("POST", "/v1/invite_users",
|
||||||
|
reqInviteUsers{Emails: emails}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// internal type to retrieve users from the api
|
||||||
|
type usersData struct {
|
||||||
|
Users []User `json:"users"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUsers returns all user, or an error if not found
|
||||||
|
func (self *Client) GetUsers() (users []User, err error) {
|
||||||
|
var udata usersData
|
||||||
|
uri := "/v1/user"
|
||||||
|
err = self.doJsonRequest("GET", uri, nil, &udata)
|
||||||
|
users = udata.Users
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// internal type to retrieve single user from the api
|
||||||
|
type userData struct {
|
||||||
|
User User `json:"user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUser returns the user that match a handle, or an error if not found
|
||||||
|
func (self *Client) GetUser(handle string) (user User, err error) {
|
||||||
|
var udata userData
|
||||||
|
uri := "/v1/user/" + handle
|
||||||
|
err = self.doJsonRequest("GET", uri, nil, &udata)
|
||||||
|
user = udata.User
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUser updates a user with the content of `user`,
|
||||||
|
// and returns an error if the update failed
|
||||||
|
func (self *Client) UpdateUser(user User) error {
|
||||||
|
uri := "/v1/user/" + user.Handle
|
||||||
|
return self.doJsonRequest("PUT", uri, user, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteUser deletes a user and returns an error if deletion failed
|
||||||
|
func (self *Client) DeleteUser(handle string) error {
|
||||||
|
uri := "/v1/user/" + handle
|
||||||
|
return self.doJsonRequest("DELETE", uri, nil, nil)
|
||||||
|
}
|
Loading…
Reference in New Issue