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
- Anonymous10 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.