net.oneandone.gocd:gocd-pico-dsl

Parent POM for 1-and-1 open source projects. Defines often used dependencies and plugins.

License

License

Categories

Categories

Net
GroupId

GroupId

net.oneandone.gocd
ArtifactId

ArtifactId

gocd-pico-dsl
Last Version

Last Version

0.3.4
Release Date

Release Date

Type

Type

jar
Description

Description

Parent POM for 1-and-1 open source projects. Defines often used dependencies and plugins.
Project URL

Project URL

https://github.com/1and1/gocd-pico-dsl
Project Organization

Project Organization

1&1
Source Code Management

Source Code Management

https://github.com/1and1/gocd-pico-dsl/

Download gocd-pico-dsl

How to add to project

<!-- https://jarcasting.com/artifacts/net.oneandone.gocd/gocd-pico-dsl/ -->
<dependency>
    <groupId>net.oneandone.gocd</groupId>
    <artifactId>gocd-pico-dsl</artifactId>
    <version>0.3.4</version>
</dependency>
// https://jarcasting.com/artifacts/net.oneandone.gocd/gocd-pico-dsl/
implementation 'net.oneandone.gocd:gocd-pico-dsl:0.3.4'
// https://jarcasting.com/artifacts/net.oneandone.gocd/gocd-pico-dsl/
implementation ("net.oneandone.gocd:gocd-pico-dsl:0.3.4")
'net.oneandone.gocd:gocd-pico-dsl:jar:0.3.4'
<dependency org="net.oneandone.gocd" name="gocd-pico-dsl" rev="0.3.4">
  <artifact name="gocd-pico-dsl" type="jar" />
</dependency>
@Grapes(
@Grab(group='net.oneandone.gocd', module='gocd-pico-dsl', version='0.3.4')
)
libraryDependencies += "net.oneandone.gocd" % "gocd-pico-dsl" % "0.3.4"
[net.oneandone.gocd/gocd-pico-dsl "0.3.4"]

Dependencies

compile (7)

Group / Artifact Type Version
io.github.microutils : kotlin-logging jar 1.7.6
org.jetbrains.kotlin : kotlin-stdlib-jdk8 jar 1.4.21
org.yaml : snakeyaml jar 1.24
org.jgrapht : jgrapht-core jar 1.3.0
org.jgrapht : jgrapht-io jar 1.3.0
org.reflections : reflections jar 0.9.11
commons-cli : commons-cli jar 1.4

runtime (2)

Group / Artifact Type Version
ch.qos.logback : logback-classic Optional jar 1.2.3
org.slf4j : jul-to-slf4j Optional jar 1.7.25

test (9)

Group / Artifact Type Version
net.javacrumbs.json-unit : json-unit jar 2.8.0
com.fasterxml.jackson.core : jackson-databind jar 2.10.0
com.fasterxml.jackson.dataformat : jackson-dataformat-yaml jar 2.10.0
org.spekframework.spek2 : spek-dsl-jvm jar 2.0.10
org.spekframework.spek2 : spek-runner-junit5 jar 2.0.10
org.jetbrains.kotlin : kotlin-reflect jar 1.4.21
org.jetbrains.kotlin : kotlin-test jar 1.4.21
org.assertj : assertj-core jar 3.13.2
nl.jqno.equalsverifier : equalsverifier jar 3.1.10

Project Modules

There are no modules declared in this project.

GoCD Pipeline Code DSL in Kotlin

Benefits

Typesafe DSL

  • Code completion

  • No typos

Remove boilerplate

  • no need to define upstream pipelines, they are recognized by DSL structure

  • group wrapper (group("groupName") { …​ }) which sets the group in every contained pipeline

Validation

When writing the YAML configuration you can make many mistakes which only occur when you import the configuration in GoCD. With the validation you recognize errors earlier:

Check if pipeline has

  • template or stage

  • defined material or upstream pipeline

  • a group defined

Usage

Setup Kotlin project with GoCD DSL as dependency

pom.xml
<build>
    <sourceDirectory>src/main/kotlin</sourceDirectory>
    <testSourceDirectory>src/test/kotlin</testSourceDirectory>
    <plugins>
        <plugin>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>
<dependencies>
    <dependency>
        <groupId>net.oneandone.gocd</groupId>
        <artifactId>gocd-pico-dsl</artifactId>
        <version>0.3.2</version>
    </dependency>
</dependencies>

Define DSL

Define your pipelines with DSL. Call in a main function ConfigSuite(gocd, outputFolder = Paths.get(outputFolder)).writeFiles() to start generation.

net.oneandone.gocd.picodsl.examples.MinimalExample.kt
fun main(args: Array<String>) {
    val gocd = gocd {
        pipelines {
            sequence {
                group("dev") {
                    pipeline("deploy") {
                        materials {
                            repoPackage("myArtifact")
                        }
                        template = Template("deploy", "last-stage")
                    }
                }
            }
        }
    }

    val outputFolder = if (args.isNotEmpty()) args[0] else "target/gocd-config"
    ConfigSuite(gocd, outputFolder = Paths.get(outputFolder)).writeFiles()
}

Generate DSL

Call the main function above via maven:

mvn compile exec:java -Dexec.mainClass="net.oneandone.gocd.picodsl.samples.MinimalExampleKt" -Dexec.args="target/gocd-config"

Configure Exec Plugin in pom.xml

