Migrate legacy plugins to pdk

8 minute readReferenceExtensibilityDeveloper productivity

As of CloudBees CD/RO release 23.02.0, the legacy CloudBees plugin manager is deprecated. Any plugin using a legacy model should be migrated to the plugin developer kit (pdk) configuration model to continue support. The following instructions and examples may be used as a blueprint to migrate your plugins to pdk. However, depending on how a project-specific plugin was implemented within your project, it may have different and project-specific steps that require project-specific migration.

Get started

The following are requirements or helpful information to migrate your plugins to pdk:

  • You must have the pdk CLI tool installed and configured to migrate plugins from PluginWizard to pdk. If you have not already installed and configured pdk, refer to Install pdk CLI.

  • When migrating any plugin to pdk, CloudBees strongly suggests to create a new project, copy your source files to it, and perform the migration steps on the copied files. This is to prevent errors that may occur during migration from affecting your source files in a severe manner.

  • You can display many helpful pdk command references in your CLI by running pdk -help.

Bootstrap plugin project

Using pdk generate workspace, you can bootstrap a project workspace in the current directory to use to create and migrate plugins. To get started, navigate to the desired directory and run:

pdk generate workspace

After doing so, you are prompted to enter the details for your plugin. The following is a sample output:

Bootstrap sample output
➜  /tmp pdk generate workspace
WARN: Congrats, you are the plugin developer. Fasten the seat belts, I'll show you how deep the rabbit hole goes.
INFO: Writing log to the file /Users/imago/.flowpdf/pdk.log
WARN: You are using EDGE version of flowpdf-cli
INFO: Plugin name is not provided
Please provide a name for the new plugin, e.g. MyPlugin: EC-CloudFoundry
Please provide a workspace type or types.
Available types are rest, reporting or default (default: 'default', separated by a comma or a space):
Please provide the author name (default: none):
Please provide the support URL (default: none):
Please provide the plugin category (default: Utilities):
Please provide the description for the plugin:
Please provide the language for the plugin (default: groovy, available languages are perl, groovy):
WARN: Language  is not supported, Groovy will be used by default
INFO: Wrote initial declaration to /private/tmp/EC-CloudFoundry/config/pluginspec.yaml
INFO: Plugin folder is /private/tmp/EC-CloudFoundry
INFO: Wrote flowpdf specification into /private/tmp/EC-CloudFoundry/config/flowpdf.yaml
INFO: Initial plugin spec has been placed into /private/tmp/EC-CloudFoundry/config/pluginspec.yaml
INFO: Go into the plugin directory: 'cd /private/tmp/EC-CloudFoundry'
INFO: Change the plugin spec and run 'pdk generate plugin' from the plugin directory
INFO: Command took 16443 ms to execute

This creates the <plugin-name> directory with a config directory that contains a flowpdf.yaml and skeleton pluginspec.yaml.

Configure procedure interfaces

In previous plugin configuration models multiple resources were used to configure your plugin’s procedure interfaces. Using pdk, the process of configuring your plugin’s procedure interfaces have been centralized in the pluginspec.yaml.

Define plugin configuration procedures

In previous plugin development tools, the form.xml from the CreateConfiguration procedure was used to define the plugin’s configuration in CloudBees CD/RO’s UI. For pdk, the config/pluginspec.yaml is the primary file used to configure your plugin. This includes information needed to create configurations for the plugin itself in CloudBees CD/RO’s UI.

To configure your plugin’s Configuration procedure, you must convert the XML used in the flow.xml to YAML in the pluginspec.yaml.

