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.
Classes | Description |
---|---|
udm.ConfigurationItem and udm.BaseConfigurationItem | The 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.BaseDeployable | The 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.BaseEmbeddedDeployable | The 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.BaseContainer | The 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.BaseDeployed | The 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.BaseEmbeddedDeployed | The 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.
Classes | Description |
---|---|
udm.Environment | The environment is the target for a deployment in Deploy. It has members of type udm.Container . |
udm.Application | The 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.DeploymentPackage | A deployment package has a set of udm.Deployable CIs, and it is the source for a deployment in Deploy. |
udm.DeployedApplication | The DeployedApplication resembles the deployment of a udm.DeploymentPackage to a udm.Environment with a number of specific udm.Deployed CIs. |
udm.Artifact | An implementation of a udm.Deployable which resembles a 'physical' artifact on disk (or memory). |
udm.FileArtifact | A udm.Artifact which points to a single file. |
udm.FolderArtifact | A 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));
}
}
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 entirepre-plan
phase and will not be available in a different phase such as theplan
phase, for example.However, you can use the
globalContext
object to set attributes globally and get those attributes in different planning contexts (such aspre-plan
,deployed
,plan
, andpost-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
.