Skip to content

Commit

Permalink
Added spock tests
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-variacode committed Oct 23, 2024
1 parent e67d6d0 commit 93a9894
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@
import com.rundeck.plugins.ansible.ansible.AnsibleRunner;
import com.rundeck.plugins.ansible.ansible.InventoryList;
import com.rundeck.plugins.ansible.util.VaultPrompt;
import com.rundeck.plugins.ansible.util.YamlUtil;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;
import lombok.extern.slf4j.Slf4j;
import org.rundeck.app.spi.Services;
import org.rundeck.storage.api.PathUtil;
import org.rundeck.storage.api.StorageException;
Expand Down Expand Up @@ -55,9 +58,14 @@
import java.util.Properties;
import java.util.Set;

import static com.rundeck.plugins.ansible.ansible.InventoryList.*;
import static com.rundeck.plugins.ansible.ansible.AnsibleDescribable.*;
import static com.rundeck.plugins.ansible.ansible.AnsibleDescribable.ANSIBLE_YAML_DATA_SIZE;
import static com.rundeck.plugins.ansible.ansible.AnsibleDescribable.ANSIBLE_YAML_MAX_ALIASES;
import static com.rundeck.plugins.ansible.ansible.InventoryList.ALL;
import static com.rundeck.plugins.ansible.ansible.InventoryList.CHILDREN;
import static com.rundeck.plugins.ansible.ansible.InventoryList.HOSTS;
import static com.rundeck.plugins.ansible.ansible.InventoryList.NodeTag;

