Skip to content

Commit

Permalink
Merge pull request #4 from rundeck/nodeexec-and-packing
Browse files Browse the repository at this point in the history
Node-Executor  + packaging
  • Loading branch information
ltamaster authored Oct 17, 2018
2 parents ebcbe3a + 6ffbee0 commit b99f9d7
Show file tree
Hide file tree
Showing 9 changed files with 342 additions and 1 deletion.
66 changes: 66 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
plugins {
id 'groovy'
id 'application'
id "nebula.ospackage" version "4.8.0"
id 'pl.allegro.tech.build.axion-release' version '1.4.1'
id 'com.github.johnrengelman.shadow' version '2.0.3'

}

mainClassName = 'com.rundeck.plugin.Generator'
applicationName = 'rundeck-plugin-bootstrap'
ext.distInstallPath = '/var/lib/rundeck-pb'
defaultTasks 'clean', 'build'

dependencies {
compile 'org.codehaus.groovy:groovy-all:2.4.13'
Expand All @@ -19,3 +26,62 @@ repositories {
mavenLocal()
maven { url "https://jitpack.io" }
}

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
}
}
}

allprojects {
project.version = scmVersion.version
ext.rpmVersion=project.version.replaceAll('-', '.')
}


//force distZip/distTar artifacts to be overwritten by shadow versions
shadowDistZip.mustRunAfter distZip
shadowDistTar.mustRunAfter distTar

/**
* Define rpm/deb details
*/
ospackage {
version = rpmVersion
release = 1
summary = "Bootstrap your Rundeck plugin development with this easy command line utility."
packageDescription = "The rundeck-plugin-bootstrap program provides an easy command line utility to create plugins for rundeck"
url = 'https://github.com/rundeck/plugin-bootstrap'
vendor = 'Rundeck, Inc.'
license = 'APL'
os = 'LINUX'
packageGroup = 'System'
user = 'root'

//packaging includes the shadowDistZip contents, exclude *.bat
from(zipTree(shadowDistZip.outputs.files.singleFile)) {
exclude '**/*.bat'
into distInstallPath
}

def archivedir=shadowDistZip.archiveName - ".${shadowDistZip.extension}"

//symlink /usr/bin/rd to the rd script
link("/usr/bin/${applicationName}", "${distInstallPath}/${archivedir}/bin/${applicationName}")
}

