Enhancements to Couchbase Discovery - Discover secured (HTTPS/TLS) Couchbase instances

BobinV
Mega Guru

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)
  1. 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&colon; 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&colon; 
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&colon; 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&colon; 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&colon; //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&colon; 
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

0 REPLIES 0