The following is an example of a form.xml``s XML being converted to YAML in the `pluginspec.yaml:

Plugin configuration in XML example
<!--

     Copyright 2016 CloudBees, Inc.

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

         https://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.

-->
<editor>
<formElement>
<type>entry</type>
<label>Configuration:</label>
<property>config</property>
<required>1</required>
<documentation>Unique name for the plugin configuration.</documentation>
</formElement>
<formElement>
<type>entry</type>
<label>Description:</label>
<property>desc</property>
<required>0</required>
<documentation>Description for the plugin configuration.</documentation>
</formElement>
<formElement>
<type>entry</type>
<label>API endpoint:</label>
<property>endpoint</property>
<required>1</required>
<documentation>API endpoint for Cloud Foundry, e.g. api.mycloud.com.</documentation>
</formElement>
<formElement>
<type>entry</type>
<label>Organization:</label>
<property>organization</property>
<required>1</required>
<documentation></documentation>
</formElement>
<formElement>
<type>entry</type>
<label>Space:</label>
<property>space</property>
<required>0</required>
<documentation></documentation>
</formElement>
<formElement>
<type>credential</type>
<label>Username and password:</label>
<property>credential</property>
<required>1</required>
<documentation>Credentials to connect</documentation>
<attachedAsParameterToStep>createAndAttachCredential</attachedAsParameterToStep>
</formElement>
<formElement>
<type>select</type>
<label>Debug level:</label>
<property>logLevel</property>
<required>0</required>
<documentation>Verbosity level of logs</documentation>
<option>
<name>Info</name>
<value>1</value>
</option>
<option>
<name>Debug</name>
<value>2</value>
</option>
<option>
<name>Trace</name>
<value>3</value>
</option>
</formElement>
</editor>

Is converted to:

Plugin configuration in YAML example
  # This is a shell used for checking connection shell: 'ec-groovy'
  # A script for checking connection will be generated
  checkConnection: 'true'
  # A set of fields will be added to process debug level in the configuration
  hasDebugLevel: true
  parameters:
  -
    name: config
    documentation: The name for the created configuration
    required: true
    type: entry
    label: Configuration Name
  -
    name: desc
    documentation: Description for the configuration
    required: false
    type: entry
    label: Description
  -
    name: endpoint
    documentation: Third-party endpoint to connect to.
    required: false
    type: entry
    label: Endpoint
  -
    name: organization
    documentation: Third-party endpoint to connect to.
    required: true
    type: entry
    label: Orgainization
  -
    name: space
    documentation: Third-party endpoint to connect to.
    required: true
    type: entry
    label: Space
  -
    name: credential
    documentation: A sample credential
    required: true
    type: credential
    label: Credential
# No need to transfer debugLevel or any other parameter that regulates the log level - it is enabled by the hasDebugLevel: true parameter.

Define plugin procedures

In previous plugin development tools, the plugin’s procedures were stored in a combination of the procedure.dsl and form.xml. For pdk, the config/pluginspec.yaml is the primary file used to define your plugin’s procedures. This includes information needed to create configurations for the plugin’s procedures in CloudBees CD/RO’s UI.

To define your plugin’s procedures, you must convert the DSL and XML used in the previous files to YAML in the pluginspec.yaml.

The following is an example of a procedure.dsl being converted to YAML in the pluginspec.yaml:

Procedure configuration in DSL example
import java.io.File

def procName = 'Unmap Route'
procedure procName,
description: 'Remove a url route from an app', {

    step 'setup',
        subprocedure: 'flowpdk-setup',
        errorHandling: 'failProcedure',
        exclusiveMode: 'none',
        postProcessor: 'postp',
        releaseMode: 'none',
        shell: 'ec-perl',
        timeLimitUnits: 'minutes'

    step 'unmapRoute',
            command: '''
            $[/myProject/scripts/preamble]

            def plugin = CFPlugin.build()
            plugin.stepUnmapRoute()
            ''',
            errorHandling: 'abortProcedure',
            exclusiveMode: 'none',
            postProcessor: 'postp',
            releaseMode: 'none',
            resourceName: '$[grabbedResource]',
            shell: 'ec-groovy',
            timeLimitUnits: 'minutes'
}

Procedure configuration in YAML

From here, you need the procedure name, description, and code for the step. The name and the description will go to the definition in the pluginspec.yaml along with the parameters from the form.xml:

Procedure configuration in YAML example
-
  name: Unmap Route
  description: Remove a url route from an app
  # configuration field will be generated automatically
  hasConfig: true

  parameters:
  -
    name: space
    documentation: A space provides users with access to a shared location for application development, deployment, and maintenance. Will be taken from the config, if defined.
    required: true
    type: entry
    label: Space
  -
    name: applicationName
    documentation: Application name
    required: true
    type: entry
    label: Application name
  -
    name: domain
    documentation: Domain for the route.
    required: true
    type: entry
    label: Domain name

  -
    name: hostname
    documentation: Hostname for the HTTP route (required for shared domains).
    required: false
    type: entry
    label: Hostname

  -
    name: path
    documentation: Path for the HTTP route.
    required: false
    type: entry
    label: Path

  -
    name: port
    documentation: ''
    required: false
    type: entry
    label: Port
Config parameters are not transferred but now handled by the hasConfig field instead. Later, you will move the content from the command field of the procedure.dsl into the main plugin’s code file.

Compose plugin code

Once you have configured your pluginspec.yaml, to compile the plugin, run:

pdk generate plugin

This generates a <pluginName>.groovy. In this file, there will be a placeholder for the step code similar to:

<pluginName>.groovy example
    def unmapRoute(StepParameters p, StepResult sr) {
        // Use this parameters wrapper for convenient access to your parameters
        UnmapRouteParameters sp = UnmapRouteParameters.initParameters(p)

        // Calling logger:
        log.info p.asMap.get('config')
        log.info p.asMap.get('space')
        log.info p.asMap.get('applicationName')
        log.info p.asMap.get('domain')
        log.info p.asMap.get('hostname')
        log.info p.asMap.get('path')
        log.info p.asMap.get('port')


        // Setting job step summary to the config name
        sr.setJobStepSummary(p.getParameter('config')?.getValue() ?: 'null')

        sr.setReportUrl("Sample Report", 'https://cloudbees.com')
        sr.apply()
        log.info("step Unmap Route has been finished")
    }

For now, the parameters and job summary are generated. This is where your previous command content goes, excluding the $[]:

  def plugin = CFPlugin.build()
  plugin.stepUnmapRoute()

Depending on how your code was organized before, some refactoring may be required. However, all CloudBees CD/RO-related calls are now be handled by pdk and all third-party related calls must be handled by another separated module.

Other modules can be placed along the <pluginName>.groovy, and called by their names, without a package name used. The resulting modules are concatenated and the step’s method are called when the procedure runs.

The following is an example of a plugin that used a legacy REST client to communicate with the CloudBees CD/RO server, which is no longer needed, and a wrapper around the plugin (Cloud Foundry), which may be reused with a slightly different handling of the input parameters.

The dependencies that are declared in form of grapes are to be turned into gradle dependencies and put into the build.gradle within the plugin’s root folder.

Legacy REST client example
import com.cloudbees.flowpdf.*

/**
* CloudFoundry
*/
class CloudFoundry extends FlowPlugin {

    @Override
    Map<String, Object> pluginInfo() {
        return [
            pluginName         : '@PLUGIN_KEY@',
            pluginVersion      : '@PLUGIN_VERSION@',
            configFields       : ['config'],
            configLocations    : ['ec_plugin_cfgs'],
            defaultConfigValues: [:]
        ]
    }

    /** This is a special method for checking connection during configuration creation
     */
    def checkConnection(StepParameters p, StepResult sr) {
        // Use this pre-defined method to check connection parameters
        try {
            // Put some checks here
            def config = context.configValues
            log.info(config)
            // Getting parameters:
            // log.info config.asMap.get('config')
            // log.info config.asMap.get('desc')
            // log.info config.asMap.get('endpoint')
            // log.info config.asMap.get('credential')

            // assert config.getRequiredCredential("credential").secretValue == "secret"
        } catch (Throwable e) {
            // Set this property to show the error in the UI
            sr.setOutcomeProperty("/myJob/configError", e.message + System.lineSeparator() + "Please change the code of checkConnection method to incorporate your own connection checking logic")
            sr.apply()
            throw e
        }
    }

    // === check connection ends ===
    /**
     * unmapRoute - Unmap Route/Unmap Route
     * Add your code into this method and it will be called when the step runs
     * @param config (required: true)
     * @param space (required: true)
     * @param applicationName (required: true)
     * @param domain (required: true)
     * @param hostname (required: false)
     * @param path (required: false)
     * @param port (required: false)

     */
    def unmapRoute(StepParameters p, StepResult sr) {
        // Use this parameters wrapper for convenient access to your parameters
        UnmapRouteParameters sp = UnmapRouteParameters.initParameters(p)

        Map params = p.asMap
        params.space = context.configValues.getParameter('space').value
        cfClient.unmapRoute(params)
        sr.setJobStepSummary("The route has been unmapped")
        sr.apply()
    }

    @Lazy
    def cfClient = {
        new CFClient(
            endpoint: context.configValues.getParameter('endpoint').value,
            userName: context.configValues.getCredential('credential')?.userName,
            password: context.configValues.getCredential('credential').secretValue,
            space: context.configValues.getParameter('space')?.value,
            organization: context.configValues.getParameter('organization')?.value,
            logLevel: context.configValues.getParameter('debugLevel')?.value as int
        )
    }()

    // === step ends ===

}

For more examples of REST PDK development, refer to:

Configure plugin UI text

Within your pluginspec.yaml, you can use the documentation tag to enter UI content for each field. For example:

  parameters:
  -
    name: space
    documentation: A space provides users with access to a shared location for application development, deployment, and maintenance. Will be taken from the config, if defined.
    required: true
    type: entry
    label: Space
You can use both plain text and HTML for UI text.

Migrating your plugin configurations

After the plugin manager is updated, your plugin configurations based on GWT may not be available to view. To view legacy plugin configurations:

  1. Navigate to DevOps Essentials  Platform Home page.

  2. Select the Administration link. The Event Logs page displays.

  3. Select the Plugins page link. The Plugins Manager page displays.

  4. Select the name of the plugin name in the Plugin Label column. The Project Details page displays.

  5. Select the Properties tab.

  6. In the property <PluginName>_cfgs, you can find all configurations for the plugin.

  7. Note any specific configuration names that need to be migrated. You can also migrate all configurations.

    If you are migrating all configurations, you do not need the names. By default, the Migrate configurations service catalog item migrates all configurations when no configuration names are specified.

To migrate the plugin’s configurations, you can use the Migrate configurations service catalog item:

  1. Select the Service catalog from the main CloudBees CD/RO navigation header.

  2. In the Service catalog, search for Migrate configurations, and select migrate.

  3. (Optional) If you have only specific plugin configurations to migrate, enter them in the Configuration names one per line. If you want to migrate all the plugin’s configurations, leave this field blank.

  4. Enter the plugin you want to migrate the in Plugin name field.

  5. Use the Project name list to select the specific project for which you want to migrate the plugin configuration(s).

  6. Select OK to migrate the plugin configurations.