Skip to content

Commit

Permalink
Merge pull request #397 from rundeck-plugins/issue/rpl-66_model-sourc…
Browse files Browse the repository at this point in the history
…e-nullpointerexception

RPL-66: Fix - Ansible plugin - Resource Model Source throws NullPointerException
  • Loading branch information
alexander-variacode authored Oct 24, 2024
2 parents bafa421 + af523bc commit a30cec5
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.rundeck.plugins.ansible.ansible.InventoryList;
import com.rundeck.plugins.ansible.util.VaultPrompt;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.rundeck.app.spi.Services;
import org.rundeck.storage.api.PathUtil;
import org.rundeck.storage.api.StorageException;
Expand All @@ -47,7 +48,6 @@
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
Expand All @@ -56,8 +56,12 @@
import java.util.Properties;
import java.util.Set;

import static com.rundeck.plugins.ansible.ansible.InventoryList.*;
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 @@ -707,36 +711,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 (isTagMapValid(all, ALL)) {
Map<String, Object> children = InventoryList.getValue(all, CHILDREN);

if (isTagMapValid(children, 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);

if (isTagMapValid(hosts, 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);
}
}
}
}
}
}
Expand Down Expand Up @@ -840,4 +851,19 @@ public List<String> listSecretsPathResourceModel(Map<String, Object> configurati

}

/**
* Validates if a tag is empty.
*
* @param tagMap The map containing the tag content.
* @param tagName The name of the tag to validate.
* @return True if the tag is empty, false otherwise.
*/
private boolean isTagMapValid(Map<String, Object> tagMap, String tagName) {
if (tagMap == null) {
log.warn("Tag '{}' is empty!", tagName);
return false;
}
return true;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -154,23 +154,50 @@ class AnsibleResourceModelSourceSpec extends Specification {
thrown(ResourceModelSourceException)
}

private AnsibleInventoryListBuilder mockInventoryList(int qtyNodes) {
void "tag hosts is empty"() {
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 = 2

when: "inventory with null hosts"
AnsibleInventoryListBuilder inventoryListBuilder = mockInventoryList(1, true)
plugin.ansibleInventoryListBuilder = inventoryListBuilder
plugin.getNodes()

then: "no exception thrown due to null tag is handled"
notThrown(Exception)
}

private AnsibleInventoryListBuilder mockInventoryList(int qtyNodes, boolean nullHosts = false) {
return Mock(AnsibleInventoryListBuilder) {
build() >> Mock(AnsibleInventoryList) {
getNodeList() >> createNodes(qtyNodes)
getNodeList() >> createNodes(qtyNodes, nullHosts)
}
}
}

private static String createNodes(int qty) {
private static String createNodes(int qty, boolean nullHosts) {
Yaml yaml = new Yaml()
Map<String, Object> host = [:]
for (int i=1; i <= qty; i++) {
String nodeName = "node-$i"
String hostValue = "any-name-$i"
host.put((nodeName), ['hostname' : (hostValue)])
}
def hosts = ['hosts' : host]
def hosts = ['hosts' : nullHosts ? null : host]
def groups = ['ungrouped' : hosts]
def children = ['children' : groups]
def all = ['all' : children]
Expand Down

0 comments on commit a30cec5

Please sign in to comment.