-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit da36877
Showing
10 changed files
with
521 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
.idea | ||
build | ||
.gradle | ||
gradle |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# jq-JSON mapper log filter plugin | ||
|
||
This plugin maps a JSON String output from a step to key/value context variables. | ||
It uses the [jackson-jq](https://github.com/eiiches/jackson-jq) java library. | ||
|
||
## Build | ||
|
||
``` | ||
gradle clean install | ||
``` | ||
|
||
## Install | ||
|
||
``` | ||
cp build/lib/jq-json-logfilter-X.X.X.jar $RDECKBASE/libext | ||
``` | ||
|
||
## How to use | ||
|
||
- Add a Log filter to the workflow step what has a json output string | ||
|
||
![Add Filter](examples/example1.png) | ||
|
||
More information about the available jq Filters [here](https://github.com/eiiches/jackson-jq#implementation-status-and-current-limitations) | ||
|
||
|
||
- Use the context variables on the next steps | ||
|
||
![Add Filter](examples/example2.png) | ||
|
||
![Add Filter](examples/example3.png) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
plugins { | ||
id 'groovy' | ||
id 'java' | ||
id 'pl.allegro.tech.build.axion-release' version '1.7.1' | ||
} | ||
|
||
group 'com.rundeck.plugin' | ||
|
||
ext.rundeckVersion='3.0.6-20180917' | ||
defaultTasks 'clean','build' | ||
apply plugin: 'java' | ||
apply plugin: 'idea' | ||
|
||
sourceCompatibility = 1.8 | ||
ext.rundeckPluginVersion= '1.2' | ||
|
||
scmVersion { | ||
ignoreUncommittedChanges = false | ||
tag { | ||
prefix = '' | ||
versionSeparator = '' | ||
def origDeserialize=deserialize | ||
//apend .0 to satisfy semver if the tag version is only X.Y | ||
deserialize = { config, position, tagName -> | ||
def orig = origDeserialize(config, position, tagName) | ||
if (orig.split('\\.').length < 3) { | ||
orig += ".0" | ||
} | ||
orig | ||
} | ||
} | ||
} | ||
project.version = scmVersion.version | ||
|
||
/** | ||
* Set this to a comma-separated list of full classnames of your implemented Rundeck | ||
* plugins. | ||
*/ | ||
ext.pluginClassNames='com.rundeck.plugins.logging.JsonLogFilter' | ||
|
||
|
||
sourceCompatibility = 1.8 | ||
|
||
repositories { | ||
mavenCentral() | ||
} | ||
|
||
configurations{ | ||
//declare custom pluginLibs configuration to include only libs for this plugin | ||
pluginLibs | ||
|
||
//declare compile to extend from pluginLibs so it inherits the dependencies | ||
compile{ | ||
extendsFrom pluginLibs | ||
} | ||
} | ||
|
||
|
||
dependencies { | ||
compile 'org.codehaus.groovy:groovy-all:2.3.11' | ||
|
||
compile group: 'org.rundeck', name: 'rundeck-core', version: rundeckVersion | ||
pluginLibs group: 'net.thisptr', name: 'jackson-jq', version: '0.0.9' | ||
|
||
|
||
testCompile group: 'junit', name: 'junit', version: '4.12' | ||
testCompile "org.spockframework:spock-core:0.7-groovy-2.0" | ||
} | ||
|
||
|
||
// task to copy plugin libs to output/lib dir | ||
task copyToLib(type: Copy) { | ||
into "$buildDir/output/lib" | ||
from configurations.pluginLibs | ||
} | ||
|
||
|
||
jar { | ||
from "$buildDir/output" | ||
manifest { | ||
def libList = configurations.pluginLibs.collect{'lib/'+it.name}.join(' ') | ||
attributes 'Rundeck-Plugin-Classnames': pluginClassNames | ||
attributes 'Rundeck-Plugin-File-Version': version | ||
attributes 'Rundeck-Plugin-Version': rundeckPluginVersion, 'Rundeck-Plugin-Archive': 'true' | ||
attributes 'Rundeck-Plugin-Libs': "${libList}" | ||
attributes 'Main-Class': "io.github.valfadeev.rundeck.plugin.vault.VaultStoragePlugin" | ||
attributes 'Class-Path': "${libList} lib/rundeck-core-${rundeckVersion}.jar" | ||
} | ||
} | ||
//set jar task to depend on copyToLib | ||
jar.dependsOn(copyToLib) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
<joblist> | ||
<job> | ||
<defaultTab>summary</defaultTab> | ||
<description></description> | ||
<executionEnabled>true</executionEnabled> | ||
<group>JSON</group> | ||
<loglevel>INFO</loglevel> | ||
<name>test-json-array</name> | ||
<nodeFilterEditable>false</nodeFilterEditable> | ||
<scheduleEnabled>true</scheduleEnabled> | ||
<sequence keepgoing='false' strategy='node-first'> | ||
<command> | ||
<plugins> | ||
<LogFilter type='json-mapper'> | ||
<config> | ||
<filter>.[]</filter> | ||
<logData>true</logData> | ||
<prefix>data</prefix> | ||
</config> | ||
</LogFilter> | ||
</plugins> | ||
<script><![CDATA[cat <<-END | ||
{ | ||
"result": { | ||
"parent": { | ||
"link": "https://rundeck-test/api", | ||
"value": "123345" | ||
}, | ||
"user": "rundeck", | ||
"created_by": { | ||
"link": "https://rundeck-test/user/1", | ||
"value": "123345" | ||
}, | ||
"created_date": "2018-10-03 19:17:54", | ||
"status": "1", | ||
"closed_date": "2018-10-03 19:46:49" | ||
} | ||
} | ||
END]]></script> | ||
<scriptargs /> | ||
</command> | ||
<command> | ||
<exec>echo ${data.created_date}</exec> | ||
</command> | ||
</sequence> | ||
</job> | ||
</joblist> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
<joblist> | ||
<job> | ||
<defaultTab>summary</defaultTab> | ||
<description></description> | ||
<executionEnabled>true</executionEnabled> | ||
<group>JSON</group> | ||
<loglevel>INFO</loglevel> | ||
<name>test-json-simple</name> | ||
<nodeFilterEditable>false</nodeFilterEditable> | ||
<scheduleEnabled>true</scheduleEnabled> | ||
<sequence keepgoing='false' strategy='node-first'> | ||
<command> | ||
<plugins> | ||
<LogFilter type='json-mapper'> | ||
<config> | ||
<filter>.</filter> | ||
<logData>true</logData> | ||
<prefix>data</prefix> | ||
</config> | ||
</LogFilter> | ||
</plugins> | ||
<script><![CDATA[cat <<-END | ||
{ | ||
"result": { | ||
"parent": { | ||
"link": "https://rundeck-test/api", | ||
"value": "123345" | ||
}, | ||
"user": "rundeck", | ||
"created_by": { | ||
"link": "https://rundeck-test/user/1", | ||
"value": "123345" | ||
}, | ||
"created_date": "2018-10-03 19:17:54", | ||
"status": "1", | ||
"closed_date": "2018-10-03 19:46:49" | ||
} | ||
} | ||
END]]></script> | ||
<scriptargs /> | ||
</command> | ||
<command> | ||
<exec>echo ${data.result.created_date}</exec> | ||
</command> | ||
</sequence> | ||
</job> | ||
</joblist> |
165 changes: 165 additions & 0 deletions
165
src/main/groovy/com/rundeck/plugins/logging/JsonLogFilter.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
package com.rundeck.plugins.logging | ||
|
||
import com.dtolabs.rundeck.core.execution.workflow.OutputContext | ||
import com.dtolabs.rundeck.core.logging.LogEventControl | ||
import com.dtolabs.rundeck.core.logging.LogLevel | ||
import com.dtolabs.rundeck.core.logging.PluginLoggingContext | ||
import com.dtolabs.rundeck.core.plugins.Plugin | ||
import com.dtolabs.rundeck.plugins.descriptions.PluginDescription | ||
import com.dtolabs.rundeck.plugins.descriptions.PluginProperty | ||
import com.dtolabs.rundeck.plugins.logging.LogFilterPlugin | ||
import com.fasterxml.jackson.databind.JsonNode | ||
import com.fasterxml.jackson.databind.ObjectMapper | ||
import com.fasterxml.jackson.databind.node.JsonNodeType | ||
import net.thisptr.jackson.jq.JsonQuery | ||
import net.thisptr.jackson.jq.Scope | ||
|
||
@Plugin(name = JsonLogFilter.PROVIDER_NAME, service = 'LogFilter') | ||
@PluginDescription(title = 'JSON jq key/value mapper', | ||
description = 'Map a json logoutput on key/value to context data using jq filters') | ||
class JsonLogFilter implements LogFilterPlugin{ | ||
public static final String PROVIDER_NAME = 'json-mapper' | ||
|
||
@PluginProperty( | ||
title = 'jq Filter', | ||
description = '''Add a jq Filter''', | ||
defaultValue=".", | ||
required=true | ||
|
||
) | ||
String filter | ||
|
||
@PluginProperty( | ||
title = 'Prefix', | ||
description = '''(Optional) Result prefix''', | ||
defaultValue="result" | ||
) | ||
String prefix | ||
|
||
@PluginProperty( | ||
title = 'Log Data', | ||
description = '''If true, log the captured data''', | ||
defaultValue = 'false' | ||
) | ||
Boolean logData | ||
|
||
private StringBuffer buffer; | ||
OutputContext outputContext | ||
Map<String, String> allData | ||
private ObjectMapper mapper | ||
|
||
|
||
@Override | ||
void init(final PluginLoggingContext context) { | ||
outputContext = context.getOutputContext() | ||
buffer = new StringBuffer() | ||
mapper = new ObjectMapper() | ||
allData = [:] | ||
|
||
} | ||
|
||
@Override | ||
void handleEvent(final PluginLoggingContext context, final LogEventControl event) { | ||
if (event.eventType == 'log' && event.loglevel == LogLevel.NORMAL && event.message?.length() > 0) { | ||
buffer.append(event.message) | ||
} | ||
|
||
} | ||
|
||
@Override | ||
void complete(final PluginLoggingContext context) { | ||
|
||
if(buffer.size()>0) { | ||
//apply json transform | ||
this.processJson(context) | ||
|
||
if (logData) { | ||
context.log( | ||
2, | ||
mapper.writeValueAsString(allData), | ||
[ | ||
'content-data-type' : 'application/json', | ||
'content-meta:table-title': 'Key Value Data: Results' | ||
] | ||
) | ||
} | ||
} | ||
} | ||
|
||
void processJson(final PluginLoggingContext context){ | ||
|
||
try{ | ||
|
||
Scope rootScope = Scope.newEmptyScope() | ||
rootScope.loadFunctions(Scope.class.getClassLoader()) | ||
JsonQuery q = JsonQuery.compile(filter) | ||
JsonNode inData = mapper.readTree(buffer.toString()); | ||
|
||
List<JsonNode> out = q.apply(rootScope, inData) | ||
|
||
out.each {it-> | ||
//process object | ||
if(it.getNodeType()==JsonNodeType.OBJECT){ | ||
def map = it.fields() | ||
for (Map.Entry<String, JsonNode> entry : map) { | ||
this.iterateJsonObject(entry) | ||
} | ||
}else{ | ||
if(it.getNodeType()==JsonNodeType.ARRAY){ | ||
this.iterateArray(it.elements()) | ||
}else{ | ||
allData.put(prefix, it.toString()) | ||
} | ||
} | ||
} | ||
|
||
outputContext.addOutput("data",allData) | ||
|
||
}catch(Exception exception){ | ||
context.log(0, "[error] cannot map the json output: " + exception.message) | ||
} | ||
} | ||
|
||
def iterateArray(def list){ | ||
|
||
Integer i=0 | ||
list.each{it-> | ||
if(it.getNodeType() == JsonNodeType.STRING){ | ||
allData.put(prefix +"."+ i.toString(),it.textValue()) | ||
}else{ | ||
allData.put(prefix +"."+ i.toString(),it.toString()) | ||
} | ||
|
||
i++ | ||
} | ||
} | ||
|
||
def iterateJsonObject(Map.Entry<String, JsonNode> entry, String path=""){ | ||
|
||
def key = entry.getKey() | ||
def value = entry.getValue() | ||
def newPath | ||
|
||
if(!path){ | ||
newPath=key | ||
}else{ | ||
newPath=path + "."+ key | ||
} | ||
|
||
if(value.getNodeType()== JsonNodeType.OBJECT){ | ||
for (Map.Entry<String, JsonNode> subKey : entry.getValue().fields()) { | ||
iterateJsonObject(subKey, newPath) | ||
} | ||
}else{ | ||
def extractValue | ||
if(value.getNodeType() == JsonNodeType.STRING){ | ||
extractValue=value.textValue() | ||
}else{ | ||
extractValue = value.toString() | ||
} | ||
|
||
allData.put(newPath,extractValue) | ||
} | ||
} | ||
|
||
} |
Oops, something went wrong.