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 and use PDK.

  • 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 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. Enter the plugin you want to migrate in the Plugin name field. For example, EC-Jira.

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

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

  6. (Optional) For Update configuration path

    1. Selecting Update configuration path:

      • If specific plugin configurations are listed in Configuration names, all instances of these configurations are updated in all CloudBees CD/RO objects where they are used.

      • If Configuration names is blank, selecting Update configuration path updates all instances of all plugin configurations in all CloudBees CD/RO objects where this plugin is used.

        If you use the Update configuration path, this process cannot be undone through this automation, and the only way to restore specific instances of the legacy plugin configuration in CloudBees CD/RO objects is through manual efforts.
    2. Not selecting Update configuration path:

      • Using the Migrate configurations service catalog item creates completely new plugin configurations, and does not overwrite legacy configurations or replace instances where plugins used legacy configurations. The legacy configurations will still be in use and function.

      • After running the Migrate configurations service catalog item on a project, to use the new plugin configurations, you must manually update plugin instances with the new plugin configurations. This includes plugin instances in pipeline tasks, procedure steps, applications components, etc.

  7. Select OK to migrate the plugin configurations.