From 1c78865e84accfeea23b7aa2a9f2e90a29675368 Mon Sep 17 00:00:00 2001 From: Alexander Abarca Date: Thu, 18 Jul 2024 11:55:40 -0400 Subject: [PATCH] Added tests to validate yaml data size parameter --- .../plugin/AnsibleResourceModelSource.java | 47 ++++----- .../AnsibleResourceModelSourceSpec.groovy | 95 +++++++++++++++++-- 2 files changed, 105 insertions(+), 37 deletions(-) diff --git a/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java b/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java index 39349bf..0df5b78 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java @@ -24,14 +24,15 @@ import com.rundeck.plugins.ansible.ansible.AnsibleInventoryList; import com.rundeck.plugins.ansible.ansible.AnsibleRunner; import com.rundeck.plugins.ansible.ansible.InventoryList; -import com.rundeck.plugins.ansible.ansible.PropertyResolver; import com.rundeck.plugins.ansible.util.VaultPrompt; +import lombok.Setter; import org.rundeck.app.spi.Services; import org.rundeck.storage.api.PathUtil; import org.rundeck.storage.api.StorageException; import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.SafeConstructor; +import org.yaml.snakeyaml.error.YAMLException; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; @@ -46,6 +47,7 @@ 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; @@ -61,8 +63,10 @@ public class AnsibleResourceModelSource implements ResourceModelSource, ProxyRun public static final String HOST_TPL_J2 = "host-tpl.j2"; public static final String GATHER_HOSTS_YML = "gather-hosts.yml"; + @Setter private Framework framework; + @Setter private Services services; private String project; @@ -73,6 +77,7 @@ public class AnsibleResourceModelSource implements ResourceModelSource, ProxyRun private String inventory; private boolean gatherFacts; + @Setter private Integer yamlDataSize; private boolean ignoreErrors = false; private String limit; @@ -120,17 +125,14 @@ public class AnsibleResourceModelSource implements ResourceModelSource, ProxyRun protected boolean encryptExtraVars = false; + @Setter private AnsibleInventoryList.AnsibleInventoryListBuilder ansibleInventoryListBuilder = null; public AnsibleResourceModelSource(final Framework framework) { this.framework = framework; } - public void setAnsibleInventoryListBuilder(AnsibleInventoryList.AnsibleInventoryListBuilder builder) { - this.ansibleInventoryListBuilder = builder; - } - - private static String resolveProperty( + private static String resolveProperty( final String attribute, final String defaultValue, final Properties configuration, @@ -169,11 +171,7 @@ private static Boolean skipVar(final String hostVar, final List varList) return false; } - public void setServices(Services services) { - this.services = services; - } - - public void configure(Properties configuration) throws ConfigurationException { + public void configure(Properties configuration) throws ConfigurationException { project = configuration.getProperty("project"); configDataContext = new HashMap>(); @@ -701,7 +699,13 @@ public void ansibleInventoryList(NodeSetImpl nodes, AnsibleRunner.AnsibleRunnerB String listResp = getNodesFromInventory(runnerBuilder); - Map allInventory = yaml.load(listResp); + Map allInventory; + try { + allInventory = yaml.load(listResp); + } catch (YAMLException e) { + throw new ResourceModelSourceException("Cannot load yaml data coming from Ansible: " + e.getMessage(), e); + } + Map all = InventoryList.getValue(allInventory, ALL); Map children = InventoryList.getValue(all, CHILDREN); @@ -836,23 +840,4 @@ public List listSecretsPathResourceModel(Map configurati } - /** - * - * @param value parameter from gui - * @param paramName parameter name - * @return an integer value - * @throws ConfigurationException - */ - private Integer getIntegerValue(String value, String paramName) throws ConfigurationException { - if (value == null) { - throw new ConfigurationException("Value cannot be null for parameter: " + paramName); - } - - try { - return Integer.parseInt(value); - } catch (NumberFormatException e) { - throw new ConfigurationException("Error parsing " + paramName + " as integer: " + e.getMessage(), e); - } - } - } diff --git a/src/test/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSourceSpec.groovy b/src/test/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSourceSpec.groovy index 540ea6f..ed5637f 100644 --- a/src/test/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSourceSpec.groovy +++ b/src/test/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSourceSpec.groovy @@ -1,20 +1,20 @@ package com.rundeck.plugins.ansible.plugin -import com.dtolabs.rundeck.core.storage.StorageTree -import com.dtolabs.rundeck.core.storage.keys.KeyStorageTree -import org.rundeck.app.spi.Services - -import static com.rundeck.plugins.ansible.ansible.AnsibleInventoryList.AnsibleInventoryListBuilder - import com.dtolabs.rundeck.core.common.Framework import com.dtolabs.rundeck.core.common.INodeEntry import com.dtolabs.rundeck.core.common.INodeSet import com.dtolabs.rundeck.core.resources.ResourceModelSource +import com.dtolabs.rundeck.core.resources.ResourceModelSourceException +import com.dtolabs.rundeck.core.storage.keys.KeyStorageTree import com.rundeck.plugins.ansible.ansible.AnsibleDescribable import com.rundeck.plugins.ansible.ansible.AnsibleInventoryList +import org.rundeck.app.spi.Services import org.yaml.snakeyaml.Yaml +import org.yaml.snakeyaml.error.YAMLException import spock.lang.Specification +import static com.rundeck.plugins.ansible.ansible.AnsibleInventoryList.AnsibleInventoryListBuilder + /** * AnsibleResourceModelSource test */ @@ -94,4 +94,87 @@ class AnsibleResourceModelSourceSpec extends Specification { 'NODE_3' | 'ansible_os_family' | 'ansible_os_name' | 'ansible_kernel' | 'ansible_architecture' | 'ansible_user_id' | 'ansible_distribution' } + void "ansible yaml data size parameter without an Exception"() { + 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: "small inventory" + AnsibleInventoryListBuilder inventoryListBuilder = mockInventoryList(qtyNodes) + plugin.ansibleInventoryListBuilder = inventoryListBuilder + INodeSet nodes = plugin.getNodes() + + then: "non exception is thrown because data can be handled" + notThrown(YAMLException) + nodes.size() == qtyNodes + + where: + qtyNodes | _ + 2_0000 | _ + 3_0000 | _ + } + + void "ansible yaml data size parameter with an Exception"() { + 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: "huge inventory" + AnsibleInventoryListBuilder inventoryListBuilder = mockInventoryList(100_000) + plugin.ansibleInventoryListBuilder = inventoryListBuilder + plugin.getNodes() + + then: "throws an exception because data is too big to be precessed" + thrown(ResourceModelSourceException) + } + + private AnsibleInventoryListBuilder mockInventoryList(int qtyNodes) { + return Mock(AnsibleInventoryListBuilder) { + build() >> Mock(AnsibleInventoryList) { + getNodeList() >> createNodes(qtyNodes) + } + } + } + + private static String createNodes(int qty) { + Yaml yaml = new Yaml() + Map 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 groups = ['ungrouped' : hosts] + def children = ['children' : groups] + def all = ['all' : children] + return yaml.dump(all) + } + }