Rest Plugin Concepts¶
Terminology¶
Rest Plugin¶
A Rest Plugin (aka Rest Based Plugin) is a Plugin which contains procedures that are implemented using REST Requests over HTTP. Note that a Rest Plugin could have procedures whose implementation does not use REST.
Rest Methods¶
Rest Methods acts 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 which is a collection of Rest Methods created for the purpose of encapsulation as well as performance optimization where in 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.
Rest Procedures¶
A Rest Procedure (aka Rest backed Procedure) is a Flow Procedure, whose implementation involves making a Rest Request.
User Agent¶
The LWP::UserAgent is a perl class implementing a web user agent. LWP::UserAgent objects are used to dispatch web requests. The term userAgent in the pluginspec is used to represent the name of this object.
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 to Github.'
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: My Github Client
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.
- To 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 which uses a combination of username and password as below. The Flow UI will render the username and password as 2 different fields.
authSchemes:
basic:
userNameLabel: Github User Name
passwordLabel: Github Password
description: 'Username and password to connect to github'
credentialLabel: Github Credentials
Bearer Token which passes a Bearer Token as part of the Authorization Header. The Flow UI will render the Token as a single field of credential type secret.
authSchemes:
bearer:
passwordLabel: 'Github Token'
description: Github PAT
prefix: Bearer
OAuth 1.0 where in OAuth is performed using a Secret and Consumer Key based on the RSA-SHA1 signature method. This is typically with Vendors (for example JIRA) that support this type of authorization.
authSchemes:
oauth1:
tokenLabel: token
signatureMethod: RSA-SHA1
Anonymous where in no Authorization is performed. This is typically applicable when there is a need for Read only use cases, where in 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. They are beyond the scope of this guide.
Modules generated for Rest Plugins¶
For a Rest plugin whose name is CB-Git there are 2 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 (the one provided in the pluginspec.yaml):
print $self->method
Returns the map with parameters passed to the method from the original call:
$self->methodParameters
They are useful if one needs to get to the original method name and original request parameters, e.g.
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 Messages 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);
}
}