Terminology
REST plugin
A REST plugin, also known as a REST-based plugin, is a plugin that contains procedures implemented using REST requests over HTTP. Note that a REST plugin could have procedures whose implementation does not use REST.
REST methods
REST methods act as proxies for a REST request in a specific language
of implementation. For example a REST API endpoint /foobar/info
that
supports an operation called GET
can be represented as a Perl function
called getInfo
adequately parametrized based on the interface of the
REST request. The REST methods generated for Perl use LWP (Library for
WWW in Perl) to both perform HTTP requests as well as handle HTTP
responses.
REST methods occur in two ways:
* Directly corresponding to the implementation of a REST Procedure. For example refer to getUser in the snippets below.
* As dependencies that are in turn used by the REST Methods of a REST procedure. For example, refer to uploadReleaseAsset
in the snippets below.
REST client
A REST client is a module that is a collection of REST Methods created for the purpose of encapsulation, as well as performance optimization, where a single object can be instantiated to handle REST calls, including authorization, for the life time of the request response cycle in a plugin invocation.
Usage of REST in pluginspec.yaml
The different usages of REST in pluginspec.yaml
are described below.
Plugin configuration
The REST key here represents a dictionary of fields required to create a plugin configuration. Salient fields are described below.
-
defaultEndpointValue
represents the root-endpoint for making a HTTP request. -
checkConnectionUri
represents the path that needs to be appended to the root-endpoint in order to make a HTTP request to check connection. -
authSchemes
represent the various authorization schemes that can be used in order to complete the HTTP Request.
It is important to note that the details pertaining to a REST request mentioned in the configuration are implicitly used by all REST methods in the plugin, through the REST client.
# REST-backed plugin configuration configuration: checkConnection: true restConfigInfo: endpointLabel: 'Github Endpoint' endpointDescription: Endpoint to connect to. checkConnectionUri: '/user' defaultEndpointValue: https://api.github.com headers: Accept: 'application/json' authSchemes: basic: userNameLabel: 'Username' passwordLabel: 'Password' description: 'Username and password to connect to ...' bearer: passwordLabel: 'Bearer token' description: 'Bearer token to connect toGitHub.' anonymous: checkConnectionUri: '/emojis' hasProxySupport: true hasDebugLevel: true
REST client
The REST key here represents a dictionary of fields required to create a REST client in Perl. Salient fields are described below.
-
The
endpoints
key represents a list of REST Methods. For each REST method:-
The
methodName
represents the name of the Method. -
The
httpMethod
represents the REST Verb (Operation). -
The
parameters
key represents the list of parameters. -
The
in
key represents if the parameter is a PATH or a QUERY parameter. -
The
url
represents the path that would be appended to the root-endpoint in the Plugin configuration to make the HTTP request.
restClient: userAgent: MyGitHubClient language: perl endpoints: - methodName: uploadReleaseAsset httpMethod: POST parameters: - name: name in: query - name: repositoryOwner in: path - name: releaseId in: path - name: repositoryName in: path url: /repos/{{repositoryOwner}}/{{repositoryName}}/releases/{{releaseId}}/assets
-
REST procedures
The REST key is used with two connotations in the creation of a REST procedure to:
-
Specify that a certain parameter in a procedure is mapped to a path parameter of its REST implementation. For example refer to
getUser
in the snippet below. -
Specify a dictionary of salient fields required to create the REST Method.
-
url
which will get appended to the root end-point in plugin configuration when making a HTTP request. -
methodName
which represents the name of the method. -
httpMethod
which represents the REST operation performed.
-
procedures: - name: 'Get User' description: 'This procedure downloads the specified user data' hasConfig: true shell: 'ec-perl' parameters: - name: username documentation: Name of the GitHub user, e.g. `octocat` required: true type: entry label: Username restParamInfo: in: path restProcedureInfo: url: '/users/{{username}}' methodName: 'getUser' httpMethod: 'GET'
Usage of authorization schemes in pluginspec.yaml
Currently the following REST authorization schemes are supported as part of the plugin configuration.
-
basic
: Uses a combination of username and password as below. The UI renders theusername
andpassword
as two different fields.authSchemes: basic: userNameLabel:GitHubUser Name passwordLabel:GitHubPassword description: 'Username and password to connect to github' credentialLabel:GitHubCredentials
-
bearer
: Passes a bearer token as part of the authorization header. The UI renders the token as a single field of credential type secret.authSchemes: bearer: passwordLabel: 'Github Token' description:GitHubPAT prefix: Bearer
-
oauth1
: OAuth authorization is performed using a secret and consumer key based on the RSA-SHA1 signature method. This is typically with vendors, like Jira, that support this type of authorization.authSchemes: oauth1: tokenLabel: token signatureMethod: RSA-SHA1
-
anonymous
: No authorization is performed. This is typically applicable when there is a need for read-only use cases, where a plugin contains procedures that do not need authorization.authSchemes: anonymous: # Since there is no authorization, the URI may be different from the default checkConnectionUri: ''
Authorization schemes beyond the above would require custom code to be written by a developer and are beyond the scope of this guide.
Modules generated for REST plugins
For a REST plugin whose name is CB-Git
there are two modules that are
generated.
CB-Git.pm
# Auto-generated method for the procedure Sample REST Procedure/Sample REST Procedure # Add your code into this method and it will be called when step runs sub sampleRESTProcedure { my ($pluginObject) = @_; my $context = $pluginObject->getContext(); my $params = $context->getStepParameters(); my $RestRESTClient = $pluginObject->getRestRESTClient; my %params = ( username => $params->getParameter('username')->getValue, ); my $response = $RestRESTClient->getUser(%params); logInfo("Got response from the server: ", $response); my $stepResult = $context->newStepResult; $stepResult->apply(); } ## === step ends === # Generated, once sub getRestRESTClient { my ($self) = @_; my $context = $self->getContext(); my $config = $context->getRuntimeParameters(); require FlowPlugin::RestRESTClient; my $client = FlowPlugin::RestRESTClient->createFromConfig($config); return $client; }
CB-GitRestClient.pm
## === REST client ends, checksum: 57bb11176e6b2635ec06ddf1a2b94d95 === sub augmentRequest { my ($self, $r, $p) = @_; # empty, for user to fill return $r; } # can be redefined sub encodePayload { my ($self, $payload) = @_; return encode_json($payload); } sub processResponse { my ($self, $response) = @_; return undef; } # can be redefined sub parseResponse { my ($self, $response) = @_; if ($response->content) { return decode_json($response->content); } } 1;
Extending generated methods (code snippets)
Special getters
In all redefined methods you may use the following getters:
-
Returns the name of the generated method provided in the
pluginspec.yaml
:print $self->method
-
Returns the map with parameters passed to the method from the original call:
$self->methodParameters
-
Gets the original method name and original request parameters:
# Somewhere in the calling code $client->methodName(param1 => $param1); # Somewhere in the REST client if ($self->method eq 'methodName') { my $parameters = $self->methodParameters; print $parameters->{param1}; }
Custom authorization
Pass signing secrets to the client during object creation.
sub augmentRequest { my ($self, $r, $params) = @_; my $secret = $self->{secret}; my $signature = md5_hex($secret . $r->uri . $r->method . $r->content); $r->header('X-Signature', $signature); return $r; }
File upload
sub augmentRequest { my ($self, $r, $params) = @_; # empty, for user to fill if ($self->method eq 'uploadFile') { open my $fh, $params->{fileName} or die "Cannot open file $params->{fileName}: $!"; binmode $fh; my $bytes = join '' => $fh; close $fh; $r->content($bytes); } return $r; }
Altering default payload structure
sub encodePayload { my ($self, $payload) = @_; if ($self->method eq 'createIssue') { $payload = { issue => {title => $payload->{title}} }; } return encode_json($payload); }
Error message processing
sub processResponse { my ($self, $response) = @_; if ($response->is_success) { return; } my $json = decode_json($response->content); my $message = $json->{error} || "No error message received"; die "Failed to execute request: $message"; }
Wrapping response JSON with objects
sub parseResponse { my ($self, $response) = @_; if ($self->method eq 'createRepository') { my $rawData = decode_json($response->content); my $repository = GH::Client::Repository->new($rawData); return $repository; } if ($response->content) { return decode_json($response->content); } }