Skip to content

Commit 268c6bf

Browse files
committed
2 parents be46ae1 + b99f9d7 commit 268c6bf

9 files changed

Lines changed: 342 additions & 1 deletion

File tree

build.gradle

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
plugins {
22
id 'groovy'
33
id 'application'
4+
id "nebula.ospackage" version "4.8.0"
5+
id 'pl.allegro.tech.build.axion-release' version '1.4.1'
6+
id 'com.github.johnrengelman.shadow' version '2.0.3'
7+
48
}
59

610
mainClassName = 'com.rundeck.plugin.Generator'
11+
applicationName = 'rundeck-plugin-bootstrap'
12+
ext.distInstallPath = '/var/lib/rundeck-pb'
13+
defaultTasks 'clean', 'build'
714

815
dependencies {
916
compile 'org.codehaus.groovy:groovy-all:2.4.13'
@@ -19,3 +26,62 @@ repositories {
1926
mavenLocal()
2027
maven { url "https://jitpack.io" }
2128
}
29+
30+
scmVersion {
31+
ignoreUncommittedChanges = false
32+
tag {
33+
prefix = ''
34+
versionSeparator = ''
35+
def origDeserialize=deserialize
36+
//apend .0 to satisfy semver if the tag version is only X.Y
37+
deserialize = { config, position, tagName ->
38+
def orig = origDeserialize(config, position, tagName)
39+
if (orig.split('\\.').length < 3) {
40+
orig += ".0"
41+
}
42+
orig
43+
}
44+
}
45+
}
46+
47+
allprojects {
48+
project.version = scmVersion.version
49+
ext.rpmVersion=project.version.replaceAll('-', '.')
50+
}
51+
52+
53+
//force distZip/distTar artifacts to be overwritten by shadow versions
54+
shadowDistZip.mustRunAfter distZip
55+
shadowDistTar.mustRunAfter distTar
56+
57+
/**
58+
* Define rpm/deb details
59+
*/
60+
ospackage {
61+
version = rpmVersion
62+
release = 1
63+
summary = "Bootstrap your Rundeck plugin development with this easy command line utility."
64+
packageDescription = "The rundeck-plugin-bootstrap program provides an easy command line utility to create plugins for rundeck"
65+
url = 'https://github.com/rundeck/plugin-bootstrap'
66+
vendor = 'Rundeck, Inc.'
67+
license = 'APL'
68+
os = 'LINUX'
69+
packageGroup = 'System'
70+
user = 'root'
71+
72+
//packaging includes the shadowDistZip contents, exclude *.bat
73+
from(zipTree(shadowDistZip.outputs.files.singleFile)) {
74+
exclude '**/*.bat'
75+
into distInstallPath
76+
}
77+
78+
def archivedir=shadowDistZip.archiveName - ".${shadowDistZip.extension}"
79+
80+
//symlink /usr/bin/rd to the rd script
81+
link("/usr/bin/${applicationName}", "${distInstallPath}/${archivedir}/bin/${applicationName}")
82+
}
83+
84+
//depend on the shadow artifact
85+
buildDeb.dependsOn shadowDistZip
86+
buildRpm.dependsOn shadowDistZip
87+
assemble.dependsOn buildRpm, buildDeb

src/main/groovy/com/rundeck/plugin/generator/JavaPluginTemplateGenerator.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class JavaPluginTemplateGenerator extends AbstractTemplateGenerator {
2424
private static final String TEMPLATE_BASE = "templates/java-plugin/"
2525
private static final String JAVA_STRUCTURE = "java-plugin.structure"
2626

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

2929
@Override
3030
Map makeTemplateProperties(final String pluginName, final String providedService) {
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package com.plugin.${javaPluginClass.toLowerCase()};
2+
3+
import com.dtolabs.rundeck.core.common.INodeEntry;
4+
import com.dtolabs.rundeck.core.execution.ExecutionContext;
5+
import com.dtolabs.rundeck.core.execution.ExecutionLogger;
6+
import com.dtolabs.rundeck.core.execution.service.NodeExecutor;
7+
import com.dtolabs.rundeck.core.execution.service.NodeExecutorResult;
8+
import com.dtolabs.rundeck.core.execution.service.NodeExecutorResultImpl;
9+
import com.dtolabs.rundeck.core.execution.utils.ResolverUtil;
10+
import com.dtolabs.rundeck.core.execution.workflow.steps.StepFailureReason;
11+
import com.dtolabs.rundeck.core.plugins.Plugin;
12+
import com.dtolabs.rundeck.core.plugins.configuration.Describable;
13+
import com.dtolabs.rundeck.core.plugins.configuration.Description;
14+
import com.dtolabs.rundeck.plugins.ServiceNameConstants;
15+
import com.dtolabs.rundeck.plugins.descriptions.PluginDescription;
16+
import com.dtolabs.rundeck.plugins.util.DescriptionBuilder;
17+
import com.dtolabs.rundeck.plugins.util.PropertyBuilder;
18+
import java.util.Arrays;
19+
20+
@Plugin(service=ServiceNameConstants.NodeExecutor,name="${sanitizedPluginName}")
21+
@PluginDescription(title="${pluginName}", description="My WorkflowStep plugin description")
22+
public class ${javaPluginClass} implements NodeExecutor, Describable{
23+
24+
public static final String SERVICE_PROVIDER_NAME = "${sanitizedPluginName}";
25+
26+
public static final String PROJ_PROP_PREFIX = "project.";
27+
public static final String FWK_PROP_PREFIX = "framework.";
28+
29+
public static final String SIMPLE_CONFIG_STRING = "exampleConfig";
30+
public static final String SIMPLE_CONFIG_BOOLEAN = "forceFail";
31+
public static final String SIMPLE_CONFIG_SELECT= "exampleSelect";
32+
33+
/**
34+
* Overriding this method gives the plugin a chance to take part in building the {@link
35+
* com.dtolabs.rundeck.core.plugins.configuration.Description} presented by this plugin. This subclass can use the
36+
* {@link DescriptionBuilder} to modify all aspects of the description, add or remove properties, etc.
37+
*/
38+
@Override
39+
public Description getDescription() {
40+
DescriptionBuilder builder = DescriptionBuilder.builder()
41+
.name(SERVICE_PROVIDER_NAME)
42+
.title("${pluginName}")
43+
.description("Example Workflow Step")
44+
.property(PropertyBuilder.builder()
45+
.string(SIMPLE_CONFIG_STRING)
46+
.title("Example String")
47+
.description("Example description")
48+
.required(true)
49+
.build()
50+
)
51+
.property(PropertyBuilder.builder()
52+
.booleanType(SIMPLE_CONFIG_BOOLEAN)
53+
.title("Force Fail")
54+
.description("Example Boolean, Force Fail Command?")
55+
.required(false)
56+
.defaultValue("false")
57+
.build()
58+
)
59+
.property(PropertyBuilder.builder()
60+
.freeSelect(SIMPLE_CONFIG_SELECT)
61+
.title("Example Free Select")
62+
.description("Example Free Select")
63+
.required(false)
64+
.defaultValue("Blue")
65+
.values("Blue", "Beige", "Black")
66+
.build()
67+
);
68+
69+
//mapping config input on project and framework level
70+
builder.mapping(SIMPLE_CONFIG_STRING, PROJ_PROP_PREFIX + SIMPLE_CONFIG_STRING);
71+
builder.frameworkMapping(SIMPLE_CONFIG_STRING, FWK_PROP_PREFIX + SIMPLE_CONFIG_STRING);
72+
73+
builder.mapping(SIMPLE_CONFIG_BOOLEAN, PROJ_PROP_PREFIX + SIMPLE_CONFIG_BOOLEAN);
74+
builder.frameworkMapping(SIMPLE_CONFIG_BOOLEAN, FWK_PROP_PREFIX + SIMPLE_CONFIG_BOOLEAN);
75+
76+
builder.mapping(SIMPLE_CONFIG_SELECT, PROJ_PROP_PREFIX + SIMPLE_CONFIG_SELECT);
77+
builder.frameworkMapping(SIMPLE_CONFIG_SELECT, FWK_PROP_PREFIX + SIMPLE_CONFIG_SELECT);
78+
79+
return builder.build();
80+
}
81+
82+
@Override
83+
public NodeExecutorResult executeCommand(ExecutionContext context, String[] command, INodeEntry node) {
84+
ExecutionLogger logger= context.getExecutionLogger();
85+
86+
String exampleValue = ResolverUtil.resolveProperty(SIMPLE_CONFIG_STRING,
87+
null,
88+
node,
89+
context.getFramework().getFrameworkProjectMgr().getFrameworkProject(context.getFrameworkProject()),
90+
context.getFramework());
91+
92+
93+
String exampleFreeSelect = ResolverUtil.resolveProperty(SIMPLE_CONFIG_SELECT,
94+
null,
95+
node,
96+
context.getFramework().getFrameworkProjectMgr().getFrameworkProject(context.getFrameworkProject()),
97+
context.getFramework());
98+
99+
Boolean forceFail = ResolverUtil.resolveBooleanProperty(SIMPLE_CONFIG_BOOLEAN,
100+
false,
101+
node,
102+
context.getFramework().getFrameworkProjectMgr().getFrameworkProject(context.getFrameworkProject()),
103+
context.getFramework());
104+
105+
logger.log(2,"[demo-info] Running command: " + Arrays.toString(command) + " on node " + node.getNodename());
106+
logger.log(2,"[demo-info] node attribute hostname: " + node.getAttributes().get("hostname"));
107+
logger.log(2,"[demo-info] node attribute osFamily: " + node.getAttributes().get("osFamily"));
108+
logger.log(2,"[demo-info] Example Value: " + exampleValue);
109+
logger.log(2,"[demo-info] ExampleFree Value: " + exampleFreeSelect);
110+
logger.log(2,"[demo-info] forceFail Value: " + forceFail);
111+
if (forceFail) {
112+
logger.log(0,"[demo-error] force to fail");
113+
return NodeExecutorResultImpl.createFailure(
114+
StepFailureReason.ConfigurationFailure,
115+
"[demo-error] Error connecting with node '" + node.getNodename() + "'",
116+
node
117+
);
118+
}
119+
120+
return NodeExecutorResultImpl.createSuccess(node);
121+
}
122+
123+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package com.plugin.${javaPluginClass.toLowerCase()}
2+
3+
import com.dtolabs.rundeck.plugins.step.PluginStepContext
4+
import com.dtolabs.rundeck.core.execution.ExecutionContext
5+
import com.dtolabs.rundeck.core.execution.ExecutionLogger
6+
import com.dtolabs.rundeck.core.execution.workflow.steps.StepException
7+
import com.dtolabs.rundeck.plugins.PluginLogger
8+
import com.dtolabs.rundeck.core.common.Framework
9+
import com.dtolabs.rundeck.core.common.INodeEntry;
10+
import com.dtolabs.rundeck.core.common.IRundeckProject
11+
import spock.lang.Specification
12+
import com.dtolabs.rundeck.core.common.ProjectManager
13+
import com.dtolabs.rundeck.core.common.IRundeckProject
14+
15+
class ${javaPluginClass}Spec extends Specification {
16+
17+
def getContext(ExecutionLogger logger, Boolean fail){
18+
19+
def manager = Mock(ProjectManager){
20+
getFrameworkProject(_)>> Mock(IRundeckProject) {
21+
hasProperty(('project.exampleConfig')) >> true
22+
getProperty(('project.exampleConfig')) >> "123345"
23+
hasProperty(('project.exampleSelect')) >> true
24+
getProperty(('project.exampleSelect')) >> "Blue"
25+
hasProperty(('project.forceFail')) >> fail
26+
getProperty(('project.forceFail')) >> fail
27+
}
28+
}
29+
30+
Mock(ExecutionContext){
31+
getExecutionLogger()>>logger
32+
getFrameworkProject() >> "test"
33+
getFramework() >> Mock(Framework) {
34+
getFrameworkProjectMgr() >> manager
35+
}
36+
37+
}
38+
}
39+
40+
def "check Boolean parameter"(){
41+
42+
given:
43+
44+
String[] command = ["ls","-lrt"]
45+
def logger = Mock(ExecutionLogger)
46+
def example = new ${javaPluginClass}()
47+
def context = getContext(logger,true)
48+
def node = Mock(INodeEntry){
49+
getNodename()>>"test"
50+
getAttributes()>>["hostname":"Test","osFamily":"linux","forceFail":"true"]
51+
}
52+
53+
when:
54+
example.executeCommand(context, command, node)
55+
56+
then:
57+
1 * logger.log(0, '[demo-error] force to fail')
58+
59+
}
60+
61+
def "run OK"(){
62+
63+
given:
64+
65+
String[] command = ["ls","-lrt"]
66+
def logger = Mock(ExecutionLogger)
67+
def example = new ${javaPluginClass}()
68+
def context = getContext(logger,false)
69+
def node = Mock(INodeEntry){
70+
getNodename()>>"test"
71+
getAttributes()>>["hostname":"Test","osFamily":"linux"]
72+
}
73+
74+
when:
75+
example.executeCommand(context, command, node)
76+
77+
then:
78+
1 * logger.log(2, '[demo-info] Running command: [ls, -lrt] on node test')
79+
80+
}
81+
82+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# ${pluginName} Rundeck Plugin
2+
3+
This is a node executor plugin.
4+
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
version = '0.1.0'
2+
defaultTasks 'clean','build'
3+
apply plugin: 'java'
4+
apply plugin: 'groovy'
5+
apply plugin: 'idea'
6+
sourceCompatibility = 1.8
7+
ext.rundeckPluginVersion= '2.0'
8+
ext.rundeckVersion= '${rundeckVersion}'
9+
10+
11+
repositories {
12+
mavenLocal()
13+
mavenCentral()
14+
}
15+
16+
dependencies {
17+
compile 'org.rundeck:rundeck-core:3.0.1+'
18+
19+
testCompile 'junit:junit:4.12'
20+
testCompile "org.codehaus.groovy:groovy-all:2.4.15"
21+
testCompile "org.spockframework:spock-core:1.0-groovy-2.4"
22+
testCompile "cglib:cglib-nodep:2.2.2"
23+
testCompile group: 'org.objenesis', name: 'objenesis', version: '1.2'
24+
}
25+
26+
ext.pluginClassNames='com.plugin.${sanitizedPluginName}.${javaPluginClass}'
27+
jar {
28+
manifest {
29+
attributes 'Rundeck-Plugin-Classnames': pluginClassNames
30+
attributes 'Rundeck-Plugin-File-Version': version
31+
attributes 'Rundeck-Plugin-Name': '${pluginName}'
32+
attributes 'Rundeck-Plugin-Description': 'Provide a short description of your plugin here.'
33+
attributes 'Rundeck-Plugin-Rundeck-Compatibility-Version': '3.x'
34+
attributes 'Rundeck-Plugin-Tags': 'java,notification'
35+
attributes 'Rundeck-Plugin-Version': rundeckPluginVersion, 'Rundeck-Plugin-Archive': 'true'
36+
}
37+
from("rundeck-verb-artifact.yaml") {
38+
into("")
39+
}
40+
}
41+
42+
task wrapper(type: Wrapper) {
43+
gradleVersion = '4.4.1'
44+
}
1.66 KB
Loading
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
build.gradle.template->build.gradle
2+
README.md.template->README.md
3+
icon.png->src/main/resources/resources/icon.png
4+
Plugin.java.template->src/main/java/com/plugin/${javaPluginClass.toLowerCase()}/${javaPluginClass}.java
5+
PluginSpec.groovy.template->src/test/groovy/com/plugin/${javaPluginClass.toLowerCase()}/${javaPluginClass}Spec.groovy
6+

src/test/groovy/com/rundeck/plugin/generator/JavaPluginTemplateGeneratorTest.groovy

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,20 @@ class JavaPluginTemplateGeneratorTest extends Specification {
101101
new File(tmpDir,"/my-logfilter-plugin/src/test/groovy/com/plugin/mylogfilterplugin/MyLogfilterPluginSpec.groovy").exists()
102102
}
103103

104+
def "Create NodeExecutor Template"() {
105+
when:
106+
File tmpDir = File.createTempDir()
107+
JavaPluginTemplateGenerator generator = new JavaPluginTemplateGenerator()
108+
generator.createTemplate("My NodeExecutor Plugin","NodeExecutor",tmpDir.absolutePath)
109+
int compileResult = TestUtils.buildGradle(new File(tmpDir,"my-nodeexecutor-plugin"))
110+
111+
then:
112+
compileResult == 0
113+
new File(tmpDir,"/my-nodeexecutor-plugin/build.gradle").exists()
114+
new File(tmpDir,"/my-nodeexecutor-plugin/src/main/resources/resources/icon.png").exists()
115+
new File(tmpDir,"/my-nodeexecutor-plugin/README.md").exists()
116+
new File(tmpDir,"/my-nodeexecutor-plugin/src/main/java/com/plugin/mynodeexecutorplugin/MyNodeexecutorPlugin.java").exists()
117+
new File(tmpDir,"/my-nodeexecutor-plugin/src/test/groovy/com/plugin/mynodeexecutorplugin/MyNodeexecutorPluginSpec.groovy").exists()
118+
}
119+
104120
}

0 commit comments

Comments
 (0)