Skip to content

Commit

Permalink
[JENKINS-38433] Make the agent section pluggable.
Browse files Browse the repository at this point in the history
This is very preliminary - I expect a lot more iteration. But it
works. The DeclarativeAgent* classes are in a separate plugin so that
they can be depended on by other plugins with minimal transitive
dependencies in the process (just workflow-cps-plugin currently - may
try to find a way to narrow that down further).
  • Loading branch information
abayer committed Oct 22, 2016
1 parent abbde74 commit d07730e
Show file tree
Hide file tree
Showing 17 changed files with 853 additions and 93 deletions.
137 changes: 137 additions & 0 deletions pipeline-model-declarative-agent/pom.xml
@@ -0,0 +1,137 @@
<!--
~ The MIT License
~
~ Copyright (c) 2016, CloudBees, Inc.
~
~ Permission is hereby granted, free of charge, to any person obtaining a copy
~ of this software and associated documentation files (the "Software"), to deal
~ in the Software without restriction, including without limitation the rights
~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
~ copies of the Software, and to permit persons to whom the Software is
~ furnished to do so, subject to the following conditions:
~
~ The above copyright notice and this permission notice shall be included in
~ all copies or substantial portions of the Software.
~
~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
~ THE SOFTWARE.
-->

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>2.13</version>
<relativePath/>
</parent>

<groupId>org.jenkinsci.plugins</groupId>
<artifactId>pipeline-model-declarative-agent</artifactId>
<version>0.5-SNAPSHOT</version>
<packaging>hpi</packaging>
<name>Pipeline: Declarative Agent API</name>
<description>An opinionated, declarative Pipeline</description>
<url>https://wiki.jenkins-ci.org/display/JENKINS/Pipeline+Model+Definition+Plugin</url>

<licenses>
<license>
<name>MIT</name>
<url>http://opensource.org/licenses/MIT</url>
<distribution>repo</distribution>
</license>
</licenses>

<developers>
<developer>
<id>abayer</id>
<name>Andrew Bayer</name>
</developer>
</developers>

<scm>
<connection>scm:git:git://git@github.com/jenkinsci/pipeline-config-plugin.git</connection>
<developerConnection>scm:git:git@github.com:jenkinsci/pipeline-config-plugin.git</developerConnection>
<url>https://github.com/jenkinsci/pipeline-config-plugin</url>
<tag>HEAD</tag>
</scm>

<dependencies>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-cps</artifactId>
<version>2.18</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.codehaus.gmaven</groupId>
<artifactId>gmaven-plugin</artifactId>
<version>1.4</version>
<configuration>
<providerSelection>1.8</providerSelection>
</configuration>
<executions>
<execution>
<goals>
<goal>generateStubs</goal>
<goal>compile</goal>
<goal>generateTestStubs</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemProperties>
<property>
<name>hudson.udp</name>
<value>33849</value>
</property>
</systemProperties>
<argLine>-Xmx4g -XX:MaxPermSize=256m</argLine>
<reuseForks>true</reuseForks>
<forkCount>0.5C</forkCount>
<!-- TODO: I hate the retrying tests but given the nondeterministic whackiness of JENKINS-37101... -->
<rerunFailingTestsCount>3</rerunFailingTestsCount>
</configuration>
</plugin>

</plugins>
</build>

<properties>
<jenkins.version>2.7.1</jenkins.version>
<java.level>7</java.level>
<groovy.version>2.4.7</groovy.version>
</properties>

<repositories>
<repository>
<id>repo.jenkins-ci.org</id>
<url>http://repo.jenkins-ci.org/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>repo.jenkins-ci.org</id>
<url>http://repo.jenkins-ci.org/public/</url>
</pluginRepository>
</pluginRepositories>

</project>
@@ -0,0 +1,68 @@
/*
* The MIT License
*
* Copyright (c) 2016, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package org.jenkinsci.plugins.pipeline.modeldefinition.agent;

import groovy.lang.GroovyCodeSource;
import groovy.lang.GroovyShell;
import hudson.ExtensionPoint;
import hudson.model.AbstractDescribableImpl;
import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.Whitelisted;
import org.jenkinsci.plugins.structs.describable.DescribableModel;
import org.jenkinsci.plugins.workflow.cps.CpsScript;
import org.jenkinsci.plugins.workflow.cps.CpsThread;

import javax.annotation.Nonnull;
import java.io.Serializable;
import java.net.URL;

import static org.apache.commons.lang.exception.ExceptionUtils.getFullStackTrace;

public abstract class DeclarativeAgent extends AbstractDescribableImpl<DeclarativeAgent> implements Serializable, ExtensionPoint {

/**
* ONLY TO BE RUN FROM WITHIN A CPS THREAD. Parses the script source and loads it.
* TODO: Decide if we want to cache the resulting objects or just *shrug* and re-parse them every time.
*
* @return The script object for this declarative agent.
* @throws Exception if the script source cannot be loaded or we're called from outside a CpsThread.
*/
@SuppressWarnings("unchecked")
@Whitelisted
public DeclarativeAgentScript getScript(CpsScript cpsScript) throws Exception {
CpsThread c = CpsThread.current();
if (c == null)
throw new IllegalStateException("Expected to be called from CpsThread");

return (DeclarativeAgentScript) cpsScript.getClass().getClassLoader()
.loadClass(getDescriptor().getDeclarativeAgentScriptClass())
.getConstructor(CpsScript.class, DeclarativeAgent.class)
.newInstance(cpsScript, this);
}

