Enhancements to Couchbase Discovery - Discover secured (HTTPS/TLS) Couchbase instances
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
2 hours ago
Technical Article: Enhancing ServiceNow Couchbase Discovery (SSL, Naming, & XDCR)
Overview:
This article outlines a reusable customization for the Out-of-the-Box (OOTB) ServiceNow Discovery pattern for Couchbase.
Organizations running Couchbase in production often face visibility gaps because the default pattern supports only standard HTTP connections on port 8091. This enhanced pattern introduces dynamic protocol detection to support HTTPS/TLS connections on port 18091. Additionally, it implements strict naming standards to prevent CI duplication ("thrashing") in the CMDB and adds logic to track cross-datacentre replication (XDCR).
OOTB vs. Enhanced Logic
The following table summarizes the functional improvements provided by this script.
Feature | OOTB Behaviour | Enhanced Behaviour | Benefit |
SSL/TLS Support | Hardcoded to HTTP (Port 8091). Fails to discover secured clusters. | Dynamic Fallback: Probes HTTP (8091) first; if unreachable, switches to HTTPS (Port 18091). | Enables discovery of secure production clusters. |
Instance Naming | Generic name: babysitter_of_ns_1. Causes duplicates if multiple servers are discovered. | Unique name: babysitter_of_ns_1@ServerName. | Prevents duplicate CIs and ensures accurate identification. |
Node Naming | Often captured as alias:8091 regardless of actual port. | Cleaned name: IP:ActualPort (e.g., 10.x.x.x:18091). | Accurately represents the running endpoint. |
Bucket Naming | Generic names like default_users. Hard to distinguish between clusters. | Context-aware: bucket@ClusterName. | Clear association between storage and clusters. |
XDCR Replication | Not tracked. | XDCR Flag: Sets u_xdcr_enabled to true if replication is found. | Visibility into data redundancy topology. |
Cluster Version | Not captured at Cluster level. | Auto-Sync: Version rolls up from Node to Cluster via Business Rule. | Centralized visibility into patch levels. |
Implementation Guide
Prerequisites
Before applying the pattern, ensure the following custom field exists in your CMDB to support the XDCR logic:
- Table: cmdb_ci_couchbase_cluster
- Column: u_xdcr_enabled (remember to create this new field in the table)
- Type: Boolean (True/False)
- The Enhanced Pattern Script
- Description: Replaces the standard pattern text. Implements the 18091 port check and naming sanitization.
-------------------------------------------------------------------------
pattern {
metadata {
id = "f0f8c34d1bc84554ad19c995624bcb65"
name = "Couchbase Instance"
description = "Enhanced by Bobz to support HTTPS/SSL (Port 18091), Unique Naming, and XDCR Detection."
citype = "cmdb_ci_couchbase_instance"
apply_to_os_families = "cmdb_ci_linux_server"
}
identification {
name = "Couchbase Instance ID"
entry_point {type = "cmdb_ci_endpoint_tcp,cmdb_ci_endpoint_couchbase"}
find_process_strategy {strategy = LISTENING_PORT}
step {
name = "Verify Couchbase"
disabled = "true"
match {
contains {
get_attr {"process.executable"}
"beam.smp"
}
terminate_op = terminate
terminate_msg = ""
}
}
step {
name = "set install dir"
parse_var_to_var {
from_var_name = "process.commandLine"
to_var_names = "install_directory"
parsing_strategy = regex_parsing {regex = "-home ([^-]*)"}
if_not_found_do = nop {}
}
}
step {
name = "set version"
parse_text_file_to_var {
file_path = get_files_by_filter {
expression = concat {
get_attr {"install_directory"}
"/VERSION.txt"
}
foreach_attribute_name = "forEach"
}
var_names = "version"
parsing_strategy = delimited_parsing {
selected_positions = 1
}
if_not_found_do = nop {}
cache_flag = 0
}
}
step {
name = "set config file"
parse_var_to_var {
from_var_name = "process.commandLine"
to_var_names = "config_file"
parsing_strategy = regex_parsing {regex = "-couch_ini ([^-]*)"}
if_not_found_do = nop {}
}
}
step {
name = "set name"
parse_var_to_var {
from_var_name = "process.commandLine"
to_var_names = "name"
parsing_strategy = regex_parsing {regex = "-name ([^-|@]*)"}
if_not_found_do = nop {}
}
}
step {
name = "Get the systemctl status command response"
runcmd_to_var {
cmd = "systemctl status couchbase-server"
execution_mode = "DEFAULT"
var_names = "systemctl_status"
parsing_strategy = regex_parsing {regex = "(.*)"}
if_not_found_do = nop {}
cache_flag = 0
}
}
step {
name = "Try to get the port from the systemctl status command "
parse_var_to_var {
from_var_name = "systemctl_status"
to_var_names = table {
name = "datastore_url"
col_names = "host_url","tcp_port"
}
parsing_strategy = regex_parsing {regex = "--datastore=([^ ]*):([0-9]+)"}
if_not_found_do = nop {}
}
}
step {
name = "Set the tcp_port"
if {
condition = is_not_empty {get_attr {"datastore_url"}}
on_true = set_attr {
"tcp_port"
get_attr {"datastore_url[1].tcp_port"}
}
on_false = nop {}
}
}
step {
name = "Get the default configuration file if no port"
if {
condition = all {
is_not_empty {get_attr {"config_file"}}
is_empty {get_attr {"tcp_port"}}
}
on_true = set_attr {
"default_configuration_file"
eval {"javascript: var rtrn = '';
var config_files = ${config_file}.split(\" \");
for (var i=0; i<config_files.length; i++) {
if (config_files[i].indexOf(\"default.ini\") > 0) {
rtrn = config_files[i];
break;
}
}
rtrn;"}
}
on_false = nop {}
}
}
step {
name = "Check for Command Line connection port"
if {
condition = is_not_empty {get_attr {"default_configuration_file"}}
on_true = parse_text_file_to_var {
file_path = get_files_by_filter {
expression = get_attr {"default_configuration_file"}
foreach_attribute_name = "forEach"
}
var_names = "tcp_port"
parsing_strategy = delimited_parsing {
include_lines_pattern = "interface"
delimiters = ":"
selected_positions = 2
}
if_not_found_do = nop {}
cache_flag = 0
}
on_false = nop {}
}
}
step {
name = "Specify default tcp_port if empty"
if {
condition = is_empty {get_attr {"tcp_port"}}
on_true = set_attr {
"tcp_port"
"8091"
}
on_false = nop {}
}
}
// -----------------------------------------------------------------
// START OF NEW HTTPS LOGIC (Updated by Bobz)
// -----------------------------------------------------------------
step {
name = "Initialize Protocol"
set_attr {
"url_protocol"
"http"
}
}
step {
name = "Test HTTP Connection"
runcmd_to_var {
cmd = concat {
get_attr {"install_directory"}
"/bin/curl -u $$username$$:'$$password$$' --connect-timeout 3 http://"
get_attr {"computer_system.managementIP"}
":8091/pools/"
}
execution_mode = "DEFAULT"
var_names = "http_check_result"
parsing_strategy = regex_parsing {regex = "(\"isEnterprise\")"}
if_not_found_do = nop {}
cache_flag = 0
}
}
step {
name = "Test HTTPS Connection"
// Logic: If HTTP failed, try the TLS port 18091
if {
condition = is_empty {get_attr {"http_check_result"}}
on_true = runcmd_to_var {
cmd = concat {
get_attr {"install_directory"}
"/bin/curl -k -u $$username$$:'$$password$$' --connect-timeout 3 https://"
get_attr {"computer_system.managementIP"}
":18091/pools/"
}
execution_mode = "DEFAULT"
var_names = "https_check_result"
parsing_strategy = regex_parsing {regex = "(\"isEnterprise\")"}
if_not_found_do = nop {}
cache_flag = 0
}
on_false = nop {}
}
}
step {
name = "Set Protocol to HTTPS"
if {
condition = is_not_empty {get_attr {"https_check_result"}}
on_true = set_attr {
"url_protocol"
"https"
}
on_false = nop {}
}
}
step {
name = "Set Port to 18091"
if {
condition = is_not_empty {get_attr {"https_check_result"}}
on_true = set_attr {
"tcp_port"
"18091"
}
on_false = nop {}
}
}
// -----------------------------------------------------------------
// END OF NEW HTTPS LOGIC
// -----------------------------------------------------------------
step {
name = "Parse Active From from the systemctl status command "
parse_var_to_var {
from_var_name = "systemctl_status"
to_var_names = table {
name = "active_from"
col_names = "active_from"
}
parsing_strategy = delimited_parsing {
include_lines_pattern = "Active:"
delimiters = "Active:"
selected_positions = 1
}
if_not_found_do = nop {}
}
}
step {
name = "Extract the UTC time from the Active From "
parse_var_to_var {
from_var_name = "active_from[1].active_from"
to_var_names = "active_from"
parsing_strategy = regex_parsing {regex = "\\d*-\\d*-\\d* \\d*:\\d*:\\d*"}
if_not_found_do = nop {}
}
}
step {
name = "Check for enterprise edition from the systemctl status command "
comment = "It seems isEnterprise can be present more than once, so modifying the output to table."
parse_var_to_var {
from_var_name = "systemctl_status"
to_var_names = table {
name = "is_enterprise"
col_names = "is_enterprise"
}
parsing_strategy = regex_parsing {regex = "-isEnterprise=([^ ]*)"}
if_not_found_do = nop {}
}
}
step {
name = "Verify Couchbase Instance is found and can be identified via its name"
if {
condition = contains {
get_attr {"computer_system.primaryHostname"}
""
}
on_true = match {
is_not_empty {get_attr {"name"}}
terminate_op = graceful
terminate_msg = concat {
"No Couchbase Instance found for PID "
get_attr {"process.pid"}
" on server "
get_attr {"computer_system.primaryHostname"}
"."
}
}
on_false = nop {}
}
}
// -----------------------------------------------------------------
// Populate CI in HD (Updated by Bobz)
// -----------------------------------------------------------------
step {
name = "Populate CI in HD"
if {
condition = eq {
get_attr {"pattern_runtime_mode"}
"horizontal"
}
on_true = transform {
src_table_name = "cmdb_ci_couchbase_instance"
target_table_name = "cmdb_ci_couchbase_instance"
operation {
// Updated by Bobz: Appends @hostname to create unique Instance Name
set_field {
field_name = "name"
value = concat {
get_attr {"name"}
"@"
get_attr {"computer_system.primaryHostname"}
}
}
set_field {
field_name = "install_directory"
value = get_attr {"install_directory"}
}
set_field {
field_name = "config_file"
value = get_attr {"config_file"}
}
set_field {
field_name = "version"
value = get_attr {"version"}
}
set_field {
field_name = "tcp_port"
value = get_attr {"tcp_port"}
}
set_field {
field_name = "edition"
value = eval {"javascript: var rtrn = \"Basic\"
if (${is_enterprise[1].is_enterprise} == \"true\") {
rtrn = \"Enterprise\";
}
rtrn;"}
}
set_field {
field_name = "start_date"
value = get_attr {"active_from"}
}
}
}
on_false = nop {}
}
}
step {
name = "Populate the managementIP if empty"
if {
condition = is_empty {get_attr {"computer_system.managementIP"}}
on_true = set_attr {
"computer_system.managementIP"
get_attr {"computer_system.primaryHostname"}
}
on_false = nop {}
}
}
// -----------------------------------------------------------------
// OLD LOGIC DISABLED (Commented out by Bobz)
// -----------------------------------------------------------------
step {
name = "Get Clusters"
disabled = "true"
if {
condition = all {
is_not_empty {get_attr {"install_directory"}}
eq {
get_attr {"pattern_runtime_mode"}
"horizontal"
}
}
on_true = runcmd_to_var {
cmd = concat {
get_attr {"install_directory"}
"/bin/curl -u $$username$$:'$$password$$' http://"
get_attr {"computer_system.managementIP"}
":"
get_attr {"tcp_port"}
"/pools/"
}
execution_mode = "DEFAULT"
var_names = "cluster_response"
parsing_strategy = delimited_parsing {
selected_positions = 1
line_seperator = "nonExistingLineSeparator"
}
if_not_found_do = nop {}
cache_flag = 0
}
on_false = nop {}
}
}
// -----------------------------------------------------------------
// NEW LOGIC ENABLED (Updated by Bobz)
// -----------------------------------------------------------------
step {
name = "Get Clusters"
comment = "//Updated by Bobz: Uses dynamic protocol and -k flag"
if {
condition = all {
is_not_empty {get_attr {"install_directory"}}
eq {
get_attr {"pattern_runtime_mode"}
"horizontal"
}
}
on_true = runcmd_to_var {
cmd = concat {
get_attr {"install_directory"}
"/bin/curl -k -u $$username$$:'$$password$$' "
get_attr {"url_protocol"}
"://"
get_attr {"computer_system.managementIP"}
":"
get_attr {"tcp_port"}
"/pools/"
}
execution_mode = "DEFAULT"
var_names = "cluster_response"
parsing_strategy = delimited_parsing {
selected_positions = 1
line_seperator = "nonExistingLineSeparator"
}
if_not_found_do = nop {}
cache_flag = 0
}
on_false = nop {}
}
}
step {
name = "Parse the Clusters names"
if {
condition = is_not_empty {get_attr {"cluster_response"}}
on_true = custom_operation {
attributes {
attribute {
name = "source"
value = "cluster_response"
}
attribute {
name = "source_column"
value = ""
}
attribute {
name = "table_name"
value = "cluster_info"
}
attribute {
name = "target_columns"
value = "cluster_id:pools.name,cluster_uri:pools.uri"
}
}
sys_id_op = "eb6ef2e353a533003e76ddeeff7b124d"
to_var_names = "SystemVariable"
parsing_strategy = empty_strategy {}
if_not_found_do = nop {}
is_paginated = 0
}
on_false = nop {}
}
}
step {
name = "Parse the Clusters Allowed Service"
if {
condition = is_not_empty {get_attr {"cluster_response"}}
on_true = custom_operation {
attributes {
attribute {
name = "source"
value = "cluster_response"
}
attribute {
name = "source_column"
value = ""
}
attribute {
name = "table_name"
value = "cmdb_ci_couchbase_cluster_resource"
}
attribute {
name = "target_columns"
value = "name:allowedServices"
}
}
sys_id_op = "eb6ef2e353a533003e76ddeeff7b124d"
to_var_names = "SystemVariable"
parsing_strategy = empty_strategy {}
if_not_found_do = nop {}
is_paginated = 0
}
on_false = nop {}
}
}
step {
name = "Filter out empty rows"
if {
condition = is_not_empty {get_attr {"cmdb_ci_couchbase_cluster_resource"}}
on_true = filter {
src_table_name = "cmdb_ci_couchbase_cluster_resource"
target_table_name = "cmdb_ci_couchbase_cluster_resource"
condition = is_not_empty {get_attr {"cmdb_ci_couchbase_cluster_resource[].name"}}
}
on_false = nop {}
}
}
step {
name = "Add Status fields to the Allowed Services"
if {
condition = is_not_empty {get_attr {"cmdb_ci_couchbase_cluster_resource"}}
on_true = transform {
src_table_name = "cmdb_ci_couchbase_cluster_resource"
target_table_name = "cmdb_ci_couchbase_cluster_resource"
operation {
set_field {
field_name = "install_status"
value = "1"
}
set_field {
field_name = "operational_status"
value = "1"
}
}
}
on_false = nop {}
}
}
// -----------------------------------------------------------------
// OLD LOGIC DISABLED
// -----------------------------------------------------------------
step {
name = "Get the Cluster Details"
disabled = "true"
if {
condition = any {
eq {
get_attr {"pattern_runtime_mode"}
"horizontal"
}
is_not_empty {get_attr {"install_directory"}}
}
on_true = runcmd_to_var {
cmd = concat {
get_attr {"install_directory"}
"/bin/curl -u $$username$$:'$$password$$' http://"
get_attr {"computer_system.managementIP"}
":"
get_attr {"tcp_port"}
"/pools/"
get_attr {"cluster_info[1].cluster_id"}
}
execution_mode = "DEFAULT"
var_names = "cluster_details_response"
parsing_strategy = delimited_parsing {
selected_positions = 1
line_seperator = "nonExistingLineSeparator"
}
if_not_found_do = nop {}
cache_flag = 0
}
on_false = nop {}
}
}
// -----------------------------------------------------------------
// NEW LOGIC ENABLED
// -----------------------------------------------------------------
step {
name = "Get the Cluster Details"
comment = "//Updated by Bobz: Uses dynamic protocol and -k flag"
if {
condition = any {
eq {
get_attr {"pattern_runtime_mode"}
"horizontal"
}
is_not_empty {get_attr {"install_directory"}}
}
on_true = runcmd_to_var {
cmd = concat {
get_attr {"install_directory"}
"/bin/curl -k -u $$username$$:'$$password$$' "
get_attr {"url_protocol"}
"://"
get_attr {"computer_system.managementIP"}
":"
get_attr {"tcp_port"}
"/pools/"
get_attr {"cluster_info[1].cluster_id"}
}
execution_mode = "DEFAULT"
var_names = "cluster_details_response"
parsing_strategy = delimited_parsing {
selected_positions = 1
line_seperator = "nonExistingLineSeparator"
}
if_not_found_do = nop {}
cache_flag = 0
}
on_false = nop {}
}
}
step {
name = "Modify the response to full JSON format"
if {
condition = is_not_empty {get_attr {"cluster_details_response"}}
on_true = set_attr {
"cluster_details_response"
concat {
"{\"clusters\" : ["
get_attr {"cluster_details_response"}
"]}"
}
}
on_false = nop {}
}
}
step {
name = "Parse the Cluster Details"
if {
condition = is_not_empty {get_attr {"cluster_details_response"}}
on_true = custom_operation {
attributes {
attribute {
name = "source"
value = "cluster_details_response"
}
attribute {
name = "source_column"
value = ""
}
attribute {
name = "table_name"
value = "cluster"
}
attribute {
name = "target_columns"
value = "cluster_id:clusters.name,cluster_name:clusters.clusterName"
}
}
sys_id_op = "eb6ef2e353a533003e76ddeeff7b124d"
to_var_names = "SystemVariable"
parsing_strategy = empty_strategy {}
if_not_found_do = nop {}
is_paginated = 0
}
on_false = nop {}
}
}
step {
name = "Add the URI to the Cluster table"
merge {
table1_name = "cluster"
key1_name = "cluster_id"
table2_name = "cluster_info"
key2_name = "cluster_id"
result_table_name = "cluster"
unmatched_lines = keep
}
}
step {
name = "Populate the Couchbase cluster"
if {
condition = is_not_empty {get_attr {"cluster"}}
on_true = transform {
src_table_name = "cluster"
target_table_name = "cmdb_ci_couchbase_cluster"
operation {
set_field {
field_name = "short_cluster_id"
value = get_attr {"cluster[].cluster_id"}
}
set_field {
field_name = "cluster_id"
value = get_attr {"cluster[].cluster_uri"}
}
set_field {
field_name = "name"
value = get_attr {"cluster[].cluster_name"}
}
set_field {
field_name = "install_status"
value = "1"
}
set_field {
field_name = "cluster_type"
value = "Couchbase"
}
}
}
on_false = nop {}
}
}
step {
name = "Create relation between Couchbase cluster and the Couchbase cluster resources"
if {
condition = is_not_empty {get_attr {"cmdb_ci_couchbase_cluster_resource"}}
on_true = relation_reference {
table1_name = "cmdb_ci_couchbase_cluster_resource"
table2_name = "cmdb_ci_couchbase_cluster"
result_table_name = "cluster_to_cluster_resource"
unmatched_lines = keep
relation_type = "Defines resources for::Gets resources from"
ref_direction = parentToChild
ref_field_name = "cluster"
}
on_false = nop {}
}
}
step {
name = "Parse the Cluster Nodes Details"
if {
condition = is_not_empty {get_attr {"cluster_details_response"}}
on_true = custom_operation {
attributes {
attribute {
name = "source"
value = "cluster_details_response"
}
attribute {
name = "source_column"
value = ""
}
attribute {
name = "table_name"
value = "cluster_node"
}
attribute {
name = "target_columns"
value = "cluster_id:clusters.name,node_uuid:clusters.nodes.nodeUUID,hostname:clusters.nodes.hostname,configuredHostname:clusters.nodes.configuredHostname,nodeEncryption:clusters.nodes.nodeEncryption,node_status:clusters.nodes.status,memoryTotal:clusters.nodes.memoryTotal,memoryFree:clusters.nodes.memoryFree,mcdMemoryReserved:clusters.nodes.mcdMemoryReserved,mcdMemoryAllocated:clusters.nodes.mcdMemoryAllocated,couchApiBase:clusters.nodes.couchApiBase,couchApiBaseHTTPS:clusters.nodes.couchApiBaseHTTPS,clusterMembership:clusters.nodes.clusterMembership,recoveryType:clusters.nodes.recoveryType,otpNode:clusters.nodes.otpNode,clusterCompatibility:clusters.nodes.clusterCompatibility,version:clusters.nodes.version,os:clusters.nodes.os,cpuCount:clusters.nodes.cpuCount"
}
}
sys_id_op = "eb6ef2e353a533003e76ddeeff7b124d"
to_var_names = "SystemVariable"
parsing_strategy = empty_strategy {}
if_not_found_do = nop {}
is_paginated = 0
}
on_false = nop {}
}
}
step {
name = "Filter out empty rows"
if {
condition = is_not_empty {get_attr {"cluster_node"}}
on_true = filter {
src_table_name = "cluster_node"
target_table_name = "cluster_node"
condition = is_not_empty {get_attr {"cluster_node[].node_uuid"}}
}
on_false = nop {}
}
}
// -----------------------------------------------------------------
// Fix Cluster Node IPs (Updated by Bobz)
// -----------------------------------------------------------------
step {
name = "Fix Cluster Node IPs"
comment = "//Updated by Bobz: Strips port from hostname using ctx API to avoid eval errors"
set_attr {
"temp_fix_status"
eval {"javascript:
var table = ctx.getAttribute('cluster_node');
if (table != null) {
for (var i = 0; i < table.size(); i++) {
var row = table.get(i);
var host = row.get('hostname');
if (host != null && host.indexOf(':') > -1) {
// Strip the existing port
row.put('clean_ip', host.split(':')[0]);
} else {
// No port found, just use host
row.put('clean_ip', host);
}
}
}
'done';"}
}
}
// -----------------------------------------------------------------
// Populate Cluster Nodes (Updated by Bobz)
// -----------------------------------------------------------------
step {
name = "Populate Cluster Nodes"
if {
condition = is_not_empty {get_attr {"cluster_node"}}
on_true = transform {
src_table_name = "cluster_node"
target_table_name = "cmdb_ci_couchbase_cluster_node"
operation {
// Updated by Bobz: Uses clean_ip + tcp_port via simple concat
set_field {
field_name = "name"
value = concat {
get_attr {"cluster_node[].clean_ip"}
":"
get_attr {"tcp_port"}
}
}
set_field {
field_name = "install_status"
value = "1"
}
set_field {
field_name = "operational_status"
value = eval {"javascript: var rtrn = '2';
if (${cluster_node[].node_status}==\"healthy\") {rtrn = \"1\";}
rtrn;"}
}
set_field {
field_name = "node_status"
value = get_attr {"cluster_node[].clusterMembership"}
}
set_field {
field_name = "node_encryption"
value = get_attr {"cluster_node[].nodeEncryption"}
}
set_field {
field_name = "configured_host_name"
value = get_attr {"cluster_node[].configuredHostname"}
}
set_field {
field_name = "total_memory"
value = get_attr {"cluster_node[].memoryTotal"}
}
set_field {
field_name = "free_memory"
value = get_attr {"cluster_node[].memoryFree"}
}
set_field {
field_name = "mcd_reserved_memory"
value = get_attr {"cluster_node[].mcdMemoryReserved"}
}
set_field {
field_name = "mcd_allocated_memory"
value = get_attr {"cluster_node[].mcdMemoryAllocated"}
}
set_field {
field_name = "couch_api_base"
value = get_attr {"cluster_node[].couchApiBase"}
}
set_field {
field_name = "couch_api_base_https"
value = get_attr {"cluster_node[].couchApiBaseHTTPS"}
}
set_field {
field_name = "recovery_type"
value = get_attr {"cluster_node[].recoveryType"}
}
set_field {
field_name = "otpnode"
value = get_attr {"cluster_node[].otpNode"}
}
set_field {
field_name = "host_name"
value = get_attr {"cluster_node[].hostname"}
}
set_field {
field_name = "node_uuid"
value = get_attr {"cluster_node[].node_uuid"}
}
set_field {
field_name = "cluster_compatibility"
value = get_attr {"cluster_node[].clusterCompatibility"}
}
set_field {
field_name = "version"
value = get_attr {"cluster_node[].version"}
}
set_field {
field_name = "os"
value = get_attr {"cluster_node[].os"}
}
set_field {
field_name = "cpu_count"
value = get_attr {"cluster_node[].cpuCount"}
}
set_field {
field_name = "edition"
value = get_attr {"cmdb_ci_couchbase_instance[1].edition"}
}
}
}
on_false = nop {}
}
}
step {
name = "Create relation between Couchbase cluster and the Couchbase cluster Nodes"
if {
condition = is_not_empty {get_attr {"cmdb_ci_couchbase_cluster_node"}}
on_true = relation_reference {
table1_name = "cmdb_ci_couchbase_cluster_node"
key1_name = "cluster_id"
table2_name = "cmdb_ci_couchbase_cluster"
key2_name = "short_cluster_id"
result_table_name = "cluster_to_cluster_node"
unmatched_lines = remove
relation_type = "Cluster of::Cluster"
ref_direction = parentToChild
ref_field_name = "cluster"
}
on_false = nop {}
}
}
step {
name = "Set Linux server name"
if {
condition = all {
eq {
get_attr {"computer_system.osType"}
"LINUX"
}
is_not_empty {get_attr {"computer_system.primaryHostname"}}
eq {
get_attr {"pattern_runtime_mode"}
"horizontal"
}
}
on_true = parse_var_to_var {
from_var_name = "computer_system.primaryHostname"
to_var_names = table {
name = "cmdb_ci_linux_server"
col_names = "name"
}
parsing_strategy = delimited_parsing {
selected_positions = 1
}
if_not_found_do = nop {}
}
on_false = nop {}
}
}
step {
name = "Add the SN to the Linux CI for easier identification"
if {
condition = is_not_empty {get_attr {"cmdb_ci_linux_server"}}
on_true = transform {
src_table_name = "cmdb_ci_linux_server"
target_table_name = "cmdb_ci_linux_server"
operation {set_field {
field_name = "serial_number"
value = get_attr {"computer_system.hostSerialNumber"}
}}
}
on_false = nop {}
}
}
step {
name = "Create relation between Couchbase Cluster Node and Linux Server"
if {
condition = all {
eq {
get_attr {"computer_system.osType"}
"LINUX"
}
is_not_empty {get_attr {"cmdb_ci_couchbase_cluster_node"}}
}
on_true = relation_reference {
table1_name = "cmdb_ci_couchbase_cluster_node"
table2_name = "cmdb_ci_linux_server"
result_table_name = "node_to_server"
unmatched_lines = remove
condition = any {
contains {
get_attr {"cmdb_ci_couchbase_cluster_node[].hostname"}
get_attr {"computer_system.primaryHostname"}
}
contains {
concat {
get_attr {"cmdb_ci_couchbase_cluster_node[].hostname"}
""
}
concat {
get_attr {"computer_system.managementIP"}
":"
}
}
}
relation_type = "Hosted on::Hosts"
ref_direction = parentToChild
ref_field_name = "server"
}
on_false = nop {}
}
}
step {
name = "Create relation between Couchbase Instance and Couchbase Cluster Node"
if {
condition = is_not_empty {get_attr {"cmdb_ci_couchbase_cluster_node"}}
on_true = relation_reference {
table1_name = "cmdb_ci_couchbase_instance"
table2_name = "cmdb_ci_couchbase_cluster_node"
result_table_name = "instance_to_node"
unmatched_lines = keep
relation_type = "Hosted on::Hosts"
ref_direction = parentToChild
ref_field_name = ""
}
on_false = nop {}
}
}
// -----------------------------------------------------------------
// OLD LOGIC DISABLED
// -----------------------------------------------------------------
step {
name = "Get the Cluster Buckets"
disabled = "true"
if {
condition = eq {
get_attr {"pattern_runtime_mode"}
"horizontal"
}
on_true = runcmd_to_var {
cmd = concat {
get_attr {"install_directory"}
"/bin/curl -u $$username$$:'$$password$$' http://"
get_attr {"computer_system.managementIP"}
":"
get_attr {"tcp_port"}
"/pools/"
get_attr {"cluster[1].cluster_id"}
"/buckets"
}
execution_mode = "DEFAULT"
var_names = "cluster_buckets_response"
parsing_strategy = regex_parsing {regex = "(.*)"}
if_not_found_do = nop {}
cache_flag = 0
}
on_false = nop {}
}
}
// -----------------------------------------------------------------
// NEW LOGIC ENABLED
// -----------------------------------------------------------------
step {
name = "Get the Cluster Buckets"
comment = "//Updated by Bobz: Uses dynamic protocol and -k flag"
if {
condition = eq {
get_attr {"pattern_runtime_mode"}
"horizontal"
}
on_true = runcmd_to_var {
cmd = concat {
get_attr {"install_directory"}
"/bin/curl -k -u $$username$$:'$$password$$' "
get_attr {"url_protocol"}
"://"
get_attr {"computer_system.managementIP"}
":"
get_attr {"tcp_port"}
"/pools/"
get_attr {"cluster[1].cluster_id"}
"/buckets"
}
execution_mode = "DEFAULT"
var_names = "cluster_buckets_response"
parsing_strategy = regex_parsing {regex = "(.*)"}
if_not_found_do = nop {}
cache_flag = 0
}
on_false = nop {}
}
}
step {
name = "Parse the Cluster Buckets"
if {
condition = is_not_empty {get_attr {"cluster_buckets_response"}}
on_true = custom_operation {
attributes {
attribute {
name = "source"
value = "cluster_buckets_response"
}
attribute {
name = "source_column"
value = ""
}
attribute {
name = "table_name"
value = "cluster_buckets"
}
attribute {
name = "target_columns"
value = "bucket_name:*.name,bucket_id:*.uuid,uri:*.uri,bucket_type:*.bucketType,replica_nmber:*.replicaNumber,compression_mode:*.compressionMode"
}
}
sys_id_op = "eb6ef2e353a533003e76ddeeff7b124d"
to_var_names = "SystemVariable"
parsing_strategy = empty_strategy {}
if_not_found_do = nop {}
is_paginated = 0
}
on_false = nop {}
}
}
step {
name = "Filter out empty rows"
if {
condition = is_not_empty {get_attr {"cluster_buckets"}}
on_true = filter {
src_table_name = "cluster_buckets"
target_table_name = "cluster_buckets"
condition = is_not_empty {get_attr {"cluster_buckets[].bucket_name"}}
}
on_false = nop {}
}
}
step {
name = "Parse the cluster_id out of the Bucket URI"
if {
condition = is_not_empty {get_attr {"cluster_buckets"}}
on_true = transform {
src_table_name = "cluster_buckets"
target_table_name = "cluster_buckets"
operation {set_field {
field_name = "cluster_id"
value = eval {"javascript: var uri = ${cluster_buckets[].uri};
var rtrn = '';
if (uri){
rtrn = uri.split(\"/\")[2];
}
rtrn;"}
}}
}
on_false = nop {}
}
}
// -----------------------------------------------------------------
// Populate the Couchbase Buckets (Updated by Bobz)
// -----------------------------------------------------------------
step {
name = "Populate the Couchbase Buckets"
if {
condition = is_not_empty {get_attr {"cluster_buckets"}}
on_true = transform {
src_table_name = "cluster_buckets"
target_table_name = "cmdb_ci_couchbase_bucket"
operation {
// Updated by Bobz: Uses bucket@ClusterName format
set_field {
field_name = "name"
value = concat {
get_attr {"cluster_buckets[].bucket_name"}
"@"
get_attr {"cmdb_ci_couchbase_cluster[1].name"}
}
}
set_field {
field_name = "install_status"
value = "1"
}
}
}
on_false = nop {}
}
}
step {
name = "Create relation between Couchbase Bucket and Couchbase Instance"
if {
condition = is_not_empty {get_attr {"cmdb_ci_couchbase_bucket"}}
on_true = relation_reference {
table1_name = "cmdb_ci_couchbase_bucket"
table2_name = "cmdb_ci_couchbase_instance"
result_table_name = "bucket_to_instance"
unmatched_lines = keep
relation_type = "Uses::Used by"
ref_direction = parentToChild
ref_field_name = "database_instance"
}
on_false = nop {}
}
}
step {
name = "Create relation between Couchbase Bucket and Couchbase Cluster"
if {
condition = is_not_empty {get_attr {"cmdb_ci_couchbase_bucket"}}
on_true = relation_reference {
table1_name = "cmdb_ci_couchbase_bucket"
table2_name = "cmdb_ci_couchbase_cluster"
result_table_name = "bucket_to_cluster"
unmatched_lines = keep
relation_type = "Hosted on::Hosts"
ref_direction = parentToChild
ref_field_name = "cluster"
}
on_false = nop {}
}
}
// -----------------------------------------------------------------
// OLD LOGIC DISABLED
// -----------------------------------------------------------------
step {
name = "Get the Remote Clusters"
disabled = "true"
if {
condition = eq {
get_attr {"pattern_runtime_mode"}
"horizontal"
}
on_true = runcmd_to_var {
cmd = concat {
get_attr {"install_directory"}
"/bin/curl -u $$username$$:'$$password$$' http://"
get_attr {"computer_system.managementIP"}
":"
get_attr {"tcp_port"}
"/pools/"
get_attr {"cluster[1].cluster_id"}
"/remoteClusters"
}
execution_mode = "DEFAULT"
var_names = "remote_clusters_response"
parsing_strategy = regex_parsing {regex = "(.*)"}
if_not_found_do = nop {}
cache_flag = 0
}
on_false = nop {}
}
}
// -----------------------------------------------------------------
// NEW LOGIC ENABLED
// -----------------------------------------------------------------
step {
name = "Get the Remote Clusters"
comment = "//Updated by Bobz: Uses dynamic protocol and -k flag"
if {
condition = eq {
get_attr {"pattern_runtime_mode"}
"horizontal"
}
on_true = runcmd_to_var {
cmd = concat {
get_attr {"install_directory"}
"/bin/curl -k -u $$username$$:'$$password$$' "
get_attr {"url_protocol"}
"://"
get_attr {"computer_system.managementIP"}
":"
get_attr {"tcp_port"}
"/pools/"
get_attr {"cluster[1].cluster_id"}
"/remoteClusters"
}
execution_mode = "DEFAULT"
var_names = "remote_clusters_response"
parsing_strategy = regex_parsing {regex = "(.*)"}
if_not_found_do = nop {}
cache_flag = 0
}
on_false = nop {}
}
}
step {
name = "RemoteCluster_Response_IS_JSON"
if {
condition = is_not_empty {get_attr {"remote_clusters_response"}}
on_true = set_attr {
"remoteclusters_response_is_json"
eval {"javascript: //This was created to verify that the response is a valid JSON response.
//Next steps parse it as JSON.
try{
var rtn = \"\";
var data = ${remote_clusters_response};
JSON.parse(data);
rtn = 'true';
}
catch(e){
rtn = 'false';
}"}
}
on_false = nop {}
}
}
step {
name = "Modify the response to full JSON format"
if {
condition = all {
is_not_empty {get_attr {"remote_clusters_response"}}
contains {
get_attr {"remoteclusters_response_is_json"}
"true"
}
}
on_true = set_attr {
"remote_clusters_response"
concat {
"{\"remote_clusters\" : ["
get_attr {"remote_clusters_response"}
"]}"
}
}
on_false = nop {}
}
}
step {
name = "Parse the Remote Clusters"
comment = "plamen - example response:
{\"name\":\"remote1\",\"uri\":\"/pools/default/remoteClusters/remote1\",
\"validateURI\":\"/pools/default/remoteClusters/remote1?just_validate=1\",
\"hostname\":\"10.4.2.6:8091\",
\"username\":\"Administrator\",
\"uuid\":\"9eee38236f3bf28406920213d93981a3\",
\"deleted\":false}"
if {
condition = all {
is_not_empty {get_attr {"remote_clusters_response"}}
contains {
get_attr {"remoteclusters_response_is_json"}
"true"
}
}
on_true = custom_operation {
attributes {
attribute {
name = "source"
value = "remote_clusters_response"
}
attribute {
name = "source_column"
value = ""
}
attribute {
name = "table_name"
value = "remote_clusters"
}
attribute {
name = "target_columns"
value = "name:remote_clusters.*.name,cluster_id:remote_clusters.*.uri"
}
}
sys_id_op = "eb6ef2e353a533003e76ddeeff7b124d"
to_var_names = "SystemVariable"
parsing_strategy = empty_strategy {}
if_not_found_do = nop {}
is_paginated = 0
}
on_false = nop {}
}
}
step {
name = "Filter out empty rows"
if {
condition = all {
is_not_empty {get_attr {"remote_clusters"}}
contains {
get_attr {"remoteclusters_response_is_json"}
"true"
}
}
on_true = filter {
src_table_name = "remote_clusters"
target_table_name = "remote_clusters"
condition = is_not_empty {get_attr {"remote_clusters[].cluster_id"}}
}
on_false = nop {}
}
}
// -----------------------------------------------------------------
// Set XDCR Enabled Flag (Updated by Bobz)
// -----------------------------------------------------------------
step {
name = "Set XDCR Enabled Flag"
comment = "//Updated by Bobz: Sets custom field u_xdcr_enabled based on presence of remote clusters"
if {
condition = is_not_empty {get_attr {"remote_clusters"}}
on_true = transform {
src_table_name = "remote_clusters"
target_table_name = "cmdb_ci_couchbase_cluster"
operation {
set_field {
field_name = "u_xdcr_enabled"
value = "true"
}
}
}
on_false = transform {
src_table_name = "cmdb_ci_couchbase_cluster"
target_table_name = "cmdb_ci_couchbase_cluster"
operation {
set_field {
field_name = "u_xdcr_enabled"
value = "false"
}
}
}
}
}
step {
name = "Add attributes to the Remote Clusters"
if {
condition = all {
is_not_empty {get_attr {"remote_clusters"}}
contains {
get_attr {"remoteclusters_response_is_json"}
"true"
}
}
on_true = transform {
src_table_name = "remote_clusters"
target_table_name = "remote_clusters"
operation {
set_field {
field_name = "cluster_type"
value = "Couchbase-Replica"
}
set_field {
field_name = "install_status"
value = "1"
}
}
}
on_false = nop {}
}
}
step {
name = "Add the Remote Clusters"
if {
condition = all {
is_not_empty {get_attr {"remote_clusters"}}
contains {
get_attr {"remoteclusters_response_is_json"}
"true"
}
}
on_true = union {
table1_name = "cmdb_ci_couchbase_cluster"
table2_name = "remote_clusters"
result_table_name = "cmdb_ci_couchbase_cluster"
}
on_false = nop {}
}
}
step {
name = "Add replication relation between the Cluster and its Remote Clusters"
if {
condition = all {
is_not_empty {get_attr {"remote_clusters"}}
contains {
get_attr {"remoteclusters_response_is_json"}
"true"
}
}
on_true = set_attr {
"tmp"
eval {"javascript:
var clusters = ${cmdb_ci_couchbase_cluster[*].name}.toArray();
var relation_table = new Packages.java.util.ArrayList();
var length = clusters.length;
var relationType = \"Replicates to::Replicated by\";
// The discoverable Cluster is always the first row in cmdb_ci_couchbase_cluster table
for (var i=1; i<length; i++){
var row = new Packages.java.util.HashMap();
//Add relation rows
row.put(\"relationType\", relationType);
row.put(\"parentTableName\", \"cmdb_ci_couchbase_cluster\");
row.put(\"parentTableIndex\", \"0\");
row.put(\"childTableIndex\", i.toString());
row.put(\"childTableName\", \"cmdb_ci_couchbase_cluster\");
relation_table.add(row);
}
ctx.setAttribute(\"cluster_to_remote_cluster\", relation_table);
ctx.getWork().addRelationtTable(\"cluster_to_remote_cluster\");"}
}
on_false = nop {}
}
}
}
}2. The Helper Business Rule
This business rule ensures the Cluster Version field is populated by "rolling up" the version detected on the child nodes.
Name: Sync Couchbase Version to Cluster
Table: cmdb_ci_couchbase_cluster_node
When: After (Insert, Update)
Conditions: Version is not empty AND Cluster is not empty.
-----------------------------------------------------------------
(function executeRule(current, previous /*null when async*/) {
// 1. Instantiate the Cluster record using the reference from the Node
var clusterGr = new GlideRecord('cmdb_ci_couchbase_cluster');
// 2. Check if the parent cluster exists
if (clusterGr.get(current.cluster)) {
// 3. Compare current Node version with Parent Cluster version
// We only update if they are different to avoid unnecessary DB writes
if (clusterGr.cluster_version != current.version) {
clusterGr.cluster_version = current.version;
clusterGr.setWorkflow(false); // Prevents looping triggers
clusterGr.update();
gs.info("Couchbase Discovery: Updated Cluster " + clusterGr.name + " version to " + current.version);
}
}
})(current, previous);------------------------------------------------------------------
This enhancement package was developed based on specific organizational requirements and my personal understanding of the Couchbase topology and ServiceNow Discovery architecture. It addresses unique challenges encountered in our environment regarding HTTPS/TLS visibility and naming conventions.
I am sharing this in the hope that it helps the wider community. If you find this script useful for your own implementation or use it as a base for further customizations, a mention would be appreciated!
— Bobz
