Rest-API python script to add labels to existing hosts

Hey guys.

I created a script using the rest API documentation and with some ideas out of this community. It is planned to be used to add labels to existing hosts using the “update_attribute” option which indicates it just updates the value. That made me believe i can add more labels to an existing host with this while keeping the existing labels.

The actual results of my script (which has a following discovery and activating changes section) proved me wrong, because my labels get overwritten.

Is it either possible to make checkmk really add those labels to the existing ones or to get a list of the existing labels for that host and autofill them into the json payload?

Thank you for your trouble and have a nice day!

Following code includes argument parsing, getting etag for host, insert changes, service discovery and activate changes processes

#!/usr/bin/env python3
import pprint
import os
import sys
import getopt
import requests
import json
import argparse
import urllib.request
import time

#Initializing variables

HOST_NAME = "hostname"
SITE_NAME = "sitename"
API_URL = f"http://{HOST_NAME}/{SITE_NAME}/check_mk/api/1.0"
USERNAME = "username"
PASSWORD = "password"

#Parsing arguments (key and value separated because json payload does not accept them combined)

parser = argparse.ArgumentParser(allow_abbrev=False)
parser.add_argument('-n', '--name', required=True, help='helptext')
parser.add_argument('-k', '--key', required=True, help='helptext')
parser.add_argument('-v', '--value', required=True, help='helptext')

args = parser.parse_args()

session = requests.session()
session.headers['Authorization'] = f"Bearer {USERNAME} {PASSWORD}"
session.headers['Accept'] = 'application/json'

#Requesting #ETAG (checksum) to ensure noone modified in between

query_string = urllib.parse.urlencode({})
HOSTNAME=args.name

request = urllib.request.Request(
    f"{API_URL}/objects/host_config/{HOSTNAME}?{query_string}",
    method="GET",
    headers={
        "Authorization": f"Bearer {USERNAME} {PASSWORD}",
        "Accept": "application/json",
   },
)

resp = urllib.request.urlopen(request)

if resp.status == 200:
    etag = str(resp.headers["ETag"])

#Print for Testing purposes
#    print("ETag fetched from "+args.name+": "+etag)

elif resp.status == 204:
    print("Done")
else:
    raise RuntimeError(resp.read())

#Adding Label to specified Host

print("Adding Label "+args.key+":"+args.value+" to "+args.name+" ...")

resp = session.put(
    f"{API_URL}/objects/host_config/"+args.name,
    headers={
        "If-Match": etag,  # (required) The value of the, to be modified, object's ETag header.
        "Content-Type": 'application/json',  # (required) A header specifying which type of content is in the request/response body.
    },
    json={
        'update_attributes': {
            'labels':{
                args.key:args.value
            }
        },
    },
)

if resp.status_code == 200:
    print("Done")
elif resp.status_code == 204:
    print("Done")
else:
    raise RuntimeError(pprint.pformat(resp.json()))

#A little time-window to make sure service discovery is not conflicting with last step

    time.sleep(5)

#Discovering and accepting previously made changes in Check-MK
try:
    resp = session.post(
        f"{API_URL}/domain-types/discovery_run/actions/bulk-discovery-start/invoke",
        headers={
            "Content-Type": 'application/json',  # (required) A header specifying which type of content is in the request/response body.
        },
        json={
            'hostnames': [args.name],
            'mode': 'new',
            'do_full_scan': True,
            'bulk_size': 1,
            'ignore_errors': True
        },
    )

    if resp.status_code == 200:
        print("Service discovery  ... started (might take a moment)")
        print("Service Discovery  ...", end = " ")
    elif resp.status_code == 204:
        print("Service discovery ... done")
    else:
        raise RuntimeError(pprint.pformat(resp.json()))

#A little time-window to make sure service discovery is finished before attempting to move on

    time.sleep(10)
    print("done")

#Activation process starting after Discovery

    resp = session.post(
    f"{API_URL}/domain-types/activation_run/actions/activate-changes/invoke",
    headers={
        "Content-Type": 'application/json', 
    },
    json={
        'redirect': False,
        'sites': [],
        'force_foreign_changes': False
    },
    allow_redirects=True,
    )
    if resp.status_code == 200:
        print("Activating changes ... done")
    elif resp.status_code == 204:
        print("Activating changes ... done")
    else:
        raise RuntimeError(pprint.pformat(resp.json()))

except:
    print(args.name + " could not finish service discovery and/or activate changes process")

To get a list you can use LQL
Following command gets a sorted list of all labels on existing hosts, run as OMD site user in linux cli.

lq "GET hosts\nColumns: labels" | tr , '\n' | sort -u

More on Livestatus Query Language (LQL):

Unsure if LQL is available in REST API.

Hey Yggy,

thanks for your reply.
The problem i have is, that i want to request the information inside my script and directly insert it into the json payload of the label adding process.
That would be an automation user and not the site user.
My hope is, that i can do something like the “show a host” action and filter only the labels out, so i can get it in perfect json format to reinsert it in the next step. but i do not understand, how i can address a single attribute (maybe in the params section?) to get that result and work with it
thank you for your trouble again :slight_smile:

I don’t have much experience with the api.
Just went to Help | REST-API Interactive GUI and there is GET ​/domain-types​/host_config​/collections​/all and GET ​/objects​/host_config​/{host_name}. Tried the last one out for a host.

In the response body, labels were shown:

  "domainType": "host_config",
  "id": "hostname",
  "title": "hostalias",
  "members": {},
  "extensions": {
    "folder": "/folder_name",
    "attributes": {
      "labels": {
        "sub/key1": "value1",
        "sub/key2": "value2",
        "sub/key3": "value3"
      },

But maybe there is a simpler way for getting the lables than the provided examples in the GUI.

1 Like

Thanks for all the effort,
the return is almost what i need, just too much.
Is it possible, to just return the key:value1/2/3 from attributes:labels: ?
The reason i try that is because extracting those values from a string is kinda hard, when u don’t know beforehand how many labels each host might have and getting it right away would make things a lot less complicated.
The script should do the work of investigating this instead of manually doing it every time, so it adds the existing labels and the one given to the script additionaly.
My favorite version would be variablename=“$gethostlabelsviamagic” to then insert variablename into the put json payload when adding my new label.

What i found in the API Documentation ist this:

resp = session.get(
    f"{API_URL}/objects/host_config/example.com",
    params={  # goes into query string
        "effective_attributes": False,  # Show all effective attributes on hosts, not just the attributes which were set on this host specifically.
    },
)

But i don’t see where/how i can specify the request to labels only, my suggestion ist the params section, but there is no example of how to address the labels.

Maybe by adding the columns parameter?
Untested, unclear if it available from that API_URL

resp = session.get(
    f"{API_URL}/objects/host_config/example.com",
    params={  
        "effective_attributes": False, 
        "columns": ['labels'],
    },
)