Skip to main content
Version: Deploy 22.2

Create a Deploy plugin

This topic covers the customization of Deploy using the Java programming language. By implementing a server plugpoint, you can change certain Deploy server functionality to adapt the product to your needs. And if you want to use Deploy with new middleware, you can implement a custom plugin.

Before you customize Deploy functionality, you should understand the Deploy architecture. See Understanding Deploy's architecture for more information.

You can use the Generic plugin as a basis to create a new plugin, or write a custom plugin from scratch, providing you with powerful ways to extend Deploy.

New and customized plugins are integrated using Deploy's Java plugin API. The plugin API controls the relationship between the Deploy core and a plugin, and ensures that each plugin can safely contribute to the calculated deployment plan.

Refer to the Javadoc for detailed information about the Java API.

To build your own java plugin, include the udm-plugin-api artifact from the com.xebialabs.deployit group from the following maven repository as a dependency:

https://dist.xebialabs.com/public/maven2

For maven projects, your pom.xml should look like this:

<project>
...
<repositories>
<repository>
<id>xebialabs</id>
<url>https://dist.xebialabs.com/public/maven2</url>
</repository>
...
</repositories>

<dependencies>
<dependency>
<groupId>com.xebialabs.deployit</groupId>
<artifactId>udm-plugin-api</artifactId>
<version>2018.5.2</version>
</dependency>
...
</dependencies>

</project>

UDM and Java

The UDM concepts are represented in Java by interfaces:

  • Deployable classes represent deployable CIs
  • Container classes represent container CIs
  • Deployed classes represent deployed CIs

In addition to these types, plugins also specify the behavior required to perform the deployment. That is, which actions (steps) are needed to ensure that a deployable ends up in the container as a deployed. In good OO-fashion, this behavior is part of the deployed class.

Let's look at the mechanisms available to plugin writers in each of the two deployment phases, specification and planning.

Specifying a namespace

All of the CIs in Deploy are part of a namespace to distinguish them from other, similarly named CIs. For instance, CIs that are part of the UDM plugin all use the udm namespace (such as udm.Deployable).

Plugins implemented in Java must specify their namespace in a source file called package-info.java. This file provides package-level annotations and is required to be in the same package as your CIs.

This is an example package-info file:

@Prefix("yak")
package com.xebialabs.deployit.plugin.test.yak.ci;

import com.xebialabs.deployit.plugin.api.annotation.Prefix;

Specification

This section describes Java classes used in defining CIs that are used in the specification stage.

ClassesDescription
udm.ConfigurationItem and udm.BaseConfigurationItemThe udm.BaseConfigurationItem is the base class for all the standard CIs in Deploy. It provides the syntheticProperties map and a default implementation for the name of a CI.
udm.Deployable and udm.BaseDeployableThe udm.BaseDeployable is the default base class for types that are deployable to udm.Container CIs. It does not add any additional behavior
udm.EmbeddedDeployable and udm.BaseEmbeddedDeployableThe udm.BaseEmbeddedDeployable is the default base class for types that can be nested under a udm.Deployable CI, and which participate in the deployment of the udm.Deployable to a udm.Container. It does not add any additional behavior.
udm.Container and udm.BaseContainerThe udm.BaseContainer is the default base class for types that can contain udm.Deployable CIs. It does not add any additional behavior
udm.Deployed and udm.BaseDeployedThe udm.BaseDeployed is the default base class for types that specify which udm.Deployable CI can be deployed onto which udm.Container CI
udm.EmbeddedDeployed and udm.BaseEmbeddedDeployedThe udm.BaseEmbeddedDeployed is the default base class for types that are nested under a udm.Deployed CI. It specifies which udm.EmbeddedDeployable can be nested under which udm.Deployed or udm.EmbeddedDeployed CI.

Additional UDM concepts

In addition to the base types, the UDM defines a number of implementations with higher level concepts that facilitate deployments.

ClassesDescription
udm.EnvironmentThe environment is the target for a deployment in Deploy. It has members of type udm.Container.
udm.ApplicationThe application is a grouping of multiple udm.DeploymentPackage CIs that can each be the source of a deployment (for example: application = PetClinic; version = 1.0, 2.0, ...)
udm.DeploymentPackageA deployment package has a set of udm.Deployable CIs, and it is the source for a deployment in Deploy.
udm.DeployedApplicationThe DeployedApplication resembles the deployment of a udm.DeploymentPackage to a udm.Environment with a number of specific udm.Deployed CIs.
udm.ArtifactAn implementation of a udm.Deployable which resembles a 'physical' artifact on disk (or memory).
udm.FileArtifactA udm.Artifact which points to a single file.
udm.FolderArtifactA udm.Artifact which points to a directory structure.

