###################### 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. .. code:: perl # 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. .. code:: perl 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. .. code:: perl 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. .. code:: perl 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. .. code:: perl 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. .. code:: perl 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. .. code:: perl 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** .. code:: perl # 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** .. code:: perl ## === 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): .. code:: perl print $self->method Returns the map with parameters passed to the method from the original call: .. code:: perl $self->methodParameters They are useful if one needs to get to the original method name and original request parameters, e.g. .. code::perl # 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. .. code:: perl 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** .. code:: perl 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** .. code:: perl sub encodePayload { my ($self, $payload) = @_; if ($self->method eq 'createIssue') { $payload = { issue => {title => $payload->{title}} }; } return encode_json($payload); } **Error Messages Processing** .. code:: perl 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** .. code:: perl 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); } }