Forum Discussion

ldoodle's avatar
ldoodle
Icon for Advisor rankAdvisor
10 months ago

Dynamic Device Groups - constrain membership to parent folder

Hi,

We have the following hierarchy setup:

Our company is top level, e.g. LOGICMONITOR
|--Customer Name, e.g. GOOGLE, MICROSOFT
|----’All Devices’ static group (when we add new devices, we add them here)
|----Category named group, e.g. ‘Infrastructure’ static group (with no actual devices added here)
|------Function named group, e.g. ‘Active Directory’, ‘DNS Server’, etc. dynamic group with custom query (hasCategory("MicrosoftDomainController"), hasCategory("Windows_DNS") etc.)

So:

LOGICMONITOR
|--GOOGLE
|----All Devices
|------DEVICE1 (pretend this is an AD server)
|------DEVICE2 (pretend this is an AD server)
|----Infrastructure
|------Active Directory
|--------DEVICE1 (correct)
|--------DEVICE2 (correct)
|--------DEVICE3 (incorrect)
|--------DEVICE4 (incorrect)
|------DNS Server
|--MICROSOFT
|----All Devices
|------DEVICE3 (pretend this is an AD server)
|------DEVICE4 (pretend this is an AD server)
|----Infrastructure
|------Active Directory
|--------DEVICE1 (incorrect)
|--------DEVICE2 (incorrect)
|--------DEVICE3 (correct)
|--------DEVICE4 (correct)
|------DNS Server
 

The problem with this, and it is partly a misunderstanding on my part, is that I expected the (nested) dynamic groups to automatically be constrained to their parent container.

How do I achieve this? The only thing I can think of is having the function-named group custom query be something like this:

join(system.groups,",")=~"GOOGLE/_All Devices" && hasCategory("MicrosoftDomainController")

But that means every dynamic group for every customer will need that, which is fine. But, is there a better, more native/default way?

Thanks

  • Anonymous's avatar
    Anonymous
    10 months ago

    So it seems tenant id would only work if each customer resourced is statically placed in a customer nested folder in the first place.

    Yes, the relationship between the device and the customer will have to be defined by you somewhere.

    It does seem a shame (maybe omission) that you can’t map a collector to its collector group and in turn the collector group name, since for us the collector group name is the exact acronym we need.

    You can do this. You just need to make an API call to grab the collector groups. Unintuitively, that wouldn’t be a call to /setting/collector/groups as that doesn’t return the IDs of the collectors in the group. Instead, do a get to /setting/collector/collectors/:id. This will give you back the collector’s group name (among other things, but we can specify that we only want collectorGroupName). 

    So, it would be something like this:

    import com.santaba.agent.util.Settings
    import groovy.json.*
    import javax.crypto.Mac
    import javax.crypto.spec.SecretKeySpec
    import org.apache.commons.codec.binary.Hex
    import org.apache.http.client.methods.*
    import org.apache.http.entity.*
    import org.apache.http.HttpEntity
    import org.apache.http.impl.client.*
    import org.apache.http.util.EntityUtils

    deviceCollectorId = hostProps.get("system.collectorId")
    collector = LM_API("GET","/setting/collector/collectors/${deviceCollectorId}","lmaccess",[:],["fields": "collectorGroupName"])
    if (collector.code == 200){
    println("organization=${collector?.body?.collectorGroupName}")
    }
    return 0

    def LM_API(httpVerb, endpointPath, creds="lmaccess", data = [:], queryParams=[:]){
    def api_id = hostProps.get("${creds}.id")
    def api_key = hostProps.get("${creds}.key")
    def api_company = hostProps.get("${creds}.company") ?: Settings.getSetting(Settings.AGENT_COMPANY)
    def queryParamsString = queryParams.collect{k,v->"${k}=${v}"}.join("&")
    def url = "https://${api_company}.logicmonitor.com/santaba/rest${endpointPath}?${queryParamsString}"
    epoch_time = System.currentTimeMillis()
    def json_data = JsonOutput.toJson(data)
    if ((httpVerb == "GET") || (httpVerb == "DELETE")){requestVars = httpVerb + epoch_time + endpointPath}
    else {requestVars = httpVerb + epoch_time + json_data + endpointPath}
    hmac = Mac.getInstance("HmacSHA256")
    secret = new SecretKeySpec(api_key.getBytes(), "HmacSHA256")
    hmac.init(secret)
    hmac_signed = Hex.encodeHexString(hmac.doFinal(requestVars.getBytes()))
    signature = hmac_signed.bytes.encodeBase64()
    CloseableHttpClient httpclient = HttpClients.createDefault()
    if (httpVerb == "GET"){http_request = new HttpGet(url)}
    else if (httpVerb == "PATCH"){
    http_request = new HttpPatch(url)
    http_request.setEntity(new StringEntity(json_data, ContentType.APPLICATION_JSON))}
    else if (httpVerb == "PUT"){
    http_request = new HttpPut(url)
    http_request.setEntity(new StringEntity(json_data, ContentType.APPLICATION_JSON))}
    else if (httpVerb == "POST"){
    http_request = new HttpPost(url)
    http_request.setEntity(new StringEntity(json_data, ContentType.APPLICATION_JSON))}
    else {http_request = null}
    if (http_request){
    http_request.setHeader("Authorization" , "LMv1 " + api_id + ":" + signature + ":" + epoch_time)
    http_request.setHeader("Accept", "application/json")
    http_request.setHeader("Content-type", "application/json")
    http_request.setHeader("X-Version", "3")
    response = httpclient.execute(http_request)
    body = EntityUtils.toString(response.getEntity())
    code = response.getStatusLine().getStatusCode()
    return ["body":new JsonSlurper().parseText(body), "code":code]}
    else {return ["body":"", "code": -1]}
    }

    You’d need to set properties at the root for lmaccess.id, lmaccess.key and optionally, lmaccess.company. If you wanted to generate an API credential set specifically to this task (and you should), just change the prefix in line 13 from “lmaccess” to whatever you want to prefix the properties with.

    This outputs a property called auto.organization with the value of the collector group name of the collector identified by system.collectorId.