Mapping deployables to containers

When creating a deployment, the deployables in the package are targeted to one or more containers. The deployable on the container is represented as a deployed. Deployeds are defined by the deployable CI type and container CI type they support. Registering a deployed CI in Deploy informs the system that the combination of the deployable and container is possible and how it is to be configured. Once such a CI exists, Deploy users can create them in the GUI by dragging the deployable to the container.

When you drag a deployable that contains embedded-deployables to a container, Deploy will create a deployed with embedded-deployeds.

Deployment-level properties

It is also possible to set properties on the deployment (or undeployment) operation itself rather than on the individual deployed. The properties are specified by modifying udm.DeployedApplication in the synthetic.xml.

Here's an example:

<type-modification type="udm.DeployedApplication">
<property name="username" transient="true"/>
<property name="password" transient="true" password="true"/>
<property name="nontransient" required="false" category="SomeThing"/>
</type-modification>

Here, username and password are required properties and need to be set before deployment plan is generated. This can be done in the UI by clicking on the Deployment Properties button before starting a deployment.

In the CLI, properties are set on the deployment.deployedApplication:

d = deployment.prepareInitial('Applications/AnimalZoo-ear/1.0', 'Environments/myEnv')
d.deployedApplication.username = 'scott'
d.deployedApplication.password = 'tiger'

Deployment-level properties may be defined as transient, in which case the value will not be stored after deployment. This is useful for user names and password for example. On the other hand, non-transient properties will be available afterwards when doing an update or undeployment.

Analogous to the copying of values of properties from the deployable to the deployed, Deploy will copy properties from the udm.DeploymentPackage to the deployment level properties of the udm.DeployedApplication.

Planning

During planning a Deployment plugin can contribute steps to the deployment plan. Each of the mechanisms that can be used is described below.

@PrePlanProcessor and @PostPlanProcessor

The @PrePlanProcessor and @PostPlanProcessor annotations can be specified on a static method to define a pre- or postprocessor. The pre- or postprocessor takes an optional order attribute which defaults to '100'; lower order means it is earlier, higher order means it is later in the processor chain. The method should take a DeltaSpecification and return either a Step, List of Step or null, the name can be anything, so you can define multiple pre- and postprocessors in one class. See these examples:

@PrePlanProcessor
public static Step preProcess(DeltaSpecification specification) { ... }

@PrePlanProcessor
public static List<Step> foo(DeltaSpecification specification) { ... }

@PostPlanProcessor
public static Step postProcess(DeltaSpecification specification) { ... }

@PostPlanProcessor
public static List<Step> bar(DeltaSpecification specification) { ... }

@Create, @Modify, @Destroy, @Noop

Deployeds can contribute steps to a deployment in which it is present. The methods that are invoked should also be specified in the udm.Deployed CI. It should take a DeploymentPlanningContext (to which one or more Steps can be added with specific ordering) and a Delta (specifying the operation that is being executed on the CI). The return type of the method should be void.

The method is annotated with the operation that is currently being performed on the deployed CI. The following operations are available:

  • @Create when deploying a member for the first time
  • @Modify when upgrading a member
  • @Destroy when undeploying a member
  • @Noop when there is no change

In the following example, the method createEar() is called for both a create and modify operation of the DeployedWasEar.

public class DeployedWasEar extends BaseDeployed<Ear, WasServer> {
...

@Create @Modify
public void createEar(DeploymentPlanningContext context, Delta delta) {
// do something with my field and add my steps to the result
// for a particular order
context.addStep(new CreateEarStep(this));
}
}

Note: These methods cannot occur on udm.EmbeddedDeployed CIs. The EmbeddedDeployed CIs do not add any additional behavior, but can be checked by the owning udm.Deployed and that can generate steps for the EmbeddedDeployed CIs.

@Contributor

A @Contributor contributes steps for the set of Deltas in the current subplan being evaluated. The methods annotated with @Contributor can be present on any static method. The generated steps should be added to the collector argument context.

    @Contributor
public static void contribute(Deltas deltas, DeploymentPlanningContext context) { ... }

The DeploymentPlanningContext