@Slf4j
public class AnsibleResourceModelSource implements ResourceModelSource, ProxyRunnerPlugin {

public static final String HOST_TPL_J2 = "host-tpl.j2";
Expand Down Expand Up @@ -706,6 +714,8 @@ public void ansibleInventoryList(NodeSetImpl nodes, AnsibleRunner.AnsibleRunnerB

String listResp = getNodesFromInventory(runnerBuilder);

validateAliases(listResp);

Map<String, Object> allInventory;
try {
allInventory = yaml.load(listResp);
Expand All @@ -714,36 +724,43 @@ public void ansibleInventoryList(NodeSetImpl nodes, AnsibleRunner.AnsibleRunnerB
}

Map<String, Object> all = InventoryList.getValue(allInventory, ALL);
Map<String, Object> children = InventoryList.getValue(all, CHILDREN);

for (Map.Entry<String, Object> pair : children.entrySet()) {
String hostGroup = pair.getKey();
Map<String, Object> hostNames = InventoryList.getType(pair.getValue());
Map<String, Object> hosts = InventoryList.getValue(hostNames, HOSTS);

for (Map.Entry<String, Object> hostNode : hosts.entrySet()) {
NodeEntryImpl node = new NodeEntryImpl();
node.setTags(Set.of(hostGroup));
String hostName = hostNode.getKey();
node.setHostname(hostName);
node.setNodename(hostName);
Map<String, Object> nodeValues = InventoryList.getType(hostNode.getValue());

InventoryList.tagHandle(NodeTag.HOSTNAME, node, nodeValues);
InventoryList.tagHandle(NodeTag.USERNAME, node, nodeValues);
InventoryList.tagHandle(NodeTag.OS_FAMILY, node, nodeValues);
InventoryList.tagHandle(NodeTag.OS_NAME, node, nodeValues);
InventoryList.tagHandle(NodeTag.OS_ARCHITECTURE, node, nodeValues);
InventoryList.tagHandle(NodeTag.OS_VERSION, node, nodeValues);
InventoryList.tagHandle(NodeTag.DESCRIPTION, node, nodeValues);

nodeValues.forEach((key, value) -> {
if (value != null) {
node.setAttribute(key, value.toString());
}
});

nodes.putNode(node);
if (all != null) {
Map<String, Object> children = InventoryList.getValue(all, CHILDREN);

if (children != null) {
for (Map.Entry<String, Object> pair : children.entrySet()) {
String hostGroup = pair.getKey();
Map<String, Object> hostNames = InventoryList.getType(pair.getValue());
Map<String, Object> hosts = InventoryList.getValue(hostNames, HOSTS);

if (hosts != null) {
for (Map.Entry<String, Object> hostNode : hosts.entrySet()) {
NodeEntryImpl node = new NodeEntryImpl();
node.setTags(Set.of(hostGroup));
String hostName = hostNode.getKey();
node.setHostname(hostName);
node.setNodename(hostName);
Map<String, Object> nodeValues = InventoryList.getType(hostNode.getValue());

InventoryList.tagHandle(NodeTag.HOSTNAME, node, nodeValues);
InventoryList.tagHandle(NodeTag.USERNAME, node, nodeValues);
InventoryList.tagHandle(NodeTag.OS_FAMILY, node, nodeValues);
InventoryList.tagHandle(NodeTag.OS_NAME, node, nodeValues);
InventoryList.tagHandle(NodeTag.OS_ARCHITECTURE, node, nodeValues);
InventoryList.tagHandle(NodeTag.OS_VERSION, node, nodeValues);
InventoryList.tagHandle(NodeTag.DESCRIPTION, node, nodeValues);

nodeValues.forEach((key, value) -> {
if (value != null) {
node.setAttribute(key, value.toString());
}
});

nodes.putNode(node);
}
}
}
}
}
}
Expand Down Expand Up @@ -847,4 +864,15 @@ public List<String> listSecretsPathResourceModel(Map<String, Object> configurati

}

/**
* Validates whether the YAML content contains aliases that exceed the maximum allowed.
* @param content String yaml
*/
public void validateAliases(String content) {
Map<String, Integer> checkMap = YamlUtil.checkAliasesAndAnchors(content);
if (!checkMap.isEmpty() && checkMap.get(YamlUtil.ALIASES) > yamlMaxAliases) {
log.warn("The yaml inventory received has {} aliases and the maximum allowed is {}.", checkMap.get(YamlUtil.ALIASES), yamlMaxAliases);
}
}

}
38 changes: 38 additions & 0 deletions src/main/groovy/com/rundeck/plugins/ansible/util/YamlUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.rundeck.plugins.ansible.util;

import org.apache.commons.lang.StringUtils;

import java.util.HashMap;
import java.util.Map;

/**
* A utility class providing helper methods for working with YAML data.
*/
public class YamlUtil {

public static final String ALIASES = "aliases";
public static final String ALIAS_MATCH = "<<:";

/**
* Checks for aliases and anchors within the provided YAML content and returns a map of alias occurrences.
*
* @param content The YAML content string to analyze for aliases and anchors.
* @return A map where keys are alias names and values are their corresponding counts within the content.
*/
public static Map<String, Integer> checkAliasesAndAnchors(String content) {
Map<String, Integer> resp = new HashMap<>();
int total = countAliases(content);
resp.put(ALIASES, total);
return resp;
}

/**
* Counts the number of aliases within the provided YAML content string.
*
* @param content The YAML content string to analyze for aliases.
* @return The total number of aliases found in the content.
*/
private static int countAliases(String content) {
return StringUtils.countMatches(content, ALIAS_MATCH);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,39 @@ class AnsibleResourceModelSourceSpec extends Specification {
thrown(ResourceModelSourceException)
}

private AnsibleInventoryListBuilder mockInventoryList(int qtyNodes) {
void "inventory with null host"() {
given:
Framework framework = Mock(Framework) {
getBaseDir() >> Mock(File) {
getAbsolutePath() >> '/tmp'
}
}
ResourceModelSource plugin = new AnsibleResourceModelSource(framework)
Properties config = new Properties()
config.put('project', 'project_1')
config.put(AnsibleDescribable.ANSIBLE_GATHER_FACTS, 'false')
plugin.configure(config)
Services services = Mock(Services) {
getService(KeyStorageTree.class) >> Mock(KeyStorageTree)
}
plugin.setServices(services)
plugin.yamlDataSize = 10
plugin.yamlMaxAliases = 5

when: "nodes using aliases"
AnsibleInventoryListBuilder inventoryListBuilder = mockInventoryList(0, true)
plugin.ansibleInventoryListBuilder = inventoryListBuilder
plugin.getNodes()

then: "not exception because all, children and host tags are null"
notThrown(Exception)
}

private AnsibleInventoryListBuilder mockInventoryList(int qtyNodes, boolean aliases = false) {
String nodes = aliases ? yamlInventoryWithAliases(qtyNodes) : createNodes(qtyNodes)
return Mock(AnsibleInventoryListBuilder) {
build() >> Mock(AnsibleInventoryList) {
getNodeList() >> createNodes(qtyNodes)
getNodeList() >> nodes
}
}
}
Expand All @@ -177,4 +206,47 @@ class AnsibleResourceModelSourceSpec extends Specification {
return yaml.dump(all)
}

private static String yamlInventory(int qty) {
String inventory = """all:
children:
ungrouped:
hosts:
"""
for (int i=0; i<qty; i++) {
inventory += """ web${(i + 1)}.server.com:
common_var1: value1
common_var2: value2
web_specific_var: "web${(i + 1)}_value
"""
}
return inventory
}

private static String yamlInventoryWithAliases(int qty) {
String inventory = """all:
hosts:
web1: &web1
ansible_host: 192.168.1.10
ansible_user: user1
web2: &web2
ansible_host: 192.168.1.11
ansible_user: user1
db1: &db1
ansible_host: 192.168.1.20
ansible_user: user2
db2: &db2
ansible_host: 192.168.1.21
ansible_user: user2
children:
webservers:
hosts:
web1: *web1
web2: *web2
dbservers:
hosts:
db1: *db1
db2: *db2
"""
return inventory
}
}

0 comments on commit 93a9894

Please sign in to comment.