From c4a2d86b66fef9f1a572c0c3b255c44674d8b80f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JESTIN=20R=C3=A9my?= Date: Fri, 3 Feb 2017 13:52:05 +0100 Subject: [PATCH 1/6] Considered state.sls output --- .../output/SaltFullJsonReturnHandler.java | 131 ++++++++++++++++++ src/main/resources/defaultReturners.yaml | 4 +- 2 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/rundeck/plugin/salt/output/SaltFullJsonReturnHandler.java diff --git a/src/main/java/org/rundeck/plugin/salt/output/SaltFullJsonReturnHandler.java b/src/main/java/org/rundeck/plugin/salt/output/SaltFullJsonReturnHandler.java new file mode 100644 index 0000000..a01362f --- /dev/null +++ b/src/main/java/org/rundeck/plugin/salt/output/SaltFullJsonReturnHandler.java @@ -0,0 +1,131 @@ +/** +* Copyright (c) 2013, salesforce.com, inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, are permitted provided +* that the following conditions are met: +* +* Redistributions of source code must retain the above copyright notice, this list of conditions and the +* following disclaimer. +* +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and +* the following disclaimer in the documentation and/or other materials provided with the distribution. +* +* Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or +* promote products derived from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.rundeck.plugin.salt.output; + +import java.lang.reflect.Type; +import java.util.Map; +import java.util.ArrayList; +import java.util.List; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; +import java.util.Collection; +import java.util.HashMap; +import java.util.Set; + +/** +* Handler for generating {@link SaltReturnResponse} from minion json responses. +*/ +public class SaltFullJsonReturnHandler implements SaltReturnHandler { + + protected String exitCodeKey; + protected String standardOutputKey; + protected String standardErrorKey; + + public void setExitCodeKey(String exitCodeKey) { + this.exitCodeKey = exitCodeKey; + } + + public void setStandardOutputKey(String standardOutputKey) { + this.standardOutputKey = standardOutputKey; + } + + public void setStandardErrorKey(String standardErrorKey) { + this.standardErrorKey = standardErrorKey; + } + + @Override + public String toString() { + return "SaltFullJsonReturnHandler [exitCodeKey=" + exitCodeKey + ", standardOutputKey=" + standardOutputKey + + ", standardErrorKey=" + standardErrorKey + "]"; + } + + /** + * Deserializes a {@link SaltReturnResponse} from a salt minion json response using + * the specified exit code, standard output, and standard error keys. + * + * @param rawResponse + * a minion's full json response. + * + * @throws SaltReturnResponseParseException + * if there was an error interpreting the response + */ + @Override + public SaltReturnResponse extractResponse(String rawResponse) throws SaltReturnResponseParseException { + try { + Gson gson = new Gson(); + Object result = gson.fromJson(rawResponse, Object.class); + SaltReturnResponse response = new SaltReturnResponse(); + + if (exitCodeKey != null) { + List results=getValues(result, exitCodeKey); + Integer exitCode=new Integer(0); + for (String tmp : results) { + if (!"true".equalsIgnoreCase(tmp)) + exitCode=1; + } + response.setExitCode(exitCode); + } + + if (standardOutputKey != null) { + response.addOutput(getValues(result, standardOutputKey).toString()); + } + + if (standardErrorKey != null) { + response.addError(getValues(result, standardErrorKey).toString()); + } + + return response; + } catch (JsonSyntaxException e) { + throw new SaltReturnResponseParseException(e); + } + } + + private List getValues(Object object, String attribute) { + ArrayList attributeValues = new ArrayList(); + if (object instanceof Map) { + Map map = (Map) object; + for ( String key : (Set)map.keySet() ) { + if (attribute.equalsIgnoreCase(key)) { + attributeValues.add(map.get(key).toString()); + } + } + Collection values = map.values(); + for (Object value : values) + attributeValues.addAll(getValues(value, attribute)); + } + else if (object instanceof Collection) { + Collection values = (Collection) object; + for (Object value : values) { + attributeValues.addAll(getValues(value, attribute)); + } + } + return attributeValues; + } +} diff --git a/src/main/resources/defaultReturners.yaml b/src/main/resources/defaultReturners.yaml index 2ffeaa6..804ded3 100644 --- a/src/main/resources/defaultReturners.yaml +++ b/src/main/resources/defaultReturners.yaml @@ -2,9 +2,11 @@ defaultCommandParser: &defaultCommandParser !!org.rundeck.plugin.salt.output.SaltJsonReturnHandler {exitCodeKey: retcode, standardOutputKey: stdout, standardErrorKey: stderr} alwaysSuccessful: &alwaysSuccessful !!org.rundeck.plugin.salt.output.DefaultSaltReturnHandler { exitCode: 0 } +fullJsonCommandParser: &fullJsonCommandParser !!org.rundeck.plugin.salt.output.SaltFullJsonReturnHandler {exitCodeKey: result, standardOutputKey: comment, standardErrorKey: comment} handlerMappings: cmd.run_all: *defaultCommandParser + state.sls : *fullJsonCommandParser file.touch: *alwaysSuccessful file.append: *alwaysSuccessful - file.remove: *alwaysSuccessful + file.remove: *alwaysSuccessful \ No newline at end of file From 7831eb564edc1a509fa53c3141a6b321a59d6eca Mon Sep 17 00:00:00 2001 From: rmjstn Date: Fri, 3 Feb 2017 14:06:46 +0100 Subject: [PATCH 2/6] Considered state.sls output --- src/main/resources/defaultReturners.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/defaultReturners.yaml b/src/main/resources/defaultReturners.yaml index 804ded3..f1285f0 100644 --- a/src/main/resources/defaultReturners.yaml +++ b/src/main/resources/defaultReturners.yaml @@ -6,7 +6,7 @@ fullJsonCommandParser: &fullJsonCommandParser !!org.rundeck.plugin.salt.output.S handlerMappings: cmd.run_all: *defaultCommandParser - state.sls : *fullJsonCommandParser + state.sls : *fullJsonCommandParser file.touch: *alwaysSuccessful file.append: *alwaysSuccessful - file.remove: *alwaysSuccessful \ No newline at end of file + file.remove: *alwaysSuccessful From 1fd0b38a72368b536ba8768fb6f6edceb2d37f0a Mon Sep 17 00:00:00 2001 From: rmjstn Date: Fri, 3 Feb 2017 15:49:54 +0100 Subject: [PATCH 3/6] Considered state.sls output --- .../salt/output/SaltFullJsonReturnHandler.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/rundeck/plugin/salt/output/SaltFullJsonReturnHandler.java b/src/main/java/org/rundeck/plugin/salt/output/SaltFullJsonReturnHandler.java index a01362f..34e5b26 100644 --- a/src/main/java/org/rundeck/plugin/salt/output/SaltFullJsonReturnHandler.java +++ b/src/main/java/org/rundeck/plugin/salt/output/SaltFullJsonReturnHandler.java @@ -34,7 +34,6 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; import com.google.gson.JsonSyntaxException; -import com.google.gson.reflect.TypeToken; import java.util.Collection; import java.util.HashMap; import java.util.Set; @@ -85,6 +84,9 @@ public SaltReturnResponse extractResponse(String rawResponse) throws SaltReturnR if (exitCodeKey != null) { List results=getValues(result, exitCodeKey); + if (results.size() == 0) { + throw new SaltReturnResponseParseException("No " + exitCodeKey + " attribute in JSON output"); + } Integer exitCode=new Integer(0); for (String tmp : results) { if (!"true".equalsIgnoreCase(tmp)) @@ -94,11 +96,19 @@ public SaltReturnResponse extractResponse(String rawResponse) throws SaltReturnR } if (standardOutputKey != null) { - response.addOutput(getValues(result, standardOutputKey).toString()); + List results=getValues(result, standardOutputKey); + if (results.size() == 0) { + throw new SaltReturnResponseParseException("No " + standardOutputKey + " attribute in JSON output"); + } + response.addOutput(results.toString()); } if (standardErrorKey != null) { - response.addError(getValues(result, standardErrorKey).toString()); + List results=getValues(result, standardErrorKey); + if (results.size() == 0) { + throw new SaltReturnResponseParseException("No " + standardErrorKey + " attribute in JSON output"); + } + response.addError(results.toString()); } return response; From 91f2867dbba9bb915e9c5d32be1317c0bb9aff7d Mon Sep 17 00:00:00 2001 From: rmjstn Date: Fri, 3 Feb 2017 15:51:40 +0100 Subject: [PATCH 4/6] Considered state.sls output --- .../output/SaltFullJsonReturnHandlerTest.java | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 src/test/java/org/rundeck/plugin/salt/output/SaltFullJsonReturnHandlerTest.java diff --git a/src/test/java/org/rundeck/plugin/salt/output/SaltFullJsonReturnHandlerTest.java b/src/test/java/org/rundeck/plugin/salt/output/SaltFullJsonReturnHandlerTest.java new file mode 100644 index 0000000..428c091 --- /dev/null +++ b/src/test/java/org/rundeck/plugin/salt/output/SaltFullJsonReturnHandlerTest.java @@ -0,0 +1,112 @@ +/** + * Copyright (c) 2013, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package org.rundeck.plugin.salt.output; + +import java.util.Map; + +import org.junit.Assert; +import org.junit.Test; + +import com.google.common.collect.Maps; + +public class SaltFullJsonReturnHandlerTest { + + protected static final String EXIT_CODE_KEY = "result"; + protected static final String ERR_KEY = "comment"; + protected static final String OUT_KEY = "comment"; + protected static final String SAMPLE_JSON_TEMPLATE = "{\"service_\":{\"" + OUT_KEY + "\":\"%s\",\"start_time\":\"10:03:59.046156\",\"" + EXIT_CODE_KEY + "\":%s,\"duration\":144.388\"}}"; + + @Test + public void testExtractResponse() { + SaltFullJsonReturnHandler handler = new SaltFullJsonReturnHandler(); + handler.setExitCodeKey(EXIT_CODE_KEY); + handler.setStandardOutputKey(OUT_KEY); + handler.setStandardErrorKey(ERR_KEY); + + String result = "true"; + String comment = "some comment"; + String json = String.format(SAMPLE_JSON_TEMPLATE, comment, result, comment, result); + SaltReturnResponse response = handler.extractResponse(json); + Assert.assertEquals("Expected passed in exit code", new Integer(0), response.getExitCode()); + Assert.assertEquals("Expected single stdout line", 1, response.getStandardOutput().size()); + Assert.assertEquals("Expected passed in stdout line", "[" + comment + "]", response.getStandardOutput().get(0)); + Assert.assertEquals("Expected single stderr line", 1, response.getStandardError().size()); + Assert.assertEquals("Expected passed in stderr line", "[" + comment + "]", response.getStandardError().get(0)); + } + + @Test + public void testExtractResponseWithoutKeys() { + SaltFullJsonReturnHandler handler = new SaltFullJsonReturnHandler(); + + String result = "true"; + String comment = "some comment"; + String json = String.format(SAMPLE_JSON_TEMPLATE, comment, result, comment, result); + SaltReturnResponse response = handler.extractResponse(json); + Assert.assertNull("Expected not to parse exit code", response.getExitCode()); + Assert.assertTrue("Expected not to parse stdout", response.getStandardOutput().isEmpty()); + Assert.assertTrue("Expected not to parse stderr", response.getStandardError().isEmpty()); + } + + @Test(expected = SaltReturnResponseParseException.class) + public void testExtractResponseExitCodeKeySpecifiedButNoExitCode() { + SaltFullJsonReturnHandler handler = new SaltFullJsonReturnHandler(); + handler.setExitCodeKey("someotherkey"); + handler.setStandardOutputKey(OUT_KEY); + handler.setStandardErrorKey(ERR_KEY); + + String result = "true"; + String comment = "some comment"; + String json = String.format(SAMPLE_JSON_TEMPLATE, comment, result, comment, result); + handler.extractResponse(json); + } + + @Test(expected = SaltReturnResponseParseException.class) + public void testExtractResponseStandardOutputKeySpecifiedButNoStandardOutput() { + SaltFullJsonReturnHandler handler = new SaltFullJsonReturnHandler(); + handler.setExitCodeKey(EXIT_CODE_KEY); + handler.setStandardOutputKey("someotherkey"); + handler.setStandardErrorKey(ERR_KEY); + + String result = "true"; + String comment = "some comment"; + String json = String.format(SAMPLE_JSON_TEMPLATE, comment, result, comment, result); + handler.extractResponse(json); + } + + @Test(expected = SaltReturnResponseParseException.class) + public void testExtractResponseStandardErrorKeySpecifiedButNoStandardError() { + SaltFullJsonReturnHandler handler = new SaltFullJsonReturnHandler(); + handler.setExitCodeKey(EXIT_CODE_KEY); + handler.setStandardOutputKey(OUT_KEY); + handler.setStandardErrorKey("someotherkey"); + + String result = "true"; + String comment = "some comment"; + String json = String.format(SAMPLE_JSON_TEMPLATE, comment, result, comment, result); + handler.extractResponse(json); + } +} From 981b173eef29b259e07472d7132779f262afe462 Mon Sep 17 00:00:00 2001 From: rmjstn Date: Fri, 3 Feb 2017 16:16:03 +0100 Subject: [PATCH 5/6] Considered state.sls output --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6ee7a48..32e27ec 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ This plugin requires three properties that need to be configured for each step: - `SALT_API_END_POINT`: the URL of the salt-api endpoint (e.g. https://localhost:8000) - `Function`: the function to be passed to the salt-api call (excluding the target) --- For example, if you enter `test.ping` for the function value, the resulting salt call will be `salt <​yourHostName>​ test.ping`. The target will always default to the hostname of the Rundeck server. +-- For example, if you enter `test.ping` for the function value, the resulting salt call will be `salt <​yourHostName>​ test.ping`. The target will always default to the hostname of the Rundeck server. You can enter for example `cmd.run_all "ls -l /tmp"` or `state.sls test001` for the function value. - `SALT_API_EAUTH`: the authenticati​on mechanism that should be used by salt-api -- This would be the equivalent to the `-a` parameter being passed on the command line (e.g. `salt -a pam test.ping`) - `SALT_API_VERSION` (optional): The expected version of salt-api. Defaults to latest if left blank. From eefdf668b143250a134fec56a3c59f0705ec162c Mon Sep 17 00:00:00 2001 From: rmjstn Date: Fri, 3 Feb 2017 16:20:41 +0100 Subject: [PATCH 6/6] Considered state.sls output --- .../output/SaltFullJsonReturnHandler.java | 84 +++++++++---------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/src/main/java/org/rundeck/plugin/salt/output/SaltFullJsonReturnHandler.java b/src/main/java/org/rundeck/plugin/salt/output/SaltFullJsonReturnHandler.java index 34e5b26..91d0dd6 100644 --- a/src/main/java/org/rundeck/plugin/salt/output/SaltFullJsonReturnHandler.java +++ b/src/main/java/org/rundeck/plugin/salt/output/SaltFullJsonReturnHandler.java @@ -79,36 +79,36 @@ public String toString() { public SaltReturnResponse extractResponse(String rawResponse) throws SaltReturnResponseParseException { try { Gson gson = new Gson(); - Object result = gson.fromJson(rawResponse, Object.class); + Object result = gson.fromJson(rawResponse, Object.class); SaltReturnResponse response = new SaltReturnResponse(); if (exitCodeKey != null) { - List results=getValues(result, exitCodeKey); - if (results.size() == 0) { - throw new SaltReturnResponseParseException("No " + exitCodeKey + " attribute in JSON output"); - } - Integer exitCode=new Integer(0); - for (String tmp : results) { - if (!"true".equalsIgnoreCase(tmp)) - exitCode=1; - } - response.setExitCode(exitCode); + List results=getValues(result, exitCodeKey); + if (results.size() == 0) { + throw new SaltReturnResponseParseException("No " + exitCodeKey + " attribute in JSON output"); + } + Integer exitCode=new Integer(0); + for (String tmp : results) { + if (!"true".equalsIgnoreCase(tmp)) + exitCode=1; + } + response.setExitCode(exitCode); } if (standardOutputKey != null) { - List results=getValues(result, standardOutputKey); - if (results.size() == 0) { - throw new SaltReturnResponseParseException("No " + standardOutputKey + " attribute in JSON output"); - } - response.addOutput(results.toString()); + List results=getValues(result, standardOutputKey); + if (results.size() == 0) { + throw new SaltReturnResponseParseException("No " + standardOutputKey + " attribute in JSON output"); + } + response.addOutput(results.toString()); } if (standardErrorKey != null) { - List results=getValues(result, standardErrorKey); - if (results.size() == 0) { - throw new SaltReturnResponseParseException("No " + standardErrorKey + " attribute in JSON output"); - } - response.addError(results.toString()); + List results=getValues(result, standardErrorKey); + if (results.size() == 0) { + throw new SaltReturnResponseParseException("No " + standardErrorKey + " attribute in JSON output"); + } + response.addError(results.toString()); } return response; @@ -117,25 +117,25 @@ public SaltReturnResponse extractResponse(String rawResponse) throws SaltReturnR } } - private List getValues(Object object, String attribute) { - ArrayList attributeValues = new ArrayList(); - if (object instanceof Map) { - Map map = (Map) object; - for ( String key : (Set)map.keySet() ) { - if (attribute.equalsIgnoreCase(key)) { - attributeValues.add(map.get(key).toString()); - } - } - Collection values = map.values(); - for (Object value : values) - attributeValues.addAll(getValues(value, attribute)); - } - else if (object instanceof Collection) { - Collection values = (Collection) object; - for (Object value : values) { - attributeValues.addAll(getValues(value, attribute)); - } - } - return attributeValues; - } + private List getValues(Object object, String attribute) { + ArrayList attributeValues = new ArrayList(); + if (object instanceof Map) { + Map map = (Map) object; + for ( String key : (Set)map.keySet() ) { + if (attribute.equalsIgnoreCase(key)) { + attributeValues.add(map.get(key).toString()); + } + } + Collection values = map.values(); + for (Object value : values) + attributeValues.addAll(getValues(value, attribute)); + } + else if (object instanceof Collection) { + Collection values = (Collection) object; + for (Object value : values) { + attributeValues.addAll(getValues(value, attribute)); + } + } + return attributeValues; + } }