From f6f102de883e969a97190b01f4c2dee9205eaa80 Mon Sep 17 00:00:00 2001 From: Alexander Abarca Date: Mon, 13 May 2024 18:11:17 -0400 Subject: [PATCH 01/19] Included snake yaml dependency --- build.gradle | 1 + .../plugins/ansible/plugin/AnsibleResourceModelSource.java | 3 +++ 2 files changed, 4 insertions(+) diff --git a/build.gradle b/build.gradle index 58075b4..ab84992 100644 --- a/build.gradle +++ b/build.gradle @@ -52,6 +52,7 @@ dependencies { implementation 'org.codehaus.groovy:groovy-all:3.0.15' pluginLibs group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.16.1' pluginLibs group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.16.1' + implementation "org.yaml:snakeyaml:2.0" compileOnly 'org.projectlombok:lombok:1.18.30' annotationProcessor 'org.projectlombok:lombok:1.18.30' 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 553d58c..d3a5011 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java @@ -351,6 +351,9 @@ public INodeSet getNodes() throws ResourceModelSourceException { NodeSetImpl nodes = new NodeSetImpl(); final Gson gson = new Gson(); + // ansible-inventory -i inventory.yaml --list + // snake yaml + Path tempDirectory; try { tempDirectory = Files.createTempDirectory("ansible-hosts"); From d5751762bef5ab0457ba482519bdd1c7eba011d1 Mon Sep 17 00:00:00 2001 From: Alexander Abarca Date: Tue, 14 May 2024 16:48:24 -0400 Subject: [PATCH 02/19] Added AnsibleInventoryList class and modified getNodes method --- .../ansible/ansible/AnsibleInventoryList.java | 69 +++++++++++++++++++ .../plugin/AnsibleResourceModelSource.java | 69 ++++++++++++------- 2 files changed, 115 insertions(+), 23 deletions(-) create mode 100644 src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java diff --git a/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java b/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java new file mode 100644 index 0000000..27fef9d --- /dev/null +++ b/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java @@ -0,0 +1,69 @@ +package com.rundeck.plugins.ansible.ansible; + +import com.rundeck.plugins.ansible.util.ProcessExecutor; +import lombok.Builder; +import lombok.Data; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +@Data +@Builder +public class AnsibleInventoryList { + + private String inventory; + private boolean debug; + + public static final String ANSIBLE_INVENTORY = "ansible-inventory"; + + public String getNodeList() { + + List procArgs = new ArrayList<>(); + + procArgs.add(ANSIBLE_INVENTORY); + procArgs.add("-i " + inventory); + procArgs.add("--list"); + procArgs.add("-y"); + + if(debug){ + System.out.println("getNodeList " + procArgs); + } + + Process proc = null; + + try { + proc = ProcessExecutor.builder().procArgs(procArgs) + .redirectErrorStream(true) + .build().run(); + + StringBuilder stringBuilder = new StringBuilder(); + + final InputStream stdoutInputStream = proc.getInputStream(); + final BufferedReader stdoutReader = new BufferedReader(new InputStreamReader(stdoutInputStream)); + + String line1; + while ((line1 = stdoutReader.readLine()) != null) { + stringBuilder.append(line1).append("\n"); + } + + int exitCode = proc.waitFor(); + + if (exitCode != 0) { + System.err.println("ERROR: getNodeList: " + procArgs); + return null; + } + return stringBuilder.toString(); + + } catch (Exception e) { + System.err.println("error getNodeList: " + e.getMessage()); + return null; + } finally { + if (proc != null) { + proc.destroy(); + } + } + } +} 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 d3a5011..182da22 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java @@ -6,7 +6,7 @@ import com.dtolabs.rundeck.core.storage.keys.KeyStorageTree; import com.rundeck.plugins.ansible.ansible.AnsibleDescribable; import com.rundeck.plugins.ansible.ansible.AnsibleDescribable.AuthenticationType; -import com.rundeck.plugins.ansible.ansible.AnsibleException; +import com.rundeck.plugins.ansible.ansible.AnsibleInventoryList; import com.rundeck.plugins.ansible.ansible.AnsibleRunner; import com.dtolabs.rundeck.core.common.Framework; import com.dtolabs.rundeck.core.common.INodeSet; @@ -25,6 +25,7 @@ import org.rundeck.app.spi.Services; import org.rundeck.storage.api.PathUtil; import org.rundeck.storage.api.StorageException; +import org.yaml.snakeyaml.Yaml; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; @@ -38,6 +39,9 @@ public class AnsibleResourceModelSource implements ResourceModelSource, ProxyRunnerPlugin { + public static final String HOST_TPL_J2 = "host-tpl.j2"; + public static final String GATHER_HOSTS_YML = "gather-hosts.yml"; + private Framework framework; private Services services; @@ -197,9 +201,9 @@ public void configure(Properties configuration) throws ConfigurationException { } - public AnsibleRunner.AnsibleRunnerBuilder buildAnsibleRunner() throws ResourceModelSourceException{ + public AnsibleRunner.AnsibleRunnerBuilder buildAnsibleRunner() throws ResourceModelSourceException { - AnsibleRunner.AnsibleRunnerBuilder runnerBuilder = AnsibleRunner.playbookPath("gather-hosts.yml"); + AnsibleRunner.AnsibleRunnerBuilder runnerBuilder = AnsibleRunner.playbookPath(GATHER_HOSTS_YML); if ("true".equals(System.getProperty("ansible.debug"))) { runnerBuilder.debug(true); @@ -345,50 +349,58 @@ public AnsibleRunner.AnsibleRunnerBuilder buildAnsibleRunner() throws ResourceMo return runnerBuilder; } - @Override public INodeSet getNodes() throws ResourceModelSourceException { NodeSetImpl nodes = new NodeSetImpl(); - final Gson gson = new Gson(); - // ansible-inventory -i inventory.yaml --list + // ansible-inventory -i inventory.yaml --list -y // snake yaml + if (gatherFacts) { + ansibleRunnerByPath(nodes); + } else { + ansibleInventoryList(nodes); + } + + return nodes; + } + + public void ansibleRunnerByPath(NodeSetImpl nodes) throws ResourceModelSourceException { + AnsibleRunner.AnsibleRunnerBuilder runnerBuilder = buildAnsibleRunner(); + final Gson gson = new Gson(); Path tempDirectory; try { tempDirectory = Files.createTempDirectory("ansible-hosts"); } catch (IOException e) { - throw new ResourceModelSourceException("Error creating temporary directory.", e); + throw new ResourceModelSourceException("Error creating temporary directory.", e); } try { - Files.copy(this.getClass().getClassLoader().getResourceAsStream("host-tpl.j2"), tempDirectory.resolve("host-tpl.j2")); - Files.copy(this.getClass().getClassLoader().getResourceAsStream("gather-hosts.yml"), tempDirectory.resolve("gather-hosts.yml")); + Files.copy(this.getClass().getClassLoader().getResourceAsStream(HOST_TPL_J2), tempDirectory.resolve(HOST_TPL_J2)); + Files.copy(this.getClass().getClassLoader().getResourceAsStream(GATHER_HOSTS_YML), tempDirectory.resolve(GATHER_HOSTS_YML)); } catch (IOException e) { - throw new ResourceModelSourceException("Error copying files."); + throw new ResourceModelSourceException("Error copying files."); } - AnsibleRunner.AnsibleRunnerBuilder runnerBuilder = buildAnsibleRunner(); - runnerBuilder.tempDirectory(tempDirectory); runnerBuilder.retainTempDirectory(true); StringBuilder args = new StringBuilder(); args.append("facts: ") - .append(gatherFacts ? "True" : "False") - .append("\n") - .append("tmpdir: '") - .append(tempDirectory.toFile().getAbsolutePath()) - .append("'"); + .append(gatherFacts ? "True" : "False") + .append("\n") + .append("tmpdir: '") + .append(tempDirectory.toFile().getAbsolutePath()) + .append("'"); runnerBuilder.extraVars(args.toString()); AnsibleRunner runner = runnerBuilder.build(); try { - runner.run(); + runner.run(); } catch (Exception e) { - throw new ResourceModelSourceException(e.getMessage(),e); + throw new ResourceModelSourceException(e.getMessage(),e); } try { @@ -546,8 +558,8 @@ public INodeSet getNodes() throws ResourceModelSourceException { } } else { if (root.has(item.getKey()) - && root.get(item.getKey()).isJsonPrimitive() - && root.get(item.getKey()).getAsString().length() > 0) { + && root.get(item.getKey()).isJsonPrimitive() + && root.get(item.getKey()).getAsString().length() > 0) { node.setAttribute(item.getValue(), root.get(item.getKey()).getAsString()); } } @@ -628,11 +640,22 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOEx } catch (IOException e) { throw new ResourceModelSourceException("Error deleting temporary directory.", e); } - - return nodes; } + public void ansibleInventoryList(NodeSetImpl nodes) { + + Yaml yml = new Yaml(); + AnsibleInventoryList inventoryList = AnsibleInventoryList.builder() + .inventory(inventory) + .debug(debug) + .build(); + + String listResp = inventoryList.getNodeList(); + Map nodesYml = yml.load(listResp); + + + } private String getStorageContentString(String storagePath, StorageTree storageTree) throws ConfigurationException { return new String(this.getStorageContent(storagePath, storageTree)); From 841c9d04162101f8ea7d4a6aad4d8820a8ec35d3 Mon Sep 17 00:00:00 2001 From: Alexander Abarca Date: Wed, 15 May 2024 21:21:45 -0400 Subject: [PATCH 03/19] Iterating over ansible nodes to convert into rundeck nodes --- .../ansible/ansible/AnsibleInventoryList.java | 2 +- .../ansible/ansible/InventoryList.java | 30 ++++++++ .../plugin/AnsibleResourceModelSource.java | 74 +++++++++++++++---- 3 files changed, 90 insertions(+), 16 deletions(-) create mode 100644 src/main/groovy/com/rundeck/plugins/ansible/ansible/InventoryList.java diff --git a/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java b/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java index 27fef9d..995b92a 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java @@ -24,7 +24,7 @@ public String getNodeList() { List procArgs = new ArrayList<>(); procArgs.add(ANSIBLE_INVENTORY); - procArgs.add("-i " + inventory); + procArgs.add("--inventory-file" + "=" + inventory); procArgs.add("--list"); procArgs.add("-y"); diff --git a/src/main/groovy/com/rundeck/plugins/ansible/ansible/InventoryList.java b/src/main/groovy/com/rundeck/plugins/ansible/ansible/InventoryList.java new file mode 100644 index 0000000..1fa9d1e --- /dev/null +++ b/src/main/groovy/com/rundeck/plugins/ansible/ansible/InventoryList.java @@ -0,0 +1,30 @@ +package com.rundeck.plugins.ansible.ansible; + +import com.dtolabs.rundeck.core.resources.ResourceModelSourceException; +import lombok.Data; + +import java.util.Map; +import java.util.Optional; + +import static java.lang.String.format; + +@Data +public class InventoryList { + + public static final String ALL = "all"; + public static final String CHILDREN = "children"; + public static final String HOSTS = "hosts"; + public static final String ERROR_MISSING_FIELD = "Error: Missing tag '%s'"; + + private Map all; + + public static Map getField(Map tag, final String field) throws ResourceModelSourceException { + return getMap( Optional.ofNullable(tag.get(field)) + .orElseThrow(() -> new ResourceModelSourceException(format(ERROR_MISSING_FIELD, field)))); + } + + @SuppressWarnings("unchecked") + public static T getMap(Object obj) { + return (T) obj; + } +} 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 182da22..5be6978 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java @@ -1,27 +1,34 @@ package com.rundeck.plugins.ansible.plugin; -import com.dtolabs.rundeck.core.execution.proxy.ProxyRunnerPlugin; -import com.dtolabs.rundeck.core.storage.ResourceMeta; -import com.dtolabs.rundeck.core.storage.StorageTree; -import com.dtolabs.rundeck.core.storage.keys.KeyStorageTree; -import com.rundeck.plugins.ansible.ansible.AnsibleDescribable; -import com.rundeck.plugins.ansible.ansible.AnsibleDescribable.AuthenticationType; -import com.rundeck.plugins.ansible.ansible.AnsibleInventoryList; -import com.rundeck.plugins.ansible.ansible.AnsibleRunner; +import static com.rundeck.plugins.ansible.ansible.InventoryList.ALL; +import static com.rundeck.plugins.ansible.ansible.InventoryList.HOSTS; +import static com.rundeck.plugins.ansible.ansible.InventoryList.CHILDREN; +import static com.rundeck.plugins.ansible.ansible.InventoryList.ERROR_MISSING_FIELD; +import static java.lang.String.format; + import com.dtolabs.rundeck.core.common.Framework; import com.dtolabs.rundeck.core.common.INodeSet; import com.dtolabs.rundeck.core.common.NodeEntryImpl; import com.dtolabs.rundeck.core.common.NodeSetImpl; import com.dtolabs.rundeck.core.dispatcher.DataContextUtils; -import com.dtolabs.rundeck.core.resources.ResourceModelSource; -import com.dtolabs.rundeck.core.resources.ResourceModelSourceException; +import com.dtolabs.rundeck.core.execution.proxy.ProxyRunnerPlugin; import com.dtolabs.rundeck.core.plugins.ScriptDataContextUtil; import com.dtolabs.rundeck.core.plugins.configuration.ConfigurationException; +import com.dtolabs.rundeck.core.resources.ResourceModelSource; +import com.dtolabs.rundeck.core.resources.ResourceModelSourceException; +import com.dtolabs.rundeck.core.storage.ResourceMeta; +import com.dtolabs.rundeck.core.storage.StorageTree; +import com.dtolabs.rundeck.core.storage.keys.KeyStorageTree; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; +import com.rundeck.plugins.ansible.ansible.AnsibleDescribable; +import com.rundeck.plugins.ansible.ansible.AnsibleDescribable.AuthenticationType; +import com.rundeck.plugins.ansible.ansible.AnsibleInventoryList; +import com.rundeck.plugins.ansible.ansible.AnsibleRunner; +import com.rundeck.plugins.ansible.ansible.InventoryList; import org.rundeck.app.spi.Services; import org.rundeck.storage.api.PathUtil; import org.rundeck.storage.api.StorageException; @@ -32,10 +39,23 @@ import java.io.File; import java.io.IOException; import java.nio.charset.Charset; -import java.nio.file.*; +import java.nio.file.DirectoryStream; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; +import java.util.Properties; +import java.util.Set; public class AnsibleResourceModelSource implements ResourceModelSource, ProxyRunnerPlugin { @@ -642,9 +662,10 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOEx } } - public void ansibleInventoryList(NodeSetImpl nodes) { + public void ansibleInventoryList(NodeSetImpl nodes) throws ResourceModelSourceException { - Yaml yml = new Yaml(); + NodeEntryImpl node = new NodeEntryImpl(); + Yaml yaml = new Yaml(); AnsibleInventoryList inventoryList = AnsibleInventoryList.builder() .inventory(inventory) @@ -652,9 +673,32 @@ public void ansibleInventoryList(NodeSetImpl nodes) { .build(); String listResp = inventoryList.getNodeList(); - Map nodesYml = yml.load(listResp); + //Map>>> allInventory = yaml.load(listResp); + Map allInventory = yaml.load(listResp); + + Map all = InventoryList.getField(allInventory, ALL); + Map children = InventoryList.getField(all, CHILDREN); + + for (Map.Entry pair : children.entrySet()) { + String hostGroup = pair.getKey(); + node.setTags(Set.of(hostGroup)); + Map hostNames = InventoryList.getMap(pair.getValue()); + Map hosts = InventoryList.getField(hostNames, HOSTS); + + for (Map.Entry hostNode : hosts.entrySet()) { + String hostName = hostNode.getKey(); + node.setHostname(hostName); + Map hostValues = InventoryList.getMap(hostNode.getValue()); + + Optional.ofNullable(hostValues.get("hostname")).ifPresent(node::setNodename); + + Optional.ofNullable(hostValues.get("username")).ifPresent(node::setUsername); + + nodes.putNode(node); + } + } } private String getStorageContentString(String storagePath, StorageTree storageTree) throws ConfigurationException { From 17df830abe148563c7b8d0ee6bbbb2f52c69e8e9 Mon Sep 17 00:00:00 2001 From: Alexander Abarca Date: Fri, 17 May 2024 09:47:41 -0400 Subject: [PATCH 04/19] Added ansible config to environment variables --- .../ansible/ansible/AnsibleInventoryList.java | 13 ++- .../ansible/ansible/InventoryList.java | 79 ++++++++++++++++++- .../plugin/AnsibleResourceModelSource.java | 26 +++--- 3 files changed, 102 insertions(+), 16 deletions(-) diff --git a/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java b/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java index 995b92a..bca9bf8 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java @@ -8,13 +8,16 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; @Data @Builder public class AnsibleInventoryList { private String inventory; + private String configFile; private boolean debug; public static final String ANSIBLE_INVENTORY = "ansible-inventory"; @@ -22,12 +25,19 @@ public class AnsibleInventoryList { public String getNodeList() { List procArgs = new ArrayList<>(); - procArgs.add(ANSIBLE_INVENTORY); procArgs.add("--inventory-file" + "=" + inventory); procArgs.add("--list"); procArgs.add("-y"); + Map processEnvironment = new HashMap<>(); + if (configFile != null && !configFile.isEmpty()) { + if (debug) { + System.out.println(" ANSIBLE_CONFIG: " + configFile); + } + processEnvironment.put("ANSIBLE_CONFIG", configFile); + } + if(debug){ System.out.println("getNodeList " + procArgs); } @@ -37,6 +47,7 @@ public String getNodeList() { try { proc = ProcessExecutor.builder().procArgs(procArgs) .redirectErrorStream(true) + .environmentVariables(processEnvironment) .build().run(); StringBuilder stringBuilder = new StringBuilder(); diff --git a/src/main/groovy/com/rundeck/plugins/ansible/ansible/InventoryList.java b/src/main/groovy/com/rundeck/plugins/ansible/ansible/InventoryList.java index 1fa9d1e..3e6827a 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/ansible/InventoryList.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/ansible/InventoryList.java @@ -1,8 +1,11 @@ package com.rundeck.plugins.ansible.ansible; +import com.dtolabs.rundeck.core.common.NodeEntryImpl; import com.dtolabs.rundeck.core.resources.ResourceModelSourceException; import lombok.Data; +import java.util.Arrays; +import java.util.List; import java.util.Map; import java.util.Optional; @@ -15,10 +18,12 @@ public class InventoryList { public static final String CHILDREN = "children"; public static final String HOSTS = "hosts"; public static final String ERROR_MISSING_FIELD = "Error: Missing tag '%s'"; + public static final String ERROR_MISSING_TAG = "Error: Not tag found among these searched: '%s'"; private Map all; - public static Map getField(Map tag, final String field) throws ResourceModelSourceException { + public static Map getField(Map tag, final String field) + throws ResourceModelSourceException { return getMap( Optional.ofNullable(tag.get(field)) .orElseThrow(() -> new ResourceModelSourceException(format(ERROR_MISSING_FIELD, field)))); } @@ -27,4 +32,76 @@ public static Map getField(Map tag, final String public static T getMap(Object obj) { return (T) obj; } + + public static void tagHandle(NodeTag nodetag, NodeEntryImpl node, Map tags) + throws ResourceModelSourceException { + nodetag.handle(node, tags); + } + + private static String findTag(List nameTags, Map tags) { + String found = null; + for (String nameTag : nameTags) { + if (tags.containsKey(nameTag)) { + found = tags.get(nameTag); + break; + } + } + return found; + } + + public enum NodeTag { + + HOSTNAME { + @Override + public void handle(NodeEntryImpl node, Map tags) throws ResourceModelSourceException{ + final List hostnames = List.of("hostname", "ansible_host", "ansible_ssh_host"); + String nameTag = InventoryList.findTag(hostnames, tags); + node.setHostname(Optional.ofNullable(nameTag) + .orElseThrow(() -> new ResourceModelSourceException(format(ERROR_MISSING_TAG, hostnames)))); + } + }, + USERNAME { + @Override + public void handle(NodeEntryImpl node, Map tags) throws ResourceModelSourceException { + final List usernames = List.of("username", "ansible_user", "ansible_ssh_user", "ansible_user_id"); + String nameTag = InventoryList.findTag(usernames, tags); + node.setUsername(Optional.ofNullable(nameTag) + .orElseThrow(() -> new ResourceModelSourceException(format(ERROR_MISSING_TAG, usernames)))); + } + }, + OS_FAMILY { + @Override + public void handle(NodeEntryImpl node, Map tags) { + final List osNames = List.of("osFamily", "ansible_os_family"); + String nameTag = InventoryList.findTag(osNames, tags); + Optional.ofNullable(nameTag).ifPresent(node::setOsFamily); + } + }, + OS_NAME { + @Override + public void handle(NodeEntryImpl node, Map tags) { + final List familyNames = List.of("osName", "ansible_os_name"); + String nameTag = InventoryList.findTag(familyNames, tags); + Optional.ofNullable(nameTag).ifPresent(node::setOsName); + } + }, + OS_ARCHITECTURE { + @Override + public void handle(NodeEntryImpl node, Map tags) { + final List architectureNames = List.of("osArch", "ansible_architecture"); + String nameTag = InventoryList.findTag(architectureNames, tags); + Optional.ofNullable(nameTag).ifPresent(node::setOsArch); + } + }, + OS_VERSION { + @Override + public void handle(NodeEntryImpl node, Map tags) { + final List versionNames = List.of("osVersion"); + String nameTag = InventoryList.findTag(versionNames, tags); + Optional.ofNullable(nameTag).ifPresent(node::setOsArch); + } + }; + + public abstract void handle(NodeEntryImpl node, Map tags) throws ResourceModelSourceException; + } } 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 5be6978..7eacda8 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java @@ -3,7 +3,7 @@ import static com.rundeck.plugins.ansible.ansible.InventoryList.ALL; import static com.rundeck.plugins.ansible.ansible.InventoryList.HOSTS; import static com.rundeck.plugins.ansible.ansible.InventoryList.CHILDREN; -import static com.rundeck.plugins.ansible.ansible.InventoryList.ERROR_MISSING_FIELD; +import static com.rundeck.plugins.ansible.ansible.InventoryList.NodeTag; import static java.lang.String.format; import com.dtolabs.rundeck.core.common.Framework; @@ -47,13 +47,11 @@ import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.Optional; import java.util.Properties; import java.util.Set; @@ -663,38 +661,38 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOEx } public void ansibleInventoryList(NodeSetImpl nodes) throws ResourceModelSourceException { - - NodeEntryImpl node = new NodeEntryImpl(); Yaml yaml = new Yaml(); AnsibleInventoryList inventoryList = AnsibleInventoryList.builder() .inventory(inventory) + .configFile(configFile) .debug(debug) .build(); String listResp = inventoryList.getNodeList(); - //Map>>> allInventory = yaml.load(listResp); Map allInventory = yaml.load(listResp); - Map all = InventoryList.getField(allInventory, ALL); - Map children = InventoryList.getField(all, CHILDREN); for (Map.Entry pair : children.entrySet()) { String hostGroup = pair.getKey(); - node.setTags(Set.of(hostGroup)); Map hostNames = InventoryList.getMap(pair.getValue()); Map hosts = InventoryList.getField(hostNames, HOSTS); for (Map.Entry hostNode : hosts.entrySet()) { + NodeEntryImpl node = new NodeEntryImpl(); + node.setTags(Set.of(hostGroup)); String hostName = hostNode.getKey(); node.setHostname(hostName); - Map hostValues = InventoryList.getMap(hostNode.getValue()); - - Optional.ofNullable(hostValues.get("hostname")).ifPresent(node::setNodename); - - Optional.ofNullable(hostValues.get("username")).ifPresent(node::setUsername); + node.setNodename(hostName); + Map nodeValues = InventoryList.getMap(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); nodes.putNode(node); } From 39617adaec60624b52c5c386f02dbc52825d2436 Mon Sep 17 00:00:00 2001 From: Alexander Abarca Date: Mon, 20 May 2024 20:13:21 -0400 Subject: [PATCH 05/19] Added ansible properties to rundeck nodes --- .../ansible/ansible/InventoryList.java | 60 +++++++++++++------ .../plugin/AnsibleResourceModelSource.java | 21 +++---- 2 files changed, 52 insertions(+), 29 deletions(-) diff --git a/src/main/groovy/com/rundeck/plugins/ansible/ansible/InventoryList.java b/src/main/groovy/com/rundeck/plugins/ansible/ansible/InventoryList.java index 3e6827a..e7b1058 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/ansible/InventoryList.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/ansible/InventoryList.java @@ -4,7 +4,6 @@ import com.dtolabs.rundeck.core.resources.ResourceModelSourceException; import lombok.Data; -import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Optional; @@ -20,12 +19,13 @@ public class InventoryList { public static final String ERROR_MISSING_FIELD = "Error: Missing tag '%s'"; public static final String ERROR_MISSING_TAG = "Error: Not tag found among these searched: '%s'"; - private Map all; - - public static Map getField(Map tag, final String field) - throws ResourceModelSourceException { - return getMap( Optional.ofNullable(tag.get(field)) - .orElseThrow(() -> new ResourceModelSourceException(format(ERROR_MISSING_FIELD, field)))); + @SuppressWarnings("unchecked") + public static T getValue(Map tag, final String field) { + Object obj = null; + if (Optional.ofNullable(tag.get(field)).isPresent()) { + obj = tag.get(field); + } + return (T) obj; } @SuppressWarnings("unchecked") @@ -33,27 +33,37 @@ public static T getMap(Object obj) { return (T) obj; } - public static void tagHandle(NodeTag nodetag, NodeEntryImpl node, Map tags) + public static void tagHandle(NodeTag nodetag, NodeEntryImpl node, Map tags) throws ResourceModelSourceException { nodetag.handle(node, tags); } - private static String findTag(List nameTags, Map tags) { + private static String findTag(List nameTags, Map tags) { String found = null; for (String nameTag : nameTags) { if (tags.containsKey(nameTag)) { - found = tags.get(nameTag); + Object value = tags.get(nameTag); + if (value instanceof String) { + found = (String) value; + } break; } } return found; } + private static String valueString(Object obj) { + if (obj instanceof String) { + return (String) obj; + } + return null; + } + public enum NodeTag { HOSTNAME { @Override - public void handle(NodeEntryImpl node, Map tags) throws ResourceModelSourceException{ + public void handle(NodeEntryImpl node, Map tags) throws ResourceModelSourceException{ final List hostnames = List.of("hostname", "ansible_host", "ansible_ssh_host"); String nameTag = InventoryList.findTag(hostnames, tags); node.setHostname(Optional.ofNullable(nameTag) @@ -62,7 +72,7 @@ public void handle(NodeEntryImpl node, Map tags) throws Resource }, USERNAME { @Override - public void handle(NodeEntryImpl node, Map tags) throws ResourceModelSourceException { + public void handle(NodeEntryImpl node, Map tags) throws ResourceModelSourceException { final List usernames = List.of("username", "ansible_user", "ansible_ssh_user", "ansible_user_id"); String nameTag = InventoryList.findTag(usernames, tags); node.setUsername(Optional.ofNullable(nameTag) @@ -71,7 +81,7 @@ public void handle(NodeEntryImpl node, Map tags) throws Resource }, OS_FAMILY { @Override - public void handle(NodeEntryImpl node, Map tags) { + public void handle(NodeEntryImpl node, Map tags) { final List osNames = List.of("osFamily", "ansible_os_family"); String nameTag = InventoryList.findTag(osNames, tags); Optional.ofNullable(nameTag).ifPresent(node::setOsFamily); @@ -79,7 +89,7 @@ public void handle(NodeEntryImpl node, Map tags) { }, OS_NAME { @Override - public void handle(NodeEntryImpl node, Map tags) { + public void handle(NodeEntryImpl node, Map tags) { final List familyNames = List.of("osName", "ansible_os_name"); String nameTag = InventoryList.findTag(familyNames, tags); Optional.ofNullable(nameTag).ifPresent(node::setOsName); @@ -87,7 +97,7 @@ public void handle(NodeEntryImpl node, Map tags) { }, OS_ARCHITECTURE { @Override - public void handle(NodeEntryImpl node, Map tags) { + public void handle(NodeEntryImpl node, Map tags) { final List architectureNames = List.of("osArch", "ansible_architecture"); String nameTag = InventoryList.findTag(architectureNames, tags); Optional.ofNullable(nameTag).ifPresent(node::setOsArch); @@ -95,13 +105,29 @@ public void handle(NodeEntryImpl node, Map tags) { }, OS_VERSION { @Override - public void handle(NodeEntryImpl node, Map tags) { + public void handle(NodeEntryImpl node, Map tags) { final List versionNames = List.of("osVersion"); String nameTag = InventoryList.findTag(versionNames, tags); Optional.ofNullable(nameTag).ifPresent(node::setOsArch); } + }, + DESCRIPTION { + @Override + public void handle(NodeEntryImpl node, Map tags) throws ResourceModelSourceException { + Map lsbMap = InventoryList.getValue(tags, "ansible_lsb"); + if (lsbMap != null) { + String desc = InventoryList.valueString(lsbMap.get("description")); + Optional.ofNullable(desc).ifPresent(node::setDescription); + } + else { + Optional.ofNullable(InventoryList.getValue(tags, "ansible_distribution")) + .ifPresent(x -> node.setDescription(x + " ")); + Optional.ofNullable(InventoryList.getValue(tags, "ansible_distribution_version")) + .ifPresent(x -> node.setDescription(x + " ")); + } + } }; - public abstract void handle(NodeEntryImpl node, Map tags) throws ResourceModelSourceException; + public abstract void handle(NodeEntryImpl node, Map tags) throws ResourceModelSourceException; } } 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 7eacda8..7ee7416 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java @@ -1,11 +1,5 @@ package com.rundeck.plugins.ansible.plugin; -import static com.rundeck.plugins.ansible.ansible.InventoryList.ALL; -import static com.rundeck.plugins.ansible.ansible.InventoryList.HOSTS; -import static com.rundeck.plugins.ansible.ansible.InventoryList.CHILDREN; -import static com.rundeck.plugins.ansible.ansible.InventoryList.NodeTag; -import static java.lang.String.format; - import com.dtolabs.rundeck.core.common.Framework; import com.dtolabs.rundeck.core.common.INodeSet; import com.dtolabs.rundeck.core.common.NodeEntryImpl; @@ -55,6 +49,8 @@ import java.util.Properties; import java.util.Set; +import static com.rundeck.plugins.ansible.ansible.InventoryList.*; + public class AnsibleResourceModelSource implements ResourceModelSource, ProxyRunnerPlugin { public static final String HOST_TPL_J2 = "host-tpl.j2"; @@ -375,7 +371,7 @@ public INodeSet getNodes() throws ResourceModelSourceException { // snake yaml if (gatherFacts) { - ansibleRunnerByPath(nodes); + processWithGatherFacts(nodes); } else { ansibleInventoryList(nodes); } @@ -383,7 +379,7 @@ public INodeSet getNodes() throws ResourceModelSourceException { return nodes; } - public void ansibleRunnerByPath(NodeSetImpl nodes) throws ResourceModelSourceException { + public void processWithGatherFacts(NodeSetImpl nodes) throws ResourceModelSourceException { AnsibleRunner.AnsibleRunnerBuilder runnerBuilder = buildAnsibleRunner(); final Gson gson = new Gson(); Path tempDirectory; @@ -672,13 +668,13 @@ public void ansibleInventoryList(NodeSetImpl nodes) throws ResourceModelSourceEx String listResp = inventoryList.getNodeList(); Map allInventory = yaml.load(listResp); - Map all = InventoryList.getField(allInventory, ALL); - Map children = InventoryList.getField(all, CHILDREN); + Map all = InventoryList.getValue(allInventory, ALL); + Map children = InventoryList.getValue(all, CHILDREN); for (Map.Entry pair : children.entrySet()) { String hostGroup = pair.getKey(); Map hostNames = InventoryList.getMap(pair.getValue()); - Map hosts = InventoryList.getField(hostNames, HOSTS); + Map hosts = InventoryList.getValue(hostNames, HOSTS); for (Map.Entry hostNode : hosts.entrySet()) { NodeEntryImpl node = new NodeEntryImpl(); @@ -686,13 +682,14 @@ public void ansibleInventoryList(NodeSetImpl nodes) throws ResourceModelSourceEx String hostName = hostNode.getKey(); node.setHostname(hostName); node.setNodename(hostName); - Map nodeValues = InventoryList.getMap(hostNode.getValue()); + Map nodeValues = InventoryList.getMap(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); nodes.putNode(node); } From c0495336207a79c752a439b3d113345180507e9e Mon Sep 17 00:00:00 2001 From: Alexander Abarca Date: Wed, 22 May 2024 10:27:23 -0400 Subject: [PATCH 06/19] Added os version property --- .../com/rundeck/plugins/ansible/ansible/InventoryList.java | 4 ++-- .../plugins/ansible/plugin/AnsibleResourceModelSource.java | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/groovy/com/rundeck/plugins/ansible/ansible/InventoryList.java b/src/main/groovy/com/rundeck/plugins/ansible/ansible/InventoryList.java index e7b1058..6bc4ffc 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/ansible/InventoryList.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/ansible/InventoryList.java @@ -106,9 +106,9 @@ public void handle(NodeEntryImpl node, Map tags) { OS_VERSION { @Override public void handle(NodeEntryImpl node, Map tags) { - final List versionNames = List.of("osVersion"); + final List versionNames = List.of("osVersion", "ansible_kernel"); String nameTag = InventoryList.findTag(versionNames, tags); - Optional.ofNullable(nameTag).ifPresent(node::setOsArch); + Optional.ofNullable(nameTag).ifPresent(node::setOsVersion); } }, DESCRIPTION { 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 7ee7416..cb95169 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java @@ -367,9 +367,6 @@ public AnsibleRunner.AnsibleRunnerBuilder buildAnsibleRunner() throws ResourceMo public INodeSet getNodes() throws ResourceModelSourceException { NodeSetImpl nodes = new NodeSetImpl(); - // ansible-inventory -i inventory.yaml --list -y - // snake yaml - if (gatherFacts) { processWithGatherFacts(nodes); } else { From 8d6b764cc72fdb98ce93ed93387d69ea69618c41 Mon Sep 17 00:00:00 2001 From: Alexander Abarca Date: Wed, 22 May 2024 14:33:46 -0400 Subject: [PATCH 07/19] Added javadoc and improvements --- .../ansible/ansible/AnsibleInventoryList.java | 4 ++ .../ansible/ansible/InventoryList.java | 60 +++++++++++++++---- .../plugin/AnsibleResourceModelSource.java | 10 +++- 3 files changed, 58 insertions(+), 16 deletions(-) diff --git a/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java b/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java index bca9bf8..0f4ce62 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java @@ -22,6 +22,10 @@ public class AnsibleInventoryList { public static final String ANSIBLE_INVENTORY = "ansible-inventory"; + /** + * Executes Ansible command to bring all nodes from inventory + * @return output in yaml format + */ public String getNodeList() { List procArgs = new ArrayList<>(); diff --git a/src/main/groovy/com/rundeck/plugins/ansible/ansible/InventoryList.java b/src/main/groovy/com/rundeck/plugins/ansible/ansible/InventoryList.java index 6bc4ffc..f0f83d7 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/ansible/InventoryList.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/ansible/InventoryList.java @@ -19,46 +19,74 @@ public class InventoryList { public static final String ERROR_MISSING_FIELD = "Error: Missing tag '%s'"; public static final String ERROR_MISSING_TAG = "Error: Not tag found among these searched: '%s'"; - @SuppressWarnings("unchecked") + /** + * Gets value from a tag from Ansible inventory + * @param tag tag from yaml output + * @param field field to look for + * @return value from tag yaml output + * @param type you want to receive + */ public static T getValue(Map tag, final String field) { Object obj = null; if (Optional.ofNullable(tag.get(field)).isPresent()) { obj = tag.get(field); } - return (T) obj; + return getType(obj); } + /** + * Casts an object you want to receive + * @param obj generic object + * @return cast object + * @param type you want to receive + */ @SuppressWarnings("unchecked") - public static T getMap(Object obj) { + public static T getType(Object obj) { return (T) obj; } - public static void tagHandle(NodeTag nodetag, NodeEntryImpl node, Map tags) + /** + * Process an Ansible tag + * @param nodeTag Ansible node tag + * @param node Rundeck node to build + * @param tags tags to evaluate + * @throws ResourceModelSourceException + */ + public static void tagHandle(NodeTag nodeTag, NodeEntryImpl node, Map tags) throws ResourceModelSourceException { - nodetag.handle(node, tags); + nodeTag.handle(node, tags); } + /** + * Finds a tag if it exists in a map of tags + * @param nameTags map of tags to find + * @param tags tags from Ansible + * @return a value if it exists + */ private static String findTag(List nameTags, Map tags) { String found = null; for (String nameTag : nameTags) { if (tags.containsKey(nameTag)) { Object value = tags.get(nameTag); - if (value instanceof String) { - found = (String) value; - } + found = valueString(value); break; } } return found; } + /** + * Casts an object to String + * @param obj object to convert + * @return a String object + */ private static String valueString(Object obj) { - if (obj instanceof String) { - return (String) obj; - } - return null; + return getType(obj); } + /** + * Enum to manage Ansible tags + */ public enum NodeTag { HOSTNAME { @@ -113,7 +141,7 @@ public void handle(NodeEntryImpl node, Map tags) { }, DESCRIPTION { @Override - public void handle(NodeEntryImpl node, Map tags) throws ResourceModelSourceException { + public void handle(NodeEntryImpl node, Map tags) { Map lsbMap = InventoryList.getValue(tags, "ansible_lsb"); if (lsbMap != null) { String desc = InventoryList.valueString(lsbMap.get("description")); @@ -128,6 +156,12 @@ public void handle(NodeEntryImpl node, Map tags) throws Resource } }; + /** + * Processes an Ansible tag to build a Rundeck tag + * @param node Rundeck node + * @param tags Ansible tags + * @throws ResourceModelSourceException + */ public abstract void handle(NodeEntryImpl node, Map tags) throws ResourceModelSourceException; } } 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 cb95169..3a22382 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java @@ -653,6 +653,11 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOEx } } + /** + * Process nodes coming from Ansible to convert them to Rundeck node + * @param nodes Rundeck nodes + * @throws ResourceModelSourceException + */ public void ansibleInventoryList(NodeSetImpl nodes) throws ResourceModelSourceException { Yaml yaml = new Yaml(); @@ -670,7 +675,7 @@ public void ansibleInventoryList(NodeSetImpl nodes) throws ResourceModelSourceEx for (Map.Entry pair : children.entrySet()) { String hostGroup = pair.getKey(); - Map hostNames = InventoryList.getMap(pair.getValue()); + Map hostNames = InventoryList.getType(pair.getValue()); Map hosts = InventoryList.getValue(hostNames, HOSTS); for (Map.Entry hostNode : hosts.entrySet()) { @@ -679,8 +684,7 @@ public void ansibleInventoryList(NodeSetImpl nodes) throws ResourceModelSourceEx String hostName = hostNode.getKey(); node.setHostname(hostName); node.setNodename(hostName); - Map nodeValues = InventoryList.getMap(hostNode.getValue()); - //InventoryList.tagHandle(NodeTag.HOSTNAME, node, nodeValues); + Map nodeValues = InventoryList.getType(hostNode.getValue()); InventoryList.tagHandle(NodeTag.USERNAME, node, nodeValues); InventoryList.tagHandle(NodeTag.OS_FAMILY, node, nodeValues); InventoryList.tagHandle(NodeTag.OS_NAME, node, nodeValues); From e446c0367f1618eaa4eeca78d1ffe824298e81c1 Mon Sep 17 00:00:00 2001 From: Alexander Abarca Date: Mon, 27 May 2024 10:11:45 -0400 Subject: [PATCH 08/19] Added Ansible model source test --- .../plugin/AnsibleResourceModelSource.java | 30 +++++-- .../AnsibleResourceModelSourceSpec.groovy | 89 +++++++++++++++++++ 2 files changed, 112 insertions(+), 7 deletions(-) create mode 100644 src/test/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSourceSpec.groovy 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 3a22382..d8d1c82 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java @@ -114,11 +114,16 @@ public class AnsibleResourceModelSource implements ResourceModelSource, ProxyRun protected boolean encryptExtraVars = false; + 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( final String attribute, final String defaultValue, @@ -661,13 +666,7 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOEx public void ansibleInventoryList(NodeSetImpl nodes) throws ResourceModelSourceException { Yaml yaml = new Yaml(); - AnsibleInventoryList inventoryList = AnsibleInventoryList.builder() - .inventory(inventory) - .configFile(configFile) - .debug(debug) - .build(); - - String listResp = inventoryList.getNodeList(); + String listResp = getNodesFromInventory(); Map allInventory = yaml.load(listResp); Map all = InventoryList.getValue(allInventory, ALL); @@ -697,6 +696,23 @@ public void ansibleInventoryList(NodeSetImpl nodes) throws ResourceModelSourceEx } } + /** + * Gets Ansible nodes from inventory + * @return Ansible nodes + */ + public String getNodesFromInventory() { + + if (this.ansibleInventoryListBuilder == null) { + this.ansibleInventoryListBuilder = AnsibleInventoryList.builder() + .inventory(inventory) + .configFile(configFile) + .debug(debug); + } + AnsibleInventoryList inventoryList = this.ansibleInventoryListBuilder.build(); + + return inventoryList.getNodeList(); + } + private String getStorageContentString(String storagePath, StorageTree storageTree) throws ConfigurationException { return new String(this.getStorageContent(storagePath, storageTree)); } diff --git a/src/test/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSourceSpec.groovy b/src/test/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSourceSpec.groovy new file mode 100644 index 0000000..eb45d43 --- /dev/null +++ b/src/test/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSourceSpec.groovy @@ -0,0 +1,89 @@ +package com.rundeck.plugins.ansible.plugin + +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.rundeck.plugins.ansible.ansible.AnsibleDescribable +import com.rundeck.plugins.ansible.ansible.AnsibleInventoryList +import org.yaml.snakeyaml.Yaml +import spock.lang.Specification + +/** + * AnsibleResourceModelSource test + */ +class AnsibleResourceModelSourceSpec extends Specification { + + void "get nodes gather facts false"() { + given: + Yaml yaml = new Yaml() + String familyValue = 'Linux' + String nameValue = 'RED HAT' + String versionValue = "'7.9'" + String archValue = 'x64' + String usernameValue = 'test' + String descValue = 'CentOS Linux' + 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) + + when: + Map nodeData = [:] + if (osFamily) { nodeData.put(osFamily, familyValue) } + if (osName) { nodeData.put(osName, nameValue) } + if (osVersion) { nodeData.put(osVersion, versionValue) } + if (osArch) { nodeData.put(osArch, archValue) } + if (username) { nodeData.put(username, usernameValue) } + if (description) { + nodeData.put(description, descValue) + } + + def host = [(nodeName) : nodeData] + def hosts = ['hosts' : host] + def groups = ['ungrouped' : hosts] + def children = ['children' : groups] + def all = ['all' : children] + + String result = yaml.dump(all) + + AnsibleInventoryListBuilder inventoryListBuilder = Mock(AnsibleInventoryListBuilder) { + build() >> Mock(AnsibleInventoryList) { + getNodeList() >> result + } + } + + plugin.ansibleInventoryListBuilder = inventoryListBuilder + INodeSet nodes = plugin.getNodes() + + then: + nodes.size() == 1 + INodeEntry node = nodes[0] + node.tags.size() == 1 + node.tags[0] == 'ungrouped' + if (node.hostname) { node.hostname == nodeName } + if (node.nodename) { node.nodename == nodeName } + if (node.osFamily) { node.osFamily == familyValue } + if (node.osName) { node.osName == nameValue } + if (node.osVersion) { node.osVersion == versionValue } + if (node.osArch) { node.osArch == archValue } + if (node.username) { node.username == usernameValue } + if (node.description) { node.description == descValue } + + where: + nodeName | osFamily | osName | osVersion | osArch | username | description + 'NODE_0' | 'osFamily' | 'osName' | 'osVersion' | 'osArch' | 'username' | 'description' + 'NODE_1' | 'ansible_os_family' | 'ansible_os_name' | 'ansible_kernel' | 'ansible_architecture' | 'ansible_user' | 'ansible_distribution' + 'NODE_2' | 'ansible_os_family' | 'ansible_os_name' | 'ansible_kernel' | 'ansible_architecture' | 'ansible_ssh_user' | 'ansible_distribution' + 'NODE_3' | 'ansible_os_family' | 'ansible_os_name' | 'ansible_kernel' | 'ansible_architecture' | 'ansible_user_id' | 'ansible_distribution' + } + +} From 3fcdfa0644b7b5291bde40fa061c401da7404130 Mon Sep 17 00:00:00 2001 From: Alexander Abarca Date: Mon, 27 May 2024 16:37:02 -0400 Subject: [PATCH 09/19] Removed username validation --- .../com/rundeck/plugins/ansible/ansible/InventoryList.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/groovy/com/rundeck/plugins/ansible/ansible/InventoryList.java b/src/main/groovy/com/rundeck/plugins/ansible/ansible/InventoryList.java index f0f83d7..4de139c 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/ansible/InventoryList.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/ansible/InventoryList.java @@ -100,11 +100,10 @@ public void handle(NodeEntryImpl node, Map tags) throws Resource }, USERNAME { @Override - public void handle(NodeEntryImpl node, Map tags) throws ResourceModelSourceException { + public void handle(NodeEntryImpl node, Map tags) { final List usernames = List.of("username", "ansible_user", "ansible_ssh_user", "ansible_user_id"); String nameTag = InventoryList.findTag(usernames, tags); - node.setUsername(Optional.ofNullable(nameTag) - .orElseThrow(() -> new ResourceModelSourceException(format(ERROR_MISSING_TAG, usernames)))); + Optional.ofNullable(nameTag).ifPresent(node::setUsername); } }, OS_FAMILY { From 6b473727916442ab97c280543632a705af96facc Mon Sep 17 00:00:00 2001 From: Alexander Abarca Date: Tue, 11 Jun 2024 16:49:38 -0400 Subject: [PATCH 10/19] Changed snakeyaml to 2.2 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ab84992..7bee013 100644 --- a/build.gradle +++ b/build.gradle @@ -52,7 +52,7 @@ dependencies { implementation 'org.codehaus.groovy:groovy-all:3.0.15' pluginLibs group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.16.1' pluginLibs group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.16.1' - implementation "org.yaml:snakeyaml:2.0" + implementation "org.yaml:snakeyaml:2.2" compileOnly 'org.projectlombok:lombok:1.18.30' annotationProcessor 'org.projectlombok:lombok:1.18.30' From d629f2f6a58389544440250a5e8cdfcaf3f1e472 Mon Sep 17 00:00:00 2001 From: Alexander Abarca Date: Wed, 12 Jun 2024 10:21:25 -0400 Subject: [PATCH 11/19] Added safe constructor to snake yaml object --- .../plugins/ansible/plugin/AnsibleResourceModelSource.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 d8d1c82..feb6beb 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java @@ -26,7 +26,9 @@ 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 java.io.BufferedReader; import java.io.ByteArrayOutputStream; @@ -664,7 +666,7 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOEx * @throws ResourceModelSourceException */ public void ansibleInventoryList(NodeSetImpl nodes) throws ResourceModelSourceException { - Yaml yaml = new Yaml(); + Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions())); String listResp = getNodesFromInventory(); @@ -709,6 +711,7 @@ public String getNodesFromInventory() { .debug(debug); } AnsibleInventoryList inventoryList = this.ansibleInventoryListBuilder.build(); + //inventoryList.processVault(); return inventoryList.getNodeList(); } From 2e250206a6e5946c9931ec229dee4dd00d39d463 Mon Sep 17 00:00:00 2001 From: Alexander Abarca Date: Wed, 26 Jun 2024 14:31:03 -0400 Subject: [PATCH 12/19] Added vault password config --- .../ansible/ansible/AnsibleInventoryList.java | 36 ++++++++++++++++++- .../plugin/AnsibleResourceModelSource.java | 34 +++++++++++++----- 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java b/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java index 0f4ce62..84394a1 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java @@ -1,10 +1,14 @@ package com.rundeck.plugins.ansible.ansible; +import com.rundeck.plugins.ansible.util.AnsibleUtil; import com.rundeck.plugins.ansible.util.ProcessExecutor; +import com.rundeck.plugins.ansible.util.VaultPrompt; import lombok.Builder; import lombok.Data; import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; @@ -20,13 +24,19 @@ public class AnsibleInventoryList { private String configFile; private boolean debug; + private AnsibleVault ansibleVault; + private VaultPrompt vaultPrompt; + private File tempInternalVaultFile; + private File tempVaultFile; + private File vaultPromptFile; + public static final String ANSIBLE_INVENTORY = "ansible-inventory"; /** * Executes Ansible command to bring all nodes from inventory * @return output in yaml format */ - public String getNodeList() { + public String getNodeList() throws Exception { List procArgs = new ArrayList<>(); procArgs.add(ANSIBLE_INVENTORY); @@ -41,6 +51,10 @@ public String getNodeList() { } processEnvironment.put("ANSIBLE_CONFIG", configFile); } + //set STDIN variables + List stdinVariables = new ArrayList<>(); + + processAnsibleVault(stdinVariables, procArgs); if(debug){ System.out.println("getNodeList " + procArgs); @@ -52,6 +66,7 @@ public String getNodeList() { proc = ProcessExecutor.builder().procArgs(procArgs) .redirectErrorStream(true) .environmentVariables(processEnvironment) + .stdinVariables(stdinVariables) .build().run(); StringBuilder stringBuilder = new StringBuilder(); @@ -81,4 +96,23 @@ public String getNodeList() { } } } + + private void processAnsibleVault(List stdinVariables, List procArgs) + throws IOException { + + if(ansibleVault == null){ + tempInternalVaultFile = AnsibleVault.createVaultScriptAuth("ansible-script-vault"); + ansibleVault = AnsibleVault.builder() + .masterPassword(vaultPrompt.getVaultPassword()) + .vaultPasswordScriptFile(tempInternalVaultFile) + .debug(debug).build(); + } + + if (vaultPrompt != null) { + stdinVariables.add(vaultPrompt); + tempVaultFile = ansibleVault.getVaultPasswordScriptFile(); + procArgs.add("--vault-id"); + procArgs.add(tempVaultFile.getAbsolutePath()); + } + } } 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 feb6beb..5b857d1 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java @@ -23,6 +23,7 @@ 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.util.VaultPrompt; import org.rundeck.app.spi.Services; import org.rundeck.storage.api.PathUtil; import org.rundeck.storage.api.StorageException; @@ -373,18 +374,19 @@ public AnsibleRunner.AnsibleRunnerBuilder buildAnsibleRunner() throws ResourceMo @Override public INodeSet getNodes() throws ResourceModelSourceException { NodeSetImpl nodes = new NodeSetImpl(); + AnsibleRunner.AnsibleRunnerBuilder runnerBuilder = buildAnsibleRunner(); if (gatherFacts) { - processWithGatherFacts(nodes); + processWithGatherFacts(nodes, runnerBuilder); } else { - ansibleInventoryList(nodes); + ansibleInventoryList(nodes, runnerBuilder); } return nodes; } - public void processWithGatherFacts(NodeSetImpl nodes) throws ResourceModelSourceException { - AnsibleRunner.AnsibleRunnerBuilder runnerBuilder = buildAnsibleRunner(); + public void processWithGatherFacts(NodeSetImpl nodes, AnsibleRunner.AnsibleRunnerBuilder runnerBuilder) throws ResourceModelSourceException { + final Gson gson = new Gson(); Path tempDirectory; try { @@ -665,10 +667,10 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOEx * @param nodes Rundeck nodes * @throws ResourceModelSourceException */ - public void ansibleInventoryList(NodeSetImpl nodes) throws ResourceModelSourceException { + public void ansibleInventoryList(NodeSetImpl nodes, AnsibleRunner.AnsibleRunnerBuilder runnerBuilder) throws ResourceModelSourceException { Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions())); - String listResp = getNodesFromInventory(); + String listResp = getNodesFromInventory(runnerBuilder); Map allInventory = yaml.load(listResp); Map all = InventoryList.getValue(allInventory, ALL); @@ -702,7 +704,9 @@ public void ansibleInventoryList(NodeSetImpl nodes) throws ResourceModelSourceEx * Gets Ansible nodes from inventory * @return Ansible nodes */ - public String getNodesFromInventory() { + public String getNodesFromInventory(AnsibleRunner.AnsibleRunnerBuilder runnerBuilder) throws ResourceModelSourceException { + + AnsibleRunner runner = runnerBuilder.build(); if (this.ansibleInventoryListBuilder == null) { this.ansibleInventoryListBuilder = AnsibleInventoryList.builder() @@ -710,10 +714,22 @@ public String getNodesFromInventory() { .configFile(configFile) .debug(debug); } + + if(runner.getVaultPass() != null){ + VaultPrompt vaultPrompt = VaultPrompt.builder() + .vaultId("None") + .vaultPassword(runner.getVaultPass() + "\n") + .build(); + ansibleInventoryListBuilder.vaultPrompt(vaultPrompt); + } + AnsibleInventoryList inventoryList = this.ansibleInventoryListBuilder.build(); - //inventoryList.processVault(); - return inventoryList.getNodeList(); + try { + return inventoryList.getNodeList(); + } catch (Exception e) { + throw new ResourceModelSourceException(e.getMessage(),e); + } } private String getStorageContentString(String storagePath, StorageTree storageTree) throws ConfigurationException { From 70945c6d635cf68f9fd427f8980bae7e94b5208c Mon Sep 17 00:00:00 2001 From: Alexander Abarca Date: Thu, 27 Jun 2024 10:40:46 -0400 Subject: [PATCH 13/19] Added ansible limit host parameter --- .../ansible/ansible/AnsibleInventoryList.java | 21 +++++++++++++++++++ .../plugin/AnsibleResourceModelSource.java | 4 ++++ 2 files changed, 25 insertions(+) diff --git a/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java b/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java index 84394a1..c3ea871 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java @@ -26,9 +26,12 @@ public class AnsibleInventoryList { private AnsibleVault ansibleVault; private VaultPrompt vaultPrompt; + private List limits; + private File tempInternalVaultFile; private File tempVaultFile; private File vaultPromptFile; + private File tempLimitFile; public static final String ANSIBLE_INVENTORY = "ansible-inventory"; @@ -55,6 +58,7 @@ public String getNodeList() throws Exception { List stdinVariables = new ArrayList<>(); processAnsibleVault(stdinVariables, procArgs); + processLimit(procArgs); if(debug){ System.out.println("getNodeList " + procArgs); @@ -115,4 +119,21 @@ private void processAnsibleVault(List stdinVariables, List procArgs.add(tempVaultFile.getAbsolutePath()); } } + + private void processLimit(List procArgs) throws IOException { + if (limits != null && limits.size() == 1) { + procArgs.add("-l"); + procArgs.add(limits.get(0)); + + } else if (limits != null && limits.size() > 1) { + StringBuilder sb = new StringBuilder(); + for (String limit : limits) { + sb.append(limit).append("\n"); + } + tempLimitFile = AnsibleUtil.createTemporaryFile("targets", sb.toString()); + + procArgs.add("-l"); + procArgs.add("@" + tempLimitFile.getAbsolutePath()); + } + } } 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 5b857d1..b323906 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java @@ -723,6 +723,10 @@ public String getNodesFromInventory(AnsibleRunner.AnsibleRunnerBuilder runnerBui ansibleInventoryListBuilder.vaultPrompt(vaultPrompt); } + if (!runner.getLimits().isEmpty()) { + ansibleInventoryListBuilder.limits(runner.getLimits()); + } + AnsibleInventoryList inventoryList = this.ansibleInventoryListBuilder.build(); try { From 482c807d23f3bceb79a58bf8b4f5019a27e41535 Mon Sep 17 00:00:00 2001 From: Alexander Abarca Date: Thu, 27 Jun 2024 13:10:46 -0400 Subject: [PATCH 14/19] Fixed ansible model source test --- .../ansible/plugin/AnsibleResourceModelSource.java | 2 +- .../ansible/plugin/AnsibleResourceModelSourceSpec.groovy | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) 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 b323906..6ce617b 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java @@ -723,7 +723,7 @@ public String getNodesFromInventory(AnsibleRunner.AnsibleRunnerBuilder runnerBui ansibleInventoryListBuilder.vaultPrompt(vaultPrompt); } - if (!runner.getLimits().isEmpty()) { + if (runner.getLimits() != null) { ansibleInventoryListBuilder.limits(runner.getLimits()); } 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 eb45d43..540ea6f 100644 --- a/src/test/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSourceSpec.groovy +++ b/src/test/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSourceSpec.groovy @@ -1,5 +1,9 @@ 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 @@ -35,6 +39,10 @@ class AnsibleResourceModelSourceSpec extends Specification { 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) when: Map nodeData = [:] From 647013d5363177d761d4038684bd6e7ad9ef8609 Mon Sep 17 00:00:00 2001 From: Alexander Abarca Date: Mon, 1 Jul 2024 12:47:17 -0400 Subject: [PATCH 15/19] Improved exception handlers --- .../ansible/ansible/AnsibleInventoryList.java | 19 ++++++++++++++++--- .../plugin/AnsibleResourceModelSource.java | 7 ++++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java b/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java index c3ea871..ad909ce 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java @@ -39,7 +39,7 @@ public class AnsibleInventoryList { * Executes Ansible command to bring all nodes from inventory * @return output in yaml format */ - public String getNodeList() throws Exception { + public String getNodeList() throws IOException, AnsibleException { List procArgs = new ArrayList<>(); procArgs.add(ANSIBLE_INVENTORY); @@ -91,11 +91,24 @@ public String getNodeList() throws Exception { } return stringBuilder.toString(); + } catch (IOException e) { + throw new AnsibleException("ERROR: Ansible IO failure: " + e.getMessage(), e, AnsibleException.AnsibleFailureReason.IOFailure); + } catch (InterruptedException e) { + if (proc != null) { + proc.destroy(); + } + Thread.currentThread().interrupt(); + throw new AnsibleException("ERROR: Ansible Execution Interrupted.", e, AnsibleException.AnsibleFailureReason.Interrupted); } catch (Exception e) { - System.err.println("error getNodeList: " + e.getMessage()); - return null; + if (proc != null) { + proc.destroy(); + } + throw new AnsibleException("ERROR: Ansible execution returned with non zero code.", e, AnsibleException.AnsibleFailureReason.Unknown); } finally { if (proc != null) { + proc.getErrorStream().close(); + proc.getInputStream().close(); + proc.getOutputStream().close(); proc.destroy(); } } 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 6ce617b..3bb1957 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java @@ -20,6 +20,7 @@ import com.google.gson.JsonPrimitive; import com.rundeck.plugins.ansible.ansible.AnsibleDescribable; import com.rundeck.plugins.ansible.ansible.AnsibleDescribable.AuthenticationType; +import com.rundeck.plugins.ansible.ansible.AnsibleException; import com.rundeck.plugins.ansible.ansible.AnsibleInventoryList; import com.rundeck.plugins.ansible.ansible.AnsibleRunner; import com.rundeck.plugins.ansible.ansible.InventoryList; @@ -399,7 +400,7 @@ public void processWithGatherFacts(NodeSetImpl nodes, AnsibleRunner.AnsibleRunne Files.copy(this.getClass().getClassLoader().getResourceAsStream(HOST_TPL_J2), tempDirectory.resolve(HOST_TPL_J2)); Files.copy(this.getClass().getClassLoader().getResourceAsStream(GATHER_HOSTS_YML), tempDirectory.resolve(GATHER_HOSTS_YML)); } catch (IOException e) { - throw new ResourceModelSourceException("Error copying files."); + throw new ResourceModelSourceException("Error copying files.", e); } runnerBuilder.tempDirectory(tempDirectory); @@ -731,8 +732,8 @@ public String getNodesFromInventory(AnsibleRunner.AnsibleRunnerBuilder runnerBui try { return inventoryList.getNodeList(); - } catch (Exception e) { - throw new ResourceModelSourceException(e.getMessage(),e); + } catch (IOException | AnsibleException e) { + throw new ResourceModelSourceException("Failed to get node list from ansible: ", e); } } From c3fc8c530da97d72020e2b2454f60237fc55e147 Mon Sep 17 00:00:00 2001 From: Alexander Abarca Date: Mon, 1 Jul 2024 18:09:31 -0400 Subject: [PATCH 16/19] Added error message on Ansible Runner exception --- .../plugins/ansible/plugin/AnsibleResourceModelSource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3bb1957..05101fd 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java @@ -421,7 +421,7 @@ public void processWithGatherFacts(NodeSetImpl nodes, AnsibleRunner.AnsibleRunne try { runner.run(); } catch (Exception e) { - throw new ResourceModelSourceException(e.getMessage(),e); + throw new ResourceModelSourceException("Failed Ansible Runner execution",e); } try { From adfd2a7653bf8c48fc2ca848c1a96efcbce52bd0 Mon Sep 17 00:00:00 2001 From: Alexander Abarca Date: Thu, 4 Jul 2024 09:33:13 -0400 Subject: [PATCH 17/19] Added review changes --- .../plugins/ansible/plugin/AnsibleResourceModelSource.java | 4 ++-- 1 file changed, 2 insertions(+), 2 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 05101fd..d173c13 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java @@ -393,14 +393,14 @@ public void processWithGatherFacts(NodeSetImpl nodes, AnsibleRunner.AnsibleRunne try { tempDirectory = Files.createTempDirectory("ansible-hosts"); } catch (IOException e) { - throw new ResourceModelSourceException("Error creating temporary directory.", e); + throw new ResourceModelSourceException("Error creating temporary directory: " + e.getMessage(), e); } try { Files.copy(this.getClass().getClassLoader().getResourceAsStream(HOST_TPL_J2), tempDirectory.resolve(HOST_TPL_J2)); Files.copy(this.getClass().getClassLoader().getResourceAsStream(GATHER_HOSTS_YML), tempDirectory.resolve(GATHER_HOSTS_YML)); } catch (IOException e) { - throw new ResourceModelSourceException("Error copying files.", e); + throw new ResourceModelSourceException("Error copying files: " + e.getMessage(), e); } runnerBuilder.tempDirectory(tempDirectory); From 0f5654a88b43dc0a90174e319cdd1eca667ea95e Mon Sep 17 00:00:00 2001 From: Alexander Abarca Date: Thu, 4 Jul 2024 11:09:09 -0400 Subject: [PATCH 18/19] Changed gather facts description --- .../com/rundeck/plugins/ansible/ansible/AnsibleDescribable.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleDescribable.java b/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleDescribable.java index 46649b9..fb80c3b 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleDescribable.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleDescribable.java @@ -241,7 +241,7 @@ public static String[] getValues() { public static Property GATHER_FACTS_PROP = PropertyUtil.bool( ANSIBLE_GATHER_FACTS, "Gather Facts", - "Gather fresh facts before importing? (recommended)", + "Gather fresh facts before importing? (Not recommended for large inventories)", true, "true" ); From 42fb6b0179b1fec0ca188d0ddfe59d1b1167d2cf Mon Sep 17 00:00:00 2001 From: Alexander Abarca Date: Thu, 4 Jul 2024 13:53:56 -0400 Subject: [PATCH 19/19] Added original exception message to the catch handlers --- .../ansible/ansible/AnsibleInventoryList.java | 4 +-- .../plugin/AnsibleResourceModelSource.java | 28 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java b/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java index ad909ce..df75627 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleInventoryList.java @@ -98,12 +98,12 @@ public String getNodeList() throws IOException, AnsibleException { proc.destroy(); } Thread.currentThread().interrupt(); - throw new AnsibleException("ERROR: Ansible Execution Interrupted.", e, AnsibleException.AnsibleFailureReason.Interrupted); + throw new AnsibleException("ERROR: Ansible Execution Interrupted: " + e.getMessage(), e, AnsibleException.AnsibleFailureReason.Interrupted); } catch (Exception e) { if (proc != null) { proc.destroy(); } - throw new AnsibleException("ERROR: Ansible execution returned with non zero code.", e, AnsibleException.AnsibleFailureReason.Unknown); + throw new AnsibleException("ERROR: Ansible execution returned with non zero code: " + e.getMessage(), e, AnsibleException.AnsibleFailureReason.Unknown); } finally { if (proc != null) { proc.getErrorStream().close(); 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 d173c13..db252f8 100644 --- a/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java +++ b/src/main/groovy/com/rundeck/plugins/ansible/plugin/AnsibleResourceModelSource.java @@ -189,7 +189,7 @@ public void configure(Properties configuration) throws ConfigurationException { try { sshTimeout = Integer.parseInt(str_sshTimeout); } catch (NumberFormatException e) { - throw new ConfigurationException("Can't parse timeout value : " + e.getMessage()); + throw new ConfigurationException("Can't parse timeout value : " + e.getMessage(), e); } } @@ -247,7 +247,7 @@ public AnsibleRunner.AnsibleRunnerBuilder buildAnsibleRunner() throws ResourceMo try { sshPrivateKey = new String(Files.readAllBytes(Paths.get(sshPrivateKeyFile))); } catch (IOException e) { - throw new ResourceModelSourceException("Could not read privatekey file " + sshPrivateKeyFile,e); + throw new ResourceModelSourceException("Could not read privatekey file " + sshPrivateKeyFile +": "+ e.getMessage(),e); } runnerBuilder.sshPrivateKey(sshPrivateKey); } @@ -257,7 +257,7 @@ public AnsibleRunner.AnsibleRunnerBuilder buildAnsibleRunner() throws ResourceMo String sshPrivateKey = getStorageContentString(sshPrivateKeyPath, storageTree); runnerBuilder.sshPrivateKey(sshPrivateKey); } catch (ConfigurationException e) { - throw new ResourceModelSourceException("Could not read private key from storage path " + sshPrivateKeyPath,e); + throw new ResourceModelSourceException("Could not read private key from storage path " + sshPrivateKeyPath +": "+ e.getMessage(),e); } } @@ -269,7 +269,7 @@ public AnsibleRunner.AnsibleRunnerBuilder buildAnsibleRunner() throws ResourceMo String sshPassphrase = getStorageContentString(sshPassphraseStoragePath, storageTree); runnerBuilder.sshPassphrase(sshPassphrase); } catch (ConfigurationException e) { - throw new ResourceModelSourceException("Could not read passphrase from storage path " + sshPassphraseStoragePath,e); + throw new ResourceModelSourceException("Could not read passphrase from storage path " + sshPassphraseStoragePath +": "+ e.getMessage(),e); } } } @@ -284,7 +284,7 @@ public AnsibleRunner.AnsibleRunnerBuilder buildAnsibleRunner() throws ResourceMo sshPassword = getStorageContentString(sshPasswordPath, storageTree); runnerBuilder.sshUsePassword(Boolean.TRUE).sshPass(sshPassword); } catch (ConfigurationException e) { - throw new ResourceModelSourceException("Could not read password from storage path " + sshPasswordPath,e); + throw new ResourceModelSourceException("Could not read password from storage path " + sshPasswordPath +": "+ e.getMessage(),e); } } } @@ -325,7 +325,7 @@ public AnsibleRunner.AnsibleRunnerBuilder buildAnsibleRunner() throws ResourceMo becomePassword = getStorageContentString(becamePasswordStoragePath, storageTree); runnerBuilder.becomePassword(becomePassword); } catch (Exception e) { - throw new ResourceModelSourceException("Could not read becomePassword from storage path " + becamePasswordStoragePath,e); + throw new ResourceModelSourceException("Could not read becomePassword from storage path " + becamePasswordStoragePath +": "+ e.getMessage(),e); } } @@ -341,7 +341,7 @@ public AnsibleRunner.AnsibleRunnerBuilder buildAnsibleRunner() throws ResourceMo try { vaultPassword = getStorageContentString(vaultPasswordPath, storageTree); } catch (Exception e) { - throw new ResourceModelSourceException("Could not read vaultPassword " + vaultPasswordPath,e); + throw new ResourceModelSourceException("Could not read vaultPassword " + vaultPasswordPath +": "+ e.getMessage(),e); } runnerBuilder.vaultPass(vaultPassword); } @@ -351,7 +351,7 @@ public AnsibleRunner.AnsibleRunnerBuilder buildAnsibleRunner() throws ResourceMo try { vaultPassword = new String(Files.readAllBytes(Paths.get(vaultFile))); } catch (IOException e) { - throw new ResourceModelSourceException("Could not read vault file " + vaultFile,e); + throw new ResourceModelSourceException("Could not read vault file " + vaultFile +": "+ e.getMessage(),e); } runnerBuilder.vaultPass(vaultPassword); } @@ -421,7 +421,7 @@ public void processWithGatherFacts(NodeSetImpl nodes, AnsibleRunner.AnsibleRunne try { runner.run(); } catch (Exception e) { - throw new ResourceModelSourceException("Failed Ansible Runner execution",e); + throw new ResourceModelSourceException("Failed Ansible Runner execution: " + e.getMessage(),e); } try { @@ -641,7 +641,7 @@ public void processWithGatherFacts(NodeSetImpl nodes, AnsibleRunner.AnsibleRunne directoryStream.close(); } } catch (IOException e) { - throw new ResourceModelSourceException("Error reading facts.", e); + throw new ResourceModelSourceException("Error reading facts: " + e.getMessage(), e); } try { @@ -659,7 +659,7 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOEx } }); } catch (IOException e) { - throw new ResourceModelSourceException("Error deleting temporary directory.", e); + throw new ResourceModelSourceException("Error deleting temporary directory: " + e.getMessage(), e); } } @@ -733,7 +733,7 @@ public String getNodesFromInventory(AnsibleRunner.AnsibleRunnerBuilder runnerBui try { return inventoryList.getNodeList(); } catch (IOException | AnsibleException e) { - throw new ResourceModelSourceException("Failed to get node list from ansible: ", e); + throw new ResourceModelSourceException("Failed to get node list from ansible: " + e.getMessage(), e); } } @@ -750,10 +750,10 @@ private byte[] getStorageContent(String storagePath, StorageTree storageTree) th return byteArrayOutputStream.toByteArray(); } catch (StorageException e) { throw new ConfigurationException("Failed to read the ssh private key for " + - "storage path: " + storagePath + ": " + e.getMessage()); + "storage path: " + storagePath + ": " + e.getMessage(), e); } catch (IOException e) { throw new ConfigurationException("Failed to read the ssh private key for " + - "storage path: " + storagePath + ": " + e.getMessage()); + "storage path: " + storagePath + ": " + e.getMessage(), e); } }