initial implementation
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
commit
9bbddaa70b
|
|
@ -0,0 +1,5 @@
|
|||
target/
|
||||
work/
|
||||
.idea/
|
||||
*.iml
|
||||
src/main/resources/org/
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
jenkins:
|
||||
# securityRealm: local
|
||||
securityRealm:
|
||||
ldap:
|
||||
configurations:
|
||||
- server: ldap.acme.com
|
||||
rootDN: dc=acme,dc=fr
|
||||
managerPasswordSecret: ${LDAP_PASSWORD}
|
||||
cache:
|
||||
size: 100
|
||||
ttl: 10
|
||||
userIdStrategy: CaseSensitive
|
||||
groupIdStrategy: CaseSensitive
|
||||
|
||||
authorizationStrategy:
|
||||
loggedInUsersCanDoAnything:
|
||||
allowAnonymousRead: false
|
||||
|
||||
noUsageStatistics: true
|
||||
slaveAgentPort: 6666
|
||||
viewsTabBar: standard
|
||||
|
||||
nodes:
|
||||
- slave:
|
||||
name: "foo"
|
||||
remoteFS: '/tmp/2'
|
||||
launcher: "jnlp"
|
||||
numExecutors: 1
|
||||
- slave:
|
||||
name: "bar"
|
||||
remoteFS: '/tmp/1'
|
||||
launcher: "jnlp"
|
||||
numExecutors: 1
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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.11</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
<groupId>org.jenkins-ci.plugins</groupId>
|
||||
<artifactId>configuration-as-code</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<packaging>hpi</packaging>
|
||||
|
||||
<properties>
|
||||
<jenkins.version>2.60</jenkins.version>
|
||||
<java.level>8</java.level>
|
||||
</properties>
|
||||
|
||||
<name>Configuration as Code Plugin</name>
|
||||
<description>Manage Jenkins master configuration as code</description>
|
||||
<url>https://wiki.jenkins-ci.org/display/JENKINS/Configuration+as+Code+Plugin</url>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>MIT License</name>
|
||||
<url>http://opensource.org/licenses/MIT</url>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<id>ndeloof</id>
|
||||
<name>Nicolas De Loof</name>
|
||||
<email>nicolas.deloof@gmail.com</email>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<scm>
|
||||
<connection>scm:git:git://github.com/jenkinsci/${project.artifactId}-plugin.git</connection>
|
||||
<developerConnection>scm:git:git@github.com:jenkinsci/${project.artifactId}-plugin.git</developerConnection>
|
||||
<url>https://github.com/jenkinsci/${project.artifactId}-plugin</url>
|
||||
</scm>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>repo.jenkins-ci.org</id>
|
||||
<url>https://repo.jenkins-ci.org/public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>repo.jenkins-ci.org</id>
|
||||
<url>https://repo.jenkins-ci.org/public/</url>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
<version>1.18</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- for demo purpose -->
|
||||
<dependency>
|
||||
<groupId>org.jenkins-ci.plugins</groupId>
|
||||
<artifactId>ldap</artifactId>
|
||||
<version>1.16</version>
|
||||
<type>hpi</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<configuration>
|
||||
<failOnError>false</failOnError>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package org.jenkinsci.plugins.casc;
|
||||
|
||||
import org.apache.commons.beanutils.PropertyUtils;
|
||||
import org.kohsuke.stapler.lang.Klass;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
|
||||
*/
|
||||
public class Attribute<T> {
|
||||
|
||||
protected final String name;
|
||||
protected final Class<T> type;
|
||||
private boolean multiple;
|
||||
|
||||
public Attribute(String name, Class<T> type) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Class<T> getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/** Attribute acutaly is a Collection of documented type */
|
||||
public boolean isMultiple() {
|
||||
return multiple;
|
||||
}
|
||||
|
||||
public Attribute<T> withMultiple(boolean multiple) {
|
||||
this.multiple = multiple;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** If this attribute is constrained to a limited set of value, here they are */
|
||||
public List<String> possibleValues() {
|
||||
if (type.isEnum()) {
|
||||
Class<Enum> e = (Class<Enum>) type;
|
||||
return Arrays.stream(e.getEnumConstants()).map(Enum::name).collect(Collectors.toList());
|
||||
}
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
|
||||
public void setValue(Object target, T value) throws Exception {
|
||||
PropertyUtils.setProperty(target, name, value);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
package org.jenkinsci.plugins.casc;
|
||||
|
||||
import hudson.model.Describable;
|
||||
import org.apache.commons.beanutils.PropertyUtils;
|
||||
import org.jenkinsci.Symbol;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.WildcardType;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
|
||||
*/
|
||||
public abstract class BaseConfigurator<T> extends Configurator<T> {
|
||||
|
||||
public Set<Attribute> describe() {
|
||||
|
||||
Set<Attribute> attributes = new HashSet<>();
|
||||
|
||||
final PropertyDescriptor[] properties = PropertyUtils.getPropertyDescriptors(getTarget());
|
||||
for (PropertyDescriptor p : properties) {
|
||||
final String name = p.getName();
|
||||
|
||||
final Method setter = p.getWriteMethod();
|
||||
if (setter == null) continue; // read only
|
||||
if (setter.getAnnotation(Deprecated.class) != null) continue; // not actually public
|
||||
if (setter.getAnnotation(Restricted.class) != null) continue; // not actually public - require access-modifier 1.12
|
||||
|
||||
Class c;
|
||||
final Type type = setter.getGenericParameterTypes()[0];
|
||||
if (type instanceof ParameterizedType) {
|
||||
// List<? extends Foo>
|
||||
ParameterizedType pt = (ParameterizedType) type;
|
||||
c = (Class) pt.getRawType();
|
||||
} else {
|
||||
c = (Class) type;
|
||||
}
|
||||
|
||||
boolean multiple = false;
|
||||
if (Collection.class.isAssignableFrom(c)) {
|
||||
multiple = true;
|
||||
ParameterizedType pt = (ParameterizedType) type;
|
||||
Type actualType = pt.getActualTypeArguments()[0];
|
||||
if (actualType instanceof WildcardType) {
|
||||
actualType = ((WildcardType) actualType).getUpperBounds()[0];
|
||||
}
|
||||
if (!(actualType instanceof Class)) {
|
||||
throw new IllegalStateException("Can't handle "+type);
|
||||
}
|
||||
c = (Class) actualType;
|
||||
}
|
||||
|
||||
Attribute attribute;
|
||||
if (Describable.class.isAssignableFrom(c)) {
|
||||
attribute = new DescribableAttribute(p.getName(), c);
|
||||
} else {
|
||||
attribute = new Attribute(p.getName(), c);
|
||||
}
|
||||
attributes.add(attribute.withMultiple(multiple));
|
||||
|
||||
// See https://github.com/jenkinsci/structs-plugin/pull/18
|
||||
final Symbol s = setter.getAnnotation(Symbol.class);
|
||||
// TODO record symbol as preferred name / alias for this attribute
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package org.jenkinsci.plugins.casc;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
|
||||
*/
|
||||
public class Config {
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
package org.jenkinsci.plugins.casc;
|
||||
|
||||
import hudson.Plugin;
|
||||
import hudson.init.InitMilestone;
|
||||
import hudson.init.Initializer;
|
||||
import jenkins.model.Jenkins;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
|
||||
*/
|
||||
public class ConfigurationAsCode extends Plugin {
|
||||
|
||||
|
||||
@Initializer(after = InitMilestone.EXTENSIONS_AUGMENTED)
|
||||
public static void configure() throws Exception {
|
||||
final File f = new File("./jenkins.yaml");
|
||||
if (f.exists()) {
|
||||
configure(new FileInputStream(f));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void configure(InputStream in) throws Exception {
|
||||
|
||||
Map<String, Object> config = new Yaml().loadAs(in, Map.class);
|
||||
for (Map.Entry<String, Object> e : config.entrySet()) {
|
||||
final Configurator configurator = Configurator.lookupRootElement(e.getKey());
|
||||
if (configurator == null) {
|
||||
throw new IllegalArgumentException("no configurator for root element "+e.getKey());
|
||||
}
|
||||
configurator.configure(e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public List<?> getConfigurators() {
|
||||
List<Object> elements = new ArrayList<>();
|
||||
for (RootElementConfigurator c : Jenkins.getInstance().getExtensionList(RootElementConfigurator.class)) {
|
||||
elements.add(c);
|
||||
listElements(elements, c.describe());
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
|
||||
private void listElements(List<Object> elements, Set<Attribute> attributes) {
|
||||
for (Attribute attribute : attributes) {
|
||||
|
||||
final Class type = attribute.type;
|
||||
Configurator configurator = Configurator.lookup(type);
|
||||
if (configurator == null ) {
|
||||
continue;
|
||||
}
|
||||
for (Object o : configurator.getConfigurators()) {
|
||||
if (!elements.contains(o)) {
|
||||
elements.add(o);
|
||||
}
|
||||
}
|
||||
listElements(elements, configurator.describe());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private final List<Class> documented = new ArrayList<>();
|
||||
{
|
||||
|
||||
documented.add(int.class);
|
||||
documented.add(String.class);
|
||||
documented.add(boolean.class);
|
||||
documented.add(Integer.class);
|
||||
// ...
|
||||
|
||||
for (RootElementConfigurator c : Jenkins.getInstance().getExtensionList(RootElementConfigurator.class)) {
|
||||
final String name = c.getName();
|
||||
document(name, c.describe());
|
||||
}
|
||||
}
|
||||
|
||||
private void document(String name, Set<Attribute> attributes) {
|
||||
|
||||
Set<Class> next = new HashSet<>();
|
||||
System.out.println();
|
||||
System.out.println("## " + name);
|
||||
for (Attribute attribute : attributes) {
|
||||
|
||||
// FIXME filter attribute to target a component without anything configurable
|
||||
|
||||
final Class type = attribute.getType();
|
||||
System.out.print("**"+attribute.getName() + "** (");
|
||||
if (attribute.isMultiple())
|
||||
System.out.print("list of ");
|
||||
System.out.println(type+")");
|
||||
if (!attribute.possibleValues().isEmpty()) {
|
||||
System.out.println("possible values :");
|
||||
for (Object o : attribute.possibleValues()) {
|
||||
System.out.println(" - " + o);
|
||||
}
|
||||
}
|
||||
|
||||
if (! documented.contains(type)) {
|
||||
next.add(type);
|
||||
}
|
||||
}
|
||||
|
||||
for (Class type : next) {
|
||||
Configurator configurator = Configurator.lookup(type);
|
||||
if (configurator == null) continue;
|
||||
document(type.getName(), configurator.describe());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
package org.jenkinsci.plugins.casc;
|
||||
|
||||
import hudson.Extension;
|
||||
import hudson.ExtensionList;
|
||||
import hudson.ExtensionPoint;
|
||||
import hudson.model.Describable;
|
||||
import jenkins.model.Jenkins;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.jenkinsci.Symbol;
|
||||
import org.kohsuke.stapler.lang.Klass;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.WildcardType;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
|
||||
*/
|
||||
public abstract class Configurator<T> implements ExtensionPoint {
|
||||
|
||||
public static Configurator lookupRootElement(String name) {
|
||||
final ExtensionList<Configurator> l = Jenkins.getInstance().getExtensionList(Configurator.class);
|
||||
for (Configurator c : l) {
|
||||
if (c instanceof RootElementConfigurator
|
||||
&& ((RootElementConfigurator)c).getName().equals(name)) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Configurator lookup(Type type) {
|
||||
|
||||
Class clazz = asClass(type);
|
||||
|
||||
final ExtensionList<Configurator> l = Jenkins.getInstance().getExtensionList(Configurator.class);
|
||||
for (Configurator c : l) {
|
||||
if (clazz == c.getTarget()) {
|
||||
// this type has a dedicated Configurator implementation
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
if (Collection.class.isAssignableFrom(clazz)) {
|
||||
ParameterizedType pt = (ParameterizedType) type;
|
||||
Type actualType = pt.getActualTypeArguments()[0];
|
||||
if (actualType instanceof WildcardType) {
|
||||
actualType = ((WildcardType) actualType).getUpperBounds()[0];
|
||||
}
|
||||
if (!(actualType instanceof Class)) {
|
||||
throw new IllegalStateException("Can't handle "+type);
|
||||
}
|
||||
return lookup(actualType);
|
||||
}
|
||||
|
||||
if (Describable.class.isAssignableFrom(clazz)) {
|
||||
if (Modifier.isAbstract(clazz.getModifiers())) {
|
||||
return new HeteroDescribableConfigurator(clazz);
|
||||
}
|
||||
return new DescribableConfigurator(clazz);
|
||||
}
|
||||
|
||||
if (Extension.class.isAssignableFrom(clazz)) {
|
||||
return new ExtensionConfigurator(clazz);
|
||||
}
|
||||
|
||||
return new PrimitiveConfigurator(clazz);
|
||||
}
|
||||
|
||||
private static Class asClass(Type type) {
|
||||
Class clazz;
|
||||
if (type instanceof ParameterizedType) {
|
||||
ParameterizedType pt = (ParameterizedType) type;
|
||||
clazz = (Class) pt.getRawType();
|
||||
} else {
|
||||
clazz = (Class) type;
|
||||
}
|
||||
return clazz;
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
/**
|
||||
*
|
||||
* @return the list of Configurator to be considered so one can fully configure this component.
|
||||
* Typically, an extension point with multiple implementations will return Configurators for available implementations.
|
||||
*/
|
||||
public List<Configurator> getConfigurators() {
|
||||
return Collections.singletonList(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return short name for this component when used in a configuration.yaml file
|
||||
*/
|
||||
public String getName() {
|
||||
final Symbol annotation = getTarget().getAnnotation(Symbol.class);
|
||||
if (annotation != null) return annotation.value()[0];
|
||||
return getTarget().getSimpleName();
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual component being managed by this Configurator
|
||||
*/
|
||||
public abstract Class<T> getTarget();
|
||||
|
||||
/**
|
||||
* The extension point being implemented by this configurator. Can be null
|
||||
*/
|
||||
public Class getExtensionpoint() {
|
||||
return ExtensionPoint.class.isAssignableFrom(getTarget()) ? getTarget() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Human friendly display name for this component.
|
||||
*/
|
||||
public String getDisplayName() { return ""; }
|
||||
|
||||
private Klass getKlass() {
|
||||
return Klass.java(getTarget());
|
||||
}
|
||||
|
||||
public abstract T configure(Object config) throws Exception;
|
||||
|
||||
/**
|
||||
* Ordered version of {@link #describe()} for documentation generation
|
||||
*/
|
||||
public List<Attribute> getAttributes() {
|
||||
final ArrayList<Attribute> attributes = new ArrayList<>(describe());
|
||||
Collections.sort(attributes, (a,b) -> a.name.compareTo(b.name));
|
||||
return attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the list of Attribute available for configuration of the managed component.
|
||||
*/
|
||||
public abstract Set<Attribute> describe();
|
||||
|
||||
/**
|
||||
* Retrieve the html help tip associated to an attribute.
|
||||
* FIXME would prefer <st:include page="help-${a.name}.html" class="${c.target}" optional="true"/>
|
||||
*/
|
||||
public String getHtmlHelp(String attribute) throws IOException {
|
||||
final URL resource = getKlass().getResource("help-" + attribute + ".html");
|
||||
if (resource != null) {
|
||||
return IOUtils.toString(resource);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof Configurator) {
|
||||
return getTarget() == ((Configurator) obj).getTarget();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getTarget().hashCode();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package org.jenkinsci.plugins.casc;
|
||||
|
||||
import hudson.model.Describable;
|
||||
import hudson.model.Descriptor;
|
||||
import jenkins.model.Jenkins;
|
||||
import org.jenkinsci.Symbol;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
|
||||
*/
|
||||
public class DescribableAttribute extends Attribute {
|
||||
|
||||
public DescribableAttribute(String name, Class<? extends Describable> type) {
|
||||
super(name, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> possibleValues() {
|
||||
final List<Descriptor> descriptors = Jenkins.getInstance().getDescriptorList(type);
|
||||
return descriptors.stream()
|
||||
.map(d -> {
|
||||
Symbol s = (Symbol) d.getClass().getAnnotation(Symbol.class);
|
||||
if (s != null) return s.value()[0];
|
||||
// TODO truncate extension class name, so LegacyAuthorizationStrategy => "Legacy"
|
||||
else return d.getKlass().toJavaClass().getSimpleName();
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
package org.jenkinsci.plugins.casc;
|
||||
|
||||
import com.google.common.base.Defaults;
|
||||
import hudson.DescriptorExtensionList;
|
||||
import hudson.model.Describable;
|
||||
import hudson.model.Descriptor;
|
||||
import jenkins.model.Jenkins;
|
||||
import org.jenkinsci.Symbol;
|
||||
import org.kohsuke.stapler.ClassDescriptor;
|
||||
import org.kohsuke.stapler.DataBoundConstructor;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A generic {@link Configurator} to configure {@link Describable} which offer a
|
||||
* {@link org.kohsuke.stapler.DataBoundConstructor}
|
||||
*
|
||||
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
|
||||
*/
|
||||
public class DescribableConfigurator extends BaseConfigurator<Describable> {
|
||||
|
||||
private final Class target;
|
||||
|
||||
public DescribableConfigurator(Class clazz) {
|
||||
this.target = clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Describable> getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Describable configure(Object c) throws Exception {
|
||||
|
||||
Map config = c instanceof Map ? (Map) c : Collections.EMPTY_MAP;
|
||||
|
||||
final Constructor constructor = getDataBoundConstructor(target);
|
||||
if (constructor == null) {
|
||||
throw new IllegalStateException(target.getName() + " is missing a @DataBoundConstructor");
|
||||
}
|
||||
|
||||
final Parameter[] parameters = constructor.getParameters();
|
||||
final String[] names = ClassDescriptor.loadParameterNames(constructor);
|
||||
Object[] args = new Object[names.length];
|
||||
|
||||
if (parameters.length > 0) {
|
||||
// Many jenkins components haven't been migrated to @DataBoundSetter vs @NotNull constructor parameters
|
||||
// as a result it might be valid to reference a describable without parameters
|
||||
|
||||
for (int i = 0; i < names.length; i++) {
|
||||
final Object value = config.remove(names[i]);
|
||||
if (value == null && parameters[i].getAnnotation(Nonnull.class) != null) {
|
||||
throw new IllegalArgumentException(names[i] + " is required to configure " + target);
|
||||
}
|
||||
final Class t = parameters[i].getType();
|
||||
if (value != null) {
|
||||
if (Collection.class.isAssignableFrom(t)) {
|
||||
if (!(value instanceof List)) {
|
||||
throw new IllegalArgumentException(names[i] + " should be a list");
|
||||
}
|
||||
final Type pt = parameters[i].getParameterizedType();
|
||||
final Configurator lookup = Configurator.lookup(pt);
|
||||
|
||||
final ArrayList<Object> list = new ArrayList<>();
|
||||
for (Object o : (List) value) {
|
||||
list.add(lookup.configure(o));
|
||||
}
|
||||
args[i] = list;
|
||||
|
||||
} else {
|
||||
final Type pt = parameters[i].getParameterizedType();
|
||||
args[i] = Configurator.lookup(pt != null ? pt : t).configure(value);
|
||||
}
|
||||
System.out.println("Setting " + target + "." + names[i] + " = " + value);
|
||||
} else if (t.isPrimitive()) {
|
||||
args[i] = Defaults.defaultValue(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
Describable object = (Describable) constructor.newInstance(args);
|
||||
|
||||
final Set<Attribute> attributes = describe();
|
||||
|
||||
for (Attribute attribute : attributes) {
|
||||
final String name = attribute.getName();
|
||||
if (config.containsKey(name)) {
|
||||
final Object value = Configurator.lookup(attribute.getType()).configure(config.get(name));
|
||||
attribute.setValue(object, value);
|
||||
}
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
public Constructor getDataBoundConstructor(Class type) {
|
||||
for (Constructor c : type.getConstructors()) {
|
||||
if (c.getAnnotation(DataBoundConstructor.class) != null) return c;
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public String getName() {
|
||||
final Descriptor d = getDescriptor();
|
||||
final Symbol annotation = d.getClass().getAnnotation(Symbol.class);
|
||||
if (annotation != null) return annotation.value()[0];
|
||||
return getTarget().getSimpleName();
|
||||
}
|
||||
|
||||
private Descriptor getDescriptor() {
|
||||
return Jenkins.getInstance().getDescriptor(getTarget());
|
||||
}
|
||||
|
||||
public Class getExtensionpoint() {
|
||||
|
||||
// detect common pattern DescriptorImpl extends Descriptor<ExtensionPoint>
|
||||
final Type superclass = getDescriptor().getClass().getGenericSuperclass();
|
||||
if (superclass instanceof ParameterizedType) {
|
||||
final ParameterizedType genericSuperclass = (ParameterizedType) superclass;
|
||||
Type type = genericSuperclass.getActualTypeArguments()[0];
|
||||
if (type instanceof ParameterizedType) {
|
||||
type = ((ParameterizedType) type).getRawType();
|
||||
}
|
||||
if (type instanceof Class) {
|
||||
return (Class) type;
|
||||
}
|
||||
}
|
||||
return super.getExtensionpoint();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Set<Attribute> describe() {
|
||||
final Set<Attribute> attributes = super.describe();
|
||||
|
||||
final Constructor constructor = getDataBoundConstructor(target);
|
||||
|
||||
if (constructor != null) {
|
||||
final Parameter[] parameters = constructor.getParameters();
|
||||
final String[] names = ClassDescriptor.loadParameterNames(constructor);
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
final Parameter p = parameters[i];
|
||||
final Attribute a = new Attribute(names[i], p.getType());
|
||||
attributes.add(a);
|
||||
}
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
|
||||
public String getDisplayName() {
|
||||
final List<Descriptor> list = Jenkins.getInstance().getDescriptorList(target);
|
||||
return list.get(0).getDisplayName();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
package org.jenkinsci.plugins.casc;
|
||||
|
||||
import jenkins.model.Jenkins;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* TODO rely on some JenkinsRule or comparable to generate this statically from jenkins-core version + set of plugins
|
||||
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
|
||||
*/
|
||||
public class DocumentationGeneration {
|
||||
|
||||
|
||||
// @Initializer(after = InitMilestone.EXTENSIONS_AUGMENTED)
|
||||
public static void init() {
|
||||
new DocumentationGeneration().generate();
|
||||
}
|
||||
|
||||
private final List<Class> documented = new ArrayList<>();
|
||||
|
||||
public void generate() {
|
||||
|
||||
documented.add(int.class);
|
||||
documented.add(String.class);
|
||||
documented.add(boolean.class);
|
||||
documented.add(Integer.class);
|
||||
// ...
|
||||
|
||||
for (RootElementConfigurator c : Jenkins.getInstance().getExtensionList(RootElementConfigurator.class)) {
|
||||
final String name = c.getName();
|
||||
document(name, c.describe());
|
||||
}
|
||||
}
|
||||
|
||||
private void document(String name, Set<Attribute> attributes) {
|
||||
|
||||
Set<Class> next = new HashSet<>();
|
||||
System.out.println();
|
||||
System.out.println("## " + name);
|
||||
for (Attribute attribute : attributes) {
|
||||
|
||||
// FIXME filter attribute to target a component without anything configurable
|
||||
|
||||
final Class type = attribute.getType();
|
||||
System.out.print("**"+attribute.getName() + "** (");
|
||||
if (attribute.isMultiple())
|
||||
System.out.print("list of ");
|
||||
System.out.println(type+")");
|
||||
if (!attribute.possibleValues().isEmpty()) {
|
||||
System.out.println("possible values :");
|
||||
for (Object o : attribute.possibleValues()) {
|
||||
System.out.println(" - " + o);
|
||||
}
|
||||
}
|
||||
|
||||
if (! documented.contains(type)) {
|
||||
next.add(type);
|
||||
}
|
||||
}
|
||||
|
||||
for (Class type : next) {
|
||||
Configurator configurator = Configurator.lookup(type);
|
||||
if (configurator == null) continue;
|
||||
document(type.getName(), configurator.describe());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package org.jenkinsci.plugins.casc;
|
||||
|
||||
import hudson.Extension;
|
||||
import hudson.ExtensionList;
|
||||
import hudson.model.Descriptor;
|
||||
import jenkins.model.Jenkins;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A generic {@link Configurator} for {@link hudson.Extension} singletons
|
||||
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
|
||||
*/
|
||||
public class ExtensionConfigurator extends BaseConfigurator {
|
||||
|
||||
private final Class target;
|
||||
|
||||
public ExtensionConfigurator(Class clazz) {
|
||||
this.target = clazz;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Class getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object configure(Object c) throws Exception {
|
||||
final ExtensionList list = Jenkins.getInstance().getExtensionList(target);
|
||||
if (list.size() != 1) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
final Object o = list.get(0);
|
||||
|
||||
if (c instanceof Map) {
|
||||
Map config = (Map) c;
|
||||
final Set<Attribute> attributes = describe();
|
||||
for (Attribute attribute : attributes) {
|
||||
final String name = attribute.getName();
|
||||
if (config.containsKey(name)) {
|
||||
final Object value = Configurator.lookup(attribute.getType()).configure(config.get(name));
|
||||
attribute.setValue(o, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
package org.jenkinsci.plugins.casc;
|
||||
|
||||
import hudson.model.Describable;
|
||||
import hudson.model.Descriptor;
|
||||
import jenkins.model.Jenkins;
|
||||
import org.jenkinsci.Symbol;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
|
||||
*/
|
||||
public class HeteroDescribableConfigurator extends Configurator<Describable> {
|
||||
|
||||
private final Class target;
|
||||
|
||||
public HeteroDescribableConfigurator(Class clazz) {
|
||||
this.target = clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Describable> getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public List<Configurator> getConfigurators() {
|
||||
final List<Descriptor> candidates = Jenkins.getInstance().getDescriptorList(target);
|
||||
return candidates.stream()
|
||||
.map(d -> Configurator.lookup(d.getKlass().toJavaClass()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Describable configure(Object config) throws Exception {
|
||||
String shortname;
|
||||
Object subconfig = null;
|
||||
if (config instanceof String) {
|
||||
shortname = (String) config;
|
||||
} else if (config instanceof Map) {
|
||||
Map<String, ?> map = (Map) config;
|
||||
if (map.size() != 1) {
|
||||
throw new IllegalArgumentException("single entry map expected to configure a "+target.getName());
|
||||
}
|
||||
shortname = map.keySet().iterator().next();
|
||||
subconfig = map.get(shortname);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unexpected configuration type "+config);
|
||||
}
|
||||
|
||||
final List<Descriptor> candidates = Jenkins.getInstance().getDescriptorList(target);
|
||||
|
||||
Class<? extends Describable> k = findDescribableBySymbol(shortname, candidates);
|
||||
return (Describable) Configurator.lookup(k).configure(subconfig);
|
||||
}
|
||||
|
||||
private Class findDescribableBySymbol(String shortname, List<Descriptor> candidates) {
|
||||
|
||||
// Search for @Symbol annotation on Descriptor to match shortName
|
||||
for (Descriptor d : candidates) {
|
||||
final Symbol symbol = d.getClass().getAnnotation(Symbol.class);
|
||||
if (symbol == null) continue;
|
||||
for (String s : symbol.value()) {
|
||||
if (s.equals(shortname)) {
|
||||
return d.getKlass().toJavaClass();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Search for Fully qualified class name
|
||||
for (Descriptor d : candidates) {
|
||||
final String fqcn = d.getKlass().toJavaClass().getName();
|
||||
if (shortname.equals(fqcn)) {
|
||||
return d.getKlass().toJavaClass();
|
||||
}
|
||||
}
|
||||
|
||||
// Search for class name
|
||||
for (Descriptor d : candidates) {
|
||||
final String cn = d.getKlass().toJavaClass().getSimpleName();
|
||||
if (shortname.equalsIgnoreCase(cn)) {
|
||||
return d.getKlass().toJavaClass();
|
||||
}
|
||||
}
|
||||
|
||||
// Search for implicit symbol, i.e "ldap" for LdapSecurityRealm implementing SecurityRealm
|
||||
String s = shortname + target.getSimpleName();
|
||||
for (Descriptor d : candidates) {
|
||||
final String cn = d.getKlass().toJavaClass().getSimpleName();
|
||||
if (s.equalsIgnoreCase(cn)) {
|
||||
return d.getKlass().toJavaClass();
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("No "+target.getName()+ "implementation found for "+shortname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Attribute> describe() {
|
||||
return Collections.EMPTY_SET;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
package org.jenkinsci.plugins.casc;
|
||||
|
||||
import hudson.Extension;
|
||||
import hudson.ExtensionPoint;
|
||||
import hudson.model.Descriptor;
|
||||
import jenkins.model.Jenkins;
|
||||
import org.apache.commons.beanutils.PropertyUtils;
|
||||
import org.jenkinsci.Symbol;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.WildcardType;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
|
||||
*/
|
||||
@Extension
|
||||
public class JenkinsConfigurator extends BaseConfigurator<Jenkins> implements RootElementConfigurator {
|
||||
|
||||
@Override
|
||||
public Class<Jenkins> getTarget() {
|
||||
return Jenkins.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Jenkins configure(Object c) throws Exception {
|
||||
Map config = (Map) c;
|
||||
Jenkins jenkins = Jenkins.getInstance();
|
||||
|
||||
final Set<Attribute> attributes = describe();
|
||||
|
||||
for (Attribute attribute : attributes) {
|
||||
final String name = attribute.getName();
|
||||
if (config.containsKey(name)) {
|
||||
final Object sub = config.get(name);
|
||||
if (attribute.isMultiple()) {
|
||||
List values = new ArrayList<>();
|
||||
for (Object o : (List) sub) {
|
||||
Object value = Configurator.lookup(attribute.getType()).configure(o);
|
||||
values.add(value);
|
||||
}
|
||||
attribute.setValue(jenkins, values);
|
||||
} else {
|
||||
Object value = Configurator.lookup(attribute.getType()).configure(sub);
|
||||
attribute.setValue(jenkins, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return jenkins;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Set<Attribute> describe() {
|
||||
final Set<Attribute> attributes = super.describe();
|
||||
|
||||
final List<ExtensionPoint> all = Jenkins.getInstance().getExtensionList(ExtensionPoint.class);
|
||||
for (Object e : all) {
|
||||
if (e instanceof Descriptor) continue;
|
||||
final Symbol symbol = e.getClass().getAnnotation(Symbol.class);
|
||||
if (symbol == null) {
|
||||
continue; // This extension doesn't even have a shortname
|
||||
}
|
||||
|
||||
if (Configurator.lookup(e.getClass()).describe().isEmpty()) {
|
||||
// There's nothing on can configure on this extension
|
||||
// So this useless to expose it - this probably is some technical stuff
|
||||
continue;
|
||||
}
|
||||
|
||||
attributes.add(new ExtensionAttribute(symbol.value()[0], e.getClass()));
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "jenkins";
|
||||
}
|
||||
|
||||
private class ExtensionAttribute extends Attribute {
|
||||
public ExtensionAttribute(String name, Class type) {
|
||||
super(name, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(Object target, Object value) throws Exception {
|
||||
// nop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package org.jenkinsci.plugins.casc;
|
||||
|
||||
import org.kohsuke.stapler.Stapler;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
|
||||
*/
|
||||
public class PrimitiveConfigurator extends Configurator {
|
||||
private final Class target;
|
||||
|
||||
public PrimitiveConfigurator(Class clazz) {
|
||||
this.target = clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Attribute> describe() {
|
||||
return Collections.EMPTY_SET;
|
||||
}
|
||||
|
||||
public static final Pattern ENV_VARIABLE = Pattern.compile("\\$\\{(.*)\\}");
|
||||
|
||||
@Override
|
||||
public Object configure(Object config) throws Exception {
|
||||
if (config instanceof String) {
|
||||
String s = (String) config;
|
||||
// TODO I Wonder this could be done during parsing with some snakeyml extension
|
||||
final Matcher matcher = ENV_VARIABLE.matcher(s);
|
||||
if (matcher.matches()) {
|
||||
final String var = matcher.group(1);
|
||||
config = System.getenv(var);
|
||||
if (config == null) throw new IllegalStateException("Environment variable not set: "+var);
|
||||
}
|
||||
}
|
||||
return Stapler.CONVERT_UTILS.convert(config, target);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<Configurator> getConfigurators() {
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package org.jenkinsci.plugins.casc;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Define a {@link Configurator} which handles a root configuration element, identified by name.
|
||||
* Note: we assume any configurator here will use a unique name for root element.
|
||||
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
|
||||
*/
|
||||
public interface RootElementConfigurator {
|
||||
|
||||
String getName();
|
||||
|
||||
Set<Attribute> describe();
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<?jelly escape-by-default='true'?>
|
||||
<!--
|
||||
This view is used to render the installed plugins page.
|
||||
-->
|
||||
<div>
|
||||
This plugin is a sample to explain how to write a Jenkins plugin.
|
||||
</div>
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package org.jenkinsci.plugins.casc;
|
||||
|
||||
import hudson.model.AbstractDescribableImpl;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.jvnet.hudson.test.JenkinsRule;
|
||||
import org.kohsuke.stapler.DataBoundConstructor;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
|
||||
*/
|
||||
public class DescribableConfiguratorTest {
|
||||
|
||||
@Rule
|
||||
public JenkinsRule j = new JenkinsRule();
|
||||
|
||||
@Test
|
||||
public void _databound() throws Exception {
|
||||
Map<String, Object> config = new HashMap<>();
|
||||
config.put("foo", "foo");
|
||||
config.put("bar", true);
|
||||
config.put("qix", 123);
|
||||
final Foo configured = (Foo) Configurator.lookup(Foo.class).configure(config);
|
||||
assertEquals("foo", configured.foo);
|
||||
assertEquals(true, configured.bar);
|
||||
assertEquals(123, configured.qix);
|
||||
}
|
||||
|
||||
|
||||
public static class Foo extends AbstractDescribableImpl<Foo> {
|
||||
|
||||
final String foo;
|
||||
final boolean bar;
|
||||
final int qix;
|
||||
|
||||
@DataBoundConstructor
|
||||
public Foo(String foo, boolean bar, int qix) {
|
||||
this.foo = foo;
|
||||
this.bar = bar;
|
||||
this.qix = qix;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package org.jenkinsci.plugins.casc;
|
||||
|
||||
import hudson.security.FullControlOnceLoggedInAuthorizationStrategy;
|
||||
import hudson.security.HudsonPrivateSecurityRealm;
|
||||
import jenkins.model.Jenkins;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.jvnet.hudson.test.JenkinsRule;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
|
||||
*/
|
||||
public class JenkinsConfiguratorTest {
|
||||
|
||||
@Rule
|
||||
public JenkinsRule j = new JenkinsRule();
|
||||
|
||||
@Test
|
||||
public void jenkins_primitive_attributes() throws Exception {
|
||||
ConfigurationAsCode.configure(getClass().getResourceAsStream("Primitives.yml"));
|
||||
|
||||
final Jenkins jenkins = Jenkins.getInstance();
|
||||
assertEquals(6666, jenkins.getSlaveAgentPort());
|
||||
assertEquals(false, jenkins.isUsageStatisticsCollected());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jenkins_abstract_describable_attributes() throws Exception {
|
||||
ConfigurationAsCode.configure(getClass().getResourceAsStream("HeteroDescribable.yml"));
|
||||
|
||||
final Jenkins jenkins = Jenkins.getInstance();
|
||||
assertTrue(jenkins.getSecurityRealm() instanceof HudsonPrivateSecurityRealm);
|
||||
assertTrue(jenkins.getAuthorizationStrategy() instanceof FullControlOnceLoggedInAuthorizationStrategy);
|
||||
assertFalse(((FullControlOnceLoggedInAuthorizationStrategy) jenkins.getAuthorizationStrategy()).isAllowAnonymousRead());
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package org.jenkinsci.plugins.casc;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.jvnet.hudson.test.JenkinsRule;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
|
||||
*/
|
||||
public class PrimitiveConfiguratorTest {
|
||||
|
||||
@Rule
|
||||
public JenkinsRule j = new JenkinsRule();
|
||||
|
||||
@Test
|
||||
public void _boolean() throws Exception {
|
||||
Configurator c = Configurator.lookup(boolean.class);
|
||||
final Object value = c.configure("true");
|
||||
assertTrue((Boolean) value);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void _int() throws Exception {
|
||||
Configurator c = Configurator.lookup(int.class);
|
||||
final Object value = c.configure("123");
|
||||
assertEquals(123, (int) value);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void _Integer() throws Exception {
|
||||
Configurator c = Configurator.lookup(Integer.class);
|
||||
final Object value = c.configure("123");
|
||||
assertTrue(123 == ((Integer) value).intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void _string() throws Exception {
|
||||
Configurator c = Configurator.lookup(String.class);
|
||||
final Object value = c.configure("abc");
|
||||
assertEquals("abc", value);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
securityConfig:
|
||||
markupFormatter: plainText
|
||||
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
jenkins:
|
||||
securityRealm: local
|
||||
authorizationStrategy:
|
||||
loggedInUsersCanDoAnything:
|
||||
allowAnonymousRead: false
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
jenkins:
|
||||
noUsageStatistics: true
|
||||
slaveAgentPort: 6666
|
||||
authorizationStrategy: loggedInUsersCanDoAnything
|
||||
Loading…
Reference in New Issue