//depend on the shadow artifact
buildDeb.dependsOn shadowDistZip
buildRpm.dependsOn shadowDistZip
assemble.dependsOn buildRpm, buildDeb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class JavaPluginTemplateGenerator extends AbstractTemplateGenerator {
private static final String TEMPLATE_BASE = "templates/java-plugin/"
private static final String JAVA_STRUCTURE = "java-plugin.structure"

private static final List ALLOWED_TEMPLATES = ["ResourceModelSource","Notification","WorkflowStep","WorkflowNodeStep","LogFilter"]
private static final List ALLOWED_TEMPLATES = ["ResourceModelSource","Notification","WorkflowStep","WorkflowNodeStep","LogFilter","NodeExecutor"]

@Override
Map makeTemplateProperties(final String pluginName, final String providedService) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package com.plugin.${javaPluginClass.toLowerCase()};

import com.dtolabs.rundeck.core.common.INodeEntry;
import com.dtolabs.rundeck.core.execution.ExecutionContext;
import com.dtolabs.rundeck.core.execution.ExecutionLogger;
import com.dtolabs.rundeck.core.execution.service.NodeExecutor;
import com.dtolabs.rundeck.core.execution.service.NodeExecutorResult;
import com.dtolabs.rundeck.core.execution.service.NodeExecutorResultImpl;
import com.dtolabs.rundeck.core.execution.utils.ResolverUtil;
import com.dtolabs.rundeck.core.execution.workflow.steps.StepFailureReason;
import com.dtolabs.rundeck.core.plugins.Plugin;
import com.dtolabs.rundeck.core.plugins.configuration.Describable;
import com.dtolabs.rundeck.core.plugins.configuration.Description;
import com.dtolabs.rundeck.plugins.ServiceNameConstants;
import com.dtolabs.rundeck.plugins.descriptions.PluginDescription;
import com.dtolabs.rundeck.plugins.util.DescriptionBuilder;
import com.dtolabs.rundeck.plugins.util.PropertyBuilder;
import java.util.Arrays;

@Plugin(service=ServiceNameConstants.NodeExecutor,name="${sanitizedPluginName}")
@PluginDescription(title="${pluginName}", description="My WorkflowStep plugin description")
public class ${javaPluginClass} implements NodeExecutor, Describable{

public static final String SERVICE_PROVIDER_NAME = "${sanitizedPluginName}";

public static final String PROJ_PROP_PREFIX = "project.";
public static final String FWK_PROP_PREFIX = "framework.";

public static final String SIMPLE_CONFIG_STRING = "exampleConfig";
public static final String SIMPLE_CONFIG_BOOLEAN = "forceFail";
public static final String SIMPLE_CONFIG_SELECT= "exampleSelect";

/**
* Overriding this method gives the plugin a chance to take part in building the {@link
* com.dtolabs.rundeck.core.plugins.configuration.Description} presented by this plugin. This subclass can use the
* {@link DescriptionBuilder} to modify all aspects of the description, add or remove properties, etc.
*/
@Override
public Description getDescription() {
DescriptionBuilder builder = DescriptionBuilder.builder()
.name(SERVICE_PROVIDER_NAME)
.title("${pluginName}")
.description("Example Workflow Step")
.property(PropertyBuilder.builder()
.string(SIMPLE_CONFIG_STRING)
.title("Example String")
.description("Example description")
.required(true)
.build()
)
.property(PropertyBuilder.builder()
.booleanType(SIMPLE_CONFIG_BOOLEAN)
.title("Force Fail")
.description("Example Boolean, Force Fail Command?")
.required(false)
.defaultValue("false")
.build()
)
.property(PropertyBuilder.builder()
.freeSelect(SIMPLE_CONFIG_SELECT)
.title("Example Free Select")
.description("Example Free Select")
.required(false)
.defaultValue("Blue")
.values("Blue", "Beige", "Black")
.build()
);

//mapping config input on project and framework level
builder.mapping(SIMPLE_CONFIG_STRING, PROJ_PROP_PREFIX + SIMPLE_CONFIG_STRING);
builder.frameworkMapping(SIMPLE_CONFIG_STRING, FWK_PROP_PREFIX + SIMPLE_CONFIG_STRING);

builder.mapping(SIMPLE_CONFIG_BOOLEAN, PROJ_PROP_PREFIX + SIMPLE_CONFIG_BOOLEAN);
builder.frameworkMapping(SIMPLE_CONFIG_BOOLEAN, FWK_PROP_PREFIX + SIMPLE_CONFIG_BOOLEAN);

builder.mapping(SIMPLE_CONFIG_SELECT, PROJ_PROP_PREFIX + SIMPLE_CONFIG_SELECT);
builder.frameworkMapping(SIMPLE_CONFIG_SELECT, FWK_PROP_PREFIX + SIMPLE_CONFIG_SELECT);

return builder.build();
}

@Override
public NodeExecutorResult executeCommand(ExecutionContext context, String[] command, INodeEntry node) {
ExecutionLogger logger= context.getExecutionLogger();

String exampleValue = ResolverUtil.resolveProperty(SIMPLE_CONFIG_STRING,
null,
node,
context.getFramework().getFrameworkProjectMgr().getFrameworkProject(context.getFrameworkProject()),
context.getFramework());


String exampleFreeSelect = ResolverUtil.resolveProperty(SIMPLE_CONFIG_SELECT,
null,
node,
context.getFramework().getFrameworkProjectMgr().getFrameworkProject(context.getFrameworkProject()),
context.getFramework());

Boolean forceFail = ResolverUtil.resolveBooleanProperty(SIMPLE_CONFIG_BOOLEAN,
false,
node,
context.getFramework().getFrameworkProjectMgr().getFrameworkProject(context.getFrameworkProject()),
context.getFramework());

logger.log(2,"[demo-info] Running command: " + Arrays.toString(command) + " on node " + node.getNodename());
logger.log(2,"[demo-info] node attribute hostname: " + node.getAttributes().get("hostname"));
logger.log(2,"[demo-info] node attribute osFamily: " + node.getAttributes().get("osFamily"));
logger.log(2,"[demo-info] Example Value: " + exampleValue);
logger.log(2,"[demo-info] ExampleFree Value: " + exampleFreeSelect);
logger.log(2,"[demo-info] forceFail Value: " + forceFail);
if (forceFail) {
logger.log(0,"[demo-error] force to fail");
return NodeExecutorResultImpl.createFailure(
StepFailureReason.ConfigurationFailure,
"[demo-error] Error connecting with node '" + node.getNodename() + "'",
node
);
}

return NodeExecutorResultImpl.createSuccess(node);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.plugin.${javaPluginClass.toLowerCase()}

import com.dtolabs.rundeck.plugins.step.PluginStepContext
import com.dtolabs.rundeck.core.execution.ExecutionContext
import com.dtolabs.rundeck.core.execution.ExecutionLogger
import com.dtolabs.rundeck.core.execution.workflow.steps.StepException
import com.dtolabs.rundeck.plugins.PluginLogger
import com.dtolabs.rundeck.core.common.Framework
import com.dtolabs.rundeck.core.common.INodeEntry;
import com.dtolabs.rundeck.core.common.IRundeckProject
import spock.lang.Specification
import com.dtolabs.rundeck.core.common.ProjectManager
import com.dtolabs.rundeck.core.common.IRundeckProject

class ${javaPluginClass}Spec extends Specification {

def getContext(ExecutionLogger logger, Boolean fail){

def manager = Mock(ProjectManager){
getFrameworkProject(_)>> Mock(IRundeckProject) {
hasProperty(('project.exampleConfig')) >> true
getProperty(('project.exampleConfig')) >> "123345"
hasProperty(('project.exampleSelect')) >> true
getProperty(('project.exampleSelect')) >> "Blue"
hasProperty(('project.forceFail')) >> fail
getProperty(('project.forceFail')) >> fail
}
}

Mock(ExecutionContext){
getExecutionLogger()>>logger
getFrameworkProject() >> "test"
getFramework() >> Mock(Framework) {
getFrameworkProjectMgr() >> manager
}

}
}

def "check Boolean parameter"(){

given:

String[] command = ["ls","-lrt"]
def logger = Mock(ExecutionLogger)
def example = new ${javaPluginClass}()
def context = getContext(logger,true)
def node = Mock(INodeEntry){
getNodename()>>"test"
getAttributes()>>["hostname":"Test","osFamily":"linux","forceFail":"true"]
}

when:
example.executeCommand(context, command, node)

then:
1 * logger.log(0, '[demo-error] force to fail')

}

def "run OK"(){

given:

String[] command = ["ls","-lrt"]
def logger = Mock(ExecutionLogger)
def example = new ${javaPluginClass}()
def context = getContext(logger,false)
def node = Mock(INodeEntry){
getNodename()>>"test"
getAttributes()>>["hostname":"Test","osFamily":"linux"]
}

when:
example.executeCommand(context, command, node)

then:
1 * logger.log(2, '[demo-info] Running command: [ls, -lrt] on node test')

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# ${pluginName} Rundeck Plugin

This is a node executor plugin.

Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
version = '0.1.0'
defaultTasks 'clean','build'
apply plugin: 'java'
apply plugin: 'groovy'
apply plugin: 'idea'
sourceCompatibility = 1.8
ext.rundeckPluginVersion= '2.0'
ext.rundeckVersion= '${rundeckVersion}'


repositories {
mavenLocal()
mavenCentral()
}

dependencies {
compile 'org.rundeck:rundeck-core:3.0.1+'

testCompile 'junit:junit:4.12'
testCompile "org.codehaus.groovy:groovy-all:2.4.15"
testCompile "org.spockframework:spock-core:1.0-groovy-2.4"
testCompile "cglib:cglib-nodep:2.2.2"
testCompile group: 'org.objenesis', name: 'objenesis', version: '1.2'
}

ext.pluginClassNames='com.plugin.${sanitizedPluginName}.${javaPluginClass}'
jar {
manifest {
attributes 'Rundeck-Plugin-Classnames': pluginClassNames
attributes 'Rundeck-Plugin-File-Version': version
attributes 'Rundeck-Plugin-Name': '${pluginName}'
attributes 'Rundeck-Plugin-Description': 'Provide a short description of your plugin here.'
attributes 'Rundeck-Plugin-Rundeck-Compatibility-Version': '3.x'
attributes 'Rundeck-Plugin-Tags': 'java,notification'
attributes 'Rundeck-Plugin-Version': rundeckPluginVersion, 'Rundeck-Plugin-Archive': 'true'
}
from("rundeck-verb-artifact.yaml") {
into("")
}
}

task wrapper(type: Wrapper) {
gradleVersion = '4.4.1'
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
build.gradle.template->build.gradle
README.md.template->README.md
icon.png->src/main/resources/resources/icon.png
Plugin.java.template->src/main/java/com/plugin/${javaPluginClass.toLowerCase()}/${javaPluginClass}.java
PluginSpec.groovy.template->src/test/groovy/com/plugin/${javaPluginClass.toLowerCase()}/${javaPluginClass}Spec.groovy

Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,20 @@ class JavaPluginTemplateGeneratorTest extends Specification {
new File(tmpDir,"/my-logfilter-plugin/src/test/groovy/com/plugin/mylogfilterplugin/MyLogfilterPluginSpec.groovy").exists()
}

def "Create NodeExecutor Template"() {
when:
File tmpDir = File.createTempDir()
JavaPluginTemplateGenerator generator = new JavaPluginTemplateGenerator()
generator.createTemplate("My NodeExecutor Plugin","NodeExecutor",tmpDir.absolutePath)
int compileResult = TestUtils.buildGradle(new File(tmpDir,"my-nodeexecutor-plugin"))

then:
compileResult == 0
new File(tmpDir,"/my-nodeexecutor-plugin/build.gradle").exists()
new File(tmpDir,"/my-nodeexecutor-plugin/src/main/resources/resources/icon.png").exists()
new File(tmpDir,"/my-nodeexecutor-plugin/README.md").exists()
new File(tmpDir,"/my-nodeexecutor-plugin/src/main/java/com/plugin/mynodeexecutorplugin/MyNodeexecutorPlugin.java").exists()
new File(tmpDir,"/my-nodeexecutor-plugin/src/test/groovy/com/plugin/mynodeexecutorplugin/MyNodeexecutorPluginSpec.groovy").exists()
}

}

0 comments on commit b99f9d7

Please sign in to comment.