Advanced REST Plugin

5 minute read

Introduction

This tutorial provides instructions to create a REST Plugin called SampleGithubREST that has 2 functional procedures as described below. These are implemented using GitHub REST APIs.

  • getUser which takes a username and returns user information.

  • Create Release which creates a Release and uploads an artifact associated with that Release, to a Repository.

Prerequisites

These are assumptions for this Tutorial.

  1. tutorialintroductory has been completed.

  2. pdk is installed and setup.

  3. An active CloudBees CD instance.

  4. Internet connection.

  5. A GitHub account.

  6. Write Access to a Git Repository.

  7. An asset (for example a zip file) that can be uploaded to this Repository.

Step 1 : Generate a plugin using sample spec fromGitHub

After making sure pdk is available in your PATH, create a plugin workspace, typing the name SampleGithubREST for the plugin. Copy pluginspec.yaml from the cloned repository to the config directory of your plugin and generate the plugin.

cd ~/work git clone https://github.com/electric-cloud-community/flowpdf ~/temp `pdk generate workspace` cp ~/temp/flowpdf/groovy/SampleGithubREST/config/pluginspec.yaml SampleGithubREST/config cd SampleGithubREST `pdk generate plugin`

Once done, it should look like this.

INFO: Working in /home/anykey/work/SampleGithubREST INFO: Fetching versions from https://s3.amazonaws.com/flowpdf-libraries-edge/flowpdf-plugin-layout/versions.json INFO: Fetching the asset from https://s3.amazonaws.com/flowpdf-libraries-edge/flowpdf-plugin-layout/1.2.0/flowpdf-plugin-layout.zip INFO: Inflated plugin metafile (plugin.xml) INFO: Generated promote.groovy INFO: Inflated demote.groovy INFO: Wrote ec_setup.pl INFO: Wrote initial README.md INFO: Saved generic check connection step INFO: Generated procedure.dsl for the CreateConfiguration procedure INFO: Wrote createConfiguration.pl step INFO: Created DeleteConfiguration/procedure.dsl INFO: Inflated form for DeleteConfiguration INFO: Created step for DeleteConfiguration INFO: Saved procedure.dsl for the EditConfiguration procedure INFO: Configuration has been inflated INFO: Saved procedure.dsl for the procedure Get User INFO: Saved form.xml for the procedure Get User INFO: Fetching versions from https://flowpdf-libraries-edge.s3.amazonaws.com/flowpdf-groovy/versions.json INFO: Fetching latest version from https://flowpdf-libraries-edge.s3.amazonaws.com/flowpdf-groovy INFO: Fetching versions from https://flowpdf-libraries-edge.s3.amazonaws.com/flowpdf-groovy/versions.json INFO: Latest version is 1.0.0 INFO: Fetching the asset from https://flowpdf-libraries-edge.s3.amazonaws.com/flowpdf-groovy/1.0.0/flowpdf-groovy.zip INFO: Updated Flow Logic class INFO: Wrote step code for step Get User: Get User INFO: Copied agent folder files INFO: Saved preamble under /home/anykey/work/SampleGithubREST/dsl/properties/groovy/scripts/preamble.groovy.ignore INFO: Inflating REST Client SampleGithubRESTRESTClient INFO: Saved preamble under /home/anykey/work/SampleGithubREST/dsl/properties/groovy/scripts/preamble.groovy.ignore INFO: Saved build.gradle INFO: Saved procedure.dsl for the procedure Create Release INFO: Saved form.xml for the procedure Create Release WARN: The code for the step Create Release:Create Release already exists in the main module and will not be regenerated INFO: Updated Flow Logic class INFO: Wrote step code for step Create Release: Create Release INFO: Copied agent folder files INFO: Main class already exists in the preamble, skipping INFO: Inflating REST Client SampleGithubRESTRESTClient INFO: Main class already exists in the preamble, skipping INFO: build.gradle already exists in the plugin folder INFO: Icon placeholder is placed into /home/anykey/work/SampleGithubREST/htdocs/pdfgroovy/icon-plugin.svg INFO: Copied Setup procedure over INFO: Copied postprocessor over to /home/anykey/work/SampleGithubREST/dsl/properties/perl INFO: Copied agent folder files INFO: Main class already exists in the preamble, skipping INFO: Inflating REST Client SampleGithubRESTRESTClient INFO: Main class already exists in the preamble, skipping INFO: build.gradle already exists in the plugin folder INFO: Inflated core library groovy

Generated plugin contains two main classes:

anykey@hp-anykey:~/work/SampleGithubREST$ tree dsl/properties/groovy dsl/properties/groovy ├── lib │   ├── SampleGithubREST.groovy │   └── SampleGithubRESTRESTClient.groovy └── scripts └── preamble.groovy.ignore 2 directories, 3 files

SampleGithubREST.groovy contains usual plugin steps code. Because procedure has restProcedureInfo section and the desired interaction is known at generate time, step code already contains instructions necessary to perform the request. SampleGithubRESTRESTClient.groovy contains a REST client, generated from the restClient section of the pluginspec.yaml

Step 2 : Build a Plugin