Both a contributor and specific contribution methods receive a DeploymentPlanningContext object as a parameter. The context is used to add steps to the deployment plan, but it also provides some additional functionality the plugin can use:

  • getAttribute() / setAttribute(): contributors can add information to the planning context during planning. This information will be available during the entire planning phase and can be used to communicate between contributors or with the core.

    Note that the attributes set in one phase—pre-plan for example—will only be available during the entire pre-plan phase and will not be available in a different phase such as the plan phase, for example.

    However, you can use the globalContext object to set attributes globally and get those attributes in different planning contexts (such as pre-plan, deployed, plan, and post-plan) while executing Jython/Python scripts.

    Some examples to illustrate the use of the globalContext object.

    pre-plan.py

    contextValue="expectedContextValue"
    context.setAttribute("contextValue",contextValue)
    globalContext.setAttribute("VALUE_SET_AT_PREPLAN", "Example Pre-paln Value")

    deployed.py

    # access value set at pre-paln and deployed scope
    print "Testing global context value: "+str(globalContext.getAttribute("VALUE_SET_AT_PREPLAN"))
    globalContext.setAttribute("VALUE_SET_AT_DEPLOYED", "Example value set at deployed")

    plan.py

    contextValue="expectedContextValue"
    context.setAttribute("contextValue",contextValue)
    globalContext.setAttribute("VALUE_SET_AT_PREPLAN", "Example Pre-paln Value")

    post-plan.py

    # access value set at pre-paln, deployed and plan scope
    print "Testing global context value: "+str(globalContext.getAttribute("VALUE_SET_AT_PREPLAN"))
    print "Testing global context value: "+str(globalContext.getAttribute("VALUE_SET_AT_PLAN"))
    print "Testing global context value: "+str(globalContext.getAttribute("VALUE_SET_AT_DEPLOYED"))

    # set a new value at post-plan scope
    globalContext.setAttribute("VALUE_SET_AT_POSTPLAN", "Example post-paln Value")

    xl-rules.xml

    <?xml version="1.0"?>
    <rules xmlns="http://www.xebialabs.com/xl-deploy/xl-rules">
    <rule name="SuccessBaseDeployedArtifact_PRE_PLAN" scope="pre-plan">
    <planning-script-path>pre-plan.py</planning-script-path>
    </rule>
    <rule name="SuccessBaseDeployedArtifact_PLAN" scope="plan">
    <planning-script-path>plan.py</planning-script-path>
    </rule>
    <rule name="SuccessBaseDeployedArtifact_DEPLoyed" scope="deployed">
    <conditions>
    <type>udm.BaseDeployedArtifact</type>
    <operation>DESTROY</operation>
    <operation>CREATE</operation>
    <operation>MODIFY</operation>
    </conditions>
    <planning-script-path>deployed.py</planning-script-path>
    </rule>
    <rule name="SuccessBaseDeployedArtifact_POST_PLAN" scope="post-plan">
    <planning-script-path>post-plan.py</planning-script-path>
    </rule>
    </rules>

For more information about xl-rules.xml, see Get started with rules.

  • getDeployedApplication(): this allows contributors to access the deployed application that the deployeds are a part of.
  • getRepository(): contributors can access the Deploy repository to determine additional information they may need to contribute steps. The repository can be read from and written to during the planning stage.

Packaging your plugin

Plugins are distributed as standard Java archives (JAR files). Plugin JARs are put in the Deploy server plugins directory, which is added to the Deploy server classpath when it boots. Deploy will scan its classpath for plugin CIs and plugpoint classes and load these into its registry. These classes must be in the com.xebialabs or ext.deployit packages. The CIs are used and invoked during a deployment when appropriate.

Synthetic extension files packaged in the JAR file will be found and read. If there are multiple extension files present, they will be combined and the changes from all files will be combined.

Plugin versioning

Plugins, like all software, change. To support plugin changes, it is important to keep track of each plugin version as it is installed in Deploy. This makes it possible to detect when a plugin version changes and allows Deploy to take specific action, if required. Deploy keeps track of plugin versions by scanning each plugin jar for a file called plugin-version.properties. This file contains the plugin name and its current version.

For example:

    plugin=sample-plugin
version=3.7.0

This declares the plugin to be the sample-plugin, version 3.7.0.

Load order of plugins

If you create a custom plugin based on another plugin, and your custom plugin includes a CI type modification, you must name the custom plugin so that Deploy will load it before the original plugin.

For example, if you create a plugin called mycustom-jbossas-plugin-1.4.0.jar that is based on the JBoss Application Server Plugin (jbossas-plugin), you should change its name to 1-mycustom-jbossas-plugin-1.4.0.jar so it will be loaded before jbossas-plugin.