Skip to content

Instantly share code, notes, and snippets.

@clutchski
Last active May 30, 2020 17:30
Show Gist options
  • Save clutchski/7ccca3166e0b8e54450ecb2f2f71e8a8 to your computer and use it in GitHub Desktop.
Save clutchski/7ccca3166e0b8e54450ecb2f2f71e8a8 to your computer and use it in GitHub Desktop.
import argparse
from collections import defaultdict
import os
import sys
import time
import requests
import json
# the API sends back at most 100 results per page
RESULTS_PER_PAGE = 100
MAX_RETRIES = 2
def make_request(url, headers, params, retries=0):
"""
Make request, retry known errors
"""
if retries >= MAX_RETRIES:
raise Exception("Max retries of {} reached".format(MAX_RETRIES))
try:
r = requests.get(url, headers=headers, params=params)
r.raise_for_status()
except requests.exceptions.ConnectionError as e:
print("Connection Error: {}".format(e))
print("Retrying in 5 seconds...")
time.sleep(5)
return make_request(url, headers=headers, params=params, retries=retries+1)
except requests.exceptions.Timeout as e:
print("Timeout: {}".format(e))
print("Retrying in 5 seconds...")
time.sleep(5)
return make_request(url, headers=headers, params=params, retries=retries+1)
except requests.exceptions.HTTPError as e:
if r.status_code // 100 == 5:
print("Server error: {}".format(e))
print("Retrying in 5 seconds...")
time.sleep(5)
return make_request(url, headers=headers, params=params, retries=retries+1)
raise
return r
def is_affected_version(version):
if not version:
return False
parts = version.split(".")
try:
major, minor, patch = [int(x) for x in parts]
except:
return False
if major >= 6 or (major >= 5 and minor >= 32 and patch >= 7):
return False
else:
return True
return False
def write_to_file_as_json(filepath, data, out_console):
"""
Dump data as json in file
"""
with open (filepath, 'w') as outfile:
json.dump(data, outfile, indent=4)
if out_console:
print(json.dumps(data, indent=4))
def find_agents(api_key, application_key, filepath, site, out_console):
base_url = ""
base_site = ""
if site == "us":
base_url = "https://api.datadoghq.com/api"
base_site = "https://app.datadoghq.com"
print("Checking hosts at app.datadoghq.com")
elif site == "eu":
base_url = "https://api.datadoghq.eu/api"
base_site = "https://app.datadoghq.eu"
print("Checking hosts at app.datadoghq.eu")
else :
raise Exception("Unknown site {}".format(site))
url = base_url + "/v1/hosts"
headers = {'DD-API-KEY': api_key, 'DD-APPLICATION-KEY': application_key}
base_params = {'sort_field': 'name'}
page_start = 0
total_n_hosts = 0
agents_by_version = defaultdict(list)
while True:
params = base_params.copy()
params.update({'start': page_start, 'count': RESULTS_PER_PAGE})
print("Querying data of {} hosts, starting at offset: {}".format(RESULTS_PER_PAGE, page_start))
try:
r = make_request(url, headers=headers, params=params)
except requests.exceptions.HTTPError as e:
if e.response.status_code == 403:
print("Client error: {}".format(e))
print("Your API and/or Application key appear to be invalid, please double check them against {}/account/settings#api".format(base_site))
return
raise
data = r.json()
for host in data["host_list"]:
host_meta = host["meta"]
version = host_meta.get("agent_version", "")
if is_affected_version(version):
agents_by_version[version].append(host["host_name"])
if data["total_returned"] < RESULTS_PER_PAGE:
# if we're getting fewer results than we asked for, we've reached the last page
total_n_hosts = data["total_matching"]
print("Finished querying all {} hosts".format(total_n_hosts))
break
page_start += RESULTS_PER_PAGE
print("--- Results")
print("Found {} Agents running affected versions (out of {} hosts).".format(sum([len(agents_by_version[agent]) for agent in agents_by_version]), total_n_hosts))
write_to_file_as_json(filepath, agents_by_version, out_console)
print("Results written in JSON format to {}".format(filepath))
def main():
parser = argparse.ArgumentParser(description='Finds hosts that run Datadog Agent with version less than 5.32.7 and report to Datadog, and writes their hostnames to a JSON-formatted file ordered by version.')
parser.add_argument('--api-key', type=str, help='(required) your API key (can also be specified with the DD_API_KEY environment variable)', default=os.environ.get('DD_API_KEY'))
parser.add_argument('--application-key', type=str, help='(required) your Application key (can also be specified with the DD_APPLICATION_KEY environment variable)', default=os.environ.get('DD_APPLICATION_KEY'))
parser.add_argument('--site', type=str, help='Datadog site to query. Valid options are "US" and "EU". If you log in to datadoghq.com, use US; if you log into datadoghq.eu, use EU. Default: "US"', default=os.environ.get('DD_SITE', "US"))
parser.add_argument('--output-file-path', type=str, help='path of the file where the script results are written, in JSON format. Default: "./hosts_agents.json"', default="./hosts_agents.json", dest="filepath")
parser.add_argument('--out-console', help='output results to the console, in addition to writing to file"', action='store_true')
args = parser.parse_args()
error_message = ""
if args.api_key is None:
error_message += "\nAPI key required but not specified. Specify it with the DD_API_KEY env var or the --api-key parameter."
if args.application_key is None:
error_message += "\nApplication key required but not specified. Specify it with the DD_APPLICATION_KEY env var or the --application-key parameter"
if args.site.lower() != "eu" and args.site.lower() != "us":
error_message += "\nInvalid value for 'site': '{}'. Allowed values for 'site' are 'US' and 'EU'.".format(args.site)
if error_message:
parser.print_usage()
parser.exit(status=2, message=error_message+"\n")
find_agents(args.api_key, args.application_key, args.filepath, args.site.lower(), args.out_console)
if __name__ == "__main__":
main()
@ethanwp
Copy link

ethanwp commented May 30, 2020

Need a try .. catch around

write_to_file_as_json(filepath, agents_by_version, out_console)

In case filesystem permission issues no?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment