├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── alerts.go ├── api_keys.go ├── api_keys_test.go ├── app_keys.go ├── app_keys_test.go ├── board_widgets.go ├── boards.go ├── boards_test.go ├── checks.go ├── checks_test.go ├── client.go ├── cmd └── tools │ └── gen-accessors.go ├── comments.go ├── dashboard_list_items_v2.go ├── dashboard_lists.go ├── dashboards.go ├── dashboards_test.go ├── datadog-accessors.go ├── downtimes.go ├── downtimes_test.go ├── events.go ├── events_test.go ├── generate.go ├── helper_test.go ├── helpers.go ├── hosts.go ├── hosts_test.go ├── integration ├── api_keys_test.go ├── app_keys_test.go ├── boards_test.go ├── client_test.go ├── dashboard_lists_test.go ├── dashboard_lists_v2_test.go ├── dashboards_test.go ├── downtime_test.go ├── hosts_test.go ├── integrations_test.go ├── logs_indexes_test.go ├── logs_pipeline_lists_test.go ├── logs_pipelines_test.go ├── main_test.go ├── monitors_test.go ├── screen_widgets_test.go ├── screenboards_test.go ├── series_test.go ├── service_level_objectives_test.go ├── snapshot_test.go ├── synthetics_test.go └── users_test.go ├── integrations.go ├── ip_ranges.go ├── ip_ranges_test.go ├── log_lists.go ├── log_lists_test.go ├── logs_index_lists.go ├── logs_index_lists_test.go ├── logs_indexes.go ├── logs_indexes_test.go ├── logs_pipeline_lists.go ├── logs_pipeline_lists_test.go ├── logs_pipelines.go ├── logs_pipelines_test.go ├── logs_processors.go ├── metric_metadata.go ├── monitors.go ├── monitors_test.go ├── ratelimit.go ├── ratelimit_test.go ├── request.go ├── request_test.go ├── screen_widgets.go ├── screenboards.go ├── screenboards_test.go ├── scripts ├── check-code-generation-ran.sh └── check-fmt.sh ├── search.go ├── series.go ├── series_test.go ├── service_level_objectives.go ├── service_level_objectives_test.go ├── snapshot.go ├── synthetics.go ├── synthetics_test.go ├── tags.go ├── tests └── fixtures │ ├── boards_response.json │ ├── dashboards_response.json │ ├── downtimes_response.json │ ├── events_response.json │ ├── hosts │ └── get_totals_response.json │ ├── logs │ ├── index_response.json │ ├── indexlist_response.json │ ├── loglist_page_response.json │ ├── loglist_response.json │ ├── pipeline_response.json │ └── pipelinelist_response.json │ ├── screenboard_response.json │ ├── series │ ├── post_series_mixed.json │ └── post_series_valid.json │ ├── service_level_objectives │ ├── check_can_delete_response.json │ ├── create_request_metric.json │ ├── create_request_monitor.json │ ├── create_response_metric.json │ ├── create_response_monitor.json │ ├── delete_by_timeframe_request.json │ ├── delete_by_timeframe_response.json │ ├── delete_many_request.json │ ├── delete_many_response.json │ ├── delete_response.json │ ├── get_by_id_response.json │ ├── get_history_metric_response.json │ ├── get_history_monitor_response.json │ ├── get_many_response.json │ ├── search_response.json │ ├── update_request.json │ └── update_response.json │ ├── synthetics │ └── tests │ │ ├── get_test_api.json │ │ ├── get_test_browser.json │ │ └── list_tests.json │ └── users_response.json ├── users.go ├── users_test.go └── vendor ├── github.com ├── cenkalti │ └── backoff │ │ ├── LICENSE │ │ ├── README.md │ │ ├── backoff.go │ │ ├── exponential.go │ │ ├── retry.go │ │ └── ticker.go ├── davecgh │ └── go-spew │ │ ├── LICENSE │ │ └── spew │ │ ├── bypass.go │ │ ├── bypasssafe.go │ │ ├── common.go │ │ ├── config.go │ │ ├── doc.go │ │ ├── dump.go │ │ ├── format.go │ │ └── spew.go ├── pmezard │ └── go-difflib │ │ ├── LICENSE │ │ └── difflib │ │ └── difflib.go └── stretchr │ └── testify │ ├── LICENSE │ └── assert │ ├── assertion_format.go │ ├── assertion_format.go.tmpl │ ├── assertion_forward.go │ ├── assertion_forward.go.tmpl │ ├── assertions.go │ ├── doc.go │ ├── errors.go │ ├── forward_assertions.go │ └── http_assertions.go └── vendor.json /.gitignore: -------------------------------------------------------------------------------- 1 | ### Project specific ### 2 | cmd 3 | 4 | # Created by https://www.gitignore.io/api/go,vim,linux,macos,windows,sublimetext,intellij+iml,visualstudiocode 5 | # Edit at https://www.gitignore.io/?templates=go,vim,linux,macos,windows,sublimetext,intellij+iml,visualstudiocode 6 | 7 | ### Go ### 8 | # Binaries for programs and plugins 9 | *.exe 10 | *.exe~ 11 | *.dll 12 | *.so 13 | *.dylib 14 | 15 | # Test binary, built with `go test -c` 16 | *.test 17 | 18 | # Output of the go coverage tool, specifically when used with LiteIDE 19 | *.out 20 | 21 | # Dependency directories (remove the comment below to include it) 22 | # vendor/ 23 | 24 | ### Go Patch ### 25 | /Godeps/ 26 | .env 27 | .dependencies 28 | 29 | ### Intellij+iml ### 30 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 31 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 32 | 33 | # User-specific stuff 34 | .idea/**/workspace.xml 35 | .idea/**/tasks.xml 36 | .idea/**/usage.statistics.xml 37 | .idea/**/dictionaries 38 | .idea/**/shelf 39 | 40 | # Generated files 41 | .idea/**/contentModel.xml 42 | 43 | # Sensitive or high-churn files 44 | .idea/**/dataSources/ 45 | .idea/**/dataSources.ids 46 | .idea/**/dataSources.local.xml 47 | .idea/**/sqlDataSources.xml 48 | .idea/**/dynamic.xml 49 | .idea/**/uiDesigner.xml 50 | .idea/**/dbnavigator.xml 51 | 52 | # Gradle 53 | .idea/**/gradle.xml 54 | .idea/**/libraries 55 | 56 | # Gradle and Maven with auto-import 57 | # When using Gradle or Maven with auto-import, you should exclude module files, 58 | # since they will be recreated, and may cause churn. Uncomment if using 59 | # auto-import. 60 | # .idea/modules.xml 61 | # .idea/*.iml 62 | # .idea/modules 63 | # *.iml 64 | # *.ipr 65 | 66 | # CMake 67 | cmake-build-*/ 68 | 69 | # Mongo Explorer plugin 70 | .idea/**/mongoSettings.xml 71 | 72 | # File-based project format 73 | *.iws 74 | 75 | # IntelliJ 76 | out/ 77 | 78 | # mpeltonen/sbt-idea plugin 79 | .idea_modules/ 80 | 81 | # JIRA plugin 82 | atlassian-ide-plugin.xml 83 | 84 | # Cursive Clojure plugin 85 | .idea/replstate.xml 86 | 87 | # Crashlytics plugin (for Android Studio and IntelliJ) 88 | com_crashlytics_export_strings.xml 89 | crashlytics.properties 90 | crashlytics-build.properties 91 | fabric.properties 92 | 93 | # Editor-based Rest Client 94 | .idea/httpRequests 95 | 96 | # Android studio 3.1+ serialized cache file 97 | .idea/caches/build_file_checksums.ser 98 | 99 | ### Intellij+iml Patch ### 100 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 101 | 102 | *.iml 103 | modules.xml 104 | .idea/misc.xml 105 | *.ipr 106 | 107 | ### Linux ### 108 | *~ 109 | 110 | # temporary files which can be created if a process still has a handle open of a deleted file 111 | .fuse_hidden* 112 | 113 | # KDE directory preferences 114 | .directory 115 | 116 | # Linux trash folder which might appear on any partition or disk 117 | .Trash-* 118 | 119 | # .nfs files are created when an open file is removed but is still being accessed 120 | .nfs* 121 | 122 | ### macOS ### 123 | # General 124 | .DS_Store 125 | .AppleDouble 126 | .LSOverride 127 | 128 | # Icon must end with two \r 129 | Icon 130 | 131 | # Thumbnails 132 | ._* 133 | 134 | # Files that might appear in the root of a volume 135 | .DocumentRevisions-V100 136 | .fseventsd 137 | .Spotlight-V100 138 | .TemporaryItems 139 | .Trashes 140 | .VolumeIcon.icns 141 | .com.apple.timemachine.donotpresent 142 | 143 | # Directories potentially created on remote AFP share 144 | .AppleDB 145 | .AppleDesktop 146 | Network Trash Folder 147 | Temporary Items 148 | .apdisk 149 | 150 | ### SublimeText ### 151 | # Cache files for Sublime Text 152 | *.tmlanguage.cache 153 | *.tmPreferences.cache 154 | *.stTheme.cache 155 | 156 | # Workspace files are user-specific 157 | *.sublime-workspace 158 | 159 | # Project files should be checked into the repository, unless a significant 160 | # proportion of contributors will probably not be using Sublime Text 161 | # *.sublime-project 162 | 163 | # SFTP configuration file 164 | sftp-config.json 165 | 166 | # Package control specific files 167 | Package Control.last-run 168 | Package Control.ca-list 169 | Package Control.ca-bundle 170 | Package Control.system-ca-bundle 171 | Package Control.cache/ 172 | Package Control.ca-certs/ 173 | Package Control.merged-ca-bundle 174 | Package Control.user-ca-bundle 175 | oscrypto-ca-bundle.crt 176 | bh_unicode_properties.cache 177 | 178 | # Sublime-github package stores a github token in this file 179 | # https://packagecontrol.io/packages/sublime-github 180 | GitHub.sublime-settings 181 | 182 | ### Vim ### 183 | # Swap 184 | [._]*.s[a-v][a-z] 185 | [._]*.sw[a-p] 186 | [._]s[a-rt-v][a-z] 187 | [._]ss[a-gi-z] 188 | [._]sw[a-p] 189 | 190 | # Session 191 | Session.vim 192 | Sessionx.vim 193 | 194 | # Temporary 195 | .netrwhist 196 | 197 | # Auto-generated tag files 198 | tags 199 | 200 | # Persistent undo 201 | [._]*.un~ 202 | 203 | # Coc configuration directory 204 | .vim 205 | 206 | ### VisualStudioCode ### 207 | .vscode/* 208 | !.vscode/settings.json 209 | !.vscode/tasks.json 210 | !.vscode/launch.json 211 | !.vscode/extensions.json 212 | 213 | ### VisualStudioCode Patch ### 214 | # Ignore all local history of files 215 | .history 216 | 217 | ### Windows ### 218 | # Windows thumbnail cache files 219 | Thumbs.db 220 | Thumbs.db:encryptable 221 | ehthumbs.db 222 | ehthumbs_vista.db 223 | 224 | # Dump file 225 | *.stackdump 226 | 227 | # Folder config file 228 | [Dd]esktop.ini 229 | 230 | # Recycle Bin used on file shares 231 | $RECYCLE.BIN/ 232 | 233 | # Windows Installer files 234 | *.cab 235 | *.msi 236 | *.msix 237 | *.msm 238 | *.msp 239 | 240 | # Windows shortcuts 241 | *.lnk 242 | 243 | # End of https://www.gitignore.io/api/go,vim,linux,macos,windows,sublimetext,intellij+iml,visualstudiocode 244 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - "1.9" 5 | - "1.10.x" 6 | - "1.11.x" 7 | - "tip" 8 | 9 | env: 10 | - "PATH=/home/travis/gopath/bin:$PATH" 11 | 12 | install: 13 | - go get -v -t . 14 | 15 | script: 16 | - scripts/check-fmt.sh 17 | - go get -u golang.org/x/lint/golint 18 | - golint ./... | grep -v vendor/ 19 | - make 20 | - scripts/check-code-generation-ran.sh 21 | # PR's don't have access to Travis EnvVars with DDog API Keys. Skip acceptance tests on PR. 22 | - 'if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then make testacc; fi' 23 | 24 | matrix: 25 | allow_failures: 26 | - go: tip 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 by authors and contributors. 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 23 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 24 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 30 | THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TEST?=$$(go list ./... | grep -v '/go-datadog-api/vendor/') 2 | GOFMT_FILES?=$$(find . -name '*.go' | grep -v vendor) 3 | 4 | default: test fmt 5 | 6 | generate: 7 | go generate 8 | 9 | # test runs the unit tests and vets the code 10 | test: 11 | go test . $(TESTARGS) -v -timeout=30s -parallel=4 12 | @$(MAKE) vet 13 | 14 | # testacc runs acceptance tests 15 | testacc: 16 | go test integration/* -v $(TESTARGS) -timeout 90m 17 | 18 | # testrace runs the race checker 19 | testrace: 20 | go test -race $(TEST) $(TESTARGS) 21 | 22 | fmt: 23 | gofmt -w -s $(GOFMT_FILES) 24 | 25 | # vet runs the Go source code static analysis tool `vet` to find 26 | # any common errors. 27 | vet: 28 | @echo "go vet" 29 | @go vet; if [ $$? -ne 0 ]; then \ 30 | echo ""; \ 31 | echo "Vet found suspicious constructs. Please check the reported constructs"; \ 32 | echo "and fix them if necessary before submitting the code for review."; \ 33 | exit 1; \ 34 | fi 35 | 36 | .PHONY: default test testacc updatedeps vet 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GoDoc](http://img.shields.io/badge/godoc-reference-blue.svg)](https://godoc.org/gopkg.in/zorkian/go-datadog-api.v2) 2 | [![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) 3 | [![Build 4 | status](https://travis-ci.org/zorkian/go-datadog-api.svg)](https://travis-ci.org/zorkian/go-datadog-api) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/zorkian/go-datadog-api)](https://goreportcard.com/report/github.com/zorkian/go-datadog-api) 6 | 7 | # Datadog API in Go 8 | 9 | **This is the v2.0 version of the API, and has breaking changes. Use the v1.0 branch if you need 10 | legacy code to be supported.** 11 | 12 | A Go wrapper for the Datadog API. Use this library if you need to interact 13 | with the Datadog system. You can post metrics with it if you want, but this library is probably 14 | mostly used for automating dashboards/alerting and retrieving data (events, etc). 15 | 16 | The source API documentation is here: 17 | 18 | ## Installation 19 | To use the default branch, include it in your code like: 20 | ```go 21 | import "github.com/zorkian/go-datadog-api" 22 | ``` 23 | 24 | Or, if you need to control which version to use, import using [gopkg.in](http://labix.org/gopkg.in). Like so: 25 | ```go 26 | import "gopkg.in/zorkian/go-datadog-api.v2" 27 | ``` 28 | 29 | Using go get: 30 | ```bash 31 | go get gopkg.in/zorkian/go-datadog-api.v2 32 | ``` 33 | 34 | ## USAGE 35 | This library uses pointers to be able to verify if values are set or not (vs the default value for the type). Like 36 | protobuf there are helpers to enhance the API. You can decide to not use them, but you'll have to be careful handling 37 | nil pointers. 38 | 39 | Using the client: 40 | ```go 41 | client := datadog.NewClient("api key", "application key") 42 | 43 | dash, err := client.GetDashboard(*datadog.Int(10880)) 44 | if err != nil { 45 | log.Fatalf("fatal: %s\n", err) 46 | } 47 | 48 | log.Printf("dashboard %d: %s\n", dash.GetId(), dash.GetTitle()) 49 | ``` 50 | 51 | An example using datadog.String(), which allocates a pointer for you: 52 | ```go 53 | m := datadog.Monitor{ 54 | Name: datadog.String("Monitor other things"), 55 | Creator: &datadog.Creator{ 56 | Name: datadog.String("Joe Creator"), 57 | }, 58 | } 59 | ``` 60 | 61 | An example using the SetXx, HasXx, GetXx and GetXxOk accessors: 62 | ```go 63 | m := datadog.Monitor{} 64 | m.SetName("Monitor all the things") 65 | m.SetMessage("Electromagnetic energy loss") 66 | 67 | // Use HasMessage(), to verify we have interest in the message. 68 | // Using GetMessage() always safe as it returns the actual or, if never set, default value for that type. 69 | if m.HasMessage() { 70 | fmt.Printf("Found message %s\n", m.GetMessage()) 71 | } 72 | 73 | // Alternatively, use GetMessageOk(), it returns a tuple with the (default) value and a boolean expressing 74 | // if it was set at all: 75 | if v, ok := m.GetMessageOk(); ok { 76 | fmt.Printf("Found message %s\n", v) 77 | } 78 | ``` 79 | 80 | Check out the Godoc link for the available API methods and, if you can't find the one you need, 81 | let us know (or patches welcome)! 82 | 83 | ## DOCUMENTATION 84 | 85 | Please see: 86 | 87 | ## BUGS/PROBLEMS/CONTRIBUTING 88 | 89 | There are certainly some, but presently no known major bugs. If you do 90 | find something that doesn't work as expected, please file an issue on 91 | Github: 92 | 93 | 94 | 95 | Thanks in advance! And, as always, patches welcome! 96 | 97 | ## DEVELOPMENT 98 | ### Running tests 99 | * Run tests tests with `make test`. 100 | * Integration tests can be run with `make testacc`. Run specific integration tests with `make testacc TESTARGS='-run=TestCreateAndDeleteMonitor'` 101 | 102 | The acceptance tests require _DATADOG_API_KEY_ and _DATADOG_APP_KEY_ to be available 103 | in your environment variables. 104 | 105 | *Warning: the integrations tests will create and remove real resources in your Datadog account.* 106 | 107 | ### Regenerating code 108 | Accessors `HasXx`, `GetXx`, `GetOkXx` and `SetXx` are generated for each struct field type type that contains pointers. 109 | When structs are updated a contributor has to regenerate these using `go generate` and commit these changes. 110 | Optionally there is a make target for the generation: 111 | 112 | ```bash 113 | make generate 114 | ``` 115 | 116 | ## COPYRIGHT AND LICENSE 117 | 118 | Please see the LICENSE file for the included license information. 119 | 120 | Copyright 2013-2019 by authors and contributors. 121 | -------------------------------------------------------------------------------- /alerts.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Datadog API for Go 3 | * 4 | * Please see the included LICENSE file for licensing information. 5 | * 6 | * Copyright 2013 by authors and contributors. 7 | */ 8 | 9 | package datadog 10 | 11 | import ( 12 | "fmt" 13 | ) 14 | 15 | // Alert represents the data of an alert: a query that can fire and send a 16 | // message to the users. 17 | type Alert struct { 18 | Id *int `json:"id,omitempty"` 19 | Creator *int `json:"creator,omitempty"` 20 | Query *string `json:"query,omitempty"` 21 | Name *string `json:"name,omitempty"` 22 | Message *string `json:"message,omitempty"` 23 | Silenced *bool `json:"silenced,omitempty"` 24 | NotifyNoData *bool `json:"notify_no_data,omitempty"` 25 | State *string `json:"state,omitempty"` 26 | } 27 | 28 | // reqAlerts receives a slice of all alerts. 29 | type reqAlerts struct { 30 | Alerts []Alert `json:"alerts,omitempty"` 31 | } 32 | 33 | // CreateAlert adds a new alert to the system. This returns a pointer to an 34 | // Alert so you can pass that to UpdateAlert later if needed. 35 | func (client *Client) CreateAlert(alert *Alert) (*Alert, error) { 36 | var out Alert 37 | if err := client.doJsonRequest("POST", "/v1/alert", alert, &out); err != nil { 38 | return nil, err 39 | } 40 | return &out, nil 41 | } 42 | 43 | // UpdateAlert takes an alert that was previously retrieved through some method 44 | // and sends it back to the server. 45 | func (client *Client) UpdateAlert(alert *Alert) error { 46 | return client.doJsonRequest("PUT", fmt.Sprintf("/v1/alert/%d", alert.Id), 47 | alert, nil) 48 | } 49 | 50 | // GetAlert retrieves an alert by identifier. 51 | func (client *Client) GetAlert(id int) (*Alert, error) { 52 | var out Alert 53 | if err := client.doJsonRequest("GET", fmt.Sprintf("/v1/alert/%d", id), nil, &out); err != nil { 54 | return nil, err 55 | } 56 | return &out, nil 57 | } 58 | 59 | // DeleteAlert removes an alert from the system. 60 | func (client *Client) DeleteAlert(id int) error { 61 | return client.doJsonRequest("DELETE", fmt.Sprintf("/v1/alert/%d", id), 62 | nil, nil) 63 | } 64 | 65 | // GetAlerts returns a slice of all alerts. 66 | func (client *Client) GetAlerts() ([]Alert, error) { 67 | var out reqAlerts 68 | if err := client.doJsonRequest("GET", "/v1/alert", nil, &out); err != nil { 69 | return nil, err 70 | } 71 | return out.Alerts, nil 72 | } 73 | 74 | // MuteAlerts turns off alerting notifications. 75 | func (client *Client) MuteAlerts() error { 76 | return client.doJsonRequest("POST", "/v1/mute_alerts", nil, nil) 77 | } 78 | 79 | // UnmuteAlerts turns on alerting notifications. 80 | func (client *Client) UnmuteAlerts() error { 81 | return client.doJsonRequest("POST", "/v1/unmute_alerts", nil, nil) 82 | } 83 | -------------------------------------------------------------------------------- /api_keys.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Datadog API for Go 3 | * 4 | * Please see the included LICENSE file for licensing information. 5 | * 6 | * Copyright 2019 by authors and contributors. 7 | */ 8 | 9 | package datadog 10 | 11 | import ( 12 | "encoding/json" 13 | "fmt" 14 | "time" 15 | ) 16 | 17 | var createdTimeLayout = "2006-01-02 15:04:05" 18 | 19 | // APIKey represents and API key 20 | type APIKey struct { 21 | CreatedBy *string `json:"created_by,omitempty"` 22 | Name *string `json:"name,omitempty"` 23 | Key *string `json:"key,omitempty"` 24 | Created *time.Time `json:"created,omitempty"` 25 | } 26 | 27 | // reqAPIKeys retrieves a slice of all APIKeys. 28 | type reqAPIKeys struct { 29 | APIKeys []APIKey `json:"api_keys,omitempty"` 30 | } 31 | 32 | // reqAPIKey is similar to reqAPIKeys, but used for values returned by /v1/api_key/ 33 | // which contain one object (not list) "api_key" (not "api_keys") containing the found key 34 | type reqAPIKey struct { 35 | APIKey *APIKey `json:"api_key"` 36 | } 37 | 38 | // MarshalJSON is a custom method for handling datetime marshalling 39 | func (k APIKey) MarshalJSON() ([]byte, error) { 40 | // Approach for custom (un)marshalling borrowed from http://choly.ca/post/go-json-marshalling/ 41 | type Alias APIKey 42 | return json.Marshal(&struct { 43 | Created *string `json:"created,omitempty"` 44 | *Alias 45 | }{ 46 | Created: String(k.Created.Format(createdTimeLayout)), 47 | Alias: (*Alias)(&k), 48 | }) 49 | } 50 | 51 | // UnmarshalJSON is a custom method for handling datetime unmarshalling 52 | func (k *APIKey) UnmarshalJSON(data []byte) error { 53 | type Alias APIKey 54 | aux := &struct { 55 | Created *string `json:"created,omitempty"` 56 | *Alias 57 | }{ 58 | Alias: (*Alias)(k), 59 | } 60 | if err := json.Unmarshal(data, &aux); err != nil { 61 | return err 62 | } 63 | 64 | if created, err := time.Parse(createdTimeLayout, *aux.Created); err != nil { 65 | return err 66 | } else { 67 | k.Created = &created 68 | } 69 | 70 | return nil 71 | } 72 | 73 | // GetAPIKeys returns all API keys or error on failure 74 | func (client *Client) GetAPIKeys() ([]APIKey, error) { 75 | var out reqAPIKeys 76 | if err := client.doJsonRequest("GET", "/v1/api_key", nil, &out); err != nil { 77 | return nil, err 78 | } 79 | 80 | return out.APIKeys, nil 81 | } 82 | 83 | // GetAPIKey returns a single API key or error on failure 84 | func (client *Client) GetAPIKey(key string) (*APIKey, error) { 85 | var out reqAPIKey 86 | if err := client.doJsonRequest("GET", fmt.Sprintf("/v1/api_key/%s", key), nil, &out); err != nil { 87 | return nil, err 88 | } 89 | 90 | return out.APIKey, nil 91 | } 92 | 93 | // CreateAPIKey creates an API key from given struct and fills the rest of its 94 | // fields, or returns an error on failure 95 | func (client *Client) CreateAPIKey(name string) (*APIKey, error) { 96 | toPost := struct { 97 | Name *string `json:"name,omitempty"` 98 | }{ 99 | &name, 100 | } 101 | var out reqAPIKey 102 | if err := client.doJsonRequest("POST", "/v1/api_key", toPost, &out); err != nil { 103 | return nil, err 104 | } 105 | return out.APIKey, nil 106 | } 107 | 108 | // UpdateAPIKey updates given API key (only Name can be updated), returns an error 109 | func (client *Client) UpdateAPIKey(apikey *APIKey) error { 110 | out := reqAPIKey{APIKey: apikey} 111 | toPost := struct { 112 | Name *string `json:"name,omitempty"` 113 | }{ 114 | apikey.Name, 115 | } 116 | return client.doJsonRequest("PUT", fmt.Sprintf("/v1/api_key/%s", *apikey.Key), toPost, &out) 117 | } 118 | 119 | // DeleteAPIKey deletes API key given by key, returns an error 120 | func (client *Client) DeleteAPIKey(key string) error { 121 | return client.doJsonRequest("DELETE", fmt.Sprintf("/v1/api_key/%s", key), nil, nil) 122 | } 123 | -------------------------------------------------------------------------------- /api_keys_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Datadog API for Go 3 | * 4 | * Please see the included LICENSE file for licensing information. 5 | * 6 | * Copyright 2019 by authors and contributors. 7 | */ 8 | 9 | package datadog_test 10 | 11 | import ( 12 | "encoding/json" 13 | "testing" 14 | "time" 15 | 16 | "github.com/stretchr/testify/assert" 17 | dd "github.com/zorkian/go-datadog-api" 18 | ) 19 | 20 | func TestAPIKeySerialization(t *testing.T) { 21 | 22 | raw := ` 23 | { 24 | "created_by": "john@example.com", 25 | "name": "myCoolKey", 26 | "key": "3111111111111111aaaaaaaaaaaaaaaa", 27 | "created": "2019-04-05 09:47:00" 28 | }` 29 | 30 | var apikey dd.APIKey 31 | err := json.Unmarshal([]byte(raw), &apikey) 32 | assert.Equal(t, err, nil) 33 | assert.Equal(t, *apikey.Name, "myCoolKey") 34 | assert.Equal(t, *apikey.CreatedBy, "john@example.com") 35 | assert.Equal(t, *apikey.Key, "3111111111111111aaaaaaaaaaaaaaaa") 36 | assert.Equal(t, *apikey.Created, time.Date(2019, 4, 5, 9, 47, 0, 0, time.UTC)) 37 | 38 | // make sure that the date is correct after marshaling 39 | res, _ := json.Marshal(apikey) 40 | assert.Contains(t, string(res), "\"2019-04-05 09:47:00\"") 41 | } 42 | -------------------------------------------------------------------------------- /app_keys.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Datadog API for Go 3 | * 4 | * Please see the included LICENSE file for licensing information. 5 | * 6 | * Copyright 2019 by authors and contributors. 7 | */ 8 | 9 | package datadog 10 | 11 | import ( 12 | "fmt" 13 | ) 14 | 15 | // APPKey represents an APP key 16 | type APPKey struct { 17 | Owner *string `json:"owner,omitempty"` 18 | Name *string `json:"name,omitempty"` 19 | Hash *string `json:"hash,omitempty"` 20 | } 21 | 22 | // reqAPPKeys retrieves a slice of all APPKeys. 23 | type reqAPPKeys struct { 24 | APPKeys []APPKey `json:"application_keys,omitempty"` 25 | } 26 | 27 | // reqAPPKey is similar to reqAPPKeys, but used for values returned by 28 | // /v1/application_key/ which contain one object (not list) "application_key" 29 | // (not "application_keys") containing the found key 30 | type reqAPPKey struct { 31 | APPKey *APPKey `json:"application_key"` 32 | } 33 | 34 | // GetAPPKeys returns all APP keys or error on failure 35 | func (client *Client) GetAPPKeys() ([]APPKey, error) { 36 | var out reqAPPKeys 37 | if err := client.doJsonRequest("GET", "/v1/application_key", nil, &out); err != nil { 38 | return nil, err 39 | } 40 | 41 | return out.APPKeys, nil 42 | } 43 | 44 | // GetAPPKey returns a single APP key or error on failure 45 | func (client *Client) GetAPPKey(hash string) (*APPKey, error) { 46 | var out reqAPPKey 47 | if err := client.doJsonRequest("GET", fmt.Sprintf("/v1/application_key/%s", hash), nil, &out); err != nil { 48 | return nil, err 49 | } 50 | 51 | return out.APPKey, nil 52 | } 53 | 54 | // CreateAPPKey creates an APP key from given name and fills the rest of its 55 | // fields, or returns an error on failure 56 | func (client *Client) CreateAPPKey(name string) (*APPKey, error) { 57 | toPost := struct { 58 | Name *string `json:"name,omitempty"` 59 | }{ 60 | &name, 61 | } 62 | var out reqAPPKey 63 | if err := client.doJsonRequest("POST", "/v1/application_key", toPost, &out); err != nil { 64 | return nil, err 65 | } 66 | return out.APPKey, nil 67 | } 68 | 69 | // UpdateAPPKey updates given APP key (only Name can be updated), returns an error 70 | func (client *Client) UpdateAPPKey(appkey *APPKey) error { 71 | out := reqAPPKey{APPKey: appkey} 72 | toPost := struct { 73 | Name *string `json:"name,omitempty"` 74 | }{ 75 | appkey.Name, 76 | } 77 | return client.doJsonRequest("PUT", fmt.Sprintf("/v1/application_key/%s", *appkey.Hash), toPost, &out) 78 | } 79 | 80 | // DeleteAPPKey deletes APP key given by hash, returns an error 81 | func (client *Client) DeleteAPPKey(hash string) error { 82 | return client.doJsonRequest("DELETE", fmt.Sprintf("/v1/application_key/%s", hash), nil, nil) 83 | } 84 | -------------------------------------------------------------------------------- /app_keys_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Datadog API for Go 3 | * 4 | * Please see the included LICENSE file for licensing information. 5 | * 6 | * Copyright 2019 by authors and contributors. 7 | */ 8 | 9 | package datadog_test 10 | 11 | import ( 12 | "encoding/json" 13 | "testing" 14 | 15 | "github.com/stretchr/testify/assert" 16 | dd "github.com/zorkian/go-datadog-api" 17 | ) 18 | 19 | func TestAPPKeySerialization(t *testing.T) { 20 | 21 | raw := ` 22 | { 23 | "owner": "john@example.com", 24 | "name": "myCoolKey", 25 | "hash": "31111111111111111111aaaaaaaaaaaaaaaaaaaa" 26 | }` 27 | 28 | var appkey dd.APPKey 29 | err := json.Unmarshal([]byte(raw), &appkey) 30 | assert.Equal(t, err, nil) 31 | assert.Equal(t, *appkey.Name, "myCoolKey") 32 | assert.Equal(t, *appkey.Owner, "john@example.com") 33 | assert.Equal(t, *appkey.Hash, "31111111111111111111aaaaaaaaaaaaaaaaaaaa") 34 | } 35 | -------------------------------------------------------------------------------- /boards.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Datadog API for Go 3 | * 4 | * Please see the included LICENSE file for licensing information. 5 | * 6 | * Copyright 2019 by authors and contributors. 7 | */ 8 | 9 | package datadog 10 | 11 | import ( 12 | "fmt" 13 | ) 14 | 15 | // Template variable preset represents a set of template variable values on a dashboard 16 | // Not available to timeboards and screenboards 17 | type TemplateVariablePreset struct { 18 | Name *string `json:"name,omitempty"` 19 | TemplateVariables []TemplateVariablePresetValue `json:"template_variables"` 20 | } 21 | 22 | // Template variable preset value represents the value for "name" template variable to assume 23 | type TemplateVariablePresetValue struct { 24 | Name *string `json:"name,omitempty"` 25 | Value *string `json:"value,omitempty"` 26 | } 27 | 28 | // Board represents a user created dashboard. This is the full dashboard 29 | // struct when we load a dashboard in detail. 30 | type Board struct { 31 | Title *string `json:"title"` 32 | Widgets []BoardWidget `json:"widgets"` 33 | LayoutType *string `json:"layout_type"` 34 | Id *string `json:"id,omitempty"` 35 | Description *string `json:"description,omitempty"` 36 | TemplateVariables []TemplateVariable `json:"template_variables,omitempty"` 37 | TemplateVariablePresets []TemplateVariablePreset `json:"template_variable_presets,omitempty"` 38 | IsReadOnly *bool `json:"is_read_only,omitempty"` 39 | NotifyList []string `json:"notify_list,omitempty"` 40 | AuthorHandle *string `json:"author_handle,omitempty"` 41 | Url *string `json:"url,omitempty"` 42 | CreatedAt *string `json:"created_at,omitempty"` 43 | ModifiedAt *string `json:"modified_at,omitempty"` 44 | } 45 | 46 | // BoardLite represents a simplify dashboard (without widgets, notify list, ...) 47 | // It's used when we load all boards. 48 | type BoardLite struct { 49 | Title *string `json:"title,omitempty"` 50 | Description *string `json:"description,omitempty"` 51 | LayoutType *string `json:"layout_type,omitempty"` 52 | Id *string `json:"id,omitempty"` 53 | Url *string `json:"url,omitempty"` 54 | AuthorHandle *string `json:"author_handle,omitempty"` 55 | IsReadOnly *bool `json:"is_read_only,omitempty"` 56 | CreatedAt *string `json:"created_at,omitempty"` 57 | ModifiedAt *string `json:"modified_at,omitempty"` 58 | } 59 | 60 | type reqGetBoards struct { 61 | Boards []BoardLite `json:"dashboards,omitempty"` 62 | } 63 | 64 | // GetBoard returns a single dashboard created on this account. 65 | func (client *Client) GetBoard(id string) (*Board, error) { 66 | var board Board 67 | if err := client.doJsonRequest("GET", fmt.Sprintf("/v1/dashboard/%s", id), nil, &board); err != nil { 68 | return nil, err 69 | } 70 | return &board, nil 71 | } 72 | 73 | // DeleteBoard deletes a dashboard by the identifier. 74 | func (client *Client) DeleteBoard(id string) error { 75 | return client.doJsonRequest("DELETE", fmt.Sprintf("/v1/dashboard/%s", id), nil, nil) 76 | } 77 | 78 | // CreateBoard creates a new dashboard when given a Board struct. 79 | func (client *Client) CreateBoard(board *Board) (*Board, error) { 80 | var createdBoard Board 81 | if err := client.doJsonRequest("POST", "/v1/dashboard", board, &createdBoard); err != nil { 82 | return nil, err 83 | } 84 | return &createdBoard, nil 85 | } 86 | 87 | // UpdateBoard takes a Board struct and persists it back to the server. 88 | // Use this if you've updated your local and need to push it back. 89 | func (client *Client) UpdateBoard(board *Board) error { 90 | return client.doJsonRequest("PUT", fmt.Sprintf("/v1/dashboard/%s", *board.Id), board, nil) 91 | } 92 | 93 | // GetBoards returns all Dashboards. 94 | func (client *Client) GetBoards() ([]BoardLite, error) { 95 | var out reqGetBoards 96 | if err := client.doJsonRequest("GET", "/v1/dashboard", nil, &out); err != nil { 97 | return nil, err 98 | } 99 | 100 | return out.Boards, nil 101 | } 102 | -------------------------------------------------------------------------------- /boards_test.go: -------------------------------------------------------------------------------- 1 | package datadog 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func TestGetBoards(t *testing.T) { 15 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 16 | response, err := ioutil.ReadFile("./tests/fixtures/boards_response.json") 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | w.Write(response) 21 | })) 22 | defer ts.Close() 23 | 24 | datadogClient := Client{ 25 | baseUrl: ts.URL, 26 | HttpClient: http.DefaultClient, 27 | } 28 | 29 | boards, err := datadogClient.GetBoards() 30 | require.NoError(t, err) 31 | 32 | assert.Len(t, boards, 2) 33 | } 34 | -------------------------------------------------------------------------------- /checks.go: -------------------------------------------------------------------------------- 1 | package datadog 2 | 3 | type Check struct { 4 | Check *string `json:"check,omitempty"` 5 | HostName *string `json:"host_name,omitempty"` 6 | Status *Status `json:"status,omitempty"` 7 | Timestamp *string `json:"timestamp,omitempty"` 8 | Message *string `json:"message,omitempty"` 9 | Tags []string `json:"tags,omitempty"` 10 | } 11 | 12 | type Status int 13 | 14 | const ( 15 | OK Status = iota 16 | WARNING 17 | CRITICAL 18 | UNKNOWN 19 | ) 20 | 21 | // PostCheck posts the result of a check run to the server 22 | func (client *Client) PostCheck(check Check) error { 23 | return client.doJsonRequest("POST", "/v1/check_run", 24 | check, nil) 25 | } 26 | -------------------------------------------------------------------------------- /checks_test.go: -------------------------------------------------------------------------------- 1 | package datadog_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/zorkian/go-datadog-api" 7 | ) 8 | 9 | func TestCheckStatus(T *testing.T) { 10 | if datadog.OK != 0 { 11 | T.Error("status OK must be 0 to satisfy Datadog's API") 12 | } 13 | if datadog.WARNING != 1 { 14 | T.Error("status WARNING must be 1 to satisfy Datadog's API") 15 | } 16 | if datadog.CRITICAL != 2 { 17 | T.Error("status CRITICAL must be 2 to satisfy Datadog's API") 18 | } 19 | if datadog.UNKNOWN != 3 { 20 | T.Error("status UNKNOWN must be 3 to satisfy Datadog's API") 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Datadog API for Go 3 | * 4 | * Please see the included LICENSE file for licensing information. 5 | * 6 | * Copyright 2013 by authors and contributors. 7 | */ 8 | 9 | package datadog 10 | 11 | import ( 12 | "encoding/json" 13 | "io/ioutil" 14 | "net/http" 15 | "os" 16 | "sync" 17 | "time" 18 | ) 19 | 20 | // Client is the object that handles talking to the Datadog API. This maintains 21 | // state information for a particular application connection. 22 | type Client struct { 23 | apiKey, appKey, baseUrl string 24 | 25 | //The Http Client that is used to make requests 26 | HttpClient *http.Client 27 | RetryTimeout time.Duration 28 | 29 | //Option to specify extra headers like User-Agent 30 | ExtraHeader map[string]string 31 | 32 | // rateLimiting is used to store the rate limitting stats. 33 | // More information in the official documentation: https://docs.datadoghq.com/api/?lang=bash#rate-limiting 34 | rateLimitingStats map[string]RateLimit 35 | // Mutex to protect the rate limiting map. 36 | m sync.Mutex 37 | } 38 | 39 | type RateLimit struct { 40 | Limit string 41 | Period string 42 | Reset string 43 | Remaining string 44 | } 45 | 46 | // valid is the struct to unmarshal validation endpoint responses into. 47 | type valid struct { 48 | Errors []string `json:"errors"` 49 | IsValid bool `json:"valid"` 50 | } 51 | 52 | // NewClient returns a new datadog.Client which can be used to access the API 53 | // methods. The expected argument is the API key. 54 | func NewClient(apiKey, appKey string) *Client { 55 | baseUrl := os.Getenv("DATADOG_HOST") 56 | if baseUrl == "" { 57 | baseUrl = "https://api.datadoghq.com" 58 | } 59 | 60 | return &Client{ 61 | apiKey: apiKey, 62 | appKey: appKey, 63 | baseUrl: baseUrl, 64 | HttpClient: http.DefaultClient, 65 | RetryTimeout: time.Duration(60 * time.Second), 66 | rateLimitingStats: make(map[string]RateLimit), 67 | ExtraHeader: make(map[string]string), 68 | } 69 | } 70 | 71 | // SetKeys changes the value of apiKey and appKey. 72 | func (c *Client) SetKeys(apiKey, appKey string) { 73 | c.apiKey = apiKey 74 | c.appKey = appKey 75 | } 76 | 77 | // SetBaseUrl changes the value of baseUrl. 78 | func (c *Client) SetBaseUrl(baseUrl string) { 79 | c.baseUrl = baseUrl 80 | } 81 | 82 | // GetBaseUrl returns the baseUrl. 83 | func (c *Client) GetBaseUrl() string { 84 | return c.baseUrl 85 | } 86 | 87 | // Validate checks if the API key (not the APP key) is valid. 88 | func (client *Client) Validate() (bool, error) { 89 | var out valid 90 | var resp *http.Response 91 | 92 | uri, err := client.uriForAPI("/v1/validate") 93 | if err != nil { 94 | return false, err 95 | } 96 | 97 | req, err := http.NewRequest("GET", uri, nil) 98 | if err != nil { 99 | return false, err 100 | } 101 | req.Header.Set("DD-API-KEY", client.apiKey) 102 | if (client.appKey != "") { 103 | req.Header.Set("DD-APPLICATION-KEY", client.appKey) 104 | } 105 | 106 | resp, err = client.doRequestWithRetries(req, client.RetryTimeout) 107 | if err != nil { 108 | return false, err 109 | } 110 | 111 | defer resp.Body.Close() 112 | 113 | if resp.StatusCode == http.StatusForbidden { 114 | return false, nil 115 | } 116 | 117 | body, err := ioutil.ReadAll(resp.Body) 118 | if err != nil { 119 | return false, err 120 | } 121 | 122 | if err = json.Unmarshal(body, &out); err != nil { 123 | return false, err 124 | } 125 | 126 | return out.IsValid, nil 127 | } 128 | -------------------------------------------------------------------------------- /comments.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Datadog API for Go 3 | * 4 | * Please see the included LICENSE file for licensing information. 5 | * 6 | * Copyright 2013 by authors and contributors. 7 | */ 8 | 9 | package datadog 10 | 11 | import ( 12 | "fmt" 13 | ) 14 | 15 | // Comment is a special form of event that appears in a stream. 16 | type Comment struct { 17 | Id *int `json:"id,omitempty"` 18 | RelatedId *int `json:"related_event_id,omitempty"` 19 | Handle *string `json:"handle,omitempty"` 20 | Message *string `json:"message,omitempty"` 21 | Resource *string `json:"resource,omitempty"` 22 | Url *string `json:"url,omitempty"` 23 | } 24 | 25 | // reqComment is the container for receiving commenst. 26 | type reqComment struct { 27 | Comment *Comment `json:"comment,omitempty"` 28 | } 29 | 30 | // CreateComment adds a new comment to the system. 31 | func (client *Client) CreateComment(handle, message string) (*Comment, error) { 32 | var out reqComment 33 | comment := Comment{Message: String(message)} 34 | if len(handle) > 0 { 35 | comment.Handle = String(handle) 36 | } 37 | if err := client.doJsonRequest("POST", "/v1/comments", &comment, &out); err != nil { 38 | return nil, err 39 | } 40 | return out.Comment, nil 41 | } 42 | 43 | // CreateRelatedComment adds a new comment, but lets you specify the related 44 | // identifier for the comment. 45 | func (client *Client) CreateRelatedComment(handle, message string, 46 | relid int) (*Comment, error) { 47 | var out reqComment 48 | comment := Comment{Message: String(message), RelatedId: Int(relid)} 49 | if len(handle) > 0 { 50 | comment.Handle = String(handle) 51 | } 52 | if err := client.doJsonRequest("POST", "/v1/comments", &comment, &out); err != nil { 53 | return nil, err 54 | } 55 | return out.Comment, nil 56 | } 57 | 58 | // EditComment changes the message and possibly handle of a particular comment. 59 | func (client *Client) EditComment(id int, handle, message string) error { 60 | comment := Comment{Message: String(message)} 61 | if len(handle) > 0 { 62 | comment.Handle = String(handle) 63 | } 64 | return client.doJsonRequest("PUT", fmt.Sprintf("/v1/comments/%d", id), 65 | &comment, nil) 66 | } 67 | 68 | // DeleteComment does exactly what you expect. 69 | func (client *Client) DeleteComment(id int) error { 70 | return client.doJsonRequest("DELETE", fmt.Sprintf("/v1/comments/%d", id), 71 | nil, nil) 72 | } 73 | -------------------------------------------------------------------------------- /dashboard_list_items_v2.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Datadog API for Go 3 | * 4 | * Please see the included LICENSE file for licensing information. 5 | * 6 | * Copyright 2019 by authors and contributors. 7 | */ 8 | 9 | package datadog 10 | 11 | import ( 12 | "fmt" 13 | ) 14 | 15 | // DashboardListItemV2 represents a single dashboard in a dashboard list. 16 | type DashboardListItemV2 struct { 17 | ID *string `json:"id,omitempty"` 18 | Type *string `json:"type,omitempty"` 19 | } 20 | 21 | type reqDashboardListItemsV2 struct { 22 | Dashboards []DashboardListItemV2 `json:"dashboards,omitempty"` 23 | } 24 | 25 | type reqAddedDashboardListItemsV2 struct { 26 | Dashboards []DashboardListItemV2 `json:"added_dashboards_to_list,omitempty"` 27 | } 28 | 29 | type reqDeletedDashboardListItemsV2 struct { 30 | Dashboards []DashboardListItemV2 `json:"deleted_dashboards_from_list,omitempty"` 31 | } 32 | 33 | // GetDashboardListItemsV2 fetches the dashboard list's dashboard definitions. 34 | func (client *Client) GetDashboardListItemsV2(id int) ([]DashboardListItemV2, error) { 35 | var out reqDashboardListItemsV2 36 | if err := client.doJsonRequest("GET", fmt.Sprintf("/v2/dashboard/lists/manual/%d/dashboards", id), nil, &out); err != nil { 37 | return nil, err 38 | } 39 | return out.Dashboards, nil 40 | } 41 | 42 | // AddDashboardListItemsV2 adds dashboards to an existing dashboard list. 43 | // 44 | // Any items already in the list are ignored (not added twice). 45 | func (client *Client) AddDashboardListItemsV2(dashboardListID int, items []DashboardListItemV2) ([]DashboardListItemV2, error) { 46 | req := reqDashboardListItemsV2{items} 47 | var out reqAddedDashboardListItemsV2 48 | if err := client.doJsonRequest("POST", fmt.Sprintf("/v2/dashboard/lists/manual/%d/dashboards", dashboardListID), req, &out); err != nil { 49 | return nil, err 50 | } 51 | return out.Dashboards, nil 52 | } 53 | 54 | // UpdateDashboardListItemsV2 updates dashboards of an existing dashboard list. 55 | // 56 | // This will set the list of dashboards to contain only the items in items. 57 | func (client *Client) UpdateDashboardListItemsV2(dashboardListID int, items []DashboardListItemV2) ([]DashboardListItemV2, error) { 58 | req := reqDashboardListItemsV2{items} 59 | var out reqDashboardListItemsV2 60 | if err := client.doJsonRequest("PUT", fmt.Sprintf("/v2/dashboard/lists/manual/%d/dashboards", dashboardListID), req, &out); err != nil { 61 | return nil, err 62 | } 63 | return out.Dashboards, nil 64 | } 65 | 66 | // DeleteDashboardListItemsV2 deletes dashboards from an existing dashboard list. 67 | // 68 | // Deletes any dashboards in the list of items from the dashboard list. 69 | func (client *Client) DeleteDashboardListItemsV2(dashboardListID int, items []DashboardListItemV2) ([]DashboardListItemV2, error) { 70 | req := reqDashboardListItemsV2{items} 71 | var out reqDeletedDashboardListItemsV2 72 | if err := client.doJsonRequest("DELETE", fmt.Sprintf("/v2/dashboard/lists/manual/%d/dashboards", dashboardListID), req, &out); err != nil { 73 | return nil, err 74 | } 75 | return out.Dashboards, nil 76 | } 77 | -------------------------------------------------------------------------------- /dashboard_lists.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Datadog API for Go 3 | * 4 | * Please see the included LICENSE file for licensing information. 5 | * 6 | * Copyright 2018 by authors and contributors. 7 | */ 8 | 9 | package datadog 10 | 11 | import ( 12 | "fmt" 13 | ) 14 | 15 | const ( 16 | DashboardListItemCustomTimeboard = "custom_timeboard" 17 | DashboardListItemCustomScreenboard = "custom_screenboard" 18 | DashboardListItemIntegerationTimeboard = "integration_timeboard" 19 | DashboardListItemIntegrationScreenboard = "integration_screenboard" 20 | DashboardListItemHostTimeboard = "host_timeboard" 21 | ) 22 | 23 | // DashboardList represents a dashboard list. 24 | type DashboardList struct { 25 | Id *int `json:"id,omitempty"` 26 | Name *string `json:"name,omitempty"` 27 | DashboardCount *int `json:"dashboard_count,omitempty"` 28 | } 29 | 30 | // DashboardListItem represents a single dashboard in a dashboard list. 31 | type DashboardListItem struct { 32 | Id *int `json:"id,omitempty"` 33 | Type *string `json:"type,omitempty"` 34 | } 35 | 36 | type reqDashboardListItems struct { 37 | Dashboards []DashboardListItem `json:"dashboards,omitempty"` 38 | } 39 | 40 | type reqAddedDashboardListItems struct { 41 | Dashboards []DashboardListItem `json:"added_dashboards_to_list,omitempty"` 42 | } 43 | 44 | type reqDeletedDashboardListItems struct { 45 | Dashboards []DashboardListItem `json:"deleted_dashboards_from_list,omitempty"` 46 | } 47 | 48 | type reqUpdateDashboardList struct { 49 | Name string `json:"name,omitempty"` 50 | } 51 | 52 | type reqGetDashboardLists struct { 53 | DashboardLists []DashboardList `json:"dashboard_lists,omitempty"` 54 | } 55 | 56 | // GetDashboardList returns a single dashboard list created on this account. 57 | func (client *Client) GetDashboardList(id int) (*DashboardList, error) { 58 | var out DashboardList 59 | if err := client.doJsonRequest("GET", fmt.Sprintf("/v1/dashboard/lists/manual/%d", id), nil, &out); err != nil { 60 | return nil, err 61 | } 62 | return &out, nil 63 | } 64 | 65 | // GetDashboardLists returns a list of all dashboard lists created on this account. 66 | func (client *Client) GetDashboardLists() ([]DashboardList, error) { 67 | var out reqGetDashboardLists 68 | if err := client.doJsonRequest("GET", "/v1/dashboard/lists/manual", nil, &out); err != nil { 69 | return nil, err 70 | } 71 | return out.DashboardLists, nil 72 | } 73 | 74 | // CreateDashboardList returns a single dashboard list created on this account. 75 | func (client *Client) CreateDashboardList(list *DashboardList) (*DashboardList, error) { 76 | var out DashboardList 77 | if err := client.doJsonRequest("POST", "/v1/dashboard/lists/manual", list, &out); err != nil { 78 | return nil, err 79 | } 80 | return &out, nil 81 | } 82 | 83 | // UpdateDashboardList returns a single dashboard list created on this account. 84 | func (client *Client) UpdateDashboardList(list *DashboardList) error { 85 | req := reqUpdateDashboardList{list.GetName()} 86 | return client.doJsonRequest("PUT", fmt.Sprintf("/v1/dashboard/lists/manual/%d", *list.Id), req, nil) 87 | } 88 | 89 | // DeleteDashboardList deletes a dashboard list by the identifier. 90 | func (client *Client) DeleteDashboardList(id int) error { 91 | return client.doJsonRequest("DELETE", fmt.Sprintf("/v1/dashboard/lists/manual/%d", id), nil, nil) 92 | } 93 | 94 | // GetDashboardListItems fetches the dashboard list's dashboard definitions. 95 | func (client *Client) GetDashboardListItems(id int) ([]DashboardListItem, error) { 96 | var out reqDashboardListItems 97 | if err := client.doJsonRequest("GET", fmt.Sprintf("/v1/dashboard/lists/manual/%d/dashboards", id), nil, &out); err != nil { 98 | return nil, err 99 | } 100 | return out.Dashboards, nil 101 | } 102 | 103 | // AddDashboardListItems adds dashboards to an existing dashboard list. 104 | // 105 | // Any items already in the list are ignored (not added twice). 106 | func (client *Client) AddDashboardListItems(dashboardListId int, items []DashboardListItem) ([]DashboardListItem, error) { 107 | req := reqDashboardListItems{items} 108 | var out reqAddedDashboardListItems 109 | if err := client.doJsonRequest("POST", fmt.Sprintf("/v1/dashboard/lists/manual/%d/dashboards", dashboardListId), req, &out); err != nil { 110 | return nil, err 111 | } 112 | return out.Dashboards, nil 113 | } 114 | 115 | // UpdateDashboardListItems updates dashboards of an existing dashboard list. 116 | // 117 | // This will set the list of dashboards to contain only the items in items. 118 | func (client *Client) UpdateDashboardListItems(dashboardListId int, items []DashboardListItem) ([]DashboardListItem, error) { 119 | req := reqDashboardListItems{items} 120 | var out reqDashboardListItems 121 | if err := client.doJsonRequest("PUT", fmt.Sprintf("/v1/dashboard/lists/manual/%d/dashboards", dashboardListId), req, &out); err != nil { 122 | return nil, err 123 | } 124 | return out.Dashboards, nil 125 | } 126 | 127 | // DeleteDashboardListItems deletes dashboards from an existing dashboard list. 128 | // 129 | // Deletes any dashboards in the list of items from the dashboard list. 130 | func (client *Client) DeleteDashboardListItems(dashboardListId int, items []DashboardListItem) ([]DashboardListItem, error) { 131 | req := reqDashboardListItems{items} 132 | var out reqDeletedDashboardListItems 133 | if err := client.doJsonRequest("DELETE", fmt.Sprintf("/v1/dashboard/lists/manual/%d/dashboards", dashboardListId), req, &out); err != nil { 134 | return nil, err 135 | } 136 | return out.Dashboards, nil 137 | } 138 | -------------------------------------------------------------------------------- /downtimes.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Datadog API for Go 3 | * 4 | * Please see the included LICENSE file for licensing information. 5 | * 6 | * Copyright 2013 by authors and contributors. 7 | */ 8 | 9 | package datadog 10 | 11 | import ( 12 | "fmt" 13 | "strings" 14 | ) 15 | 16 | // DowntimeType are a classification of a given downtime scope 17 | type DowntimeType int 18 | 19 | // The three downtime type classifications. 20 | const ( 21 | StarDowntimeType DowntimeType = 0 22 | HostDowntimeType DowntimeType = 1 23 | OtherDowntimeType DowntimeType = 2 24 | ) 25 | 26 | type Recurrence struct { 27 | Period *int `json:"period,omitempty"` 28 | Type *string `json:"type,omitempty"` 29 | UntilDate *int `json:"until_date,omitempty"` 30 | UntilOccurrences *int `json:"until_occurrences,omitempty"` 31 | WeekDays []string `json:"week_days,omitempty"` 32 | } 33 | 34 | type Downtime struct { 35 | Active *bool `json:"active,omitempty"` 36 | Canceled *int `json:"canceled,omitempty"` 37 | Disabled *bool `json:"disabled,omitempty"` 38 | End *int `json:"end,omitempty"` 39 | Id *int `json:"id,omitempty"` 40 | Message *string `json:"message,omitempty"` 41 | MonitorId *int `json:"monitor_id,omitempty"` 42 | MonitorTags []string `json:"monitor_tags,omitempty"` 43 | ParentId *int `json:"parent_id,omitempty"` 44 | Timezone *string `json:"timezone,omitempty"` 45 | Recurrence *Recurrence `json:"recurrence,omitempty"` 46 | Scope []string `json:"scope,omitempty"` 47 | Start *int `json:"start,omitempty"` 48 | CreatorID *int `json:"creator_id,omitempty"` 49 | UpdaterID *int `json:"updater_id,omitempty"` 50 | Type *int `json:"downtime_type,omitempty"` 51 | } 52 | 53 | // DowntimeType returns the canonical downtime type classification. 54 | // This is calculated based on the provided server response, but the logic is copied down here to calculate locally. 55 | func (d *Downtime) DowntimeType() DowntimeType { 56 | if d.Type != nil { 57 | switch *d.Type { 58 | case 0: 59 | return StarDowntimeType 60 | case 1: 61 | return HostDowntimeType 62 | default: 63 | return OtherDowntimeType 64 | } 65 | } 66 | if len(d.Scope) == 1 { 67 | if d.Scope[0] == "*" { 68 | return StarDowntimeType 69 | } 70 | if strings.HasPrefix(d.Scope[0], "host:") { 71 | return HostDowntimeType 72 | } 73 | } 74 | return OtherDowntimeType 75 | } 76 | 77 | // reqDowntimes retrieves a slice of all Downtimes. 78 | type reqDowntimes struct { 79 | Downtimes []Downtime `json:"downtimes,omitempty"` 80 | } 81 | 82 | // CreateDowntime adds a new downtme to the system. This returns a pointer 83 | // to a Downtime so you can pass that to UpdateDowntime or CancelDowntime 84 | // later if needed. 85 | func (client *Client) CreateDowntime(downtime *Downtime) (*Downtime, error) { 86 | var out Downtime 87 | if err := client.doJsonRequest("POST", "/v1/downtime", downtime, &out); err != nil { 88 | return nil, err 89 | } 90 | return &out, nil 91 | } 92 | 93 | // UpdateDowntime takes a downtime that was previously retrieved through some method 94 | // and sends it back to the server. 95 | func (client *Client) UpdateDowntime(downtime *Downtime) error { 96 | return client.doJsonRequest("PUT", fmt.Sprintf("/v1/downtime/%d", *downtime.Id), 97 | downtime, downtime) 98 | } 99 | 100 | // Getdowntime retrieves an downtime by identifier. 101 | func (client *Client) GetDowntime(id int) (*Downtime, error) { 102 | var out Downtime 103 | if err := client.doJsonRequest("GET", fmt.Sprintf("/v1/downtime/%d", id), nil, &out); err != nil { 104 | return nil, err 105 | } 106 | return &out, nil 107 | } 108 | 109 | // DeleteDowntime removes an downtime from the system. 110 | func (client *Client) DeleteDowntime(id int) error { 111 | return client.doJsonRequest("DELETE", fmt.Sprintf("/v1/downtime/%d", id), 112 | nil, nil) 113 | } 114 | 115 | // GetDowntimes returns a slice of all downtimes. 116 | func (client *Client) GetDowntimes() ([]Downtime, error) { 117 | var out reqDowntimes 118 | if err := client.doJsonRequest("GET", "/v1/downtime", nil, &out.Downtimes); err != nil { 119 | return nil, err 120 | } 121 | return out.Downtimes, nil 122 | } 123 | -------------------------------------------------------------------------------- /downtimes_test.go: -------------------------------------------------------------------------------- 1 | package datadog 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | ) 9 | 10 | func TestGetDowntime(t *testing.T) { 11 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 12 | response, err := ioutil.ReadFile("./tests/fixtures/downtimes_response.json") 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | w.Write(response) 17 | })) 18 | defer ts.Close() 19 | 20 | datadogClient := Client{ 21 | baseUrl: ts.URL, 22 | HttpClient: http.DefaultClient, 23 | } 24 | 25 | downtime, err := datadogClient.GetDowntime(2910) 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | 30 | expectedID := 2910 31 | if id := downtime.GetId(); id != expectedID { 32 | t.Fatalf("expect ID %d. Got %d", expectedID, id) 33 | } 34 | 35 | expectedActive := true 36 | if active := downtime.GetActive(); active != expectedActive { 37 | t.Fatalf("expect active %t. Got %v", expectedActive, active) 38 | } 39 | 40 | expectedEnd := 1420447087 41 | if end := downtime.GetEnd(); end != expectedEnd { 42 | t.Fatalf("expect end %d. Got %d", expectedEnd, end) 43 | } 44 | 45 | expectedDisabled := false 46 | if disabled := downtime.GetDisabled(); expectedDisabled != disabled { 47 | t.Fatalf("expect active %t. Got %v", expectedActive, disabled) 48 | } 49 | 50 | expectedMessage := "Doing some testing on staging." 51 | if message := downtime.GetMessage(); expectedMessage != message { 52 | t.Fatalf("expect message %s. Got %s", expectedMessage, message) 53 | } 54 | 55 | expectedStart := 1420387032 56 | if start := downtime.GetStart(); expectedStart != start { 57 | t.Fatalf("expect end %d. Got %d", expectedStart, start) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /events.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Datadog API for Go 3 | * 4 | * Please see the included LICENSE file for licensing information. 5 | * 6 | * Copyright 2013 by authors and contributors. 7 | */ 8 | 9 | package datadog 10 | 11 | import ( 12 | "fmt" 13 | "net/url" 14 | "strconv" 15 | ) 16 | 17 | // Event is a single event. If this is being used to post an event, then not 18 | // all fields will be filled out. 19 | type Event struct { 20 | Id *int `json:"id,omitempty"` 21 | Title *string `json:"title,omitempty"` 22 | Text *string `json:"text,omitempty"` 23 | Time *int `json:"date_happened,omitempty"` // UNIX time. 24 | Priority *string `json:"priority,omitempty"` 25 | AlertType *string `json:"alert_type,omitempty"` 26 | Host *string `json:"host,omitempty"` 27 | Aggregation *string `json:"aggregation_key,omitempty"` 28 | SourceType *string `json:"source_type_name,omitempty"` 29 | Tags []string `json:"tags,omitempty"` 30 | Url *string `json:"url,omitempty"` 31 | Resource *string `json:"resource,omitempty"` 32 | EventType *string `json:"event_type,omitempty"` 33 | } 34 | 35 | // reqGetEvent is the container for receiving a single event. 36 | type reqGetEvent struct { 37 | Event *Event `json:"event,omitempty"` 38 | } 39 | 40 | // reqGetEvents is for returning many events. 41 | type reqGetEvents struct { 42 | Events []Event `json:"events,omitempty"` 43 | } 44 | 45 | // PostEvent takes as input an event and then posts it to the server. 46 | func (client *Client) PostEvent(event *Event) (*Event, error) { 47 | var out reqGetEvent 48 | if err := client.doJsonRequest("POST", "/v1/events", event, &out); err != nil { 49 | return nil, err 50 | } 51 | return out.Event, nil 52 | } 53 | 54 | // GetEvent gets a single event given an identifier. 55 | func (client *Client) GetEvent(id int) (*Event, error) { 56 | var out reqGetEvent 57 | if err := client.doJsonRequest("GET", fmt.Sprintf("/v1/events/%d", id), nil, &out); err != nil { 58 | return nil, err 59 | } 60 | return out.Event, nil 61 | } 62 | 63 | // GetEvents returns a slice of events from the query stream. 64 | func (client *Client) GetEvents(start, end int, 65 | priority, sources, tags string) ([]Event, error) { 66 | // Since this is a GET request, we need to build a query string. 67 | vals := url.Values{} 68 | vals.Add("start", strconv.Itoa(start)) 69 | vals.Add("end", strconv.Itoa(end)) 70 | if priority != "" { 71 | vals.Add("priority", priority) 72 | } 73 | if sources != "" { 74 | vals.Add("sources", sources) 75 | } 76 | if tags != "" { 77 | vals.Add("tags", tags) 78 | } 79 | 80 | // Now the request and response. 81 | var out reqGetEvents 82 | if err := client.doJsonRequest("GET", 83 | fmt.Sprintf("/v1/events?%s", vals.Encode()), nil, &out); err != nil { 84 | return nil, err 85 | } 86 | return out.Events, nil 87 | } 88 | -------------------------------------------------------------------------------- /events_test.go: -------------------------------------------------------------------------------- 1 | package datadog 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | ) 9 | 10 | func TestGetEvent(t *testing.T) { 11 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 12 | response, err := ioutil.ReadFile("./tests/fixtures/events_response.json") 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | w.Write(response) 17 | })) 18 | defer ts.Close() 19 | 20 | datadogClient := Client{ 21 | baseUrl: ts.URL, 22 | HttpClient: http.DefaultClient, 23 | } 24 | 25 | // Test a single event 26 | event, err := datadogClient.GetEvent(1377281704830403917) 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | 31 | expectedID := 1377281704830403917 32 | if id := event.GetId(); id != expectedID { 33 | t.Fatalf("expect ID %d. Got %d", expectedID, id) 34 | } 35 | 36 | expectedTime := 1346355252 37 | if time := event.GetTime(); time != expectedTime { 38 | t.Fatalf("expect timestamp %d. Got %d", expectedTime, time) 39 | } 40 | 41 | expectedText := "Oh wow!" 42 | if text := event.GetText(); text != expectedText { 43 | t.Fatalf("expect text %s. Got %s", expectedText, text) 44 | } 45 | 46 | expectedTitle := "Did you hear the news today?" 47 | if title := event.GetTitle(); title != expectedTitle { 48 | t.Fatalf("expect title %s. Got %s", expectedTitle, title) 49 | } 50 | 51 | expectedAlertType := "info" 52 | if alertType := event.GetAlertType(); alertType != expectedAlertType { 53 | t.Fatalf("expect alert type %s. Got %s", expectedAlertType, alertType) 54 | } 55 | 56 | // host is null 57 | expectedHost := "" 58 | if host := event.GetHost(); host != expectedHost { 59 | t.Fatalf("expect host %s. Got %s", expectedHost, host) 60 | } 61 | 62 | expectedResource := "/api/v1/events/1377281704830403917" 63 | if resource := event.GetResource(); resource != expectedResource { 64 | t.Fatalf("expect resource %s. Got %s", expectedResource, resource) 65 | } 66 | 67 | expectedURL := "/event/jump_to?event_id=1377281704830403917" 68 | if url := event.GetUrl(); url != expectedURL { 69 | t.Fatalf("expect url %s. Got %s", expectedURL, url) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /generate.go: -------------------------------------------------------------------------------- 1 | package datadog 2 | 3 | //go:generate go run cmd/tools/gen-accessors.go -v 4 | -------------------------------------------------------------------------------- /helper_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Datadog API for Go 3 | * 4 | * Please see the included LICENSE file for licensing information. 5 | * 6 | * Copyright 2017 by authors and contributors. 7 | */ 8 | 9 | package datadog_test 10 | 11 | import ( 12 | "encoding/json" 13 | "testing" 14 | 15 | "github.com/stretchr/testify/assert" 16 | "github.com/zorkian/go-datadog-api" 17 | ) 18 | 19 | func TestHelperGetBoolSet(t *testing.T) { 20 | // Assert that we were able to get the boolean from a pointer field 21 | m := getTestMonitor() 22 | 23 | if attr, ok := datadog.GetBool(m.Options.NotifyNoData); ok { 24 | assert.Equal(t, true, attr) 25 | } 26 | } 27 | 28 | func TestHelperGetBoolNotSet(t *testing.T) { 29 | // Assert GetBool returned false for an unset value 30 | m := getTestMonitor() 31 | 32 | _, ok := datadog.GetBool(m.Options.NotifyAudit) 33 | assert.Equal(t, false, ok) 34 | } 35 | 36 | func TestHelperStringSet(t *testing.T) { 37 | // Assert that we were able to get the string from a pointer field 38 | m := getTestMonitor() 39 | 40 | if attr, ok := datadog.GetStringOk(m.Name); ok { 41 | assert.Equal(t, "Test monitor", attr) 42 | } 43 | } 44 | 45 | func TestHelperStringNotSet(t *testing.T) { 46 | // Assert GetString returned false for an unset value 47 | m := getTestMonitor() 48 | 49 | _, ok := datadog.GetStringOk(m.Message) 50 | assert.Equal(t, false, ok) 51 | } 52 | 53 | func TestHelperIntSet(t *testing.T) { 54 | // Assert that we were able to get the integer from a pointer field 55 | m := getTestMonitor() 56 | 57 | if attr, ok := datadog.GetIntOk(m.Id); ok { 58 | assert.Equal(t, 1, attr) 59 | } 60 | } 61 | 62 | func TestHelperIntNotSet(t *testing.T) { 63 | // Assert GetInt returned false for an unset value 64 | m := getTestMonitor() 65 | 66 | _, ok := datadog.GetIntOk(m.Options.RenotifyInterval) 67 | assert.Equal(t, false, ok) 68 | } 69 | 70 | func TestHelperGetJsonNumberSet(t *testing.T) { 71 | // Assert that we were able to get a JSON Number from a pointer field 72 | m := getTestMonitor() 73 | 74 | if attr, ok := datadog.GetJsonNumberOk(m.Options.Thresholds.Ok); ok { 75 | assert.Equal(t, json.Number(2), attr) 76 | } 77 | } 78 | 79 | func TestHelperGetJsonNumberNotSet(t *testing.T) { 80 | // Assert GetJsonNumber returned false for an unset value 81 | m := getTestMonitor() 82 | 83 | _, ok := datadog.GetJsonNumberOk(m.Options.Thresholds.Warning) 84 | 85 | assert.Equal(t, false, ok) 86 | } 87 | 88 | func getTestMonitor() *datadog.Monitor { 89 | 90 | o := &datadog.Options{ 91 | NotifyNoData: datadog.Bool(true), 92 | Locked: datadog.Bool(false), 93 | NoDataTimeframe: 60, 94 | Silenced: map[string]int{}, 95 | Thresholds: &datadog.ThresholdCount{ 96 | Ok: datadog.JsonNumber(json.Number(2)), 97 | }, 98 | } 99 | 100 | return &datadog.Monitor{ 101 | Query: datadog.String("avg(last_15m):avg:system.disk.in_use{*} by {host,device} > 0.8"), 102 | Name: datadog.String("Test monitor"), 103 | Id: datadog.Int(1), 104 | Options: o, 105 | Type: datadog.String("metric alert"), 106 | Tags: make([]string, 0), 107 | } 108 | } 109 | 110 | func TestHelperGetStringId(t *testing.T) { 111 | // Assert GetStringId returned the id without a change if it is a string 112 | id, err := datadog.GetStringId("abc-xyz-123") 113 | assert.Equal(t, err, nil) 114 | assert.Equal(t, id, "abc-xyz-123") 115 | 116 | // Assert GetStringId returned the id as a string if it is an integer 117 | id, err = datadog.GetStringId(123) 118 | assert.Equal(t, err, nil) 119 | assert.Equal(t, id, "123") 120 | 121 | // Assert GetStringId returned an error if the id type is boolean 122 | _, err = datadog.GetStringId(true) 123 | assert.NotNil(t, err) 124 | assert.Contains(t, err.Error(), "unsupported id type") 125 | 126 | // Assert GetStringId returned an error if the id type is float64 127 | _, err = datadog.GetStringId(5.2) 128 | assert.NotNil(t, err) 129 | assert.Contains(t, err.Error(), "unsupported id type") 130 | } 131 | 132 | func TestGetFloatFromInterface(t *testing.T) { 133 | var input interface{} 134 | 135 | input = nil 136 | val, auto, err := datadog.GetFloatFromInterface(nil) 137 | assert.Nil(t, err) 138 | assert.Equal(t, false, auto) 139 | assert.Nil(t, val) 140 | 141 | input = 0.0 142 | val, auto, err = datadog.GetFloatFromInterface(&input) 143 | assert.Nil(t, err) 144 | assert.Equal(t, false, auto) 145 | assert.Equal(t, 0.0, *val) 146 | 147 | input = 12.3 148 | val, auto, err = datadog.GetFloatFromInterface(&input) 149 | assert.Nil(t, err) 150 | assert.Equal(t, false, auto) 151 | assert.Equal(t, 12.3, *val) 152 | 153 | input = 123 154 | val, auto, err = datadog.GetFloatFromInterface(&input) 155 | assert.Nil(t, err) 156 | assert.Equal(t, false, auto) 157 | assert.Equal(t, 123.0, *val) 158 | 159 | input = int64(1234567890123456789.0) 160 | val, auto, err = datadog.GetFloatFromInterface(&input) 161 | assert.Nil(t, err) 162 | assert.Equal(t, false, auto) 163 | assert.Equal(t, 1234567890123456789.0, *val) 164 | 165 | input = "auto" 166 | val, auto, err = datadog.GetFloatFromInterface(&input) 167 | assert.Nil(t, err) 168 | assert.Equal(t, true, auto) 169 | assert.Nil(t, val) 170 | 171 | input = "wrong!" 172 | val, auto, err = datadog.GetFloatFromInterface(&input) 173 | assert.NotNil(t, err) 174 | 175 | input = false 176 | val, auto, err = datadog.GetFloatFromInterface(&input) 177 | assert.NotNil(t, err) 178 | } 179 | -------------------------------------------------------------------------------- /helpers.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Datadog API for Go 3 | * 4 | * Please see the included LICENSE file for licensing information. 5 | * 6 | * Copyright 2017 by authors and contributors. 7 | */ 8 | 9 | package datadog 10 | 11 | import ( 12 | "encoding/json" 13 | "errors" 14 | "fmt" 15 | "math" 16 | "strconv" 17 | ) 18 | 19 | // Bool is a helper routine that allocates a new bool value 20 | // to store v and returns a pointer to it. 21 | func Bool(v bool) *bool { return &v } 22 | 23 | // GetBool is a helper routine that returns a boolean representing 24 | // if a value was set, and if so, dereferences the pointer to it. 25 | func GetBool(v *bool) (bool, bool) { 26 | if v != nil { 27 | return *v, true 28 | } 29 | 30 | return false, false 31 | } 32 | 33 | // Int is a helper routine that allocates a new int value 34 | // to store v and returns a pointer to it. 35 | func Int(v int) *int { return &v } 36 | 37 | // Int64 is a helper routine that allocates a new int64 value to 38 | // store v and return a pointer to it. 39 | func Int64(v int64) *int64 { return &v } 40 | 41 | // GetIntOk is a helper routine that returns a boolean representing 42 | // if a value was set, and if so, dereferences the pointer to it. 43 | func GetIntOk(v *int) (int, bool) { 44 | if v != nil { 45 | return *v, true 46 | } 47 | 48 | return 0, false 49 | } 50 | 51 | // Float64 is a helper routine that allocates a new float64 value 52 | // to store v and returns a pointer to it. 53 | func Float64(v float64) *float64 { return &v } 54 | 55 | // GetFloat64Ok is a helper routine that returns a boolean representing 56 | // if a value was set, and if so, dereferences the pointer to it. 57 | func GetFloat64Ok(v *float64) (float64, bool) { 58 | if v != nil { 59 | return *v, true 60 | } 61 | 62 | return 0, false 63 | } 64 | 65 | // Float64AlmostEqual will return true if two floats are within a certain tolerance of each other 66 | func Float64AlmostEqual(a, b, tolerance float64) bool { 67 | return math.Abs(a-b) < tolerance 68 | } 69 | 70 | // String is a helper routine that allocates a new string value 71 | // to store v and returns a pointer to it. 72 | func String(v string) *string { return &v } 73 | 74 | // GetStringOk is a helper routine that returns a boolean representing 75 | // if a value was set, and if so, dereferences the pointer to it. 76 | func GetStringOk(v *string) (string, bool) { 77 | if v != nil { 78 | return *v, true 79 | } 80 | 81 | return "", false 82 | } 83 | 84 | // JsonNumber is a helper routine that allocates a new string value 85 | // to store v and returns a pointer to it. 86 | func JsonNumber(v json.Number) *json.Number { return &v } 87 | 88 | // GetJsonNumberOk is a helper routine that returns a boolean representing 89 | // if a value was set, and if so, dereferences the pointer to it. 90 | func GetJsonNumberOk(v *json.Number) (json.Number, bool) { 91 | if v != nil { 92 | return *v, true 93 | } 94 | 95 | return "", false 96 | } 97 | 98 | // Precision is a helper routine that allocates a new precision value 99 | // to store v and returns a pointer to it. 100 | func Precision(v PrecisionT) *PrecisionT { return &v } 101 | 102 | // GetPrecision is a helper routine that returns a boolean representing 103 | // if a value was set, and if so, dereferences the pointer to it. 104 | func GetPrecision(v *PrecisionT) (PrecisionT, bool) { 105 | if v != nil { 106 | return *v, true 107 | } 108 | 109 | return PrecisionT(""), false 110 | } 111 | 112 | // GetStringId is a helper routine that allows screenboards and timeboards to be retrieved 113 | // by either the legacy numerical format or the new string format. 114 | // It returns the id as is if it is a string, converts it to a string if it is an integer. 115 | // It return an error if the type is neither string or an integer 116 | func GetStringId(id interface{}) (string, error) { 117 | switch v := id.(type) { 118 | case int: 119 | return strconv.Itoa(v), nil 120 | case string: 121 | return v, nil 122 | default: 123 | return "", errors.New("unsupported id type") 124 | } 125 | } 126 | 127 | func GetFloatFromInterface(intf *interface{}) (*float64, bool, error) { 128 | var result *float64 129 | var auto bool 130 | 131 | if intf != nil { 132 | val := *intf 133 | switch tp := val.(type) { 134 | case float32: 135 | fv := float64(val.(float32)) 136 | result = &fv 137 | case float64: 138 | fv := val.(float64) 139 | result = &fv 140 | case int: 141 | fv := float64(val.(int)) 142 | result = &fv 143 | case int32: 144 | fv := float64(val.(int32)) 145 | result = &fv 146 | case int64: 147 | fv := float64(val.(int64)) 148 | result = &fv 149 | case string: 150 | fv := val.(string) 151 | if fv == "auto" { 152 | auto = true 153 | } else { 154 | f, err := strconv.ParseFloat(fv, 64) 155 | if err != nil { 156 | return nil, false, err 157 | } 158 | result = &f 159 | } 160 | default: 161 | return nil, false, fmt.Errorf(`bad type "%v" for Yaxis.min, expected "auto" or a number`, tp) 162 | } 163 | } 164 | return result, auto, nil 165 | } 166 | -------------------------------------------------------------------------------- /hosts.go: -------------------------------------------------------------------------------- 1 | package datadog 2 | 3 | type HostActionResp struct { 4 | Action string `json:"action"` 5 | Hostname string `json:"hostname"` 6 | Message string `json:"message,omitempty"` 7 | } 8 | 9 | type HostActionMute struct { 10 | Message *string `json:"message,omitempty"` 11 | EndTime *string `json:"end,omitempty"` 12 | Override *bool `json:"override,omitempty"` 13 | } 14 | 15 | // MuteHost mutes all monitors for the given host 16 | func (client *Client) MuteHost(host string, action *HostActionMute) (*HostActionResp, error) { 17 | var out HostActionResp 18 | uri := "/v1/host/" + host + "/mute" 19 | if err := client.doJsonRequest("POST", uri, action, &out); err != nil { 20 | return nil, err 21 | } 22 | return &out, nil 23 | } 24 | 25 | // UnmuteHost unmutes all monitors for the given host 26 | func (client *Client) UnmuteHost(host string) (*HostActionResp, error) { 27 | var out HostActionResp 28 | uri := "/v1/host/" + host + "/unmute" 29 | if err := client.doJsonRequest("POST", uri, nil, &out); err != nil { 30 | return nil, err 31 | } 32 | return &out, nil 33 | } 34 | 35 | // HostTotalsResp defines response to GET /v1/hosts/totals. 36 | type HostTotalsResp struct { 37 | TotalUp *int `json:"total_up"` 38 | TotalActive *int `json:"total_active"` 39 | } 40 | 41 | // GetHostTotals returns number of total active hosts and total up hosts. 42 | // Active means the host has reported in the past hour, and up means it has reported in the past two hours. 43 | func (client *Client) GetHostTotals() (*HostTotalsResp, error) { 44 | var out HostTotalsResp 45 | uri := "/v1/hosts/totals" 46 | if err := client.doJsonRequest("GET", uri, nil, &out); err != nil { 47 | return nil, err 48 | } 49 | return &out, nil 50 | } 51 | -------------------------------------------------------------------------------- /hosts_test.go: -------------------------------------------------------------------------------- 1 | package datadog 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestGetHostTotals(t *testing.T) { 13 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 14 | response, err := ioutil.ReadFile("./tests/fixtures/hosts/get_totals_response.json") 15 | if err != nil { 16 | t.Fatal(err) 17 | } 18 | w.Write(response) 19 | })) 20 | defer ts.Close() 21 | 22 | datadogClient := Client{ 23 | baseUrl: ts.URL, 24 | HttpClient: http.DefaultClient, 25 | } 26 | 27 | res, err := datadogClient.GetHostTotals() 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | 32 | assert.Equal(t, *res.TotalActive, 1) 33 | assert.Equal(t, *res.TotalUp, 2) 34 | } 35 | -------------------------------------------------------------------------------- /integration/api_keys_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Datadog API for Go 3 | * 4 | * Please see the included LICENSE file for licensing information. 5 | * 6 | * Copyright 2019 by authors and contributors. 7 | */ 8 | package integration 9 | 10 | import ( 11 | "testing" 12 | 13 | "github.com/zorkian/go-datadog-api" 14 | ) 15 | 16 | func TestAPIKeyCreateGetAndDelete(t *testing.T) { 17 | keyName := "client-test-key" 18 | expected, err := client.CreateAPIKey(keyName) 19 | if err != nil { 20 | t.Fatalf("Creating API key failed when it shouldn't. (%s)", err) 21 | } 22 | defer cleanUpAPIKey(t, *expected.Key) 23 | 24 | if *expected.Name != keyName { 25 | t.Errorf("Created key has wrong name. Got %s, want %s", *expected.Name, keyName) 26 | } 27 | 28 | // now try to fetch it freshly and compare it again 29 | actual, err := client.GetAPIKey(*expected.Key) 30 | if err != nil { 31 | t.Fatalf("Retrieving API key failed when it shouldn't. (%s)", err) 32 | } 33 | assertAPIKeyEquals(t, actual, expected) 34 | } 35 | 36 | func TestAPIKeyUpdateName(t *testing.T) { 37 | keyName := "client-test-key" 38 | newKeyName := "client-test-key-new" 39 | keyStruct, err := client.CreateAPIKey(keyName) 40 | if err != nil { 41 | t.Fatalf("Creating API key failed when it shouldn't. (%s)", err) 42 | } 43 | defer cleanUpAPIKey(t, *keyStruct.Key) 44 | 45 | *keyStruct.Name = newKeyName 46 | err = client.UpdateAPIKey(keyStruct) 47 | if err != nil { 48 | t.Fatalf("Updating API key failed when it shouldn't. (%s)", err) 49 | } 50 | 51 | if *keyStruct.Name != newKeyName { 52 | t.Errorf("API key name not updated. Got %s, want %s", *keyStruct.Name, newKeyName) 53 | } 54 | } 55 | 56 | func TestAPIKeyGetMultipleKeys(t *testing.T) { 57 | key1Name := "client-test-1" 58 | key2Name := "client-test-2" 59 | key1, err := client.CreateAPIKey(key1Name) 60 | if err != nil { 61 | t.Fatalf("Creating API key failed when it shouldn't. (%s)", err) 62 | } 63 | defer cleanUpAPIKey(t, *key1.Key) 64 | key2, err := client.CreateAPIKey(key2Name) 65 | if err != nil { 66 | t.Fatalf("Creating API key failed when it shouldn't. (%s)", err) 67 | } 68 | defer cleanUpAPIKey(t, *key2.Key) 69 | allKeys, err := client.GetAPIKeys() 70 | if err != nil { 71 | t.Fatalf("Getting all API keys failed when it shouldn't (%s)", err) 72 | } 73 | key1Found, key2Found := false, false 74 | for _, key := range allKeys { 75 | switch *key.Name { 76 | case key1Name: 77 | assertAPIKeyEquals(t, &key, key1) 78 | key1Found = true 79 | case key2Name: 80 | assertAPIKeyEquals(t, &key, key2) 81 | key2Found = true 82 | } 83 | } 84 | if key1Found == false { 85 | t.Errorf("Key 1 not found while getting multiple keys.") 86 | } 87 | if key2Found == false { 88 | t.Errorf("Key 2 not found while getting multiple keys.") 89 | } 90 | } 91 | 92 | func assertAPIKeyEquals(t *testing.T, actual, expected *datadog.APIKey) { 93 | if *actual.Created != *expected.Created { 94 | t.Errorf("APIKey created does not match: %s != %s", *actual.Created, *expected.Created) 95 | } 96 | if *actual.CreatedBy != *expected.CreatedBy { 97 | t.Errorf("APIKey created_by does not match: %s != %s", *actual.CreatedBy, *expected.CreatedBy) 98 | } 99 | if *actual.Key != *expected.Key { 100 | t.Errorf("APIKey key does not match: %s != %s", *actual.Key, *expected.Key) 101 | } 102 | if *actual.Name != *expected.Name { 103 | t.Errorf("APIKey name does not match: %s != %s", *actual.Name, *expected.Name) 104 | } 105 | } 106 | 107 | func cleanUpAPIKey(t *testing.T, key string) { 108 | if err := client.DeleteAPIKey(key); err != nil { 109 | t.Fatalf("Deleting api key failed when it shouldn't. Manual cleanup needed. (%s)", err) 110 | } 111 | 112 | deletedKey, err := client.GetAPIKey(key) 113 | if deletedKey != nil { 114 | t.Fatal("API key hasn't been deleted when it should have been. Manual cleanup needed.") 115 | } 116 | 117 | if err == nil { 118 | t.Fatal("Fetching deleted API key didn't lead to an error. Manual cleanup needed.") 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /integration/app_keys_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Datadog API for Go 3 | * 4 | * Please see the included LICENSE file for licensing information. 5 | * 6 | * Copyright 2019 by authors and contributors. 7 | */ 8 | package integration 9 | 10 | import ( 11 | "testing" 12 | 13 | "github.com/zorkian/go-datadog-api" 14 | ) 15 | 16 | func TestAPPKeyCreateGetAndDelete(t *testing.T) { 17 | keyName := "client-test-key" 18 | expected, err := client.CreateAPPKey(keyName) 19 | if err != nil { 20 | t.Fatalf("Creating APP key failed when it shouldn't. (%s)", err) 21 | } 22 | defer cleanUpAPPKey(t, *expected.Hash) 23 | 24 | if *expected.Name != keyName { 25 | t.Errorf("Created key has wrong name. Got %s, want %s", *expected.Name, keyName) 26 | } 27 | 28 | // now try to fetch it freshly and compare it again 29 | actual, err := client.GetAPPKey(*expected.Hash) 30 | if err != nil { 31 | t.Fatalf("Retrieving APP key failed when it shouldn't. (%s)", err) 32 | } 33 | assertAPPKeyEquals(t, actual, expected) 34 | } 35 | 36 | func TestAPPKeyUpdateName(t *testing.T) { 37 | keyName := "client-test-key" 38 | newKeyName := "client-test-key-new" 39 | keyStruct, err := client.CreateAPPKey(keyName) 40 | if err != nil { 41 | t.Fatalf("Creating APP key failed when it shouldn't. (%s)", err) 42 | } 43 | defer cleanUpAPPKey(t, *keyStruct.Hash) 44 | 45 | *keyStruct.Name = newKeyName 46 | err = client.UpdateAPPKey(keyStruct) 47 | if err != nil { 48 | t.Fatalf("Updating APP key failed when it shouldn't. (%s)", err) 49 | } 50 | 51 | if *keyStruct.Name != newKeyName { 52 | t.Errorf("APP key name not updated. Got %s, want %s", *keyStruct.Name, newKeyName) 53 | } 54 | } 55 | 56 | func TestAPPKeyGetMultipleKeys(t *testing.T) { 57 | key1Name := "client-test-1" 58 | key2Name := "client-test-2" 59 | key1, err := client.CreateAPPKey(key1Name) 60 | if err != nil { 61 | t.Fatalf("Creating APP key failed when it shouldn't. (%s)", err) 62 | } 63 | defer cleanUpAPPKey(t, *key1.Hash) 64 | key2, err := client.CreateAPPKey(key2Name) 65 | if err != nil { 66 | t.Fatalf("Creating APP key failed when it shouldn't. (%s)", err) 67 | } 68 | defer cleanUpAPPKey(t, *key2.Hash) 69 | allKeys, err := client.GetAPPKeys() 70 | if err != nil { 71 | t.Fatalf("Getting all APP keys failed when it shouldn't (%s)", err) 72 | } 73 | key1Found, key2Found := false, false 74 | for _, key := range allKeys { 75 | switch *key.Name { 76 | case key1Name: 77 | assertAPPKeyEquals(t, &key, key1) 78 | key1Found = true 79 | case key2Name: 80 | assertAPPKeyEquals(t, &key, key2) 81 | key2Found = true 82 | } 83 | } 84 | if key1Found == false { 85 | t.Errorf("Key 1 not found while getting multiple keys.") 86 | } 87 | if key2Found == false { 88 | t.Errorf("Key 2 not found while getting multiple keys.") 89 | } 90 | } 91 | 92 | func assertAPPKeyEquals(t *testing.T, actual, expected *datadog.APPKey) { 93 | if *actual.Owner != *expected.Owner { 94 | t.Errorf("APPKey owner does not match: %s != %s", *actual.Owner, *expected.Owner) 95 | } 96 | if *actual.Hash != *expected.Hash { 97 | t.Errorf("APPKey hash does not match: %s != %s", *actual.Hash, *expected.Hash) 98 | } 99 | if *actual.Name != *expected.Name { 100 | t.Errorf("APPKey name does not match: %s != %s", *actual.Name, *expected.Name) 101 | } 102 | } 103 | 104 | func cleanUpAPPKey(t *testing.T, hash string) { 105 | if err := client.DeleteAPPKey(hash); err != nil { 106 | t.Fatalf("Deleting app key failed when it shouldn't. Manual cleanup needed. (%s)", err) 107 | } 108 | 109 | deletedKey, err := client.GetAPPKey(hash) 110 | if deletedKey != nil { 111 | t.Fatal("APP key hasn't been deleted when it should have been. Manual cleanup needed.") 112 | } 113 | 114 | if err == nil { 115 | t.Fatal("Fetching deleted APP key didn't lead to an error. Manual cleanup needed.") 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /integration/client_test.go: -------------------------------------------------------------------------------- 1 | package integration 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/zorkian/go-datadog-api" 9 | ) 10 | 11 | func TestInvalidAuth(t *testing.T) { 12 | // Override the correct credentials 13 | c := datadog.NewClient("INVALID", "INVALID") 14 | 15 | valid, err := c.Validate() 16 | if err != nil { 17 | t.Fatalf("Testing authentication failed when it shouldn't: %s", err) 18 | } 19 | 20 | assert.Equal(t, valid, false) 21 | } 22 | 23 | func TestValidAuth(t *testing.T) { 24 | valid, err := client.Validate() 25 | 26 | if err != nil { 27 | t.Fatalf("Testing authentication failed when it shouldn't: %s", err) 28 | } 29 | 30 | assert.Equal(t, valid, true) 31 | } 32 | 33 | func TestValidAuthNoAppKey(t *testing.T) { 34 | c := datadog.NewClient(os.Getenv("DATADOG_API_KEY"), "") 35 | valid, err := c.Validate() 36 | 37 | if err != nil { 38 | t.Fatalf("Testing authentication failed when it shouldn't: %s", err) 39 | } 40 | 41 | assert.Equal(t, valid, true) 42 | } 43 | 44 | func TestBaseUrl(t *testing.T) { 45 | t.Run("Base url defaults to https://api.datadoghq.com", func(t *testing.T) { 46 | assert.Empty(t, os.Getenv("DATADOG_HOST")) 47 | 48 | c := datadog.NewClient("abc", "def") 49 | assert.Equal(t, "https://api.datadoghq.com", c.GetBaseUrl()) 50 | }) 51 | 52 | t.Run("Base url defaults DATADOG_HOST environment variable if set", func(t *testing.T) { 53 | os.Setenv("DATADOG_HOST", "https://custom.datadoghq.com") 54 | defer os.Unsetenv("DATADOG_HOST") 55 | 56 | c := datadog.NewClient("abc", "def") 57 | assert.Equal(t, "https://custom.datadoghq.com", c.GetBaseUrl()) 58 | }) 59 | 60 | t.Run("Base url can be set through the attribute setter", func(t *testing.T) { 61 | c := datadog.NewClient("abc", "def") 62 | c.SetBaseUrl("https://another.datadoghq.com") 63 | assert.Equal(t, "https://another.datadoghq.com", c.GetBaseUrl()) 64 | }) 65 | } 66 | 67 | func TestExtraHeader(t *testing.T) { 68 | t.Run("No Extra Header for backward compatibility", func(t *testing.T) { 69 | c := datadog.NewClient("foo", "bar") 70 | assert.Empty(t, c.ExtraHeader) 71 | }) 72 | } 73 | 74 | func TestInsertExtraHeader(t *testing.T) { 75 | t.Run("ExtraHeader map should be initialised", func(t *testing.T) { 76 | c := datadog.NewClient("foo", "bar") 77 | c.ExtraHeader["foo"] = "bar" 78 | assert.Equal(t, c.ExtraHeader["foo"], "bar") 79 | }) 80 | } 81 | -------------------------------------------------------------------------------- /integration/downtime_test.go: -------------------------------------------------------------------------------- 1 | package integration 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/zorkian/go-datadog-api" 9 | ) 10 | 11 | func TestDowntimeCreateAndDelete(t *testing.T) { 12 | expected := getTestDowntime() 13 | // create the downtime and compare it 14 | actual := createTestDowntime(t) 15 | defer cleanUpDowntime(t, *actual.Id) 16 | 17 | // Set ID of our original struct to ID of the returned struct so we can easily compare the results 18 | expected.SetId(actual.GetId()) 19 | // Set creator ID for the same reason (there's no easy way to get ID of current user ATM, 20 | // but if there was, we could do this dynamically) 21 | expected.SetCreatorID(actual.GetCreatorID()) 22 | 23 | assert.Equal(t, expected, actual) 24 | 25 | actual, err := client.GetDowntime(*actual.Id) 26 | if err != nil { 27 | t.Fatalf("Retrieving a downtime failed when it shouldn't: (%s)", err) 28 | } 29 | assert.Equal(t, expected, actual) 30 | } 31 | 32 | func TestDowntimeLinkedToMonitorCreateAndDelete(t *testing.T) { 33 | monitor := createTestMonitor(t) 34 | defer cleanUpMonitor(t, monitor.GetId()) 35 | 36 | expected := getTestDowntime() 37 | expected.SetMonitorId(monitor.GetId()) 38 | 39 | downtime, err := client.CreateDowntime(expected) 40 | defer cleanUpDowntime(t, downtime.GetId()) 41 | if err != nil { 42 | t.Fatalf("Creating a downtime failed when it shouldn't: %s", err) 43 | } 44 | 45 | expected.SetId(downtime.GetId()) 46 | expected.SetCreatorID(downtime.GetCreatorID()) 47 | 48 | assert.Equal(t, expected, downtime) 49 | 50 | actual, err := client.GetDowntime(downtime.GetId()) 51 | if err != nil { 52 | t.Fatalf("Retrieving a downtime failed when it shouldn't: (%s)", err) 53 | } 54 | assert.Equal(t, expected, actual) 55 | } 56 | 57 | func TestDowntimeUpdate(t *testing.T) { 58 | 59 | downtime := createTestDowntime(t) 60 | originalID := int(downtime.GetId()) 61 | 62 | // changing the scope will cause the downtime to be replaced in the future 63 | // this test should be updated to validate this 64 | downtime.Scope = []string{"env:downtime_test", "env:downtime_test2"} 65 | defer cleanUpDowntime(t, *downtime.Id) 66 | 67 | if err := client.UpdateDowntime(downtime); err != nil { 68 | t.Fatalf("Updating a downtime failed when it shouldn't: %s", err) 69 | } 70 | 71 | actual, err := client.GetDowntime(*downtime.Id) 72 | if err != nil { 73 | t.Fatalf("Retrieving a downtime failed when it shouldn't: %s", err) 74 | } 75 | 76 | // uncomment once immutable to validate it changed to NotEqual 77 | assert.Equal(t, originalID, actual.GetId()) 78 | assert.Equal(t, downtime.GetActive(), actual.GetActive()) 79 | assert.Equal(t, downtime.GetCanceled(), actual.GetCanceled()) 80 | assert.Equal(t, downtime.GetDisabled(), actual.GetDisabled()) 81 | assert.Equal(t, downtime.GetEnd(), actual.GetEnd()) 82 | assert.Equal(t, downtime.GetMessage(), actual.GetMessage()) 83 | assert.Equal(t, downtime.GetMonitorId(), actual.GetMonitorId()) 84 | assert.Equal(t, downtime.MonitorTags, actual.MonitorTags) 85 | assert.Equal(t, downtime.GetParentId(), actual.GetParentId()) 86 | // timezone will be automatically updated to UTC 87 | assert.Equal(t, "UTC", actual.GetTimezone()) 88 | assert.Equal(t, downtime.GetRecurrence(), actual.GetRecurrence()) 89 | assert.EqualValues(t, downtime.Scope, actual.Scope) 90 | // in the future the replaced downtime will have an updated start time, flip this to NotEqual 91 | assert.Equal(t, downtime.GetStart(), actual.GetStart()) 92 | } 93 | 94 | func TestDowntimeGet(t *testing.T) { 95 | downtimes, err := client.GetDowntimes() 96 | if err != nil { 97 | t.Fatalf("Retrieving downtimes failed when it shouldn't: %s", err) 98 | } 99 | num := len(downtimes) 100 | 101 | downtime := createTestDowntime(t) 102 | defer cleanUpDowntime(t, *downtime.Id) 103 | 104 | downtimes, err = client.GetDowntimes() 105 | if err != nil { 106 | t.Fatalf("Retrieving downtimes failed when it shouldn't: %s", err) 107 | } 108 | 109 | if num+1 != len(downtimes) { 110 | t.Fatalf("Number of downtimes didn't match expected: %d != %d", len(downtimes), num+1) 111 | } 112 | } 113 | 114 | func getTestDowntime() *datadog.Downtime { 115 | 116 | r := &datadog.Recurrence{ 117 | Type: datadog.String("weeks"), 118 | Period: datadog.Int(1), 119 | WeekDays: []string{"Mon", "Tue", "Wed", "Thu", "Fri"}, 120 | } 121 | sec := int(time.Now().Unix()) 122 | 123 | return &datadog.Downtime{ 124 | Active: datadog.Bool(false), 125 | CreatorID: datadog.Int(123), 126 | Disabled: datadog.Bool(false), 127 | Message: datadog.String("Test downtime message"), 128 | MonitorTags: []string{"some:tag"}, 129 | ParentId: nil, 130 | Timezone: datadog.String("UTC"), 131 | Scope: []string{"env:downtime_test"}, 132 | Start: datadog.Int(sec + 100), 133 | End: datadog.Int(sec + 200), 134 | Recurrence: r, 135 | Type: datadog.Int(2), 136 | } 137 | } 138 | 139 | func createTestDowntime(t *testing.T) *datadog.Downtime { 140 | downtime := getTestDowntime() 141 | downtime, err := client.CreateDowntime(downtime) 142 | if err != nil { 143 | t.Fatalf("Creating a downtime failed when it shouldn't: %s", err) 144 | } 145 | 146 | return downtime 147 | } 148 | 149 | func cleanUpDowntime(t *testing.T, id int) { 150 | if err := client.DeleteDowntime(id); err != nil { 151 | t.Fatalf("Deleting a downtime failed when it shouldn't. Manual cleanup needed. (%s)", err) 152 | } 153 | 154 | deletedDowntime, err := client.GetDowntime(id) 155 | if deletedDowntime != nil && *deletedDowntime.Canceled == 0 { 156 | t.Fatal("Downtime hasn't been deleted when it should have been. Manual cleanup needed.") 157 | } 158 | 159 | if err == nil && *deletedDowntime.Canceled == 0 { 160 | t.Fatal("Fetching deleted downtime didn't lead to an error and downtime Canceled not set.") 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /integration/hosts_test.go: -------------------------------------------------------------------------------- 1 | package integration 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/zorkian/go-datadog-api" 10 | ) 11 | 12 | func TestHost_Mute(t *testing.T) { 13 | muteAction := getTestMuteAction() 14 | hostname := "test.host" 15 | muteResp, err := client.MuteHost(hostname, muteAction) 16 | if err != nil { 17 | t.Fatalf("Failed to mute host: %s, err: %s", hostname, err) 18 | } 19 | 20 | defer func() { 21 | unmuteResp, err := client.UnmuteHost(hostname) 22 | if err != nil { 23 | t.Fatalf("Failed to cleanup mute on host: %s, err: %s", hostname, err) 24 | } 25 | assert.Equal(t, "Unmuted", unmuteResp.Action) 26 | assert.Equal(t, hostname, unmuteResp.Hostname) 27 | }() 28 | 29 | assert.Equal(t, "Muted", muteResp.Action) 30 | assert.Equal(t, hostname, muteResp.Hostname) 31 | assert.Equal(t, "Muting this host for a test!", muteResp.Message) 32 | 33 | } 34 | 35 | func getTestMuteAction() *datadog.HostActionMute { 36 | return &datadog.HostActionMute{ 37 | Message: datadog.String("Muting this host for a test!"), 38 | EndTime: datadog.String(fmt.Sprint(time.Now().Unix() + 100)), 39 | Override: datadog.Bool(false), 40 | } 41 | } 42 | 43 | // Just checking HTTP status is 2XX because numbers of active and up hosts are hard to fix. 44 | func TestHostTotals(t *testing.T) { 45 | _, err := client.GetHostTotals() 46 | if err != nil { 47 | t.Fatalf("Failed to get hosts totals, err: %s", err) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /integration/logs_indexes_test.go: -------------------------------------------------------------------------------- 1 | package integration 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "github.com/zorkian/go-datadog-api" 6 | "testing" 7 | ) 8 | 9 | func TestLogsIndexGet(t *testing.T) { 10 | logsIndex, err := client.GetLogsIndex("main") 11 | assert.Nil(t, err) 12 | 13 | updateLogsIndex := datadog.LogsIndex{ 14 | Filter: &datadog.FilterConfiguration{Query: datadog.String("updated query")}, 15 | ExclusionFilters: []datadog.ExclusionFilter{ 16 | { 17 | Name: datadog.String("updated Filter 1"), 18 | IsEnabled: datadog.Bool(false), 19 | Filter: &datadog.Filter{ 20 | Query: datadog.String("source:agent"), 21 | SampleRate: datadog.Float64(0.3), 22 | }, 23 | }, { 24 | Name: datadog.String("updated Filter 2"), 25 | IsEnabled: datadog.Bool(false), 26 | Filter: &datadog.Filter{ 27 | Query: datadog.String("source:info"), 28 | SampleRate: datadog.Float64(0.2), 29 | }, 30 | }, { 31 | Name: datadog.String("updated Filter 3"), 32 | IsEnabled: datadog.Bool(false), 33 | Filter: &datadog.Filter{ 34 | Query: datadog.String("source:warn"), 35 | SampleRate: datadog.Float64(1.0), 36 | }, 37 | }, 38 | }, 39 | } 40 | updatedLogsIndex, err := client.UpdateLogsIndex(*logsIndex.Name, &updateLogsIndex) 41 | defer assertRevertChange(t, logsIndex) 42 | assert.Nil(t, err) 43 | assert.Equal(t, &datadog.LogsIndex{ 44 | Name: logsIndex.Name, 45 | NumRetentionDays: logsIndex.NumRetentionDays, 46 | DailyLimit: logsIndex.DailyLimit, 47 | IsRateLimited: logsIndex.IsRateLimited, 48 | Filter: updateLogsIndex.Filter, 49 | ExclusionFilters: updateLogsIndex.ExclusionFilters, 50 | }, updatedLogsIndex) 51 | 52 | } 53 | 54 | func TestUpdateIndexList(t *testing.T) { 55 | indexList, err := client.GetLogsIndexList() 56 | assert.Nil(t, err) 57 | size := len(indexList.IndexNames) 58 | updateList := make([]string, size) 59 | for i, name := range indexList.IndexNames { 60 | updateList[size-1-i] = name 61 | } 62 | updateIndexList := &datadog.LogsIndexList{IndexNames: updateList} 63 | updatedIndexList, err := client.UpdateLogsIndexList(updateIndexList) 64 | defer assertRevertOrder(t, indexList) 65 | assert.Nil(t, err) 66 | assert.Equal(t, updateIndexList, updatedIndexList) 67 | } 68 | 69 | func assertRevertOrder(t *testing.T, indexList *datadog.LogsIndexList) { 70 | revertedList, err := client.UpdateLogsIndexList(indexList) 71 | assert.Nil(t, err) 72 | assert.Equal(t, indexList, revertedList) 73 | } 74 | 75 | func assertRevertChange(t *testing.T, logsIndex *datadog.LogsIndex) { 76 | revertLogsIndex, err := client.UpdateLogsIndex(*logsIndex.Name, &datadog.LogsIndex{ 77 | Filter: logsIndex.Filter, 78 | ExclusionFilters: logsIndex.ExclusionFilters, 79 | }) 80 | assert.Nil(t, err) 81 | assert.Equal(t, revertLogsIndex, logsIndex) 82 | } 83 | -------------------------------------------------------------------------------- /integration/logs_pipeline_lists_test.go: -------------------------------------------------------------------------------- 1 | package integration 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "github.com/zorkian/go-datadog-api" 6 | "testing" 7 | ) 8 | 9 | func TestLogsPipelineListGetAndUpdate(t *testing.T) { 10 | createdPipeline1, err := client.CreateLogsPipeline( 11 | &datadog.LogsPipeline{ 12 | Name: datadog.String("my first pipeline"), 13 | IsEnabled: datadog.Bool(true), 14 | Filter: &datadog.FilterConfiguration{ 15 | Query: datadog.String("source:redis"), 16 | }, 17 | }) 18 | assert.Nil(t, err) 19 | defer assertPipelineDelete(t, *createdPipeline1.Id) 20 | 21 | createdPipeline2, err := client.CreateLogsPipeline( 22 | &datadog.LogsPipeline{ 23 | Name: datadog.String("my second pipeline"), 24 | IsEnabled: datadog.Bool(true), 25 | Filter: &datadog.FilterConfiguration{ 26 | Query: datadog.String("source:redis"), 27 | }, 28 | }) 29 | assert.Nil(t, err) 30 | defer assertPipelineDelete(t, *createdPipeline2.Id) 31 | 32 | pipelineList, err := client.GetLogsPipelineList() 33 | assert.Nil(t, err) 34 | size := len(pipelineList.PipelineIds) 35 | assert.True(t, size >= 2) 36 | assert.Equal(t, *createdPipeline1.Id, pipelineList.PipelineIds[size-2]) 37 | assert.Equal(t, *createdPipeline2.Id, pipelineList.PipelineIds[size-1]) 38 | 39 | pipelineList.PipelineIds[size-2], pipelineList.PipelineIds[size-1] = 40 | pipelineList.PipelineIds[size-1], pipelineList.PipelineIds[size-2] 41 | updatedList, err := client.UpdateLogsPipelineList(pipelineList) 42 | assert.Nil(t, err) 43 | size = len(updatedList.PipelineIds) 44 | assert.True(t, size >= 2) 45 | assert.Equal(t, *createdPipeline1.Id, updatedList.PipelineIds[size-1]) 46 | assert.Equal(t, *createdPipeline2.Id, updatedList.PipelineIds[size-2]) 47 | 48 | } 49 | -------------------------------------------------------------------------------- /integration/main_test.go: -------------------------------------------------------------------------------- 1 | package integration 2 | 3 | import ( 4 | "github.com/zorkian/go-datadog-api" 5 | "log" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | var ( 11 | apiKey string 12 | appKey string 13 | client *datadog.Client 14 | ) 15 | 16 | func TestMain(m *testing.M) { 17 | apiKey = os.Getenv("DATADOG_API_KEY") 18 | appKey = os.Getenv("DATADOG_APP_KEY") 19 | 20 | if apiKey == "" || appKey == "" { 21 | log.Fatal("Please make sure to set the env variables 'DATADOG_API_KEY' and 'DATADOG_APP_KEY' before running this test") 22 | } 23 | 24 | client = datadog.NewClient(apiKey, appKey) 25 | os.Exit(m.Run()) 26 | } 27 | -------------------------------------------------------------------------------- /integration/series_test.go: -------------------------------------------------------------------------------- 1 | package integration 2 | 3 | import ( 4 | "testing" 5 | 6 | dd "github.com/zorkian/go-datadog-api" 7 | ) 8 | 9 | func TestSeriesSubmit(t *testing.T) { 10 | metrics := []dd.Metric{{ 11 | Metric: dd.String("test.metric"), 12 | Points: []dd.DataPoint{{dd.Float64(1.0), dd.Float64(2.0)}}, 13 | Type: dd.String("gauge"), 14 | Host: dd.String("myhost"), 15 | Tags: []string{"some:tag"}, 16 | Unit: dd.String("unit"), 17 | }} 18 | 19 | err := client.PostMetrics(metrics) 20 | if err != nil { 21 | t.Fatalf("Posting metrics failed when it shouldn't. (%s)", err) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /integration/service_level_objectives_test.go: -------------------------------------------------------------------------------- 1 | package integration 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/zorkian/go-datadog-api" 9 | ) 10 | 11 | func TestServiceLevelObjectivesCreateGetUpdateAndDelete(t *testing.T) { 12 | expected := &datadog.ServiceLevelObjective{ 13 | Name: datadog.String("Integration Test SLO - 'Test Create, Update and Delete'"), 14 | Description: datadog.String("Integration test for SLOs"), 15 | Tags: []string{"test:integration"}, 16 | Thresholds: datadog.ServiceLevelObjectiveThresholds{ 17 | { 18 | TimeFrame: datadog.String("7d"), 19 | Target: datadog.Float64(99), 20 | Warning: datadog.Float64(99.5), 21 | }, 22 | }, 23 | Type: &datadog.ServiceLevelObjectiveTypeMetric, 24 | Query: &datadog.ServiceLevelObjectiveMetricQuery{ 25 | Numerator: datadog.String("sum:my.metric{type:good}.as_count()"), 26 | Denominator: datadog.String("sum:my.metric{*}.as_count()"), 27 | }, 28 | } 29 | 30 | // Create 31 | actual, err := client.CreateServiceLevelObjective(expected) 32 | assert.NoError(t, err) 33 | assert.NotEmpty(t, actual.GetID()) 34 | assert.Equal(t, expected.Name, actual.Name) 35 | assert.Equal(t, expected.Description, actual.Description) 36 | assert.True(t, expected.Thresholds.Equal(actual.Thresholds)) 37 | 38 | time.Sleep(time.Second) 39 | 40 | // Get 41 | found, err := client.GetServiceLevelObjective(actual.GetID()) 42 | assert.NoError(t, err) 43 | assert.Equal(t, actual.GetID(), found.GetID()) 44 | 45 | time.Sleep(time.Second) 46 | 47 | // Update 48 | actual.SetDescription("Integration test for SLOs - updated") 49 | actual.Thresholds = datadog.ServiceLevelObjectiveThresholds{ 50 | { 51 | TimeFrame: datadog.String("7d"), 52 | Target: datadog.Float64(99), 53 | Warning: datadog.Float64(99.5), 54 | }, 55 | { 56 | TimeFrame: datadog.String("30d"), 57 | Target: datadog.Float64(99), 58 | Warning: datadog.Float64(99.5), 59 | }, 60 | } 61 | updated, err := client.UpdateServiceLevelObjective(actual) 62 | assert.NoError(t, err) 63 | assert.Equal(t, "Integration test for SLOs - updated", updated.GetDescription()) 64 | assert.Len(t, updated.Thresholds, 2) 65 | 66 | time.Sleep(time.Second) 67 | 68 | // Delete 69 | err = client.DeleteServiceLevelObjective(updated.GetID()) 70 | assert.NoError(t, err) 71 | } 72 | 73 | func TestServiceLevelObjectivesBulkTimeFrameDelete(t *testing.T) { 74 | expected1 := &datadog.ServiceLevelObjective{ 75 | Name: datadog.String("Integration Test SLO - 'Test Multi Time Frame Delete 1'"), 76 | Description: datadog.String("Integration test for SLOs"), 77 | Tags: []string{"test:integration"}, 78 | Thresholds: datadog.ServiceLevelObjectiveThresholds{ 79 | { 80 | TimeFrame: datadog.String("7d"), 81 | Target: datadog.Float64(99), 82 | Warning: datadog.Float64(99.5), 83 | }, 84 | { 85 | TimeFrame: datadog.String("30d"), 86 | Target: datadog.Float64(99), 87 | Warning: datadog.Float64(99.5), 88 | }, 89 | { 90 | TimeFrame: datadog.String("90d"), 91 | Target: datadog.Float64(99), 92 | Warning: datadog.Float64(99.5), 93 | }, 94 | }, 95 | Type: &datadog.ServiceLevelObjectiveTypeMetric, 96 | Query: &datadog.ServiceLevelObjectiveMetricQuery{ 97 | Numerator: datadog.String("sum:my.metric{type:good}.as_count()"), 98 | Denominator: datadog.String("sum:my.metric{*}.as_count()"), 99 | }, 100 | } 101 | expected2 := &datadog.ServiceLevelObjective{ 102 | Name: datadog.String("Integration Test SLO - 'Test Multi Time Frame Delete 2'"), 103 | Description: datadog.String("Integration test for SLOs"), 104 | Tags: []string{"test:integration"}, 105 | Thresholds: datadog.ServiceLevelObjectiveThresholds{ 106 | { 107 | TimeFrame: datadog.String("7d"), 108 | Target: datadog.Float64(99), 109 | Warning: datadog.Float64(99.5), 110 | }, 111 | }, 112 | Type: &datadog.ServiceLevelObjectiveTypeMetric, 113 | Query: &datadog.ServiceLevelObjectiveMetricQuery{ 114 | Numerator: datadog.String("sum:my.metric{type:good}.as_count()"), 115 | Denominator: datadog.String("sum:my.metric{*}.as_count()"), 116 | }, 117 | } 118 | 119 | // Create 120 | actual1, err := client.CreateServiceLevelObjective(expected1) 121 | assert.NoError(t, err) 122 | assert.NotEmpty(t, actual1.GetID()) 123 | actual2, err := client.CreateServiceLevelObjective(expected2) 124 | assert.NoError(t, err) 125 | assert.NotEmpty(t, actual2.GetID()) 126 | 127 | time.Sleep(time.Second) 128 | 129 | // Do multi-timeframe delete 130 | timeframesToDelete := map[string][]string{ 131 | // delete only 2 of 3 timeframes from 1 132 | actual1.GetID(): { 133 | "30d", "90d", 134 | }, 135 | // delete all timeframes from 2 136 | actual2.GetID(): { 137 | "7d", 138 | }, 139 | } 140 | 141 | resp, err := client.DeleteServiceLevelObjectiveTimeFrames(timeframesToDelete) 142 | assert.EqualValues(t, []string{actual2.GetID()}, resp.DeletedIDs) 143 | assert.EqualValues(t, []string{actual1.GetID()}, resp.UpdatedIDs) 144 | 145 | time.Sleep(time.Second) 146 | 147 | // Delete 148 | err = client.DeleteServiceLevelObjective(actual1.GetID()) 149 | assert.NoError(t, err) 150 | } 151 | -------------------------------------------------------------------------------- /integration/snapshot_test.go: -------------------------------------------------------------------------------- 1 | package integration 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func Jsonify(v interface{}) (string, error) { 11 | if b, err := json.MarshalIndent(v, "", " "); err != nil { 12 | return "", err 13 | } else { 14 | return string(b), nil 15 | } 16 | } 17 | 18 | func TestSnapshot(t *testing.T) { 19 | 20 | end := time.Now().Unix() 21 | start := end - 3600 22 | 23 | query_s := "avg:system.mem.used{*}" 24 | url, err := client.Snapshot(query_s, time.Unix(start, 0), time.Unix(end, 0), "") 25 | 26 | if err != nil { 27 | t.Fatalf("Couldn't create snapshot for query(%s): %s", query_s, err) 28 | } 29 | 30 | fmt.Printf("query snapshot url: %s\n", url) 31 | } 32 | 33 | func TestSnapshotGeneric(t *testing.T) { 34 | 35 | end := time.Now().Unix() 36 | start := end - 3600 37 | 38 | // create new graph def 39 | graphs := createCustomGraph() 40 | 41 | graph_def, err := Jsonify(graphs[0].Definition) 42 | if err != nil { 43 | t.Fatalf("Couldn't create graph_def: %s", err) 44 | } 45 | 46 | options := map[string]string{"graph_def": graph_def} 47 | 48 | url, err := client.SnapshotGeneric(options, time.Unix(start, 0), time.Unix(end, 0)) 49 | 50 | if err != nil { 51 | t.Fatalf("Couldn't create snapshot from graph_def(%s): %s", graph_def, err) 52 | } 53 | 54 | fmt.Printf("Graph def snapshot url: %s\n", url) 55 | } 56 | -------------------------------------------------------------------------------- /integration/users_test.go: -------------------------------------------------------------------------------- 1 | package integration 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/zorkian/go-datadog-api" 8 | ) 9 | 10 | func TestUserCreateAndDelete(t *testing.T) { 11 | handle := "test@example.com" 12 | name := "tester" 13 | 14 | user, err := client.CreateUser(datadog.String(handle), datadog.String(name)) 15 | assert.NotNil(t, user) 16 | assert.Nil(t, err) 17 | // Users aren't actually deleted; they're disabled 18 | // As a result, this doesn't really create a new user. The existing user is disabled 19 | // at the end of the test, so we enable it here so that we can test deletion (disabling). 20 | user.Disabled = datadog.Bool(false) 21 | err = client.UpdateUser(*user) 22 | assert.Nil(t, err) 23 | 24 | defer func() { 25 | err := client.DeleteUser(handle) 26 | if err != nil { 27 | t.Fatalf("Failed to delete user: %s", err) 28 | } 29 | }() 30 | 31 | assert.Equal(t, *user.Handle, handle) 32 | assert.Equal(t, *user.Name, name) 33 | 34 | newUser, err := client.GetUser(handle) 35 | assert.Nil(t, err) 36 | assert.Equal(t, *newUser.Handle, handle) 37 | assert.Equal(t, *newUser.Name, name) 38 | } 39 | -------------------------------------------------------------------------------- /ip_ranges.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Datadog API for Go 3 | * 4 | * Please see the included LICENSE file for licensing information. 5 | * 6 | * Copyright 2019 by authors and contributors. 7 | */ 8 | 9 | package datadog 10 | 11 | import ( 12 | "fmt" 13 | ) 14 | 15 | // IP ranges US: https://ip-ranges.datadoghq.com 16 | // EU: https://ip-ranges.datadoghq.eu 17 | // Same structure 18 | type IPRangesResp struct { 19 | Agents map[string][]string `json:"agents"` 20 | API map[string][]string `json:"api"` 21 | Apm map[string][]string `json:"apm"` 22 | Logs map[string][]string `json:"logs"` 23 | Process map[string][]string `json:"process"` 24 | Synthetics map[string][]string `json:"synthetics"` 25 | Webhooks map[string][]string `json:"webhooks"` 26 | } 27 | 28 | // GetIPRanges returns all IP addresses by section: agents, api, apm, logs, process, synthetics, webhooks 29 | func (client *Client) GetIPRanges() (*IPRangesResp, error) { 30 | var out IPRangesResp 31 | urlIPRanges, err := client.URLIPRanges() 32 | if err != nil { 33 | return nil, fmt.Errorf("Error getting IP Ranges URL: %s", err) 34 | } 35 | if err := client.doJsonRequest("GET", urlIPRanges, nil, &out); err != nil { 36 | return nil, err 37 | } 38 | return &out, nil 39 | } 40 | -------------------------------------------------------------------------------- /ip_ranges_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Datadog API for Go 3 | * 4 | * Please see the included LICENSE file for licensing information. 5 | * 6 | * Copyright 2019 by authors and contributors. 7 | */ 8 | 9 | package datadog_test 10 | 11 | import ( 12 | "encoding/json" 13 | "testing" 14 | 15 | "github.com/stretchr/testify/assert" 16 | dd "github.com/zorkian/go-datadog-api" 17 | ) 18 | 19 | func TestIPRangesSerialization(t *testing.T) { 20 | 21 | var mapResponse map[string][]string 22 | mapResponse = make(map[string][]string) 23 | 24 | mapResponse["prefixes_ipv4"] = []string{"10.10.10.10/32", "10.10.10.10/32"} 25 | mapResponse["prefixes_ipv6"] = []string{"2000:1900:0:100c::/128", "2000:1900:0:c100::/128"} 26 | 27 | raw := ` 28 | { 29 | "agents": { 30 | "prefixes_ipv4": [ 31 | "10.10.10.10/32", 32 | "10.10.10.10/32" 33 | ], 34 | "prefixes_ipv6": [ 35 | "2000:1900:0:100c::/128", 36 | "2000:1900:0:c100::/128" 37 | ] 38 | } 39 | }` 40 | 41 | var ipranges dd.IPRangesResp 42 | err := json.Unmarshal([]byte(raw), &ipranges) 43 | assert.Equal(t, err, nil) 44 | assert.Equal(t, ipranges.Agents["prefixes_ipv4"], mapResponse["prefixes_ipv4"]) 45 | assert.Equal(t, ipranges.Agents["prefixes_ipv6"], mapResponse["prefixes_ipv6"]) 46 | } 47 | -------------------------------------------------------------------------------- /log_lists.go: -------------------------------------------------------------------------------- 1 | package datadog 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | const logsListPath = "/v1/logs-queries/list" 8 | 9 | // LogsListRequest represents the request body sent to the list API 10 | type LogsListRequest struct { 11 | Index *string `json:"index,omitempty"` 12 | Limit *int `json:"limit,omitempty"` 13 | Query *string `json:"query"` 14 | Sort *string `json:"sort,omitempty"` 15 | StartAt *string `json:"startAt,omitempty"` 16 | Time *LogsListRequestQueryTime `json:"time"` 17 | } 18 | 19 | // LogsListRequestQueryTime represents the time object for the request sent to the list API 20 | type LogsListRequestQueryTime struct { 21 | TimeFrom *string `json:"from"` 22 | TimeTo *string `json:"to"` 23 | TimeZone *string `json:"timezone,omitempty"` 24 | Offset *int `json:"offset,omitempty"` 25 | } 26 | 27 | // LogsList represents the base API response returned by the list API 28 | type LogsList struct { 29 | Logs []Logs `json:"logs"` 30 | NextLogID *string `json:"nextLogId"` 31 | Status *string `json:"status"` 32 | } 33 | 34 | func (l *LogsList) next() bool { 35 | if l.NextLogID != nil { 36 | return true 37 | } 38 | 39 | return false 40 | } 41 | 42 | // Logs represents the data of a log entry and contains the UUID of that entry (which 43 | // is used for the StartAt option in an API request) as well as the content of that log 44 | type Logs struct { 45 | ID *string `json:"id"` 46 | Content LogsContent `json:"content"` 47 | } 48 | 49 | // LogsContent respresents the actual log content returned by the list API 50 | type LogsContent struct { 51 | Timestamp *time.Time `json:"timestamp"` 52 | Tags []string `json:"tags,omitempty"` 53 | Attributes LogsAttributes `json:"attributes,omitempty"` 54 | Host *string `json:"host"` 55 | Service *string `json:"service"` 56 | Message *string `json:"message"` 57 | } 58 | 59 | // LogsAttributes represents the Content attribute object from the list API 60 | type LogsAttributes map[string]interface{} 61 | 62 | // GetLogsList gets a page of log entries based on the values in the provided LogListRequest 63 | func (client *Client) GetLogsList(logsRequest *LogsListRequest) (logsList *LogsList, err error) { 64 | out := &LogsList{} 65 | 66 | if err = client.doJsonRequest("POST", logsListPath, logsRequest, out); err != nil { 67 | return nil, err 68 | } 69 | 70 | return out, nil 71 | } 72 | 73 | // GetLogsListPages calls GetLogsList and handles the pagination performed by the 'logs-queries/list' API 74 | func (client *Client) GetLogsListPages(logsRequest *LogsListRequest, maxResults int) (logs []Logs, err error) { 75 | 76 | // Reduce the number of results we have to fetch if the limit in our request body is greater than the value of maxResults 77 | if maxResults < logsRequest.GetLimit() && maxResults > 0 { 78 | logsRequest.SetLimit(maxResults) 79 | } 80 | 81 | response, err := client.GetLogsList(logsRequest) 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | logs = append(logs, response.Logs...) 87 | if maxResults < 0 { // Retrieve all results 88 | for response.next() && err == nil { 89 | logsRequest.StartAt = response.NextLogID 90 | response, err = client.GetLogsList(logsRequest) 91 | if err != nil { 92 | return logs, err 93 | } 94 | 95 | logs = append(logs, response.Logs...) 96 | } 97 | } else { 98 | for response.next() && err == nil && len(logs) < maxResults { 99 | logsRequest.StartAt = response.NextLogID 100 | 101 | if maxResults-len(logs) < logsRequest.GetLimit() { 102 | logsRequest.SetLimit(maxResults - len(logs)) 103 | } 104 | 105 | response, err = client.GetLogsList(logsRequest) 106 | if err != nil { 107 | return logs, err 108 | } 109 | 110 | logs = append(logs, response.Logs...) 111 | } 112 | } 113 | return logs, err 114 | } 115 | -------------------------------------------------------------------------------- /log_lists_test.go: -------------------------------------------------------------------------------- 1 | package datadog 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestGetLogsList(t *testing.T) { 14 | assert := assert.New(t) 15 | 16 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 17 | response, err := ioutil.ReadFile("./tests/fixtures/logs/loglist_response.json") 18 | assert.Nil(err) 19 | w.Write(response) 20 | })) 21 | 22 | defer ts.Close() 23 | 24 | client := Client{ 25 | baseUrl: ts.URL, 26 | HttpClient: http.DefaultClient, 27 | } 28 | 29 | req := &LogsListRequest{ 30 | Index: String("main"), 31 | Limit: Int(50), 32 | } 33 | 34 | logList, err := client.GetLogsList(req) 35 | 36 | assert.Nil(err) 37 | assert.Len(logList.Logs, 2) 38 | 39 | } 40 | 41 | func TestGetLogsListPages(t *testing.T) { 42 | assert := assert.New(t) 43 | 44 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 45 | responseBody := &LogsListRequest{} 46 | 47 | body, _ := ioutil.ReadAll(r.Body) 48 | json.Unmarshal(body, responseBody) 49 | 50 | if responseBody.StartAt == nil { 51 | response, err := ioutil.ReadFile("./tests/fixtures/logs/loglist_response.json") 52 | 53 | assert.Nil(err) 54 | w.Write(response) 55 | } else { 56 | assert.Equal(*responseBody.StartAt, "BBBBBWgN8Xwgr1vKDQAAAABBV2dOOFh3ZzZobm1mWXJFYTR0OA") 57 | 58 | response, err := ioutil.ReadFile("./tests/fixtures/logs/loglist_page_response.json") 59 | 60 | assert.Nil(err) 61 | w.Write(response) 62 | } 63 | })) 64 | 65 | defer ts.Close() 66 | 67 | client := Client{ 68 | baseUrl: ts.URL, 69 | HttpClient: http.DefaultClient, 70 | } 71 | 72 | req := &LogsListRequest{ 73 | Index: String("main"), 74 | Limit: Int(50), 75 | } 76 | 77 | logs, err := client.GetLogsListPages(req, -1) 78 | 79 | assert.Nil(err) 80 | assert.Len(logs, 3) 81 | } 82 | -------------------------------------------------------------------------------- /logs_index_lists.go: -------------------------------------------------------------------------------- 1 | package datadog 2 | 3 | const logsIndexListPath = "/v1/logs/config/index-order" 4 | 5 | // LogsIndexList represents the index list object from config API. 6 | type LogsIndexList struct { 7 | IndexNames []string `json:"index_names"` 8 | } 9 | 10 | // GetLogsIndexList gets the full list of available indexes by their names. 11 | func (client *Client) GetLogsIndexList() (*LogsIndexList, error) { 12 | var indexList LogsIndexList 13 | if err := client.doJsonRequest("GET", logsIndexListPath, nil, &indexList); err != nil { 14 | return nil, err 15 | } 16 | return &indexList, nil 17 | } 18 | 19 | // UpdateLogsIndexList updates the order of indexes. 20 | func (client *Client) UpdateLogsIndexList(indexList *LogsIndexList) (*LogsIndexList, error) { 21 | var updatedIndexList = &LogsIndexList{} 22 | if err := client.doJsonRequest("PUT", logsIndexListPath, indexList, updatedIndexList); err != nil { 23 | return nil, err 24 | } 25 | return updatedIndexList, nil 26 | } 27 | -------------------------------------------------------------------------------- /logs_index_lists_test.go: -------------------------------------------------------------------------------- 1 | package datadog 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "io/ioutil" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | ) 10 | 11 | func TestLogsIndexListGet(t *testing.T) { 12 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 13 | response, err := ioutil.ReadFile("./tests/fixtures/logs/indexlist_response.json") 14 | assert.Nil(t, err) 15 | w.Write(response) 16 | })) 17 | 18 | defer ts.Close() 19 | 20 | client := Client{ 21 | baseUrl: ts.URL, 22 | HttpClient: http.DefaultClient, 23 | } 24 | 25 | indexList, err := client.GetLogsIndexList() 26 | 27 | assert.Nil(t, err) 28 | 29 | assert.Equal(t, []string{"main", "minor"}, indexList.IndexNames) 30 | 31 | } 32 | -------------------------------------------------------------------------------- /logs_indexes.go: -------------------------------------------------------------------------------- 1 | package datadog 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | const logsIndexPath = "/v1/logs/config/indexes" 8 | 9 | // LogsIndex represents the Logs index object from config API. 10 | type LogsIndex struct { 11 | Name *string `json:"name"` 12 | NumRetentionDays *int64 `json:"num_retention_days,omitempty"` 13 | DailyLimit *int64 `json:"daily_limit,omitempty"` 14 | IsRateLimited *bool `json:"is_rate_limited,omitempty"` 15 | Filter *FilterConfiguration `json:"filter"` 16 | ExclusionFilters []ExclusionFilter `json:"exclusion_filters"` 17 | } 18 | 19 | // ExclusionFilter represents the index exclusion filter object from config API. 20 | type ExclusionFilter struct { 21 | Name *string `json:"name"` 22 | IsEnabled *bool `json:"is_enabled,omitempty"` 23 | Filter *Filter `json:"filter"` 24 | } 25 | 26 | // Filter represents the index filter object from config API. 27 | type Filter struct { 28 | Query *string `json:"query,omitempty"` 29 | SampleRate *float64 `json:"sample_rate,omitempty"` 30 | } 31 | 32 | // GetLogsIndex gets the specific logs index by specific name. 33 | func (client *Client) GetLogsIndex(name string) (*LogsIndex, error) { 34 | var index LogsIndex 35 | if err := client.doJsonRequest("GET", fmt.Sprintf("%s/%s", logsIndexPath, name), nil, &index); err != nil { 36 | return nil, err 37 | } 38 | return &index, nil 39 | } 40 | 41 | // UpdateLogsIndex updates the specific index by it's name. 42 | func (client *Client) UpdateLogsIndex(name string, index *LogsIndex) (*LogsIndex, error) { 43 | var updatedIndex = &LogsIndex{} 44 | if err := client.doJsonRequest("PUT", fmt.Sprintf("%s/%s", logsIndexPath, name), index, updatedIndex); err != nil { 45 | return nil, err 46 | } 47 | return updatedIndex, nil 48 | } 49 | -------------------------------------------------------------------------------- /logs_indexes_test.go: -------------------------------------------------------------------------------- 1 | package datadog 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "io/ioutil" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | ) 10 | 11 | func TestLogsIndexGet(t *testing.T) { 12 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 13 | response, err := ioutil.ReadFile("./tests/fixtures/logs/index_response.json") 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | w.Write(response) 18 | })) 19 | 20 | defer ts.Close() 21 | 22 | client := Client{ 23 | baseUrl: ts.URL, 24 | HttpClient: http.DefaultClient, 25 | rateLimitingStats: make(map[string]RateLimit), 26 | } 27 | 28 | logsIndex, err := client.GetLogsIndex("main") 29 | assert.Nil(t, err) 30 | assert.Equal(t, expectedIndex, logsIndex) 31 | } 32 | 33 | var expectedIndex = &LogsIndex{ 34 | Name: String("main"), 35 | NumRetentionDays: Int64(90), 36 | DailyLimit: Int64(151000000), 37 | IsRateLimited: Bool(false), 38 | Filter: &FilterConfiguration{Query: String("*")}, 39 | ExclusionFilters: []ExclusionFilter{ 40 | { 41 | Name: String("Filter 1"), 42 | IsEnabled: Bool(true), 43 | Filter: &Filter{ 44 | Query: String("source:agent status:info"), 45 | SampleRate: Float64(1.0), 46 | }, 47 | }, { 48 | Name: String("Filter 2"), 49 | IsEnabled: Bool(true), 50 | Filter: &Filter{ 51 | Query: String("source:agent"), 52 | SampleRate: Float64(0.8), 53 | }, 54 | }, { 55 | Name: String("Filter 3"), 56 | IsEnabled: Bool(true), 57 | Filter: &Filter{ 58 | Query: String("source:debug"), 59 | SampleRate: Float64(0.5), 60 | }, 61 | }, 62 | }, 63 | } 64 | -------------------------------------------------------------------------------- /logs_pipeline_lists.go: -------------------------------------------------------------------------------- 1 | package datadog 2 | 3 | const ( 4 | logsPipelineListPath = "/v1/logs/config/pipeline-order" 5 | ) 6 | 7 | // LogsPipelineList struct represents the pipeline order from Logs Public Config API. 8 | type LogsPipelineList struct { 9 | PipelineIds []string `json:"pipeline_ids"` 10 | } 11 | 12 | // GetLogsPipelineList get the full list of created pipelines. 13 | func (client *Client) GetLogsPipelineList() (*LogsPipelineList, error) { 14 | var pipelineList LogsPipelineList 15 | if err := client.doJsonRequest("GET", logsPipelineListPath, nil, &pipelineList); err != nil { 16 | return nil, err 17 | } 18 | return &pipelineList, nil 19 | } 20 | 21 | // UpdateLogsPipelineList updates the pipeline list order, it returns error (422 Unprocessable Entity) 22 | // if one tries to delete or add pipeline. 23 | func (client *Client) UpdateLogsPipelineList(pipelineList *LogsPipelineList) (*LogsPipelineList, error) { 24 | var updatedPipelineList = &LogsPipelineList{} 25 | if err := client.doJsonRequest("PUT", logsPipelineListPath, pipelineList, updatedPipelineList); err != nil { 26 | return nil, err 27 | } 28 | return updatedPipelineList, nil 29 | } 30 | -------------------------------------------------------------------------------- /logs_pipeline_lists_test.go: -------------------------------------------------------------------------------- 1 | package datadog 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "io/ioutil" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | ) 10 | 11 | func TestLogsPipelineListGet(t *testing.T) { 12 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 13 | response, err := ioutil.ReadFile("./tests/fixtures/logs/pipelinelist_response.json") 14 | assert.Nil(t, err) 15 | w.Write(response) 16 | })) 17 | 18 | defer ts.Close() 19 | 20 | client := Client{ 21 | baseUrl: ts.URL, 22 | HttpClient: http.DefaultClient, 23 | } 24 | 25 | pipelineList, err := client.GetLogsPipelineList() 26 | 27 | assert.Nil(t, err) 28 | 29 | assert.Equal(t, 3, len(pipelineList.PipelineIds)) 30 | 31 | } 32 | -------------------------------------------------------------------------------- /logs_pipelines.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Datadog API for Go 3 | * 4 | * Please see the included LICENSE file for licensing information. 5 | * 6 | * Copyright 2019 by authors and contributors. 7 | */ 8 | 9 | package datadog 10 | 11 | import ( 12 | "fmt" 13 | ) 14 | 15 | const ( 16 | logsPipelinesPath = "/v1/logs/config/pipelines" 17 | ) 18 | 19 | // LogsPipeline struct to represent the json object received from Logs Public Config API. 20 | type LogsPipeline struct { 21 | Id *string `json:"id,omitempty"` 22 | Type *string `json:"type,omitempty"` 23 | Name *string `json:"name"` 24 | IsEnabled *bool `json:"is_enabled,omitempty"` 25 | IsReadOnly *bool `json:"is_read_only,omitempty"` 26 | Filter *FilterConfiguration `json:"filter"` 27 | Processors []LogsProcessor `json:"processors,omitempty"` 28 | } 29 | 30 | // FilterConfiguration struct to represent the json object of filter configuration. 31 | type FilterConfiguration struct { 32 | Query *string `json:"query"` 33 | } 34 | 35 | // GetLogsPipeline queries Logs Public Config API with given a pipeline id for the complete pipeline object. 36 | func (client *Client) GetLogsPipeline(id string) (*LogsPipeline, error) { 37 | var pipeline LogsPipeline 38 | if err := client.doJsonRequest("GET", fmt.Sprintf("%s/%s", logsPipelinesPath, id), nil, &pipeline); err != nil { 39 | return nil, err 40 | } 41 | return &pipeline, nil 42 | } 43 | 44 | // CreateLogsPipeline sends pipeline creation request to Config API 45 | func (client *Client) CreateLogsPipeline(pipeline *LogsPipeline) (*LogsPipeline, error) { 46 | var createdPipeline = &LogsPipeline{} 47 | if err := client.doJsonRequest("POST", logsPipelinesPath, pipeline, createdPipeline); err != nil { 48 | return nil, err 49 | } 50 | return createdPipeline, nil 51 | } 52 | 53 | // UpdateLogsPipeline updates the pipeline object of a given pipeline id. 54 | func (client *Client) UpdateLogsPipeline(id string, pipeline *LogsPipeline) (*LogsPipeline, error) { 55 | var updatedPipeline = &LogsPipeline{} 56 | if err := client.doJsonRequest("PUT", fmt.Sprintf("%s/%s", logsPipelinesPath, id), pipeline, updatedPipeline); err != nil { 57 | return nil, err 58 | } 59 | return updatedPipeline, nil 60 | } 61 | 62 | // DeleteLogsPipeline deletes the pipeline for a given id, returns 200 OK when operation succeed 63 | func (client *Client) DeleteLogsPipeline(id string) error { 64 | return client.doJsonRequest("DELETE", fmt.Sprintf("%s/%s", logsPipelinesPath, id), nil, nil) 65 | } 66 | -------------------------------------------------------------------------------- /metric_metadata.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Datadog API for Go 3 | * 4 | * Please see the included LICENSE file for licensing information. 5 | * 6 | * Copyright 2013 by authors and contributors. 7 | */ 8 | 9 | package datadog 10 | 11 | import "fmt" 12 | 13 | // MetricMetadata allows you to edit fields of a metric's metadata. 14 | type MetricMetadata struct { 15 | Type *string `json:"type,omitempty"` 16 | Description *string `json:"description,omitempty"` 17 | ShortName *string `json:"short_name,omitempty"` 18 | Unit *string `json:"unit,omitempty"` 19 | PerUnit *string `json:"per_unit,omitempty"` 20 | StatsdInterval *int `json:"statsd_interval,omitempty"` 21 | } 22 | 23 | // ViewMetricMetadata allows you to get metadata about a specific metric. 24 | func (client *Client) ViewMetricMetadata(mn string) (*MetricMetadata, error) { 25 | var out MetricMetadata 26 | if err := client.doJsonRequest("GET", fmt.Sprintf("/v1/metrics/%s", mn), nil, &out); err != nil { 27 | return nil, err 28 | } 29 | return &out, nil 30 | } 31 | 32 | // EditMetricMetadata edits the metadata for the given metric. 33 | func (client *Client) EditMetricMetadata(mn string, mm *MetricMetadata) (*MetricMetadata, error) { 34 | var out MetricMetadata 35 | if err := client.doJsonRequest("PUT", fmt.Sprintf("/v1/metrics/%s", mn), mm, &out); err != nil { 36 | return nil, err 37 | } 38 | return &out, nil 39 | } 40 | -------------------------------------------------------------------------------- /monitors_test.go: -------------------------------------------------------------------------------- 1 | package datadog_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "encoding/json" 7 | 8 | "github.com/stretchr/testify/assert" 9 | dd "github.com/zorkian/go-datadog-api" 10 | ) 11 | 12 | func TestMonitorSerialization(t *testing.T) { 13 | 14 | raw := ` 15 | { 16 | "id": 91879, 17 | "message": "We may need to add web hosts if this is consistently high.", 18 | "name": "Bytes received on host0", 19 | "options": { 20 | "no_data_timeframe": 20, 21 | "notify_audit": false, 22 | "notify_no_data": false, 23 | "silenced": {} 24 | }, 25 | "org_id": 1499, 26 | "query": "avg(last_1h):sum:system.net.bytes_rcvd{host:host0} > 100", 27 | "type": "metric alert", 28 | "multi": false, 29 | "created": "2015-12-18T16:34:14.014039+00:00", 30 | "modified": "2015-12-18T16:34:14.014039+00:00", 31 | "state": { 32 | "groups": { 33 | "host:host0": { 34 | "last_nodata_ts": null, 35 | "last_notified_ts": 1481909160, 36 | "last_resolved_ts": 1481908200, 37 | "last_triggered_ts": 1481909160, 38 | "name": "host:host0", 39 | "status": "Alert", 40 | "triggering_value": { 41 | "from_ts": 1481909037, 42 | "to_ts": 1481909097, 43 | "value": 1000 44 | } 45 | } 46 | } 47 | } 48 | }` 49 | 50 | var monitor dd.Monitor 51 | err := json.Unmarshal([]byte(raw), &monitor) 52 | 53 | assert.Equal(t, err, nil) 54 | 55 | assert.Equal(t, *monitor.Id, 91879) 56 | assert.Equal(t, *monitor.State.Groups["host:host0"].Name, "host:host0") 57 | 58 | } 59 | 60 | func TestMonitorSerializationForLogAlert(t *testing.T) { 61 | 62 | raw := ` 63 | { 64 | "tags": [ 65 | "app:webserver", 66 | "frontend" 67 | ], 68 | "deleted": null, 69 | "query": "logs(\"env:develop\").index(\"main\").rollup(\"count\").last(\"5m\") > 500", 70 | "message": "Monitor Log Count", 71 | "id": 91879, 72 | "multi": false, 73 | "name": "Monitor Log Count", 74 | "created": "2018-12-06T08:26:17.235509+00:00", 75 | "created_at": 1544084777000, 76 | "org_id": 1499, 77 | "modified": "2018-12-06T08:26:17.235509+00:00", 78 | "overall_state_modified": null, 79 | "overall_state": "No Data", 80 | "type": "log alert", 81 | "options": { 82 | "notify_audit": false, 83 | "locked": false, 84 | "timeout_h": 0, 85 | "silenced": {}, 86 | "enable_logs_sample": true, 87 | "thresholds": { 88 | "comparison": ">", 89 | "critical": 1000, 90 | "period": { 91 | "seconds": 300, 92 | "text": "5 minutes", 93 | "value": "last_5m", 94 | "name": "5 minute average", 95 | "unit": "minutes" 96 | }, 97 | "timeAggregator": "avg" 98 | }, 99 | "queryConfig": { 100 | "logset": { 101 | "id": "1499", 102 | "name": "main" 103 | }, 104 | "timeRange": { 105 | "to": 1539675566736, 106 | "live": true, 107 | "from": 1539661166736 108 | }, 109 | "queryString": "env:develop", 110 | "queryIsFailed": false 111 | }, 112 | "new_host_delay": 300, 113 | "notify_no_data": false, 114 | "renotify_interval": 0, 115 | "evaluation_delay": 500, 116 | "no_data_timeframe": 2 117 | } 118 | } 119 | ` 120 | 121 | var monitor dd.Monitor 122 | err := json.Unmarshal([]byte(raw), &monitor) 123 | 124 | assert.Equal(t, err, nil) 125 | 126 | assert.Equal(t, 91879, *monitor.Id) 127 | assert.Equal(t, true, *monitor.Options.EnableLogsSample) 128 | assert.Equal(t, json.Number("1499"), *monitor.Options.QueryConfig.LogSet.ID) 129 | assert.Equal(t, json.Number("1539661166736"), *monitor.Options.QueryConfig.TimeRange.From) 130 | assert.Equal(t, "env:develop", *monitor.Options.QueryConfig.QueryString) 131 | } 132 | -------------------------------------------------------------------------------- /ratelimit.go: -------------------------------------------------------------------------------- 1 | package datadog 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/url" 7 | ) 8 | 9 | // The list of Rate Limited Endpoints of the Datadog API. 10 | // https://docs.datadoghq.com/api/?lang=bash#rate-limiting 11 | func (client *Client) updateRateLimits(resp *http.Response, api *url.URL) error { 12 | if resp == nil || resp.Header == nil || api.Path == "" { 13 | return fmt.Errorf("malformed HTTP content.") 14 | } 15 | if resp.Header.Get("X-RateLimit-Remaining") == "" { 16 | // The endpoint is not Rate Limited. 17 | return nil 18 | } 19 | client.m.Lock() 20 | defer client.m.Unlock() 21 | client.rateLimitingStats[api.Path] = RateLimit{ 22 | Limit: resp.Header.Get("X-RateLimit-Limit"), 23 | Reset: resp.Header.Get("X-RateLimit-Reset"), 24 | Period: resp.Header.Get("X-RateLimit-Period"), 25 | Remaining: resp.Header.Get("X-RateLimit-Remaining"), 26 | } 27 | return nil 28 | } 29 | 30 | // GetRateLimitStats is a threadsafe getter to retrieve the rate limiting stats associated with the Client. 31 | func (client *Client) GetRateLimitStats() map[string]RateLimit { 32 | client.m.Lock() 33 | defer client.m.Unlock() 34 | // Shallow copy to avoid corrupted data 35 | mapCopy := make(map[string]RateLimit, len(client.rateLimitingStats)) 36 | for k, v := range client.rateLimitingStats { 37 | mapCopy[k] = v 38 | } 39 | return mapCopy 40 | } 41 | -------------------------------------------------------------------------------- /ratelimit_test.go: -------------------------------------------------------------------------------- 1 | package datadog 2 | 3 | import ( 4 | "fmt" 5 | "github.com/stretchr/testify/assert" 6 | "net/http" 7 | "net/url" 8 | "testing" 9 | ) 10 | 11 | func Test_updateRateLimits(t *testing.T) { 12 | // fake client to ensure that we are race free. 13 | client := Client{ 14 | rateLimitingStats: make(map[string]RateLimit), 15 | } 16 | tests := []struct { 17 | desc string 18 | api *url.URL 19 | resp *http.Response 20 | header RateLimit 21 | error error 22 | }{ 23 | { 24 | "nominal case query", 25 | &url.URL{Path: "/v1/query"}, 26 | makeHeader(RateLimit{"1", "2", "3", "4"}), 27 | RateLimit{"1", "2", "3", "4"}, 28 | nil, 29 | }, 30 | { 31 | "nominal case logs", 32 | &url.URL{Path: "/v1/logs-queries/list"}, 33 | makeHeader(RateLimit{"2", "2", "1", "5"}), 34 | RateLimit{"2", "2", "1", "5"}, 35 | nil, 36 | }, 37 | { 38 | "no response", 39 | &url.URL{Path: ""}, 40 | nil, 41 | RateLimit{}, 42 | fmt.Errorf("malformed HTTP content."), 43 | }, 44 | { 45 | "no header", 46 | &url.URL{Path: "/v2/error"}, 47 | makeEmptyHeader(), 48 | RateLimit{}, 49 | fmt.Errorf("malformed HTTP content."), 50 | }, 51 | { 52 | "not rate limited", 53 | &url.URL{Path: "/v2/error"}, 54 | makeHeader(RateLimit{}), 55 | RateLimit{}, 56 | nil, 57 | }, 58 | { 59 | "update case query", 60 | &url.URL{Path: "/v1/query"}, 61 | makeHeader(RateLimit{"2", "4", "6", "4"}), 62 | RateLimit{"2", "4", "6", "4"}, 63 | nil, 64 | }, 65 | } 66 | 67 | for i, tt := range tests { 68 | t.Run(fmt.Sprintf("#%d %s", i, tt.desc), func(t *testing.T) { 69 | err := client.updateRateLimits(tt.resp, tt.api) 70 | assert.Equal(t, tt.error, err) 71 | assert.Equal(t, tt.header, client.rateLimitingStats[tt.api.Path]) 72 | }) 73 | } 74 | } 75 | 76 | func makeHeader(r RateLimit) *http.Response { 77 | h := http.Response{ 78 | Header: make(map[string][]string), 79 | } 80 | h.Header.Set("X-RateLimit-Limit", r.Limit) 81 | h.Header.Set("X-RateLimit-Reset", r.Reset) 82 | h.Header.Set("X-RateLimit-Period", r.Period) 83 | h.Header.Set("X-RateLimit-Remaining", r.Remaining) 84 | return &h 85 | } 86 | 87 | func makeEmptyHeader() *http.Response { 88 | return &http.Response{ 89 | Header: nil, 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /request_test.go: -------------------------------------------------------------------------------- 1 | package datadog 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/http/httptest" 7 | "net/url" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | var needKeysInQueryParams = []string{"/v1/series", "/v1/check_run", "/v1/events", "/v1/screen"} 14 | 15 | func TestUriForApi(t *testing.T) { 16 | c := Client{ 17 | apiKey: "sample_api_key", 18 | appKey: "sample_app_key", 19 | baseUrl: "https://base.datadoghq.com", 20 | HttpClient: &http.Client{}, 21 | RetryTimeout: 1000, 22 | } 23 | t.Run("Get Uri for api string with query string", func(t *testing.T) { 24 | uri, err := c.uriForAPI("/v1/events?type=critical") 25 | assert.Nil(t, err) 26 | assert.Equal(t, "https://base.datadoghq.com/api/v1/events?api_key=sample_api_key&application_key=sample_app_key&type=critical", uri) 27 | 28 | }) 29 | t.Run("Get Uri for api without query string", func(t *testing.T) { 30 | uri, err := c.uriForAPI("/v1/events") 31 | assert.Nil(t, err) 32 | assert.Equal(t, "https://base.datadoghq.com/api/v1/events?api_key=sample_api_key&application_key=sample_app_key", uri) 33 | }) 34 | t.Run("Test all endpoints that need keys in query params", func(t *testing.T) { 35 | for _, api := range needKeysInQueryParams { 36 | uri, err := c.uriForAPI(api) 37 | assert.Nil(t, err) 38 | parsed, err := url.Parse(uri) 39 | assert.Nil(t, err) 40 | assert.Equal(t, parsed.Query().Get("api_key"), "sample_api_key") 41 | assert.Equal(t, parsed.Query().Get("application_key"), "sample_app_key") 42 | } 43 | }) 44 | t.Run("Test an endpoint that doesn't need keys in query params", func(t *testing.T) { 45 | uri, err := c.uriForAPI("/v1/dashboard") 46 | assert.Nil(t, err) 47 | assert.Equal(t, "https://base.datadoghq.com/api/v1/dashboard", uri) 48 | }) 49 | } 50 | 51 | func TestCreateRequest(t *testing.T) { 52 | c := Client{ 53 | apiKey: "sample_api_key", 54 | appKey: "sample_app_key", 55 | baseUrl: "https://base.datadoghq.com", 56 | HttpClient: &http.Client{}, 57 | RetryTimeout: 1000, 58 | } 59 | t.Run("Test an endpoint that doesn't need keys in query params", func(t *testing.T) { 60 | req, err := c.createRequest("GET", "/v1/dashboard", nil) 61 | assert.Nil(t, err) 62 | assert.Equal(t, "sample_api_key", req.Header.Get("DD-API-KEY")) 63 | assert.Equal(t, "sample_app_key", req.Header.Get("DD-APPLICATION-KEY")) 64 | }) 65 | t.Run("Test endpoints that need keys in query params", func(t *testing.T) { 66 | for _, api := range needKeysInQueryParams { 67 | req, err := c.createRequest("GET", api, nil) 68 | assert.Nil(t, err) 69 | // we make sure that we *don't* have keys in query params, because some endpoints 70 | // fail if we send keys both in headers and query params 71 | assert.Equal(t, "", req.Header.Get("DD-API-KEY")) 72 | assert.Equal(t, "", req.Header.Get("DD-APPLICATION-KEY")) 73 | } 74 | }) 75 | } 76 | 77 | func TestRedactError(t *testing.T) { 78 | c := Client{ 79 | apiKey: "sample_api_key", 80 | appKey: "sample_app_key", 81 | baseUrl: "https://base.datadoghq.com", 82 | HttpClient: &http.Client{}, 83 | RetryTimeout: 1000, 84 | } 85 | t.Run("Error containing api key in string is correctly redacted", func(t *testing.T) { 86 | var leakErr = fmt.Errorf("Error test: %s,%s", c.apiKey, c.apiKey) 87 | var redactedErr = c.redactError(leakErr) 88 | 89 | if assert.NotNil(t, redactedErr) { 90 | assert.Equal(t, "Error test: redacted,redacted", redactedErr.Error()) 91 | } 92 | }) 93 | t.Run("Error containing application key in string is correctly redacted", func(t *testing.T) { 94 | var leakErr = fmt.Errorf("Error test: %s,%s", c.appKey, c.appKey) 95 | var redactedErr = c.redactError(leakErr) 96 | 97 | if assert.NotNil(t, redactedErr) { 98 | assert.Equal(t, "Error test: redacted,redacted", redactedErr.Error()) 99 | } 100 | }) 101 | t.Run("Nil error returns nil", func(t *testing.T) { 102 | var harmlessErr error = nil 103 | var redactedErr = c.redactError(harmlessErr) 104 | 105 | assert.Nil(t, redactedErr) 106 | }) 107 | } 108 | 109 | func makeTestServer(code int, response string) *httptest.Server { 110 | mux := http.NewServeMux() 111 | mux.HandleFunc("/api/v1/something", func(w http.ResponseWriter, r *http.Request) { 112 | w.Header().Set("Content-Type", "application/json") 113 | w.WriteHeader(code) 114 | w.Write([]byte(response)) 115 | }) 116 | server := httptest.NewServer(mux) 117 | return server 118 | } 119 | 120 | func TestErrorHandling(t *testing.T) { 121 | c := Client{ 122 | apiKey: "sample_api_key", 123 | appKey: "sample_app_key", 124 | HttpClient: &http.Client{}, 125 | RetryTimeout: 1000, 126 | } 127 | for _, code := range []int{401, 403, 500, 502} { 128 | t.Run(fmt.Sprintf("Returns error on http code %d", code), func(t *testing.T) { 129 | s := makeTestServer(code, "") 130 | defer s.Close() 131 | c.SetBaseUrl(s.URL) 132 | 133 | for _, method := range []string{"GET", "POST", "PUT"} { 134 | err := c.doJsonRequest(method, "/v1/something", nil, nil) 135 | assert.NotNil(t, err) 136 | } 137 | }) 138 | } 139 | t.Run("Returns error if status is error", func(t *testing.T) { 140 | s := makeTestServer(200, `{"status": "error", "error": "something wrong"}`) 141 | defer s.Close() 142 | c.SetBaseUrl(s.URL) 143 | 144 | for _, method := range []string{"GET", "POST", "PUT"} { 145 | err := c.doJsonRequest(method, "/v1/something", nil, nil) 146 | if assert.NotNil(t, err) { 147 | assert.Contains(t, err.Error(), "something wrong") 148 | } 149 | } 150 | }) 151 | t.Run("Does not return error if status is ok", func(t *testing.T) { 152 | s := makeTestServer(200, `{"status": "ok"}`) 153 | defer s.Close() 154 | c.SetBaseUrl(s.URL) 155 | 156 | for _, method := range []string{"GET", "POST", "PUT"} { 157 | err := c.doJsonRequest(method, "/v1/something", nil, nil) 158 | assert.Nil(t, err) 159 | } 160 | }) 161 | } 162 | -------------------------------------------------------------------------------- /screenboards.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Datadog API for Go 3 | * 4 | * Please see the included LICENSE file for licensing information. 5 | * 6 | * Copyright 2013 by authors and contributors. 7 | */ 8 | 9 | package datadog 10 | 11 | import ( 12 | "fmt" 13 | ) 14 | 15 | // Screenboard represents a user created screenboard. This is the full screenboard 16 | // struct when we load a screenboard in detail. 17 | type Screenboard struct { 18 | Id *int `json:"id,omitempty"` 19 | NewId *string `json:"new_id,omitempty"` 20 | Title *string `json:"board_title,omitempty"` 21 | Height *int `json:"height,omitempty"` 22 | Width *int `json:"width,omitempty"` 23 | Shared *bool `json:"shared,omitempty"` 24 | TemplateVariables []TemplateVariable `json:"template_variables,omitempty"` 25 | Widgets []Widget `json:"widgets"` 26 | ReadOnly *bool `json:"read_only,omitempty"` 27 | } 28 | 29 | // ScreenboardLite represents a user created screenboard. This is the mini 30 | // struct when we load the summaries. 31 | type ScreenboardLite struct { 32 | Id *int `json:"id,omitempty"` 33 | Resource *string `json:"resource,omitempty"` 34 | Title *string `json:"title,omitempty"` 35 | } 36 | 37 | // reqGetScreenboards from /api/v1/screen 38 | type reqGetScreenboards struct { 39 | Screenboards []*ScreenboardLite `json:"screenboards,omitempty"` 40 | } 41 | 42 | // GetScreenboard returns a single screenboard created on this account. 43 | func (client *Client) GetScreenboard(id interface{}) (*Screenboard, error) { 44 | stringId, err := GetStringId(id) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | out := &Screenboard{} 50 | if err := client.doJsonRequest("GET", fmt.Sprintf("/v1/screen/%s", stringId), nil, out); err != nil { 51 | return nil, err 52 | } 53 | return out, nil 54 | } 55 | 56 | // GetScreenboards returns a list of all screenboards created on this account. 57 | func (client *Client) GetScreenboards() ([]*ScreenboardLite, error) { 58 | var out reqGetScreenboards 59 | if err := client.doJsonRequest("GET", "/v1/screen", nil, &out); err != nil { 60 | return nil, err 61 | } 62 | return out.Screenboards, nil 63 | } 64 | 65 | // DeleteScreenboard deletes a screenboard by the identifier. 66 | func (client *Client) DeleteScreenboard(id int) error { 67 | return client.doJsonRequest("DELETE", fmt.Sprintf("/v1/screen/%d", id), nil, nil) 68 | } 69 | 70 | // CreateScreenboard creates a new screenboard when given a Screenboard struct. Note 71 | // that the Id, Resource, Url and similar elements are not used in creation. 72 | func (client *Client) CreateScreenboard(board *Screenboard) (*Screenboard, error) { 73 | out := &Screenboard{} 74 | if err := client.doJsonRequest("POST", "/v1/screen", board, out); err != nil { 75 | return nil, err 76 | } 77 | return out, nil 78 | } 79 | 80 | // UpdateScreenboard in essence takes a Screenboard struct and persists it back to 81 | // the server. Use this if you've updated your local and need to push it back. 82 | func (client *Client) UpdateScreenboard(board *Screenboard) error { 83 | return client.doJsonRequest("PUT", fmt.Sprintf("/v1/screen/%d", *board.Id), board, nil) 84 | } 85 | 86 | type ScreenShareResponse struct { 87 | BoardId int `json:"board_id"` 88 | PublicUrl string `json:"public_url"` 89 | } 90 | 91 | // ShareScreenboard shares an existing screenboard, it takes and updates ScreenShareResponse 92 | func (client *Client) ShareScreenboard(id int, response *ScreenShareResponse) error { 93 | return client.doJsonRequest("POST", fmt.Sprintf("/v1/screen/share/%d", id), nil, response) 94 | } 95 | 96 | // RevokeScreenboard revokes a currently shared screenboard 97 | func (client *Client) RevokeScreenboard(id int) error { 98 | return client.doJsonRequest("DELETE", fmt.Sprintf("/v1/screen/share/%d", id), nil, nil) 99 | } 100 | -------------------------------------------------------------------------------- /screenboards_test.go: -------------------------------------------------------------------------------- 1 | package datadog 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | ) 9 | 10 | func TestGetScreenboard(t *testing.T) { 11 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 12 | response, err := ioutil.ReadFile("./tests/fixtures/screenboard_response.json") 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | w.Write(response) 17 | })) 18 | defer ts.Close() 19 | 20 | datadogClient := Client{ 21 | baseUrl: ts.URL, 22 | HttpClient: http.DefaultClient, 23 | } 24 | 25 | screenboard, err := datadogClient.GetScreenboard(6334) 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | 30 | expectedID := 6334 31 | if id := screenboard.GetId(); id != expectedID { 32 | t.Fatalf("expect ID %d. Got %d", expectedID, id) 33 | } 34 | 35 | expectedTitle := "dogapi test" 36 | if title := screenboard.GetTitle(); title != expectedTitle { 37 | t.Fatalf("expect title %s. Got %s", expectedTitle, title) 38 | } 39 | 40 | expectedHeight := 768 41 | if height := screenboard.GetHeight(); height != expectedHeight { 42 | t.Fatalf("expect height %d. Got %d", expectedHeight, height) 43 | } 44 | 45 | expectedWidth := 1024 46 | if width := screenboard.GetWidth(); width != expectedWidth { 47 | t.Fatalf("expect width %d. Got %d", expectedWidth, width) 48 | } 49 | 50 | expectedReadOnly := false 51 | readOnly, ok := screenboard.GetReadOnlyOk() 52 | if !ok { 53 | t.Fatalf("expect to have a read_only field") 54 | } 55 | 56 | if readOnly != expectedReadOnly { 57 | t.Fatalf("expect read_only %v. Got %v", expectedReadOnly, readOnly) 58 | } 59 | 60 | for _, widget := range screenboard.Widgets { 61 | validateWidget(t, widget) 62 | } 63 | } 64 | 65 | func validateWidget(t *testing.T, wd Widget) { 66 | expectedType := "image" 67 | if widgetType := wd.GetType(); widgetType != expectedType { 68 | t.Fatalf("expect type %s. Got %s", expectedType, widgetType) 69 | } 70 | 71 | expectedHeight := 20 72 | if height := wd.GetHeight(); height != expectedHeight { 73 | t.Fatalf("expect height %d. Got %d", expectedHeight, height) 74 | } 75 | 76 | expectedWidth := 32 77 | if width := wd.GetWidth(); width != expectedWidth { 78 | t.Fatalf("expect width %d. Got %d", expectedWidth, width) 79 | } 80 | 81 | expectedX := 32 82 | if x := wd.GetX(); x != expectedX { 83 | t.Fatalf("expect x %d. Got %d", expectedX, x) 84 | } 85 | 86 | expectedY := 7 87 | if y := wd.GetY(); y != expectedY { 88 | t.Fatalf("expect y %d. Got %d", expectedY, y) 89 | } 90 | 91 | expectedURL := "http://path/to/image.jpg" 92 | if url := wd.GetURL(); url != expectedURL { 93 | t.Fatalf("expect url %s. Got %s", expectedURL, url) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /scripts/check-code-generation-ran.sh: -------------------------------------------------------------------------------- 1 | # Make generate will always update datadog-accessors.go 2 | cp datadog-accessors.go datadog-accessors.go.bak 3 | 4 | # Regenerate code and compare results 5 | make generate && cmp -s datadog-accessors.go.bak datadog-accessors.go 6 | changed=$? 7 | 8 | # Clean up after ourselves 9 | rm datadog-accessors.go.bak 10 | 11 | # See if contents have changed and error if they have 12 | if [[ $changed != 0 ]] ; then 13 | echo "Did you run 'make generate' before committing?" 14 | exit 1 15 | fi 16 | -------------------------------------------------------------------------------- /scripts/check-fmt.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | diff -u <(echo -n) <(gofmt -s -d $(find . -name '*.go' | grep -v vendor)) 4 | 5 | # See if contents have changed and error if they have 6 | if [[ $? != 0 ]] ; then 7 | echo "Did you run 'make fmt' before committing?" 8 | exit 1 9 | fi 10 | -------------------------------------------------------------------------------- /search.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Datadog API for Go 3 | * 4 | * Please see the included LICENSE file for licensing information. 5 | * 6 | * Copyright 2013 by authors and contributors. 7 | */ 8 | 9 | package datadog 10 | 11 | // reqSearch is the container for receiving search results. 12 | type reqSearch struct { 13 | Results struct { 14 | Hosts []string `json:"hosts,omitempty"` 15 | Metrics []string `json:"metrics,omitempty"` 16 | } `json:"results"` 17 | } 18 | 19 | // SearchHosts searches through the hosts facet, returning matching hostnames. 20 | func (client *Client) SearchHosts(search string) ([]string, error) { 21 | var out reqSearch 22 | if err := client.doJsonRequest("GET", "/v1/search?q=hosts:"+search, nil, &out); err != nil { 23 | return nil, err 24 | } 25 | return out.Results.Hosts, nil 26 | } 27 | 28 | // SearchMetrics searches through the metrics facet, returning matching ones. 29 | func (client *Client) SearchMetrics(search string) ([]string, error) { 30 | var out reqSearch 31 | if err := client.doJsonRequest("GET", "/v1/search?q=metrics:"+search, nil, &out); err != nil { 32 | return nil, err 33 | } 34 | return out.Results.Metrics, nil 35 | } 36 | -------------------------------------------------------------------------------- /series.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Datadog API for Go 3 | * 4 | * Please see the included LICENSE file for licensing information. 5 | * 6 | * Copyright 2013 by authors and contributors. 7 | */ 8 | 9 | package datadog 10 | 11 | import ( 12 | "net/url" 13 | "strconv" 14 | ) 15 | 16 | // DataPoint is a tuple of [UNIX timestamp, value]. This has to use floats 17 | // because the value could be non-integer. 18 | type DataPoint [2]*float64 19 | 20 | // Metric represents a collection of data points that we might send or receive 21 | // on one single metric line. 22 | type Metric struct { 23 | Metric *string `json:"metric,omitempty"` 24 | Points []DataPoint `json:"points,omitempty"` 25 | Type *string `json:"type,omitempty"` 26 | Host *string `json:"host,omitempty"` 27 | Tags []string `json:"tags,omitempty"` 28 | Unit *string `json:"unit,omitempty"` 29 | Interval *int `json:"interval,omitempty"` 30 | } 31 | 32 | // Unit represents a unit definition that we might receive when query for timeseries data. 33 | type Unit struct { 34 | Family string `json:"family"` 35 | ScaleFactor float32 `json:"scale_factor"` 36 | Name string `json:"name"` 37 | ShortName string `json:"short_name"` 38 | Plural string `json:"plural"` 39 | Id int `json:"id"` 40 | } 41 | 42 | // A Series is characterized by 2 units as: x per y 43 | // One or both could be missing 44 | type UnitPair []*Unit 45 | 46 | // Series represents a collection of data points we get when we query for timeseries data 47 | type Series struct { 48 | Metric *string `json:"metric,omitempty"` 49 | DisplayName *string `json:"display_name,omitempty"` 50 | Points []DataPoint `json:"pointlist,omitempty"` 51 | Start *float64 `json:"start,omitempty"` 52 | End *float64 `json:"end,omitempty"` 53 | Interval *int `json:"interval,omitempty"` 54 | Aggr *string `json:"aggr,omitempty"` 55 | Length *int `json:"length,omitempty"` 56 | Scope *string `json:"scope,omitempty"` 57 | Expression *string `json:"expression,omitempty"` 58 | Units *UnitPair `json:"unit,omitempty"` 59 | QueryIndex *int `json:"query_index,omitempty"` 60 | } 61 | 62 | // reqPostSeries from /api/v1/series 63 | type reqPostSeries struct { 64 | Series []Metric `json:"series,omitempty"` 65 | } 66 | 67 | // reqMetrics is the container for receiving metric results. 68 | type reqMetrics struct { 69 | Series []Series `json:"series,omitempty"` 70 | } 71 | 72 | // PostMetrics takes as input a slice of metrics and then posts them up to the 73 | // server for posting data. 74 | func (client *Client) PostMetrics(series []Metric) error { 75 | return client.doJsonRequest("POST", "/v1/series", 76 | reqPostSeries{Series: series}, nil) 77 | } 78 | 79 | // QueryMetrics takes as input from, to (seconds from Unix Epoch) and query string and then requests 80 | // timeseries data for that time peried 81 | func (client *Client) QueryMetrics(from, to int64, query string) ([]Series, error) { 82 | v := url.Values{} 83 | v.Add("from", strconv.FormatInt(from, 10)) 84 | v.Add("to", strconv.FormatInt(to, 10)) 85 | v.Add("query", query) 86 | 87 | var out reqMetrics 88 | err := client.doJsonRequest("GET", "/v1/query?"+v.Encode(), nil, &out) 89 | if err != nil { 90 | return nil, err 91 | } 92 | return out.Series, nil 93 | } 94 | -------------------------------------------------------------------------------- /series_test.go: -------------------------------------------------------------------------------- 1 | package datadog 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "io/ioutil" 7 | "net/http" 8 | "net/http/httptest" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func removeWhitespace(s string) string { 16 | s = strings.Replace(s, " ", "", -1) 17 | s = strings.Replace(s, "\n", "", -1) 18 | return s 19 | } 20 | 21 | // TestPostMetrics tests submitting series sends correct 22 | // payloads to the Datadog API for the /v1/series endpoint 23 | func TestPostMetrics(t *testing.T) { 24 | reqs := make(chan string, 1) 25 | 26 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 27 | buf := new(bytes.Buffer) 28 | buf.ReadFrom(r.Body) 29 | reqs <- buf.String() 30 | w.WriteHeader(200) 31 | w.Write([]byte("{\"status\": \"ok\"}")) 32 | return 33 | })) 34 | defer ts.Close() 35 | 36 | client := Client{ 37 | baseUrl: ts.URL, 38 | HttpClient: http.DefaultClient, 39 | } 40 | 41 | tcs := []string{ 42 | "./tests/fixtures/series/post_series_mixed.json", 43 | "./tests/fixtures/series/post_series_valid.json", 44 | } 45 | 46 | for _, tc := range tcs { 47 | b, err := ioutil.ReadFile(tc) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | 52 | var post reqPostSeries 53 | json.Unmarshal(b, &post) 54 | if err != nil { 55 | t.Fatal(err) 56 | } 57 | 58 | err = client.PostMetrics(post.Series) 59 | if err != nil { 60 | t.Fatal(err) 61 | } 62 | 63 | assert.Equal(t, nil, err) 64 | 65 | payload := <-reqs 66 | assert.Equal(t, removeWhitespace(string(b)), payload) 67 | } 68 | 69 | // Empty slice metrics test case 70 | 71 | err := client.PostMetrics([]Metric{}) 72 | if err != nil { 73 | t.Fatal(err) 74 | } 75 | 76 | payload := <-reqs 77 | assert.Equal(t, "{}", payload) 78 | } 79 | -------------------------------------------------------------------------------- /snapshot.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Datadog API for Go 3 | * 4 | * Please see the included LICENSE file for licensing information. 5 | * 6 | * Copyright 2016 by authors and contributors. 7 | */ 8 | 9 | package datadog 10 | 11 | import ( 12 | "fmt" 13 | "net/url" 14 | "time" 15 | ) 16 | 17 | func (client *Client) doSnapshotRequest(values url.Values) (string, error) { 18 | out := struct { 19 | SnapshotURL string `json:"snapshot_url,omitempty"` 20 | }{} 21 | if err := client.doJsonRequest("GET", "/v1/graph/snapshot?"+values.Encode(), nil, &out); err != nil { 22 | return "", err 23 | } 24 | return out.SnapshotURL, nil 25 | } 26 | 27 | // Snapshot creates an image from a graph and returns the URL of the image. 28 | func (client *Client) Snapshot(query string, start, end time.Time, eventQuery string) (string, error) { 29 | options := map[string]string{"metric_query": query, "event_query": eventQuery} 30 | 31 | return client.SnapshotGeneric(options, start, end) 32 | } 33 | 34 | // Generic function for snapshots, use map[string]string to create url.Values() instead of pre-defined params 35 | func (client *Client) SnapshotGeneric(options map[string]string, start, end time.Time) (string, error) { 36 | v := url.Values{} 37 | v.Add("start", fmt.Sprintf("%d", start.Unix())) 38 | v.Add("end", fmt.Sprintf("%d", end.Unix())) 39 | 40 | for opt, val := range options { 41 | v.Add(opt, val) 42 | } 43 | 44 | return client.doSnapshotRequest(v) 45 | } 46 | -------------------------------------------------------------------------------- /tags.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Datadog API for Go 3 | * 4 | * Please see the included LICENSE file for licensing information. 5 | * 6 | * Copyright 2013 by authors and contributors. 7 | */ 8 | 9 | package datadog 10 | 11 | // TagMap is used to receive the format given to us by the API. 12 | type TagMap map[string][]string 13 | 14 | // reqGetTags is the container for receiving tags. 15 | type reqGetTags struct { 16 | Tags *TagMap `json:"tags,omitempty"` 17 | } 18 | 19 | // regGetHostTags is for receiving a slice of tags. 20 | type reqGetHostTags struct { 21 | Tags []string `json:"tags,omitempty"` 22 | } 23 | 24 | // GetTags returns a map of tags. 25 | func (client *Client) GetTags(source string) (TagMap, error) { 26 | var out reqGetTags 27 | uri := "/v1/tags/hosts" 28 | if source != "" { 29 | uri += "?source=" + source 30 | } 31 | if err := client.doJsonRequest("GET", uri, nil, &out); err != nil { 32 | return nil, err 33 | } 34 | return *out.Tags, nil 35 | } 36 | 37 | // GetHostTags returns a slice of tags for a given host and source. 38 | func (client *Client) GetHostTags(host, source string) ([]string, error) { 39 | var out reqGetHostTags 40 | uri := "/v1/tags/hosts/" + host 41 | if source != "" { 42 | uri += "?source=" + source 43 | } 44 | if err := client.doJsonRequest("GET", uri, nil, &out); err != nil { 45 | return nil, err 46 | } 47 | return out.Tags, nil 48 | } 49 | 50 | // GetHostTagsBySource is a different way of viewing the tags. It returns a map 51 | // of source:[tag,tag]. 52 | func (client *Client) GetHostTagsBySource(host, source string) (TagMap, error) { 53 | var out reqGetTags 54 | uri := "/v1/tags/hosts/" + host + "?by_source=true" 55 | if source != "" { 56 | uri += "&source=" + source 57 | } 58 | if err := client.doJsonRequest("GET", uri, nil, &out); err != nil { 59 | return nil, err 60 | } 61 | return *out.Tags, nil 62 | } 63 | 64 | // AddTagsToHost does exactly what it says on the tin. Given a list of tags, 65 | // add them to the host. The source is optionally specified, and defaults to 66 | // "users" as per the API documentation. 67 | func (client *Client) AddTagsToHost(host, source string, tags []string) error { 68 | uri := "/v1/tags/hosts/" + host 69 | if source != "" { 70 | uri += "?source=" + source 71 | } 72 | return client.doJsonRequest("POST", uri, reqGetHostTags{Tags: tags}, nil) 73 | } 74 | 75 | // UpdateHostTags overwrites existing tags for a host, allowing you to specify 76 | // a new set of tags for the given source. This defaults to "users". 77 | func (client *Client) UpdateHostTags(host, source string, tags []string) error { 78 | uri := "/v1/tags/hosts/" + host 79 | if source != "" { 80 | uri += "?source=" + source 81 | } 82 | return client.doJsonRequest("PUT", uri, reqGetHostTags{Tags: tags}, nil) 83 | } 84 | 85 | // RemoveHostTags removes all tags from a host for the given source. If none is 86 | // given, the API defaults to "users". 87 | func (client *Client) RemoveHostTags(host, source string) error { 88 | uri := "/v1/tags/hosts/" + host 89 | if source != "" { 90 | uri += "?source=" + source 91 | } 92 | return client.doJsonRequest("DELETE", uri, nil, nil) 93 | } 94 | -------------------------------------------------------------------------------- /tests/fixtures/boards_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "dashboards": [ 3 | { 4 | "created_at": "2019-02-28T17:12:59.327729+00:00", 5 | "is_read_only": false, 6 | "description": "My Screenboard Description", 7 | "title": "My Screenboard", 8 | "url": "/dashboard/grh-zkj-h2c/my-screenboard", 9 | "layout_type": "free", 10 | "modified_at": "2019-03-13T19:09:40.930616+00:00", 11 | "author_handle":"user@domain.com", 12 | "id": "grh-zkj-h2c" 13 | }, 14 | { 15 | "created_at": "2019-02-28T17:45:11.535045+00:00", 16 | "is_read_only": false, 17 | "description": "My Timeboard Description", 18 | "title": "My Timeboard", 19 | "url": "/dashboard/4qf-77b-uhc/my-timeboard", 20 | "layout_type": "ordered", 21 | "modified_at": "2019-03-11T14:22:03.131495+00:00", 22 | "author_handle":"user@domain.com", 23 | "id": "4qf-77b-uhc" 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /tests/fixtures/dashboards_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "dashes": [ 3 | { 4 | "read_only": false, 5 | "resource": "/api/v1/dash/123", 6 | "description": "created by user1", 7 | "title": "Dashboard 1", 8 | "created": "2018-10-05T12:32:01.000000+00:00", 9 | "id": "123", 10 | "new_id": "abc-xyz-111", 11 | "created_by": { 12 | "disabled": true, 13 | "handle": "john.doe@company.com", 14 | "name": "John Doe", 15 | "is_admin": true, 16 | "role": null, 17 | "access_role": "adm", 18 | "verified": true, 19 | "email": "john.doe@company.com", 20 | "icon": "https://pics.site.com/123.jpg" 21 | }, 22 | "modified": "2018-09-11T06:38:09.000000+00:00" 23 | }, 24 | { 25 | "read_only": false, 26 | "resource": "/api/v1/dash/1234", 27 | "description": "created by user2", 28 | "title": "Dashboard 2", 29 | "created": "2018-01-01T00:00:00.000000+00:00", 30 | "id": "1234", 31 | "new_id": "abc-xyz-222", 32 | "created_by": { 33 | "disabled": false, 34 | "handle": "jane.doe@company.com", 35 | "name": "Jane Doe", 36 | "is_admin": false, 37 | "role": null, 38 | "access_role": "st", 39 | "verified": true, 40 | "email": "jane.doe@company.com", 41 | "icon": "https://pics.site.com/1234.jpg" 42 | }, 43 | "modified": "2018-09-11T06:38:09.000000+00:00" 44 | } 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /tests/fixtures/downtimes_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "active": true, 3 | "disabled": false, 4 | "end": 1420447087, 5 | "id": 2910, 6 | "message": "Doing some testing on staging.", 7 | "monitor_tags": ["*"], 8 | "scope": ["env:staging"], 9 | "start": 1420387032 10 | } 11 | -------------------------------------------------------------------------------- /tests/fixtures/events_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "event": { 3 | "alert_type": "info", 4 | "date_happened": 1346355252, 5 | "device_name": null, 6 | "host": null, 7 | "id": 1377281704830403917, 8 | "payload": "{}", 9 | "priority": "normal", 10 | "resource": "/api/v1/events/1377281704830403917", 11 | "tags": [ 12 | "environment:test" 13 | ], 14 | "text": "Oh wow!", 15 | "title": "Did you hear the news today?", 16 | "url": "/event/jump_to?event_id=1377281704830403917" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/fixtures/hosts/get_totals_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "total_up": 2, 3 | "total_active": 1 4 | } 5 | -------------------------------------------------------------------------------- /tests/fixtures/logs/index_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "main", 3 | "filter": { 4 | "query": "*" 5 | }, 6 | "num_retention_days": 90, 7 | "daily_limit": 151000000, 8 | "is_rate_limited": false, 9 | "exclusion_filters": [ 10 | { 11 | "name": "Filter 1", 12 | "is_enabled": true, 13 | "filter": { 14 | "query": "source:agent status:info", 15 | "sample_rate": 1.0 16 | } 17 | }, 18 | { 19 | "name": "Filter 2", 20 | "is_enabled": true, 21 | "filter": { 22 | "query": "source:agent", 23 | "sample_rate": 0.8 24 | } 25 | }, 26 | { 27 | "name": "Filter 3", 28 | "is_enabled": true, 29 | "filter": { 30 | "query": "source:debug", 31 | "sample_rate": 0.5 32 | } 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /tests/fixtures/logs/indexlist_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "index_names": [ 3 | "main", 4 | "minor" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /tests/fixtures/logs/loglist_page_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "logs": [ 3 | { 4 | "id": "BBBBBWgN8Xwgr1vKDQAAAABBV2dOOFh3ZzZobm1mWXJFYTR0OA", 5 | "content": { 6 | "timestamp": "2019-01-02T10:42:36.320Z", 7 | "tags": [ 8 | "team:B" 9 | ], 10 | "attributes": { 11 | "customAttribute": 456, 12 | "duration": 5432 13 | }, 14 | "host": "i-321", 15 | "service": "agent", 16 | "message": "host connected to remote" 17 | } 18 | } 19 | ], 20 | "status": "ok" 21 | } -------------------------------------------------------------------------------- /tests/fixtures/logs/loglist_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "logs": [ 3 | { 4 | "id": "AAAAAWgN8Xwgr1vKDQAAAABBV2dOOFh3ZzZobm1mWXJFYTR0OA", 5 | "content": { 6 | "timestamp": "2019-01-02T09:42:36.320Z", 7 | "tags": [ 8 | "team:A" 9 | ], 10 | "attributes": { 11 | "customAttribute": 123, 12 | "duration": 2345 13 | }, 14 | "host": "i-123", 15 | "service": "agent", 16 | "message": "host connected to remote" 17 | } 18 | }, 19 | { 20 | "id": "ZZZZZWgN8Xwgr1vKDQAAAABBV2dOOFh3ZzZobm1mWXJFYTR0OA", 21 | "content": { 22 | "timestamp": "2019-04-02T09:42:36.320Z", 23 | "tags": [ 24 | "team:C" 25 | ], 26 | "attributes": { 27 | "customAttribute": 654, 28 | "duration": 89798 29 | }, 30 | "host": "i-123", 31 | "service": "agent", 32 | "message": "host connected to remote" 33 | } 34 | } 35 | ], 36 | "nextLogId": "BBBBBWgN8Xwgr1vKDQAAAABBV2dOOFh3ZzZobm1mWXJFYTR0OA", 37 | "status": "ok" 38 | } -------------------------------------------------------------------------------- /tests/fixtures/logs/pipeline_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "dbJLomG9Tz-DYnAR5w-ilA", 3 | "type": "pipeline", 4 | "name": "Test pipeline", 5 | "is_enabled": true, 6 | "is_read_only": false, 7 | "filter": { 8 | "query": "source:test" 9 | }, 10 | "processors": [ 11 | { 12 | "type": "pipeline", 13 | "name": "nested pipeline", 14 | "is_enabled": true, 15 | "filter": { 16 | "query": "service:nest" 17 | }, 18 | "processors": [ 19 | { 20 | "name": "test arithmetic processor", 21 | "is_enabled": true, 22 | "expression": "(time1-time2)*1000", 23 | "target": "my_arithmetic", 24 | "is_replace_missing": false, 25 | "type": "arithmetic-processor" 26 | }, 27 | { 28 | "name": "test trace Id processor", 29 | "is_enabled": true, 30 | "sources": [ 31 | "dummy_trace_id1", 32 | "dummy_trace_id2" 33 | ], 34 | "type": "trace-id-remapper" 35 | } 36 | ] 37 | }, 38 | { 39 | "name": "test grok parser", 40 | "is_enabled": true, 41 | "source": "text", 42 | "samples": ["sample1", "sample2"], 43 | "grok": { 44 | "support_rules": "date_parser %{date(\"yyyy-MM-dd HH:mm:ss,SSS\"):timestamp}", 45 | "match_rules": "rule %{date(\"yyyy-MM-dd HH:mm:ss,SSS\"):timestamp}" 46 | }, 47 | "type": "grok-parser" 48 | }, 49 | { 50 | "name": "test remapper", 51 | "is_enabled": true, 52 | "sources": [ 53 | "tag_1" 54 | ], 55 | "source_type": "tag", 56 | "target": "tag_3", 57 | "target_type": "tag", 58 | "type": "attribute-remapper", 59 | "preserve_source": false, 60 | "override_on_conflict": true 61 | }, 62 | { 63 | "name": "test user-agent parser", 64 | "is_enabled": true, 65 | "sources": [ 66 | "user_agent" 67 | ], 68 | "target": "my_agent.details", 69 | "is_encoded": false, 70 | "type": "user-agent-parser" 71 | }, 72 | { 73 | "name": "test url parser", 74 | "is_enabled": true, 75 | "sources": [ 76 | "http_test" 77 | ], 78 | "target": "http_test.details", 79 | "normalize_ending_slashes": false, 80 | "type": "url-parser" 81 | }, 82 | { 83 | "name": "test date remapper", 84 | "is_enabled": true, 85 | "sources": [ 86 | "attribute_1", 87 | "attribute_2" 88 | ], 89 | "type": "date-remapper" 90 | }, 91 | { 92 | "name": "test message remapper", 93 | "is_enabled": true, 94 | "sources": [ 95 | "attribute_1", 96 | "attribute_2" 97 | ], 98 | "type": "message-remapper" 99 | }, 100 | { 101 | "name": "test status remapper", 102 | "is_enabled": true, 103 | "sources": [ 104 | "attribute_1", 105 | "attribute_2" 106 | ], 107 | "type": "status-remapper" 108 | }, 109 | { 110 | "name": "test service remapper", 111 | "is_enabled": true, 112 | "sources": [ 113 | "attribute_1", 114 | "attribute_2" 115 | ], 116 | "type": "service-remapper" 117 | }, 118 | { 119 | "name": "test category processor", 120 | "is_enabled": true, 121 | "categories": [ 122 | { 123 | "filter": { 124 | "query": "status_code:[500 TO 599]" 125 | }, 126 | "name": "5xx" 127 | }, 128 | { 129 | "filter": { 130 | "query": "status_code:[400 TO 499]" 131 | }, 132 | "name": "4xx" 133 | } 134 | ], 135 | "target": "test_category", 136 | "type": "category-processor" 137 | }, 138 | { 139 | "name": "test string builder processor", 140 | "is_enabled": true, 141 | "template": "hello %{user.name}", 142 | "is_replace_missing": false, 143 | "target": "target", 144 | "type": "string-builder-processor" 145 | }, 146 | { 147 | "name": "geo ip parser test", 148 | "is_enabled": false, 149 | "sources": ["source1", "source2"], 150 | "target": "target", 151 | "type": "geo-ip-parser" 152 | }, 153 | { 154 | "name": "lookup processor test", 155 | "is_enabled": false, 156 | "source": "source", 157 | "target": "target", 158 | "lookup_table": ["key1,value1", "key2,value2"], 159 | "default_lookup": "default", 160 | "type": "lookup-processor" 161 | } 162 | ] 163 | } 164 | -------------------------------------------------------------------------------- /tests/fixtures/logs/pipelinelist_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "pipeline_ids": [ 3 | "5kxIERwLRpmKST7_nyvD_Q", 4 | "dbJLomG9Tz-DYnAR5w-ilA", 5 | "9XSPBK_3Sc6hkxm8ly58Jg" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /tests/fixtures/screenboard_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "board_title": "dogapi test", 3 | "height": 768, 4 | "id": 6334, 5 | "widgets": [ 6 | { 7 | "height": 20, 8 | "type": "image", 9 | "url": "http://path/to/image.jpg", 10 | "width": 32, 11 | "x": 32, 12 | "y": 7 13 | } 14 | ], 15 | "width": 1024, 16 | "created": "2015-12-17T23:06:06.703087+00:00", 17 | "modified": "2015-12-17T23:12:26.726234+00:00", 18 | "read_only": false 19 | } 20 | -------------------------------------------------------------------------------- /tests/fixtures/series/post_series_mixed.json: -------------------------------------------------------------------------------- 1 | {"series" : 2 | [ 3 | {"metric":"test.metric", 4 | "points":[[1575591864, 20]], 5 | "type":"rate", 6 | "host":"test.example.com", 7 | "tags":["environment:test"], 8 | "interval": 20}, 9 | {"metric":"test.metric.duration", 10 | "points":[[1575591810.01, 73.45]], 11 | "type":"gauge"}, 12 | {"metric":"test.metric.hits", 13 | "points":[ 14 | [1575591810.5, 54], 15 | [1575591899.6, 73], 16 | [1575591810.9, 73] 17 | ]} 18 | ] 19 | } -------------------------------------------------------------------------------- /tests/fixtures/series/post_series_valid.json: -------------------------------------------------------------------------------- 1 | {"series" : 2 | [{"metric":"test.metric", 3 | "points":[[1575591864, 20]], 4 | "type":"rate", 5 | "host":"test.example.com", 6 | "tags":["environment:test"], 7 | "interval": 20} 8 | ] 9 | } -------------------------------------------------------------------------------- /tests/fixtures/service_level_objectives/check_can_delete_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "ok": ["12345678901234567890123456789012"] 4 | }, 5 | "errors": { 6 | "abcdefabcdefabcdefabcdefabcdefab": "SLO abcdefabcdefabcdefabcdefabcdefab is used in dashboard 123-456-789" 7 | } 8 | } -------------------------------------------------------------------------------- /tests/fixtures/service_level_objectives/create_request_metric.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Test SLO", 3 | "tags": ["product:foo"], 4 | "type": "metric", 5 | "description": "test slo description", 6 | "query": { 7 | "numerator": "sum:my.metric{type:good}.as_count()", 8 | "denominator": "sum:my.metric{*}.as_count()" 9 | }, 10 | "thresholds": [ 11 | { 12 | "timeframe": "7d", 13 | "target": 99.0, 14 | "warning": 99.5 15 | }, 16 | { 17 | "timeframe": "30d", 18 | "target": 98, 19 | "warning": 99 20 | }, 21 | { 22 | "timeframe": "90d", 23 | "target": 98, 24 | "warning": 99 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /tests/fixtures/service_level_objectives/create_request_monitor.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Test SLO", 3 | "tags": ["product:foo"], 4 | "type": "monitor", 5 | "description": "test slo description", 6 | "monitor_ids": [1], 7 | "thresholds": [ 8 | { 9 | "timeframe": "7d", 10 | "target": 99.0, 11 | "warning": 99.5 12 | }, 13 | { 14 | "timeframe": "30d", 15 | "target": 98, 16 | "warning": 99 17 | }, 18 | { 19 | "timeframe": "90d", 20 | "target": 98, 21 | "warning": 99 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /tests/fixtures/service_level_objectives/create_response_metric.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "id": "abcdefabcdefabcdefabcdefabcdefab", 5 | "name": "Test SLO 2", 6 | "tags": ["product:foo"], 7 | "monitor_tags": ["service:bar", "team:a"], 8 | "type": "metric", 9 | "type_id": 1, 10 | "description": "test metric slo description", 11 | "query": { 12 | "numerator": "sum:my.metric{type:good}.as_count()", 13 | "denominator": "sum:my.metric{*}.as_count()" 14 | }, 15 | "thresholds": [ 16 | { 17 | "timeframe": "7d", 18 | "target": 99.0, 19 | "target_display": "99.0", 20 | "warning": 99.5, 21 | "warning_display": "99.5" 22 | }, 23 | { 24 | "timeframe": "30d", 25 | "target": 98, 26 | "target_display": "98.0", 27 | "warning": 99, 28 | "warning_display": "99.0" 29 | } 30 | ], 31 | "creator": { 32 | "handle": "jane.doe@example.com", 33 | "email": "jane.doe@example.com", 34 | "name": "Jane Doe" 35 | }, 36 | "created_at": 1563283800, 37 | "modified_at": 1563283800 38 | } 39 | ], 40 | "error": null 41 | } -------------------------------------------------------------------------------- /tests/fixtures/service_level_objectives/create_response_monitor.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "id": "12345678901234567890123456789012", 5 | "name": "Test SLO", 6 | "tags": ["product:foo"], 7 | "monitor_tags": ["service:bar", "team:a"], 8 | "type": "monitor", 9 | "type_id": 0, 10 | "description": "test slo description", 11 | "monitor_ids": [1], 12 | "thresholds": [ 13 | { 14 | "timeframe": "7d", 15 | "target": 99.0, 16 | "target_display": "99.0", 17 | "warning": 99.5, 18 | "warning_display": "99.5" 19 | }, 20 | { 21 | "timeframe": "30d", 22 | "target": 98, 23 | "target_display": "98.0", 24 | "warning": 99, 25 | "warning_display": "99.0" 26 | } 27 | ], 28 | "creator": { 29 | "handle": "jane.doe@example.com", 30 | "email": "jane.doe@example.com", 31 | "name": "Jane Doe" 32 | }, 33 | "created_at": 1563283800, 34 | "modified_at": 1563283800 35 | } 36 | ], 37 | "error": null 38 | } -------------------------------------------------------------------------------- /tests/fixtures/service_level_objectives/delete_by_timeframe_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "12345678901234567890123456789012": ["7d"], 3 | "abcdefabcdefabcdefabcdefabcdefab": ["30d", "90d"] 4 | } -------------------------------------------------------------------------------- /tests/fixtures/service_level_objectives/delete_by_timeframe_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "deleted": ["abcdefabcdefabcdefabcdefabcdefab"], 4 | "updated": ["12345678901234567890123456789012"] 5 | }, 6 | "errors": null 7 | } -------------------------------------------------------------------------------- /tests/fixtures/service_level_objectives/delete_many_request.json: -------------------------------------------------------------------------------- 1 | [ 2 | "12345678901234567890123456789012", 3 | "abcdefabcdefabcdefabcdefabcdefab" 4 | ] -------------------------------------------------------------------------------- /tests/fixtures/service_level_objectives/delete_many_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": ["12345678901234567890123456789012", "abcdefabcdefabcdefabcdefabcdefab"], 3 | "error": null 4 | } -------------------------------------------------------------------------------- /tests/fixtures/service_level_objectives/delete_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": ["12345678901234567890123456789012"], 3 | "error": null 4 | } -------------------------------------------------------------------------------- /tests/fixtures/service_level_objectives/get_by_id_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id": "12345678901234567890123456789012", 4 | "name": "Test SLO", 5 | "tags": ["product:foo"], 6 | "monitor_tags": ["service:bar", "team:a"], 7 | "type": "monitor", 8 | "type_id": 0, 9 | "description": "test slo description", 10 | "monitor_ids": [1], 11 | "thresholds": [ 12 | { 13 | "timeframe": "7d", 14 | "target": 99.0, 15 | "target_display": "99.0", 16 | "warning": 99.5, 17 | "warning_display": "99.5" 18 | }, 19 | { 20 | "timeframe": "30d", 21 | "target": 98, 22 | "target_display": "98.0", 23 | "warning": 99, 24 | "warning_display": "99.0" 25 | } 26 | ], 27 | "creator": { 28 | "handle": "jane.doe@example.com", 29 | "email": "jane.doe@example.com", 30 | "name": "Jane Doe" 31 | }, 32 | "created_at": 1563283800, 33 | "modified_at": 1563283800 34 | }, 35 | "error": null 36 | } -------------------------------------------------------------------------------- /tests/fixtures/service_level_objectives/get_history_metric_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "errors": null, 4 | "to_ts": 1571766900, 5 | "series": { 6 | "res_type": "time_series", 7 | "interval": 3600, 8 | "resp_version": 2, 9 | "denominator": { 10 | "count": 2, 11 | "sum": 3698988, 12 | "metadata": { 13 | "query_index": 1, 14 | "aggr": "sum", 15 | "scope": "env:prod,status:good", 16 | "metric": "foo.count", 17 | "expression": "sum:foo.count{env:prod,status:good}.as_count()", 18 | "unit": null 19 | }, 20 | "values": [ 21 | 1738124, 22 | 1960864 23 | ], 24 | "times": [ 25 | 1571256000000, 26 | 1571259600000 27 | ] 28 | }, 29 | "numerator": { 30 | "count": 2, 31 | "sum": 3698988, 32 | "metadata": { 33 | "query_index": 0, 34 | "aggr": "sum", 35 | "scope": "env:prod", 36 | "metric": "foo.count", 37 | "expression": "sum:foo.count{env:prod}.as_count()", 38 | "unit": null 39 | }, 40 | "values": [ 41 | 1738124, 42 | 1960864 43 | ], 44 | "times": [ 45 | 1571256000000, 46 | 1571259600000 47 | ] 48 | }, 49 | "from_date": 1571162100000, 50 | "group_by": [], 51 | "to_date": 1571766900000, 52 | "timing": "0.830218076706", 53 | "query": "sum:foo.count{env:prod}.as_count(), sum:foo.count{env:prod,status:good}.as_count()", 54 | "message": "" 55 | }, 56 | "thresholds": { 57 | "7d": { 58 | "warning": 99.5, 59 | "warning_display": "99.500", 60 | "target": 99, 61 | "target_display": "99.000", 62 | "timeframe": "7d" 63 | }, 64 | "30d": { 65 | "warning": 99.5, 66 | "warning_display": "99.500", 67 | "target": 99, 68 | "target_display": "99.000", 69 | "timeframe": "30d" 70 | }, 71 | "90d": { 72 | "warning": 99.5, 73 | "warning_display": "99.500", 74 | "target": 99, 75 | "target_display": "99.000", 76 | "timeframe": "90d" 77 | } 78 | }, 79 | "overall": { 80 | "sli_value": 100, 81 | "span_precision": 0, 82 | "precision": { 83 | "7d": 0, 84 | "30d": 0, 85 | "90d": 0 86 | } 87 | }, 88 | "from_ts": 1571162100 89 | }, 90 | "error": null 91 | } 92 | -------------------------------------------------------------------------------- /tests/fixtures/service_level_objectives/get_many_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "id": "12345678901234567890123456789012", 5 | "name": "Test SLO", 6 | "tags": ["product:foo"], 7 | "monitor_tags": ["service:bar", "team:a"], 8 | "type": "monitor", 9 | "type_id": 0, 10 | "description": "test slo description", 11 | "monitor_ids": [1], 12 | "thresholds": [ 13 | { 14 | "timeframe": "7d", 15 | "target": 99.0, 16 | "target_display": "99.0", 17 | "warning": 99.5, 18 | "warning_display": "99.5" 19 | }, 20 | { 21 | "timeframe": "30d", 22 | "target": 98, 23 | "target_display": "98.0", 24 | "warning": 99, 25 | "warning_display": "99.0" 26 | } 27 | ], 28 | "creator": { 29 | "handle": "jane.doe@example.com", 30 | "email": "jane.doe@example.com", 31 | "name": "Jane Doe" 32 | }, 33 | "created_at": 1563283800, 34 | "modified_at": 1563283800 35 | }, 36 | { 37 | "id": "abcdefabcdefabcdefabcdefabcdefab", 38 | "name": "Test SLO 2", 39 | "tags": ["product:foo"], 40 | "type": "metric", 41 | "type_id": 1, 42 | "description": "test metric slo description", 43 | "query": { 44 | "numerator": "sum:my.metric{type:good}.as_count()", 45 | "denominator": "sum:my.metric{*}.as_count()" 46 | }, 47 | "thresholds": [ 48 | { 49 | "timeframe": "7d", 50 | "target": 99.0, 51 | "target_display": "99.0", 52 | "warning": 99.5, 53 | "warning_display": "99.5" 54 | }, 55 | { 56 | "timeframe": "30d", 57 | "target": 98, 58 | "target_display": "98.0", 59 | "warning": 99, 60 | "warning_display": "99.0" 61 | } 62 | ], 63 | "creator": { 64 | "handle": "john.doe@example.com", 65 | "email": "john.doe@example.com", 66 | "name": "John Doe" 67 | }, 68 | "created_at": 1563283800, 69 | "modified_at": 1563283800 70 | } 71 | ], 72 | "error": null 73 | } -------------------------------------------------------------------------------- /tests/fixtures/service_level_objectives/search_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "id": "12345678901234567890123456789012", 5 | "name": "Test SLO", 6 | "tags": ["product:foo"], 7 | "monitor_tags": ["service:bar", "team:a"], 8 | "type": "monitor", 9 | "type_id": 0, 10 | "description": "test slo description", 11 | "monitor_ids": [1], 12 | "thresholds": [ 13 | { 14 | "timeframe": "7d", 15 | "target": 99.0, 16 | "target_display": "99.0", 17 | "warning": 99.5, 18 | "warning_display": "99.5" 19 | }, 20 | { 21 | "timeframe": "30d", 22 | "target": 98, 23 | "target_display": "98.0", 24 | "warning": 99, 25 | "warning_display": "99.0" 26 | } 27 | ], 28 | "creator": { 29 | "handle": "jane.doe@example.com", 30 | "email": "jane.doe@example.com", 31 | "name": "Jane Doe" 32 | }, 33 | "created_at": 1563283800, 34 | "modified_at": 1563283800 35 | } 36 | ], 37 | "error": null 38 | } -------------------------------------------------------------------------------- /tests/fixtures/service_level_objectives/update_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "12345678901234567890123456789012", 3 | "name": "Test SLO", 4 | "tags": ["product:foo"], 5 | "type": "monitor", 6 | "description": "test slo description", 7 | "monitor_ids": [1], 8 | "thresholds": [ 9 | { 10 | "timeframe": "7d", 11 | "target": 99.0, 12 | "warning": 99.5 13 | }, 14 | { 15 | "timeframe": "30d", 16 | "target": 98, 17 | "warning": 99 18 | }, 19 | { 20 | "timeframe": "90d", 21 | "target": 98, 22 | "warning": 99 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /tests/fixtures/service_level_objectives/update_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "id": "12345678901234567890123456789012", 5 | "name": "Test SLO", 6 | "tags": ["product:foo"], 7 | "monitor_tags": ["service:bar", "team:a"], 8 | "type": "monitor", 9 | "type_id": 0, 10 | "description": "test slo description", 11 | "monitor_ids": [1], 12 | "thresholds": [ 13 | { 14 | "timeframe": "7d", 15 | "target": 99.0, 16 | "target_display": "99.0", 17 | "warning": 99.5, 18 | "warning_display": "99.5" 19 | }, 20 | { 21 | "timeframe": "30d", 22 | "target": 98, 23 | "target_display": "98.0", 24 | "warning": 99, 25 | "warning_display": "99.0" 26 | } 27 | ], 28 | "creator": { 29 | "handle": "jane.doe@example.com", 30 | "email": "jane.doe@example.com", 31 | "name": "Jane Doe" 32 | }, 33 | "created_at": 1563283800, 34 | "modified_at": 1563283900 35 | } 36 | ], 37 | "error": null 38 | } -------------------------------------------------------------------------------- /tests/fixtures/synthetics/tests/get_test_api.json: -------------------------------------------------------------------------------- 1 | { 2 | "public_id": "xxx-xxx-xxx", 3 | "monitor_id": 666, 4 | "tags": [ 5 | "example_tag" 6 | ], 7 | "locations": [ 8 | "aws:ap-northeast-1" 9 | ], 10 | "status": "live", 11 | "message": "Danger! @example@example.com", 12 | "modified_by": { 13 | "email": "example@example.com", 14 | "handle": "example@example.com", 15 | "id": 123456, 16 | "name": "John Doe" 17 | }, 18 | "name": "Check on example.com", 19 | "type": "api", 20 | "created_at": "2019-01-25T02:25:40.241032+00:00", 21 | "modified_at": "2019-02-09T18:11:12.801165+00:00", 22 | "created_by": { 23 | "email": "example@example.com", 24 | "handle": "example@example.com", 25 | "id": 123456, 26 | "name": "John Doe" 27 | }, 28 | "config": { 29 | "request": { 30 | "url": "https://example.com/", 31 | "method": "GET", 32 | "timeout": 30 33 | }, 34 | "assertions": [ 35 | { 36 | "operator": "is", 37 | "property": "content-type", 38 | "type": "header", 39 | "target": "text/html; charset=UTF-8" 40 | }, 41 | { 42 | "operator": "lessThan", 43 | "type": "responseTime", 44 | "target": 4000 45 | }, 46 | { 47 | "operator": "is", 48 | "type": "statusCode", 49 | "target": 200 50 | } 51 | ] 52 | }, 53 | "options": { 54 | "tick_every": 60, 55 | "follow_redirects": true, 56 | "min_failure_duration": 30, 57 | "min_location_failed": 3, 58 | "allow_insecure": true, 59 | "retry": { 60 | "count": 1, 61 | "interval": 10 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/fixtures/synthetics/tests/get_test_browser.json: -------------------------------------------------------------------------------- 1 | { 2 | "public_id": "xxx-xxx-xxx", 3 | "monitor_id": 666, 4 | "tags": [ 5 | "example_tag" 6 | ], 7 | "locations": [ 8 | "aws:ap-northeast-1" 9 | ], 10 | "status": "live", 11 | "message": "Danger! @example@example.com", 12 | "modified_by": { 13 | "email": "example@example.com", 14 | "handle": "example@example.com", 15 | "id": 123456, 16 | "name": "John Doe" 17 | }, 18 | "name": "Check on example.com", 19 | "type": "browser", 20 | "created_at": "2019-01-25T02:25:40.241032+00:00", 21 | "modified_at": "2019-02-09T18:11:12.801165+00:00", 22 | "created_by": { 23 | "email": "example@example.com", 24 | "handle": "example@example.com", 25 | "id": 123456, 26 | "name": "John Doe" 27 | }, 28 | "config": { 29 | "request": { 30 | "url": "https://example.com/", 31 | "method": "GET", 32 | "timeout": 30 33 | }, 34 | "assertions": [ 35 | { 36 | "operator": "is", 37 | "property": "content-type", 38 | "type": "header", 39 | "target": "text/html; charset=UTF-8" 40 | }, 41 | { 42 | "operator": "lessThan", 43 | "type": "responseTime", 44 | "target": 4000 45 | }, 46 | { 47 | "operator": "is", 48 | "type": "statusCode", 49 | "target": 200 50 | } 51 | ] 52 | }, 53 | "options": { 54 | "tick_every": 900, 55 | "device_ids": [ 56 | "laptop_large" 57 | ], 58 | "monitor_options": { 59 | "renotify_interval": 1600 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/fixtures/synthetics/tests/list_tests.json: -------------------------------------------------------------------------------- 1 | { 2 | "tests": [ 3 | { 4 | "monitor_status": "OK", 5 | "public_id": "xxx-xxx-xxx", 6 | "tags": [ 7 | "important" 8 | ], 9 | "locations": [ 10 | "aws:ap-northeast-1" 11 | ], 12 | "notifications": [ 13 | { 14 | "handle": "example@example.com", 15 | "name": "John Doe", 16 | "service": "Email", 17 | "icon": "https://example.com/avatar/example" 18 | } 19 | ], 20 | "status": "live", 21 | "message": "Alert !!! @example@example.com", 22 | "id": 1216, 23 | "last_triggered_ts": 1549732460, 24 | "name": "Check on example.com", 25 | "monitor_id": 8097920, 26 | "type": "api", 27 | "created_at": "2019-01-25T02:25:40.241032+00:00", 28 | "modified_at": "2019-02-09T18:11:12.801165+00:00", 29 | "config": { 30 | "request": { 31 | "url": "https://example.com/", 32 | "method": "GET", 33 | "timeout": 30 34 | }, 35 | "assertions": [ 36 | { 37 | "operator": "is", 38 | "property": "content-type", 39 | "type": "header", 40 | "target": "text/html; charset=UTF-8" 41 | }, 42 | { 43 | "operator": "lessThan", 44 | "type": "responseTime", 45 | "target": 4000 46 | }, 47 | { 48 | "operator": "is", 49 | "type": "statusCode", 50 | "target": 200 51 | } 52 | ] 53 | }, 54 | "options": { 55 | "tick_every": 60 56 | } 57 | }, 58 | { 59 | "monitor_status": "OK", 60 | "public_id": "yyy-yyy-yyy", 61 | "tags": [], 62 | "locations": [ 63 | "aws:ap-northeast-1" 64 | ], 65 | "notifications": [], 66 | "status": "live", 67 | "message": "", 68 | "id": 1238, 69 | "last_triggered_ts": 1549631477, 70 | "name": "Check on www.example.com", 71 | "monitor_id": 8104097, 72 | "type": "api", 73 | "created_at": "2019-01-25T14:09:43.158039+00:00", 74 | "modified_at": "2019-01-25T14:16:48.429972+00:00", 75 | "config": { 76 | "request": { 77 | "url": "https://www.example.com", 78 | "method": "GET", 79 | "timeout": 30 80 | }, 81 | "assertions": [ 82 | { 83 | "operator": "is", 84 | "property": "content-type", 85 | "type": "header", 86 | "target": "text/html; charset=UTF-8" 87 | }, 88 | { 89 | "operator": "lessThan", 90 | "type": "responseTime", 91 | "target": 2000 92 | }, 93 | { 94 | "operator": "is", 95 | "type": "statusCode", 96 | "target": 200 97 | } 98 | ] 99 | }, 100 | "options": { 101 | "tick_every": 60 102 | } 103 | }, 104 | { 105 | "monitor_status": "OK", 106 | "public_id": "zzz-zzz-zzz", 107 | "tags": [], 108 | "locations": [ 109 | "aws:eu-central-1", 110 | "aws:ap-northeast-1" 111 | ], 112 | "notifications": [], 113 | "status": "live", 114 | "message": "", 115 | "id": 2150, 116 | "last_triggered_ts": null, 117 | "name": "Check on example.org", 118 | "monitor_id": 8277216, 119 | "type": "api", 120 | "created_at": "2019-02-09T17:27:57.343339+00:00", 121 | "modified_at": "2019-02-09T17:27:57.343339+00:00", 122 | "config": { 123 | "request": { 124 | "url": "https://example.org", 125 | "method": "GET", 126 | "timeout": 30 127 | }, 128 | "assertions": [ 129 | { 130 | "operator": "is", 131 | "type": "statusCode", 132 | "target": 200 133 | } 134 | ] 135 | }, 136 | "options": { 137 | "tick_every": 60 138 | } 139 | } 140 | ] 141 | } 142 | -------------------------------------------------------------------------------- /tests/fixtures/users_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": { 3 | "handle": "test@datadoghq.com", 4 | "name": "test user", 5 | "access_role": "st", 6 | "verified": true, 7 | "disabled": false, 8 | "role": null, 9 | "is_admin": false, 10 | "email": "test@datadoghq.com", 11 | "icon": "/path/to/matching/gravatar/icon" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /users.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Datadog API for Go 3 | * 4 | * Please see the included LICENSE file for licensing information. 5 | * 6 | * Copyright 2013 by authors and contributors. 7 | */ 8 | 9 | package datadog 10 | 11 | type User struct { 12 | Handle *string `json:"handle,omitempty"` 13 | Email *string `json:"email,omitempty"` 14 | Name *string `json:"name,omitempty"` 15 | Role *string `json:"role,omitempty"` 16 | AccessRole *string `json:"access_role,omitempty"` 17 | Verified *bool `json:"verified,omitempty"` 18 | Disabled *bool `json:"disabled,omitempty"` 19 | 20 | // DEPRECATED: IsAdmin is deprecated and will be removed in the next major 21 | // revision. For more info on why it is being removed, see discussion on 22 | // https://github.com/zorkian/go-datadog-api/issues/126. 23 | IsAdmin *bool `json:"is_admin,omitempty"` 24 | } 25 | 26 | type reqUpdateUser struct { 27 | Email *string `json:"email,omitempty"` 28 | Name *string `json:"name,omitempty"` 29 | Role *string `json:"role,omitempty"` 30 | AccessRole *string `json:"access_role,omitempty"` 31 | Verified *bool `json:"verified,omitempty"` 32 | Disabled *bool `json:"disabled,omitempty"` 33 | IsAdmin *bool `json:"is_admin,omitempty"` 34 | } 35 | 36 | func reqUpdateUserFromUser(user User) reqUpdateUser { 37 | return reqUpdateUser{ 38 | Email: user.Email, 39 | Name: user.Name, 40 | Role: user.Role, 41 | AccessRole: user.AccessRole, 42 | Verified: user.Verified, 43 | Disabled: user.Disabled, 44 | IsAdmin: user.IsAdmin, 45 | } 46 | } 47 | 48 | // reqInviteUsers contains email addresses to send invitations to. 49 | type reqInviteUsers struct { 50 | Emails []string `json:"emails,omitempty"` 51 | } 52 | 53 | // InviteUsers takes a slice of email addresses and sends invitations to them. 54 | func (client *Client) InviteUsers(emails []string) error { 55 | return client.doJsonRequest("POST", "/v1/invite_users", 56 | reqInviteUsers{Emails: emails}, nil) 57 | } 58 | 59 | // CreateUser creates an user account for an email address 60 | func (self *Client) CreateUser(handle, name *string) (*User, error) { 61 | in := struct { 62 | Handle *string `json:"handle"` 63 | Name *string `json:"name"` 64 | }{ 65 | Handle: handle, 66 | Name: name, 67 | } 68 | 69 | out := struct { 70 | *User `json:"user"` 71 | }{} 72 | if err := self.doJsonRequest("POST", "/v1/user", in, &out); err != nil { 73 | return nil, err 74 | } 75 | return out.User, nil 76 | } 77 | 78 | // internal type to retrieve users from the api 79 | type usersData struct { 80 | Users []User `json:"users,omitempty"` 81 | } 82 | 83 | // GetUsers returns all user, or an error if not found 84 | func (client *Client) GetUsers() (users []User, err error) { 85 | var udata usersData 86 | uri := "/v1/user" 87 | err = client.doJsonRequest("GET", uri, nil, &udata) 88 | users = udata.Users 89 | return 90 | } 91 | 92 | // internal type to retrieve single user from the api 93 | type userData struct { 94 | User User `json:"user"` 95 | } 96 | 97 | // GetUser returns the user that match a handle, or an error if not found 98 | func (client *Client) GetUser(handle string) (user User, err error) { 99 | var udata userData 100 | uri := "/v1/user/" + handle 101 | err = client.doJsonRequest("GET", uri, nil, &udata) 102 | user = udata.User 103 | return 104 | } 105 | 106 | // UpdateUser updates a user with the content of `user`, 107 | // and returns an error if the update failed 108 | func (client *Client) UpdateUser(user User) error { 109 | uri := "/v1/user/" + *user.Handle 110 | req := reqUpdateUserFromUser(user) 111 | return client.doJsonRequest("PUT", uri, req, nil) 112 | } 113 | 114 | // DeleteUser deletes a user and returns an error if deletion failed 115 | func (client *Client) DeleteUser(handle string) error { 116 | uri := "/v1/user/" + handle 117 | return client.doJsonRequest("DELETE", uri, nil, nil) 118 | } 119 | -------------------------------------------------------------------------------- /users_test.go: -------------------------------------------------------------------------------- 1 | package datadog 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | ) 9 | 10 | func TestGetUser(t *testing.T) { 11 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 12 | response, err := ioutil.ReadFile("./tests/fixtures/users_response.json") 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | w.Write(response) 17 | })) 18 | defer ts.Close() 19 | 20 | datadogClient := Client{ 21 | baseUrl: ts.URL, 22 | HttpClient: http.DefaultClient, 23 | } 24 | 25 | user, err := datadogClient.GetUser("test@datadoghq.com") 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | 30 | expectedHandle := "test@datadoghq.com" 31 | if handle := user.GetHandle(); handle != expectedHandle { 32 | t.Fatalf("expect handle %s. Got %s", expectedHandle, handle) 33 | } 34 | 35 | expectedName := "test user" 36 | if name := user.GetName(); name != expectedName { 37 | t.Fatalf("expect name %s. Got %s", expectedName, name) 38 | } 39 | 40 | expectedEmail := "test@datadoghq.com" 41 | if email := user.GetEmail(); email != expectedEmail { 42 | t.Fatalf("expect email %s. Got %s", expectedEmail, email) 43 | } 44 | 45 | expectedAccessRole := "st" 46 | if accessRole := user.GetAccessRole(); accessRole != expectedAccessRole { 47 | t.Fatalf("expect access role %s. Got %s", expectedAccessRole, accessRole) 48 | } 49 | 50 | expectedIsAdmin := false 51 | if isAdmin := user.GetIsAdmin(); isAdmin != expectedIsAdmin { 52 | t.Fatalf("expect is_admin %t. Got %v", expectedIsAdmin, isAdmin) 53 | } 54 | 55 | expectedIsVerified := true 56 | if isVerified := user.GetVerified(); isVerified != expectedIsVerified { 57 | t.Fatalf("expect is_verified %t. Got %v", expectedIsVerified, isVerified) 58 | } 59 | 60 | expectedIsDisabled := false 61 | if isVerified := user.GetDisabled(); isVerified != expectedIsDisabled { 62 | t.Fatalf("expect is_disabled %t. Got %v", expectedIsDisabled, isVerified) 63 | } 64 | 65 | expectedRole := "" 66 | if role := user.GetRole(); role != expectedRole { 67 | t.Fatalf("expect role %s. Got %s", expectedRole, role) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /vendor/github.com/cenkalti/backoff/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Cenk Altı 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /vendor/github.com/cenkalti/backoff/README.md: -------------------------------------------------------------------------------- 1 | # Exponential Backoff [![GoDoc][godoc image]][godoc] [![Build Status][travis image]][travis] [![Coverage Status][coveralls image]][coveralls] 2 | 3 | This is a Go port of the exponential backoff algorithm from [Google's HTTP Client Library for Java][google-http-java-client]. 4 | 5 | [Exponential backoff][exponential backoff wiki] 6 | is an algorithm that uses feedback to multiplicatively decrease the rate of some process, 7 | in order to gradually find an acceptable rate. 8 | The retries exponentially increase and stop increasing when a certain threshold is met. 9 | 10 | ## Usage 11 | 12 | See https://godoc.org/github.com/cenkalti/backoff#pkg-examples 13 | 14 | ## Contributing 15 | 16 | * I would like to keep this library as small as possible. 17 | * Please don't send a PR without opening an issue and discussing it first. 18 | * If proposed change is not a common use case, I will probably not accept it. 19 | 20 | [godoc]: https://godoc.org/github.com/cenkalti/backoff 21 | [godoc image]: https://godoc.org/github.com/cenkalti/backoff?status.png 22 | [travis]: https://travis-ci.org/cenkalti/backoff 23 | [travis image]: https://travis-ci.org/cenkalti/backoff.png?branch=master 24 | [coveralls]: https://coveralls.io/github/cenkalti/backoff?branch=master 25 | [coveralls image]: https://coveralls.io/repos/github/cenkalti/backoff/badge.svg?branch=master 26 | 27 | [google-http-java-client]: https://github.com/google/google-http-java-client 28 | [exponential backoff wiki]: http://en.wikipedia.org/wiki/Exponential_backoff 29 | 30 | [advanced example]: https://godoc.org/github.com/cenkalti/backoff#example_ 31 | -------------------------------------------------------------------------------- /vendor/github.com/cenkalti/backoff/backoff.go: -------------------------------------------------------------------------------- 1 | // Package backoff implements backoff algorithms for retrying operations. 2 | // 3 | // Use Retry function for retrying operations that may fail. 4 | // If Retry does not meet your needs, 5 | // copy/paste the function into your project and modify as you wish. 6 | // 7 | // There is also Ticker type similar to time.Ticker. 8 | // You can use it if you need to work with channels. 9 | // 10 | // See Examples section below for usage examples. 11 | package backoff 12 | 13 | import "time" 14 | 15 | // BackOff is a backoff policy for retrying an operation. 16 | type BackOff interface { 17 | // NextBackOff returns the duration to wait before retrying the operation, 18 | // or backoff.Stop to indicate that no more retries should be made. 19 | // 20 | // Example usage: 21 | // 22 | // duration := backoff.NextBackOff(); 23 | // if (duration == backoff.Stop) { 24 | // // Do not retry operation. 25 | // } else { 26 | // // Sleep for duration and retry operation. 27 | // } 28 | // 29 | NextBackOff() time.Duration 30 | 31 | // Reset to initial state. 32 | Reset() 33 | } 34 | 35 | // Stop indicates that no more retries should be made for use in NextBackOff(). 36 | const Stop time.Duration = -1 37 | 38 | // ZeroBackOff is a fixed backoff policy whose backoff time is always zero, 39 | // meaning that the operation is retried immediately without waiting, indefinitely. 40 | type ZeroBackOff struct{} 41 | 42 | func (b *ZeroBackOff) Reset() {} 43 | 44 | func (b *ZeroBackOff) NextBackOff() time.Duration { return 0 } 45 | 46 | // StopBackOff is a fixed backoff policy that always returns backoff.Stop for 47 | // NextBackOff(), meaning that the operation should never be retried. 48 | type StopBackOff struct{} 49 | 50 | func (b *StopBackOff) Reset() {} 51 | 52 | func (b *StopBackOff) NextBackOff() time.Duration { return Stop } 53 | 54 | // ConstantBackOff is a backoff policy that always returns the same backoff delay. 55 | // This is in contrast to an exponential backoff policy, 56 | // which returns a delay that grows longer as you call NextBackOff() over and over again. 57 | type ConstantBackOff struct { 58 | Interval time.Duration 59 | } 60 | 61 | func (b *ConstantBackOff) Reset() {} 62 | func (b *ConstantBackOff) NextBackOff() time.Duration { return b.Interval } 63 | 64 | func NewConstantBackOff(d time.Duration) *ConstantBackOff { 65 | return &ConstantBackOff{Interval: d} 66 | } 67 | -------------------------------------------------------------------------------- /vendor/github.com/cenkalti/backoff/retry.go: -------------------------------------------------------------------------------- 1 | package backoff 2 | 3 | import "time" 4 | 5 | // An Operation is executing by Retry() or RetryNotify(). 6 | // The operation will be retried using a backoff policy if it returns an error. 7 | type Operation func() error 8 | 9 | // Notify is a notify-on-error function. It receives an operation error and 10 | // backoff delay if the operation failed (with an error). 11 | // 12 | // NOTE that if the backoff policy stated to stop retrying, 13 | // the notify function isn't called. 14 | type Notify func(error, time.Duration) 15 | 16 | // Retry the operation o until it does not return error or BackOff stops. 17 | // o is guaranteed to be run at least once. 18 | // It is the caller's responsibility to reset b after Retry returns. 19 | // 20 | // Retry sleeps the goroutine for the duration returned by BackOff after a 21 | // failed operation returns. 22 | func Retry(o Operation, b BackOff) error { return RetryNotify(o, b, nil) } 23 | 24 | // RetryNotify calls notify function with the error and wait duration 25 | // for each failed attempt before sleep. 26 | func RetryNotify(operation Operation, b BackOff, notify Notify) error { 27 | var err error 28 | var next time.Duration 29 | 30 | b.Reset() 31 | for { 32 | if err = operation(); err == nil { 33 | return nil 34 | } 35 | 36 | if next = b.NextBackOff(); next == Stop { 37 | return err 38 | } 39 | 40 | if notify != nil { 41 | notify(err, next) 42 | } 43 | 44 | time.Sleep(next) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /vendor/github.com/cenkalti/backoff/ticker.go: -------------------------------------------------------------------------------- 1 | package backoff 2 | 3 | import ( 4 | "runtime" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // Ticker holds a channel that delivers `ticks' of a clock at times reported by a BackOff. 10 | // 11 | // Ticks will continue to arrive when the previous operation is still running, 12 | // so operations that take a while to fail could run in quick succession. 13 | type Ticker struct { 14 | C <-chan time.Time 15 | c chan time.Time 16 | b BackOff 17 | stop chan struct{} 18 | stopOnce sync.Once 19 | } 20 | 21 | // NewTicker returns a new Ticker containing a channel that will send the time at times 22 | // specified by the BackOff argument. Ticker is guaranteed to tick at least once. 23 | // The channel is closed when Stop method is called or BackOff stops. 24 | func NewTicker(b BackOff) *Ticker { 25 | c := make(chan time.Time) 26 | t := &Ticker{ 27 | C: c, 28 | c: c, 29 | b: b, 30 | stop: make(chan struct{}), 31 | } 32 | go t.run() 33 | runtime.SetFinalizer(t, (*Ticker).Stop) 34 | return t 35 | } 36 | 37 | // Stop turns off a ticker. After Stop, no more ticks will be sent. 38 | func (t *Ticker) Stop() { 39 | t.stopOnce.Do(func() { close(t.stop) }) 40 | } 41 | 42 | func (t *Ticker) run() { 43 | c := t.c 44 | defer close(c) 45 | t.b.Reset() 46 | 47 | // Ticker is guaranteed to tick at least once. 48 | afterC := t.send(time.Now()) 49 | 50 | for { 51 | if afterC == nil { 52 | return 53 | } 54 | 55 | select { 56 | case tick := <-afterC: 57 | afterC = t.send(tick) 58 | case <-t.stop: 59 | t.c = nil // Prevent future ticks from being sent to the channel. 60 | return 61 | } 62 | } 63 | } 64 | 65 | func (t *Ticker) send(tick time.Time) <-chan time.Time { 66 | select { 67 | case t.c <- tick: 68 | case <-t.stop: 69 | return nil 70 | } 71 | 72 | next := t.b.NextBackOff() 73 | if next == Stop { 74 | t.Stop() 75 | return nil 76 | } 77 | 78 | return time.After(next) 79 | } 80 | -------------------------------------------------------------------------------- /vendor/github.com/davecgh/go-spew/LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2012-2013 Dave Collins 4 | 5 | Permission to use, copy, modify, and distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /vendor/github.com/davecgh/go-spew/spew/bypasssafe.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Dave Collins 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | // NOTE: Due to the following build constraints, this file will only be compiled 16 | // when the code is running on Google App Engine, compiled by GopherJS, or 17 | // "-tags safe" is added to the go build command line. The "disableunsafe" 18 | // tag is deprecated and thus should not be used. 19 | // +build js appengine safe disableunsafe 20 | 21 | package spew 22 | 23 | import "reflect" 24 | 25 | const ( 26 | // UnsafeDisabled is a build-time constant which specifies whether or 27 | // not access to the unsafe package is available. 28 | UnsafeDisabled = true 29 | ) 30 | 31 | // unsafeReflectValue typically converts the passed reflect.Value into a one 32 | // that bypasses the typical safety restrictions preventing access to 33 | // unaddressable and unexported data. However, doing this relies on access to 34 | // the unsafe package. This is a stub version which simply returns the passed 35 | // reflect.Value when the unsafe package is not available. 36 | func unsafeReflectValue(v reflect.Value) reflect.Value { 37 | return v 38 | } 39 | -------------------------------------------------------------------------------- /vendor/github.com/pmezard/go-difflib/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Patrick Mezard 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | The names of its contributors may not be used to endorse or promote 14 | products derived from this software without specific prior written 15 | permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 18 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 20 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 23 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 - 2013 Mat Ryer and Tyler Bunnell 2 | 3 | Please consider promoting this project if you find it useful. 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without restriction, 8 | including without limitation the rights to use, copy, modify, merge, 9 | publish, distribute, sublicense, and/or sell copies of the Software, 10 | and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 20 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT 21 | OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 22 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl: -------------------------------------------------------------------------------- 1 | {{.CommentFormat}} 2 | func {{.DocInfo.Name}}f(t TestingT, {{.ParamsFormat}}) bool { 3 | if h, ok := t.(tHelper); ok { h.Helper() } 4 | return {{.DocInfo.Name}}(t, {{.ForwardedParamsFormat}}) 5 | } 6 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl: -------------------------------------------------------------------------------- 1 | {{.CommentWithoutT "a"}} 2 | func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) bool { 3 | if h, ok := a.t.(tHelper); ok { h.Helper() } 4 | return {{.DocInfo.Name}}(a.t, {{.ForwardedParams}}) 5 | } 6 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/assert/doc.go: -------------------------------------------------------------------------------- 1 | // Package assert provides a set of comprehensive testing tools for use with the normal Go testing system. 2 | // 3 | // Example Usage 4 | // 5 | // The following is a complete example using assert in a standard test function: 6 | // import ( 7 | // "testing" 8 | // "github.com/stretchr/testify/assert" 9 | // ) 10 | // 11 | // func TestSomething(t *testing.T) { 12 | // 13 | // var a string = "Hello" 14 | // var b string = "Hello" 15 | // 16 | // assert.Equal(t, a, b, "The two words should be the same.") 17 | // 18 | // } 19 | // 20 | // if you assert many times, use the format below: 21 | // 22 | // import ( 23 | // "testing" 24 | // "github.com/stretchr/testify/assert" 25 | // ) 26 | // 27 | // func TestSomething(t *testing.T) { 28 | // assert := assert.New(t) 29 | // 30 | // var a string = "Hello" 31 | // var b string = "Hello" 32 | // 33 | // assert.Equal(a, b, "The two words should be the same.") 34 | // } 35 | // 36 | // Assertions 37 | // 38 | // Assertions allow you to easily write test code, and are global funcs in the `assert` package. 39 | // All assertion functions take, as the first argument, the `*testing.T` object provided by the 40 | // testing framework. This allows the assertion funcs to write the failings and other details to 41 | // the correct place. 42 | // 43 | // Every assertion function also takes an optional string message as the final argument, 44 | // allowing custom error messages to be appended to the message the assertion method outputs. 45 | package assert 46 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/assert/errors.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | // AnError is an error instance useful for testing. If the code does not care 8 | // about error specifics, and only needs to return the error for example, this 9 | // error should be used to make the test code more readable. 10 | var AnError = errors.New("assert.AnError general error for testing") 11 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/assert/forward_assertions.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | // Assertions provides assertion methods around the 4 | // TestingT interface. 5 | type Assertions struct { 6 | t TestingT 7 | } 8 | 9 | // New makes a new Assertions object for the specified TestingT. 10 | func New(t TestingT) *Assertions { 11 | return &Assertions{ 12 | t: t, 13 | } 14 | } 15 | 16 | //go:generate go run ../_codegen/main.go -output-package=assert -template=assertion_forward.go.tmpl -include-format-funcs 17 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/assert/http_assertions.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/http/httptest" 7 | "net/url" 8 | "strings" 9 | ) 10 | 11 | // httpCode is a helper that returns HTTP code of the response. It returns -1 and 12 | // an error if building a new request fails. 13 | func httpCode(handler http.HandlerFunc, method, url string, values url.Values) (int, error) { 14 | w := httptest.NewRecorder() 15 | req, err := http.NewRequest(method, url, nil) 16 | if err != nil { 17 | return -1, err 18 | } 19 | req.URL.RawQuery = values.Encode() 20 | handler(w, req) 21 | return w.Code, nil 22 | } 23 | 24 | // HTTPSuccess asserts that a specified handler returns a success status code. 25 | // 26 | // assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) 27 | // 28 | // Returns whether the assertion was successful (true) or not (false). 29 | func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool { 30 | if h, ok := t.(tHelper); ok { 31 | h.Helper() 32 | } 33 | code, err := httpCode(handler, method, url, values) 34 | if err != nil { 35 | Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err)) 36 | return false 37 | } 38 | 39 | isSuccessCode := code >= http.StatusOK && code <= http.StatusPartialContent 40 | if !isSuccessCode { 41 | Fail(t, fmt.Sprintf("Expected HTTP success status code for %q but received %d", url+"?"+values.Encode(), code)) 42 | } 43 | 44 | return isSuccessCode 45 | } 46 | 47 | // HTTPRedirect asserts that a specified handler returns a redirect status code. 48 | // 49 | // assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} 50 | // 51 | // Returns whether the assertion was successful (true) or not (false). 52 | func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool { 53 | if h, ok := t.(tHelper); ok { 54 | h.Helper() 55 | } 56 | code, err := httpCode(handler, method, url, values) 57 | if err != nil { 58 | Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err)) 59 | return false 60 | } 61 | 62 | isRedirectCode := code >= http.StatusMultipleChoices && code <= http.StatusTemporaryRedirect 63 | if !isRedirectCode { 64 | Fail(t, fmt.Sprintf("Expected HTTP redirect status code for %q but received %d", url+"?"+values.Encode(), code)) 65 | } 66 | 67 | return isRedirectCode 68 | } 69 | 70 | // HTTPError asserts that a specified handler returns an error status code. 71 | // 72 | // assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} 73 | // 74 | // Returns whether the assertion was successful (true) or not (false). 75 | func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool { 76 | if h, ok := t.(tHelper); ok { 77 | h.Helper() 78 | } 79 | code, err := httpCode(handler, method, url, values) 80 | if err != nil { 81 | Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err)) 82 | return false 83 | } 84 | 85 | isErrorCode := code >= http.StatusBadRequest 86 | if !isErrorCode { 87 | Fail(t, fmt.Sprintf("Expected HTTP error status code for %q but received %d", url+"?"+values.Encode(), code)) 88 | } 89 | 90 | return isErrorCode 91 | } 92 | 93 | // HTTPBody is a helper that returns HTTP body of the response. It returns 94 | // empty string if building a new request fails. 95 | func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) string { 96 | w := httptest.NewRecorder() 97 | req, err := http.NewRequest(method, url+"?"+values.Encode(), nil) 98 | if err != nil { 99 | return "" 100 | } 101 | handler(w, req) 102 | return w.Body.String() 103 | } 104 | 105 | // HTTPBodyContains asserts that a specified handler returns a 106 | // body that contains a string. 107 | // 108 | // assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") 109 | // 110 | // Returns whether the assertion was successful (true) or not (false). 111 | func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { 112 | if h, ok := t.(tHelper); ok { 113 | h.Helper() 114 | } 115 | body := HTTPBody(handler, method, url, values) 116 | 117 | contains := strings.Contains(body, fmt.Sprint(str)) 118 | if !contains { 119 | Fail(t, fmt.Sprintf("Expected response body for \"%s\" to contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body)) 120 | } 121 | 122 | return contains 123 | } 124 | 125 | // HTTPBodyNotContains asserts that a specified handler returns a 126 | // body that does not contain a string. 127 | // 128 | // assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") 129 | // 130 | // Returns whether the assertion was successful (true) or not (false). 131 | func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { 132 | if h, ok := t.(tHelper); ok { 133 | h.Helper() 134 | } 135 | body := HTTPBody(handler, method, url, values) 136 | 137 | contains := strings.Contains(body, fmt.Sprint(str)) 138 | if contains { 139 | Fail(t, fmt.Sprintf("Expected response body for \"%s\" to NOT contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body)) 140 | } 141 | 142 | return !contains 143 | } 144 | -------------------------------------------------------------------------------- /vendor/vendor.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "", 3 | "ignore": "test", 4 | "package": [ 5 | { 6 | "checksumSHA1": "SPQcWDUN+wcTThipcR9EzX4t3E8=", 7 | "path": "github.com/cenkalti/backoff", 8 | "revision": "b02f2bbce11d7ea6b97f282ef1771b0fe2f65ef3", 9 | "revisionTime": "2016-10-20T19:44:10Z" 10 | }, 11 | { 12 | "checksumSHA1": "OFu4xJEIjiI8Suu+j/gabfp+y6Q=", 13 | "origin": "github.com/stretchr/testify/vendor/github.com/davecgh/go-spew/spew", 14 | "path": "github.com/davecgh/go-spew/spew", 15 | "revision": "18a02ba4a312f95da08ff4cfc0055750ce50ae9e", 16 | "revisionTime": "2016-11-17T07:43:51Z" 17 | }, 18 | { 19 | "checksumSHA1": "zKKp5SZ3d3ycKe4EKMNT0BqAWBw=", 20 | "origin": "github.com/stretchr/testify/vendor/github.com/pmezard/go-difflib/difflib", 21 | "path": "github.com/pmezard/go-difflib/difflib", 22 | "revision": "18a02ba4a312f95da08ff4cfc0055750ce50ae9e", 23 | "revisionTime": "2016-11-17T07:43:51Z" 24 | }, 25 | { 26 | "checksumSHA1": "c6pbpF7eowwO59phRTpF8cQ80Z0=", 27 | "path": "github.com/stretchr/testify/assert", 28 | "revision": "f35b8ab0b5a2cef36673838d662e249dd9c94686", 29 | "revisionTime": "2018-05-06T18:05:49Z" 30 | } 31 | ], 32 | "rootPath": "github.com/zorkian/go-datadog-api" 33 | } 34 | --------------------------------------------------------------------------------