@Override
public DeclarativeAgentDescriptor getDescriptor() {
return (DeclarativeAgentDescriptor) super.getDescriptor();
}
}
@@ -0,0 +1,144 @@
/*
* The MIT License
*
* Copyright (c) 2016, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jenkinsci.plugins.pipeline.modeldefinition.agent;

import hudson.ExtensionComponent;
import hudson.ExtensionList;
import hudson.model.Descriptor;
import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.Whitelisted;
import org.jenkinsci.plugins.structs.SymbolLookup;
import org.jenkinsci.plugins.structs.describable.DescribableModel;
import org.jenkinsci.plugins.structs.describable.UninstantiatedDescribable;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

public abstract class DeclarativeAgentDescriptor extends Descriptor<DeclarativeAgent> {

public abstract @Nonnull String getName();

public abstract @Nonnull String getDeclarativeAgentScriptClass();

public DeclarativeAgent newInstance(Map<String,Object> arguments) throws Exception {
return new DescribableModel<>(clazz).instantiate(arguments);
}

public UninstantiatedDescribable uninstantiate(DeclarativeAgent declarativeAgent) throws UnsupportedOperationException {
return DescribableModel.uninstantiate2_(declarativeAgent);
}

public static ExtensionList<DeclarativeAgentDescriptor> all() {
return ExtensionList.lookup(DeclarativeAgentDescriptor.class);
}

public static Map<String,DescribableModel> getDescribableModels() {
Map<String,DescribableModel> models = new HashMap<>();

for (DeclarativeAgentDescriptor d : getOrderedDescriptors()) {
Set<String> symbolValues = SymbolLookup.getSymbolValue(d);

if (!symbolValues.isEmpty()) {
models.put(symbolValues.iterator().next(), new DescribableModel<>(d.clazz));
}

}

return models;
}

public static Map<String,DescribableModel> singleArgModels() {
Map<String,DescribableModel> models = new HashMap<>();

for (Map.Entry<String,DescribableModel> entry : getDescribableModels().entrySet()) {
if (entry.getValue().getParameters().isEmpty()) {
models.put(entry.getKey(), entry.getValue());
}
}

return models;
}

public static List<DeclarativeAgentDescriptor> getOrderedDescriptors() {
List<DeclarativeAgentDescriptor> orderedDescriptors = new ArrayList<>();

List<ExtensionComponent<DeclarativeAgentDescriptor>> extensionComponents = new ArrayList<>(all().getComponents());
Collections.sort(extensionComponents);

for (ExtensionComponent<DeclarativeAgentDescriptor> extensionComponent: extensionComponents) {
orderedDescriptors.add(extensionComponent.getInstance());
}

return orderedDescriptors;
}

public static List<String> getOrderedNames() {
List<String> orderedNames = new ArrayList<>();

for (DeclarativeAgentDescriptor d : getOrderedDescriptors()) {
Set<String> symbolValues = SymbolLookup.getSymbolValue(d);

if (!symbolValues.isEmpty()) {
orderedNames.add(symbolValues.iterator().next());
}
}

return orderedNames;
}

@Whitelisted
public static @Nullable DeclarativeAgentDescriptor byName(@Nonnull String name) {
System.err.println("byName: " + name);
for (DeclarativeAgentDescriptor d : all()) {
System.err.println("d.n: " + d.getName());
if (d.getName().equals(name)) {
System.err.println("returning " + d);
return d;
}
}
return null;
}

@Whitelisted
public static @Nullable DeclarativeAgent instanceForName(@Nonnull String name,
Map<String,Object> arguments) throws Exception {
DeclarativeAgentDescriptor descriptor = byName(name);

if (descriptor != null) {
if (singleArgModels().keySet().contains(name)) {
return descriptor.newInstance(new HashMap<String, Object>());
} else {
return descriptor.newInstance(arguments);
}
}

return null;
}

}
@@ -0,0 +1,41 @@
/*
* The MIT License
*
* Copyright (c) 2016, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jenkinsci.plugins.pipeline.modeldefinition.agent;

import groovy.lang.Closure;
import org.jenkinsci.plugins.workflow.cps.CpsScript;

import java.io.Serializable;

public abstract class DeclarativeAgentScript implements Serializable {
protected CpsScript script;
protected DeclarativeAgent declarativeAgent;

public DeclarativeAgentScript(CpsScript s, DeclarativeAgent a) {
this.script = s;
this.declarativeAgent = a;
}

public abstract Closure run(Closure body);
}

0 comments on commit d07730e

Please sign in to comment.