Build the plugin as follows.

anykey@hp-anykey:~/work/SampleGithubREST$ flowpdk build WARN: You are using EDGE version of flowpdf-cli Plugin Key: SampleGithubREST Plugin Version: 1.0.0.0 [WARNING] No form.xml was found for the procedure flowpdk-setup INFO: Added fallback code to the ec_setup.pl Adding plugin icon pdfgroovy/icon-plugin.svg Adding folder /home/anykey/work/SampleGithubREST/agent to the archive Adding folder /home/anykey/work/SampleGithubREST/config to the archive Adding folder /home/anykey/work/SampleGithubREST/htdocs to the archive Adding folder /home/anykey/work/SampleGithubREST/pages to the archive Archive /home/anykey/work/SampleGithubREST/build/SampleGithubREST.zip has been created

Step 3 : Apply and Test the Plugin for Checkconnection

Using CloudBees CD Plugin Manager upload the zip from the previous step and create a plugin configuration. If this step is successful you should be able to add the Plugin in a pipeline task as in the picture below. Note that the procedures may still not be fully functional. The purpose of this step is to verify that the built plugin from the previous step can successfully connect toGitHub.

image

Step 4 : Modify the Plugin

While the plugin has generated code for Create Release and Get User from the previous steps, what is missing is that the Create Release would require an Artifact to be uploaded. This is implemented using the following approach.

In SampleGithubRESTRESTClient.groovy define a new method called _uploadReleaseAsset in the REST Client module, which can upload to GitHub based on an asset path provided.

private HTTPRequest _uploadReleaseAsset(HTTPRequest request) { String assetPath = methodParameters.assetPath assert assetPath File asset = new File(assetPath) request.binaryContent = asset.bytes rest.httpBuilder.setUri("https://uploads.github.com") if (methodParameters.assetName) { request.setQueryParameter('name', methodParameters.assetName) } return request }

Then extend augmentRequest to trigger _uploadReleaseAsset. In essence this would make sure that when uploadReleaseAsset gets called, the augmentRequest which kicks in would call _uploadReleaseAsset.

HTTPRequest augmentRequest(HTTPRequest request) { if (method == 'uploadReleaseAsset') { request = _uploadReleaseAsset(request) } return request }

In SampleGithubREST.groovy extend createRelease in the following ways.:: * Get the parameters from the Step * Invoke the REST API to get the Release by tag name * Invoke the createRelease REST API if a Release does not exist * Call uploadReleaseAsset

def createRelease(StepParameters p, StepResult sr) { SampleGithubRESTRESTClient rest = getSampleGithubRESTRESTClient() Map restParams = [:] Map requestParams = p.asMap restParams.put('repositoryOwner', requestParams.get('repositoryOwner')) restParams.put('repositoryName', requestParams.get('repositoryName')) restParams.put('tag_name', requestParams.get('tagName')) restParams.put('name', requestParams.get('name')) def release try { restParams.tag = requestParams.tagName release = rest.getReleaseByTagName(restParams) } catch (Throwable e) { log.info e.message log.info("Failed to get release for tag $requestParams.tagName") } if (!release) { Object response = rest.createRelease(restParams) release = response log.info "Created release: $release" } String htmlUrl = release.html_url sr.setReportUrl("Release URL", htmlUrl) sr.apply() def existingAsset = release.assets?.grep { it.name == requestParams.assetName } if (existingAsset) { context.bailOut("The asset named ${requestParams.assetName} already exists in the release") } String assetPath = requestParams.assetPath File asset = new File(assetPath) if (!asset.exists()) { context.bailOut("The file $asset.absolutePath does not exist") } restParams.releaseId = release.id restParams.assetPath = asset.absolutePath restParams.assetName = requestParams.assetName def uploadedAsset = rest.uploadReleaseAsset(restParams) log.info "Asset: $uploadedAsset" }

Step 5 : Apply and Test the Plugin for CreateRelease

Run the Create Release as in the picture below. Note the following:

  • The repositoryOwner is the name of the organization

  • If the Release Name is blank the Release Name will default to the name of the Tag.

image

Running the above would create the following

image

Step 6: Summary

This Summary is provided in order to help a Developer conceptualize the steps involved in the creation of this plugin.

Specification
  • pluginspec.yaml provides the declarative interface for the plugin procedures (Create Release, Get User) as well as their dependencies viz., the REST methods getReleaseTagByName and uploadReleaseAsset.

  • The interface specification in pluginspec.yaml for the Create Release and Get User procedures, include REST endpoints that help implement their functionality.

Generated Code
  • flowpdk generates boiler plate code for procedures in SampleGithubREST.groovy as well as SampleGithubRESTRESTClient.groovy. The former consists of boiler plate code for collecting step parameters and the later for making REST calls.

  • flowpdk generates boiler plate code for all REST Methods uploadReleaseAsset, getReleaseByTagName, createRelease and getUser all in the REST Client Module.

User Modifications
  • Implement a new method called _uploadReleaseAsset to upload the asset.

  • Extend uploadReleaseAsset by using augmentRequest to call _uploadReleaseAsset.