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.
-
tutorialintroductory
has been completed. -
pdk
is installed and setup. -
An active CloudBees CD/RO instance.
-
Internet connection.
-
A GitHub account.
-
Write Access to a Git Repository.
-
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/RO 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.
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.
Running the above would create the following
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.
-