Skip to content

Commit 4ab584c

Browse files
Add hosts endpoint. (#884)
* added api.Hosts.get_all and dogshell.hosts * resolved flake8 issues * integration tests passed for test_api.py * updated test config to pass integration testing. api_client and __ini__.py got formatted by black * reverted black formatting and added muted hosts filter * changed muted_hosts and hosts_metadata to flags instead of args * fixed spaces per flake8 * removed type from argparse when using action=store_true * added muted_hosts flag to list method * commented muted_hosts filter * added totals verb * fixed totals verb * updated params for totals method * added test hosts totals * added integration tests for hosts totals * fixed formatting * fixed formatting * changed to the expert-services sandbox and added cassettes * updated cassettes generated with the right key. The IS sandbox had inconsistently tagged hosts.
1 parent a7a7594 commit 4ab584c

File tree

9 files changed

+386
-4
lines changed

9 files changed

+386
-4
lines changed

datadog/api/hosts.py

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License.
22
# This product includes software developed at Datadog (https://www.datadoghq.com/).
33
# Copyright 2015-Present Datadog, Inc
4-
from datadog.api.resources import ActionAPIResource, SearchableAPIResource
4+
from datadog.api.resources import ActionAPIResource, SearchableAPIResource, ListableAPIResource
55

66

77
class Host(ActionAPIResource):
@@ -48,7 +48,7 @@ def unmute(cls, host_name):
4848
return super(Host, cls)._trigger_class_action("POST", "unmute", host_name)
4949

5050

51-
class Hosts(ActionAPIResource, SearchableAPIResource):
51+
class Hosts(ActionAPIResource, SearchableAPIResource, ListableAPIResource):
5252
"""
5353
A wrapper around Hosts HTTP API.
5454
"""
@@ -82,10 +82,54 @@ def search(cls, **params):
8282
return super(Hosts, cls)._search(**params)
8383

8484
@classmethod
85-
def totals(cls):
85+
def totals(cls, **params):
8686
"""
8787
Get total number of hosts active and up.
8888
89+
:param from_: Number of seconds since UNIX epoch from which you want to search your hosts.
90+
:type from_: integer
91+
8992
:returns: Dictionary representing the API's JSON response
9093
"""
91-
return super(Hosts, cls)._trigger_class_action("GET", "totals")
94+
return super(Hosts, cls)._trigger_class_action("GET", "totals", **params)
95+
96+
@classmethod
97+
def get_all(cls, **params):
98+
"""
99+
Get all hosts.
100+
101+
:param filter: query to filter search results
102+
:type filter: string
103+
104+
:param sort_field: field to sort by
105+
:type sort_field: string
106+
107+
:param sort_dir: Direction of sort. Options include asc and desc.
108+
:type sort_dir: string
109+
110+
:param start: Specify the starting point for the host search results.
111+
For example, if you set count to 100 and the first 100 results have already been returned,
112+
you can set start to 101 to get the next 100 results.
113+
:type start: integer
114+
115+
:param count: number of hosts to return. Max 1000.
116+
:type count: integer
117+
118+
:param from_: Number of seconds since UNIX epoch from which you want to search your hosts.
119+
:type from_: integer
120+
121+
:param include_muted_hosts_data: Include data from muted hosts.
122+
:type include_muted_hosts_data: boolean
123+
124+
:param include_hosts_metadata: Include metadata from the hosts
125+
(agent_version, machine, platform, processor, etc.).
126+
:type include_hosts_metadata: boolean
127+
128+
:returns: Dictionary representing the API's JSON response
129+
"""
130+
131+
for param in ["filter"]:
132+
if param in params and isinstance(params[param], list):
133+
params[param] = ",".join(params[param])
134+
135+
return super(Hosts, cls).get_all(**params)

datadog/dogshell/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from datadog.dogshell.downtime import DowntimeClient
1818
from datadog.dogshell.event import EventClient
1919
from datadog.dogshell.host import HostClient
20+
from datadog.dogshell.hosts import HostsClient
2021
from datadog.dogshell.metric import MetricClient
2122
from datadog.dogshell.monitor import MonitorClient
2223
from datadog.dogshell.screenboard import ScreenboardClient
@@ -95,6 +96,7 @@ def main():
9596
ScreenboardClient.setup_parser(subparsers)
9697
DashboardListClient.setup_parser(subparsers)
9798
HostClient.setup_parser(subparsers)
99+
HostsClient.setup_parser(subparsers)
98100
DowntimeClient.setup_parser(subparsers)
99101
ServiceCheckClient.setup_parser(subparsers)
100102
ServiceLevelObjectiveClient.setup_parser(subparsers)

datadog/dogshell/hosts.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License.
2+
# This product includes software developed at Datadog (https://www.datadoghq.com/).
3+
# Copyright 2015-Present Datadog, Inc
4+
# stdlib
5+
6+
import json
7+
8+
# 3p
9+
from datadog.util.format import pretty_json
10+
11+
# datadog
12+
from datadog import api
13+
from datadog.dogshell.common import report_errors, report_warnings
14+
15+
16+
class HostsClient(object):
17+
@classmethod
18+
def setup_parser(cls, subparsers):
19+
parser = subparsers.add_parser("hosts", help="Get information about hosts")
20+
verb_parsers = parser.add_subparsers(title="Verbs", dest="verb")
21+
verb_parsers.required = True
22+
23+
list_parser = verb_parsers.add_parser("list", help="List all hosts")
24+
list_parser.add_argument("--filter", help="String to filter search results", type=str)
25+
list_parser.add_argument("--sort_field", help="Sort hosts by this field", type=str)
26+
list_parser.add_argument(
27+
"--sort_dir",
28+
help="Direction of sort. 'asc' or 'desc'",
29+
choices=["asc", "desc"],
30+
default="asc"
31+
)
32+
list_parser.add_argument(
33+
"--start",
34+
help="Specify the starting point for the host search results. \
35+
For example, if you set count to 100 and the first 100 results \
36+
have already been returned, \
37+
you can set start to 101 to get the next 100 results.",
38+
type=int,
39+
)
40+
list_parser.add_argument("--count", help="Number of hosts to return. Max 1000", type=int, default=100)
41+
list_parser.add_argument(
42+
"--from",
43+
help="Number of seconds since UNIX epoch from which you want to search your hosts.",
44+
type=int,
45+
dest="from_",
46+
)
47+
# list_parser.add_argument(
48+
# "--include_muted_hosts_data",
49+
# help="Include information on the muted status of hosts and when the mute expires.",
50+
# action="store_true",
51+
# )
52+
list_parser.add_argument(
53+
"--include_hosts_metadata",
54+
help="Include metadata from the hosts \
55+
(agent_version, machine, platform, processor, etc.).",
56+
action="store_true",
57+
)
58+
list_parser.set_defaults(func=cls._list)
59+
60+
totals_parser = verb_parsers.add_parser("totals", help="Get the total number of hosts")
61+
totals_parser.add_argument("--from",
62+
help="Number of seconds since UNIX epoch \
63+
from which you want to search your hosts.",
64+
type=int,
65+
dest="from_")
66+
totals_parser.set_defaults(func=cls._totals)
67+
68+
@classmethod
69+
def _list(cls, args):
70+
api._timeout = args.timeout
71+
format = args.format
72+
res = api.Hosts.get_all(
73+
filter=args.filter,
74+
sort_field=args.sort_field,
75+
sort_dir=args.sort_dir,
76+
start=args.start,
77+
count=args.count,
78+
from_=args.from_,
79+
include_hosts_metadata=args.include_hosts_metadata,
80+
# this doesn't seem to actually filter and I don't need it for now.
81+
# include_muted_hosts_data=args.include_muted_hosts_data
82+
)
83+
report_warnings(res)
84+
report_errors(res)
85+
if format == "pretty":
86+
print(pretty_json(res))
87+
else:
88+
print(json.dumps(res))
89+
90+
@classmethod
91+
def _totals(cls, args):
92+
api._timeout = args.timeout
93+
format = args.format
94+
res = api.Hosts.totals(from_=args.from_)
95+
report_warnings(res)
96+
report_errors(res)
97+
if format == "pretty":
98+
print(pretty_json(res))
99+
else:
100+
print(json.dumps(res))
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
interactions:
2+
- request:
3+
body: null
4+
headers:
5+
Accept:
6+
- '*/*'
7+
Accept-Encoding:
8+
- gzip, deflate
9+
Connection:
10+
- keep-alive
11+
User-Agent:
12+
- datadogpy/0.50.3-dev (python 3.8.20; os linux; arch aarch64)
13+
method: GET
14+
uri: https://api.datadoghq.com/api/v1/hosts?count=100&filter=env%3Adev&from_=0&include_hosts_metadata=False&include_muted_hosts_data=True&sort_field=host_name&sort_order=asc&start=0
15+
response:
16+
body:
17+
string: !!binary |
18+
H4sIAAAAAAAEA5xSy27EIAy89zM4J1Eg2bzO/YtqhbyEbpEIoGB2W0X595qs1EOlHracYGyPPWM2
19+
9uEjSmsisultY2Zmk+jbgfOxq0+8YAjXKC9fMvq0Ks2mjb0CwuyvlM60u02zvrHiYJlMKHkvyoaX
20+
vC1FLaoUy7uOWPJK+SUk1JVxqFcHlp33goE1EHXMTKasL506dc0wqlbUnQZFrE8REl8IB5nDQMVw
21+
1Q7ZuWCP0Y/ID+ZgITFPNXiIlP+qhHuU2do/dKbAJlyTLpgF2saqg19RzxJNnpL3TTcMXTeO5EiU
22+
C/lIVO9gIxXk15HnE23QJWsJ0wi0KXKYbqtRJH1jKiSiqvqh5Q3t1fg7GKqoq7oWQtR92wzU3gNR
23+
Z6w5Hafdd3IQPYKlsTCtLvfO/+KAFkD1YRx9BoL0JyiUvwNZ1/7yDQAA//8DAFrb9LBoAgAA
24+
headers:
25+
Connection:
26+
- keep-alive
27+
Content-Type:
28+
- application/json
29+
Date:
30+
- Tue, 14 Jan 2025 20:39:44 GMT
31+
Transfer-Encoding:
32+
- chunked
33+
content-encoding:
34+
- gzip
35+
content-security-policy:
36+
- frame-ancestors 'self'; report-uri https://logs.browser-intake-datadoghq.com/api/v2/logs?dd-api-key=pube4f163c23bbf91c16b8f57f56af9fc58&dd-evp-origin=content-security-policy&ddsource=csp-report&ddtags=site%3Adatadoghq.com
37+
strict-transport-security:
38+
- max-age=31536000; includeSubDomains; preload
39+
vary:
40+
- Accept-Encoding
41+
x-content-type-options:
42+
- nosniff
43+
x-frame-options:
44+
- SAMEORIGIN
45+
status:
46+
code: 200
47+
message: OK
48+
version: 1
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
interactions:
2+
- request:
3+
body: null
4+
headers:
5+
Accept:
6+
- '*/*'
7+
Accept-Encoding:
8+
- gzip, deflate
9+
Connection:
10+
- keep-alive
11+
User-Agent:
12+
- datadogpy/0.50.3-dev (python 3.12.3; os linux; arch aarch64)
13+
method: GET
14+
uri: https://api.datadoghq.com/api/v1/hosts/totals
15+
response:
16+
body:
17+
string: '{"total_up":8,"total_active":8}'
18+
headers:
19+
Connection:
20+
- keep-alive
21+
Content-Length:
22+
- '31'
23+
Content-Type:
24+
- application/json
25+
Date:
26+
- Thu, 19 Dec 2024 21:56:20 GMT
27+
content-security-policy:
28+
- frame-ancestors 'self'; report-uri https://logs.browser-intake-datadoghq.com/api/v2/logs?dd-api-key=pube4f163c23bbf91c16b8f57f56af9fc58&dd-evp-origin=content-security-policy&ddsource=csp-report&ddtags=site%3Adatadoghq.com
29+
strict-transport-security:
30+
- max-age=31536000; includeSubDomains; preload
31+
vary:
32+
- Accept-Encoding
33+
x-content-type-options:
34+
- nosniff
35+
x-frame-options:
36+
- SAMEORIGIN
37+
status:
38+
code: 200
39+
message: OK
40+
version: 1

tests/integration/api/test_api.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,30 @@ def test_host_muting(self, dog, get_with_retry, freezer):
803803
unmute = dog.Host.unmute(hostname)
804804
assert unmute["hostname"] == hostname
805805
assert unmute["action"] == "Unmuted"
806+
807+
def test_hosts_get_all(self, dog):
808+
params = {
809+
"filter": "env:dev",
810+
"sort_field": "host_name",
811+
"sort_order": "asc",
812+
"start": 0,
813+
"count": 100,
814+
"from_": 0,
815+
"include_muted_hosts_data": True,
816+
"include_hosts_metadata": False
817+
}
818+
819+
all_hosts = dog.Hosts.get_all(**params)
820+
assert "host_list" in all_hosts
821+
assert isinstance(all_hosts["host_list"], list)
822+
823+
def test_hosts_totals(self, dog):
824+
params = {
825+
"--from": 0,
826+
}
827+
totals = dog.Hosts.totals()
828+
assert "total_active" in totals
829+
assert "total_up" in totals
806830

807831
def test_get_all_embeds(self, dog):
808832
all_embeds = dog.Embed.get_all()
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
interactions:
2+
- request:
3+
body: null
4+
headers:
5+
Accept:
6+
- '*/*'
7+
Accept-Encoding:
8+
- gzip, deflate
9+
Connection:
10+
- keep-alive
11+
User-Agent:
12+
- datadogpy/0.50.3-dev (python 3.8.20; os linux; arch aarch64)
13+
method: GET
14+
uri: https://api.datadoghq.com/api/v1/hosts?count=100&filter=env&from_=0&include_hosts_metadata=False&sort_dir=asc&sort_field=host_name&start=0
15+
response:
16+
body:
17+
string: !!binary |
18+
H4sIAAAAAAAEA6xTTW+cMBC992f4DMgfgA23qMmtUe+tIuT1uhtLYCM8zrZa8d87ZrVt2iRVVion
19+
e8bvzcd7nMhjiDCMLgLpv56I25Oey1Z0SjFey4KAPsRh92OIIS3Gkv5EbjXofTjgc2L9Ux/DZOHR
20+
+QMpNq7+7iO/ub/5Ut5/bgSTn8jDWhA9Oh1tzBhXUsWE6Lik1u6YMY1E5AsQYuZ5A+iD9YBPPMzk
21+
oSDnRp5lMOb1hK29IDk3NLyZ1cc45IHf6CnNpIcl2YKMGne02DksYPcDuFyNSdEqVXMqCuLiMCVM
22+
kf6bHiMC8m17FxLu1adxxJgFjfvDbeBpcQZHOBEzJ6SqGinrTnBkCkftEEKxaNBImLHrWlyUqRVj
23+
XUsb9g5l9vbpoombSyZ5KVjJ6pJTXqVYHm2EklUmTDN2WzkPdvF6fEWvXWuaVqjO4LSt1QZZryL8
24+
rWXWEG+bov/W8poCf+p8HfK5B16Z8z0eaNuu+w8ekKpmAnX9ZYGKUs45lbVQFzdQjIlm++p1xQ1C
25+
AD2iNSEtPvsPLXQOTRrM9lfmkP2uDQx/J7K31w8/AQAA//8DAI9b56cCBAAA
26+
headers:
27+
Connection:
28+
- keep-alive
29+
Content-Type:
30+
- application/json
31+
Date:
32+
- Tue, 14 Jan 2025 20:39:45 GMT
33+
Transfer-Encoding:
34+
- chunked
35+
content-encoding:
36+
- gzip
37+
content-security-policy:
38+
- frame-ancestors 'self'; report-uri https://logs.browser-intake-datadoghq.com/api/v2/logs?dd-api-key=pube4f163c23bbf91c16b8f57f56af9fc58&dd-evp-origin=content-security-policy&ddsource=csp-report&ddtags=site%3Adatadoghq.com
39+
strict-transport-security:
40+
- max-age=31536000; includeSubDomains; preload
41+
vary:
42+
- Accept-Encoding
43+
x-content-type-options:
44+
- nosniff
45+
x-frame-options:
46+
- SAMEORIGIN
47+
status:
48+
code: 200
49+
message: OK
50+
version: 1

0 commit comments

Comments
 (0)