Have a look at examples for a working maven example.

The following snippet shows two kinds how pipelines can be generated. Using the config registry and net.oneandone.gocd.picodsl.GeneratePipelinesKt should be easier.

examples/pom.xml
<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>exec-maven-plugin</artifactId>
    <version>1.6.0</version>
    <executions>
        <execution>
            <id>registry-starter</id>
            <phase>process-classes</phase>
            <goals>
                <goal>java</goal>
            </goals>
            <configuration>
                <!--ready to use starter which renders all registered pipelines -->
                <mainClass>net.oneandone.gocd.picodsl.GeneratePipelinesKt</mainClass>
                <arguments>
                    <!-- sourcePackage is required -->
                    <argument>--sourcePackage=net.oneandone.gocd.picodsl.examples.registry</argument>
                    <!-- other arguments are optional -->
                    <argument>--outputFolder=target/gocd-config</argument><!-- default: target/gocd-config -->
                    <argument>--plantuml</argument>
                    <argument>--dot</argument>
                </arguments>
            </configuration>
        </execution>
        <execution>
            <id>custom-starter</id>
            <phase>process-classes</phase>
            <goals>
                <goal>java</goal>
            </goals>
            <configuration>
                <!-- custom starter which calls ConfigSuite -->
                <mainClass>net.oneandone.gocd.picodsl.examples.MinimalExampleKt</mainClass>
            </configuration>
        </execution>
    </executions>
</plugin>

Using the registry

All objects which are derived from RegisteredGocdConfig are registered in ConfigRegistry and are automatically used by net.oneandone.gocd.picodsl.GeneratePipelinesKt if they are found in the defined base package.

Second.kt
object Second : RegisteredGocdConfig({
    environments() {
        environment("devEnv") {}
    }
    pipelines {
        sequence {
            deploy("first") {
                group = "dev"
                materials {
                    repoPackage("euss")
                }
            }
        }
    }
})

Generate graphs

If you pass the --dot parameter a dot file is generated in the outputfolder. This can be converted with Graphviz to an image.:

examples/target/gocd-config$ dot pipelines-first.dot -Tpng -o pipelines-first.png

The first line is the pipeline name, second the template name.

pipelines first

With --plantuml the same dot file is generated with a PlantUML wrapper:

@startuml
....
@enduml

So it can be easily viewed in IntelliJ if you have PlantUML integration - plugin for IntelliJ IDEs | JetBrains installed.

Reference

Have a look at examples to see most elements.

Stubs

Stubs can help you if you already have existing pipelines and want to write a GoCD DSL which is based on them. Stubs will not be rendered in the YAML, but are required as they are part of the materials of downstream pipelines.

stubPipeline("existing-pipeline") {
    template = Template("existing", "existing-stage")
}

pipeline("testing") {
    template = testing
}

timer (since 0.3.3)

pipeline("p1") {
    timer("0 15 10 * * ? *", true)

generates

p1:
timer:
  only_on_changes: true
  spec: 0 15 10 * * ? *

Second parameter for onlyOnChanges is optional: timer("0 15 10 * * ? *") is valid too and the YAML element is omitted in that case.

Writing you own Extensions (for advanced users)

GoCD DSL facilitates pipeline writing as much as possible for the generic use case. If you use GoCD in your company you will define best practices and standards.

You can reflect these standards in the DSL to maker you definition even shorter.

If you provide for deployment a "deploy" template, you can define your custom deploy pipeline:

val deployTemplate = Template("deploy", "deploy-stage")

fun PipelineGroup.deploy(name: String, block: PipelineSingle.() -> Unit = {}) {
    this.pipeline(name, block).apply {
        template = deployTemplate
        parameter("param1", "value1")
    }
}

This deploy extension function creates the pipeline as usual and afterwards sets the template and defines also a parameter which is required by the template.

SecondUsingExtension.kt
pipelines {
    sequence {
        deploy("first") {
            group = "dev"
            materials {
                repoPackage("euss")
            }
        }
    }
}

For a better understanding see Extensions - Kotlin Programming Language.

If you want to use your extension function in the group scope, you must add the extension function to every possible scope. This might look scary, but the pattern is always the same: Define extension functions for PipelineGroup, SequenceContext, ParallelContext andd delegate to a function which adds your custom functionality.

PipelineExtensions.kt
fun PipelineGroup.deploy(
        name: String,
        block: PipelineSingle.() -> Unit = {}
) = deployPipeline(this, name, block)

fun SequenceContext.deploy(
        name: String,
        block: PipelineSingle.() -> Unit = {}
) = deployPipeline(this.pipelineGroup, name, block)

fun ParallelContext.deploy(
        name: String,
        block: PipelineSingle.() -> Unit = {}
) = deployPipeline(this.pipelineGroup, name, block)

private fun deployPipeline(pipelineGroup: PipelineGroup, name: String, block: PipelineSingle.() -> Unit) {
    pipelineGroup.pipeline(name, block).apply {
        template = deploy
        parameter("param1", "value1")
    }
}

For a better understanding why wee need to extend every scope have a look at Kotlin DSLs: Type-Safe Builders: Scope Control - Kotlin Programming Language.

net.oneandone.gocd

1&1

Open Source by 1&1 Group.

Versions

Version
0.3.4
0.3.3
0.3.2
0.3.1
0.3
0.2
0.1