Skip to content

Commit

Permalink
Merge pull request #1173 from casz/casc
Browse files Browse the repository at this point in the history
Support seed job creation in Configuration as Code, updated
  • Loading branch information
daspilker authored Apr 26, 2019
2 parents 6ae6797 + e28f683 commit f52cda2
Show file tree
Hide file tree
Showing 16 changed files with 379 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ gradle-app.setting
/local*
build
generated
/out
*/out/
/classes
.DS_Store

Expand Down
65 changes: 65 additions & 0 deletions docs/JCasC-Job-DSL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
Via [configuratin-as-code-plugin](https://plugins.jenkins.io/configuration-as-code) also known as JCasC

It is possible to configure initial seed jobs through a yaml config file.
The basics for job dsl is you have a root element called `jobs` that will be parsed to configure via job dsl

Examples of config file

```yml
jobs:
- script: >
multibranchPipelineJob('configuration-as-code') {
branchSources {
git {
id = 'configuration-as-code'
remote('https://github.com/jenkinsci/configuration-as-code-plugin.git')
}
}
}
```
You can also fetch your job dsl from a file or URL
```yml
jobs:
- file: ./jobdsl/job.groovy
```
```yml
jobs:
- url: https://raw.githubusercontent.com/jenkinsci/job-dsl-plugin/master/job-dsl-plugin/src/test/resources/javaposse/jobdsl/plugin/testjob.groovy
```
you can reference multiple scripts, files, and urls
```yml
jobs:
- script: >
job('testJob1') {
scm {
git('git://github.com/quidryan/aws-sdk-test.git')
}
triggers {
scm('H/15 * * * *')
}
steps {
maven('-e clean test')
}
}
- script: >
job('testJob2') {
scm {
git('git://github.com/quidryan/aws-sdk-test.git')
}
triggers {
scm('H/15 * * * *')
}
steps {
maven('-e clean test')
}
}
- file: ./jobdsl/job1.groovy
- file: ./jobdsl/job2.groovy
```
3 changes: 3 additions & 0 deletions job-dsl-plugin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ dependencies {
}
optionalJenkinsPlugins 'org.jenkins-ci.plugins:config-file-provider:2.15.4'
optionalJenkinsPlugins 'org.jenkinsci.plugins:managed-scripts:1.3'
optionalJenkinsPlugins 'io.jenkins:configuration-as-code:1.13'
jenkinsTest 'io.jenkins:configuration-as-code:1.13'
jenkinsTest 'io.jenkins:configuration-as-code:1.13:tests'
jenkinsTest 'org.jenkins-ci.plugins:cloudbees-folder:5.14'
jenkinsTest 'org.jenkins-ci.plugins:matrix-auth:1.3'
jenkinsTest 'org.jenkins-ci.plugins:nested-view:1.14'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package javaposse.jobdsl.plugin.casc;

import io.jenkins.plugins.casc.Configurable;
import io.jenkins.plugins.casc.ConfiguratorException;
import io.jenkins.plugins.casc.model.CNode;

public abstract class ConfigurableScriptSource extends ScriptSource implements Configurable {

@Override
public void configure(CNode node) throws ConfiguratorException {
configure(node.asScalar().getValue());
}

protected abstract void configure(String value);

@Override
public void check(CNode node) throws ConfiguratorException {
node.asScalar();
}

@Override
public CNode describe() throws Exception {
return null; // Not relevant here
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package javaposse.jobdsl.plugin.casc;

import hudson.Extension;
import hudson.model.Descriptor;
import org.apache.commons.io.FileUtils;
import org.jenkinsci.Symbol;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class FromFileScriptSource extends ConfigurableScriptSource {

public String path;

@Override
public void configure(String path) {
this.path = path;
}

@Override
public String getScript() throws IOException {
return FileUtils.readFileToString(new File(path), StandardCharsets.UTF_8);
}

@Extension
@Symbol("file")
public static class DescriptorImpl extends Descriptor<ScriptSource> {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package javaposse.jobdsl.plugin.casc;

import hudson.Extension;
import hudson.model.Descriptor;
import io.jenkins.plugins.casc.Configurable;
import org.apache.commons.io.IOUtils;
import org.jenkinsci.Symbol;

import java.io.IOException;
import java.net.URI;

public class FromUrlScriptSource extends ConfigurableScriptSource implements Configurable {

public String url;

@Override
public void configure(String url) {
this.url = url;
}

@Override
public String getScript() throws IOException {
return IOUtils.toString(URI.create(url));
}

@Extension
@Symbol("url")
public static class DescriptorImpl extends Descriptor<ScriptSource> {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package javaposse.jobdsl.plugin.casc;

import hudson.Extension;
import hudson.model.Descriptor;
import io.jenkins.plugins.casc.Configurable;
import org.jenkinsci.Symbol;

import java.io.IOException;

public class InlineGroovyScriptSource extends ConfigurableScriptSource implements Configurable {

public String script;

@Override
public void configure(String script) {
this.script = script;
}

@Override
public String getScript() throws IOException {
return script;
}

@Extension
@Symbol("script")
public static class DescriptorImpl extends Descriptor<ScriptSource> {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package javaposse.jobdsl.plugin.casc;

import hudson.ExtensionPoint;
import hudson.model.AbstractDescribableImpl;

import java.io.IOException;

public abstract class ScriptSource extends AbstractDescribableImpl<ScriptSource> implements ExtensionPoint {

public abstract String getScript() throws IOException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package javaposse.jobdsl.plugin.casc;

import hudson.Extension;
import io.jenkins.plugins.casc.Attribute;
import io.jenkins.plugins.casc.ConfigurationContext;
import io.jenkins.plugins.casc.ConfiguratorException;
import io.jenkins.plugins.casc.Configurator;
import io.jenkins.plugins.casc.RootElementConfigurator;
import io.jenkins.plugins.casc.SecretSourceResolver;
import io.jenkins.plugins.casc.impl.attributes.MultivaluedAttribute;
import io.jenkins.plugins.casc.model.CNode;
import io.jenkins.plugins.casc.model.Mapping;
import java.util.Map;
import javaposse.jobdsl.dsl.GeneratedItems;
import javaposse.jobdsl.plugin.JenkinsDslScriptLoader;
import javaposse.jobdsl.plugin.JenkinsJobManagement;
import javaposse.jobdsl.plugin.LookupStrategy;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;

import javax.annotation.Nonnull;
import javax.annotation.CheckForNull;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import static io.vavr.API.Try;
import static io.vavr.API.unchecked;

@Extension(optional = true, ordinal = -50) // Ordinal -50 Ensure it is loaded after GlobalJobDslSecurityConfiguration
@Restricted(NoExternalUse.class)
public class SeedJobConfigurator implements RootElementConfigurator<GeneratedItems[]> {

@Nonnull
@Override
public String getName() {
return "jobs";
}

@Override
@SuppressWarnings("unchecked")
public Class getTarget() {
return GeneratedItems[].class;
}

@Nonnull
@Override
@SuppressWarnings("unchecked")
public Set<Attribute<GeneratedItems[], ?>> describe() {
return Collections.singleton(new MultivaluedAttribute("", ScriptSource.class));
}

@Override
public GeneratedItems[] getTargetComponent(ConfigurationContext context) {
return new GeneratedItems[0]; // Doesn't really make sense
}

@Nonnull
@Override
@SuppressWarnings("unchecked")
public GeneratedItems[] configure(CNode config, ConfigurationContext context) throws ConfiguratorException {
JenkinsJobManagement management = new JenkinsJobManagement(System.out, System.getenv(), null, null, LookupStrategy.JENKINS_ROOT);
Configurator<ScriptSource> configurator = context.lookupOrFail(ScriptSource.class);
return config.asSequence().stream()
.map(source -> getActualValue(source, context))
.map(source -> getScriptFromSource(source, context, configurator))
.map(script -> generateFromScript(script, management))
.toArray(GeneratedItems[]::new);
}

@Override
public GeneratedItems[] check(CNode config, ConfigurationContext context) throws ConfiguratorException {
return new GeneratedItems[0];
}

@Nonnull
@Override
public List<Configurator<GeneratedItems[]>> getConfigurators(ConfigurationContext context) {
return Collections.singletonList(context.lookup(ScriptSource.class));
}

@CheckForNull
@Override
public CNode describe(GeneratedItems[] instance, ConfigurationContext context) throws Exception {
return null;
}

private CNode getActualValue(CNode config, ConfigurationContext context) {
return unchecked(() -> config.asMapping().entrySet().stream().findFirst()).apply()
.map(entry -> {
Mapping mapping = new Mapping();
mapping.put(entry.getKey(), revealSourceOrGetValue(entry, context));
return (CNode) mapping;
}).orElse(config);
}

private String revealSourceOrGetValue(Map.Entry<String, CNode> entry, ConfigurationContext context) {
String value = unchecked(() -> entry.getValue().asScalar().getValue()).apply();
return SecretSourceResolver.resolve(context, value);
}

private GeneratedItems generateFromScript(String script, JenkinsJobManagement management) {
return unchecked(() ->
Try(() -> new JenkinsDslScriptLoader(management).runScript(script))
.getOrElseThrow(t -> new ConfiguratorException(this, "Failed to execute script with hash " + script.hashCode(), t))).apply();
}

private String getScriptFromSource(CNode source, ConfigurationContext context, Configurator<ScriptSource> configurator) {
return unchecked(() ->
Try(() -> configurator.configure(source, context))
.getOrElseThrow(t -> new ConfiguratorException(this, "Failed to retrieve job-dsl script", t))
.getScript()).apply();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler">
"io.jenkins.plugins.casc.support.jobdsl.FromFileScriptSource": { "type": "string" }
</j:jelly>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler">
"io.jenkins.plugins.casc.support.jobdsl.FromUrlScriptSource": { "type": "string" }
</j:jelly>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler">
"io.jenkins.plugins.casc.support.jobdsl.InlineGroovyScriptSource": { "type": "string" }
</j:jelly>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler">
"[javaposse.jobdsl.dsl.GeneratedItems;": {
"type": "array",
"items": {
"type": "#/definitions/io.jenkins.plugins.casc.support.jobdsl.ScriptSource"
}
}
</j:jelly>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package javaposse.jobdsl.plugin;

import io.jenkins.plugins.casc.misc.ConfiguredWithCode;
import io.jenkins.plugins.casc.misc.JenkinsConfiguredWithCodeRule;
import org.junit.ClassRule;
import org.junit.Test;

import static org.junit.Assert.assertNotNull;

public class ConfigurationAsCodeTest {

@ClassRule
@ConfiguredWithCode("ConfigurationAsCodeTest.yaml")
public static JenkinsConfiguredWithCodeRule j = new JenkinsConfiguredWithCodeRule();

@Test
public void configure_seed_job() {
assertNotNull(j.jenkins.getItem("testJob1"));
assertNotNull(j.jenkins.getItem("testJob2"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
jobs:
- script: >
job('testJob1') {
scm {
git('git://github.com/quidryan/aws-sdk-test.git')
}
triggers {
scm('H/15 * * * *')
}
steps {
maven('-e clean test')
}
}
- file: ./src/test/resources/javaposse/jobdsl/plugin/testjob.groovy
Loading

0 comments on commit f52cda2

Please sign in to comment.