Hands on IBM Cloud Functions with CLI

Hands on IBM Cloud Functions with CLI

learn from examples

Tools

2020.10.20

0 #faas #ibm #cloud

IBM Cloud CLI allows complete management of the Cloud Functions system. You can use the Cloud Functions CLI plugin-in to manage your code snippets in actions, create triggers, and rules to enable your actions to respond to events, and bundle actions into packages.

Setting up the IBM Cloud CLI

  1. Download and install the IBM Cloud CLI. Alternatively, you can use Homebrew.

    brew cask install ibm-cloud-cli
    
  2. Log in to the IBM Cloud CLI.

    ibmcloud login
    
  3. If you have more than one account, you’re prompted to select which account to use. Follow the prompts or use the target command to select your IBM Cloud account.

    ibmcloud target -c <account_id>
    
  4. You must specify a region to deploy. You can use the target command to target or change regions. Check the document for the regions that you can use.

    ibmcloud target -r <region>
    
  5. You must also specify a resource group. To get a list of your resource groups, run the following command.

    ibmcloud resource groups
    

    Example output:

    Retrieving all resource groups under account <account_name> as email@ibm.com...
    OK
    Name      ID                                  Default Group   State   
    default   a8a12accd63b437bbd6d58fb8b462ca7    true            ACTIVE
    test      a8a12abbbd63b437cca6d58fb8b462ca7   false           ACTIVE
    
  6. Target a resource group by running the following command.

    ibmcloud target -g <resource_group>
    

Setting up the Cloud Functions CLI plug-in

To work with Cloud Functions, download and install the CLI plug-in.

  1. Install the Cloud Functions plug-in.

    ibmcloud plugin install cloud-functions
    
  2. All Cloud Functions commands begin with ibmcloud fn.

For more information about Cloud Functions commands, see the Cloud Functions CLI reference.

Managing namespaces

What is a namespace?

Namespaces contain Cloud Functions entities, such as actions and triggers, and belong to a resource group. You can let users access your entities by granting them access to the namespace.

When you create a namespace, the following components are created:

Component Description
Service ID You can use the service ID as a functional ID when you make outbound calls. All of the actions that are created in this namespace can use this service ID for access to other resources. The functional user gets the Reader role by default. Reader access means it can read namespace entities and invoke actions. The Reader role is used by triggers to invoke actions. To control inbound traffic, you might want to grant access to other users such as assigning Reader role to invoke actions.
API key An API Key for the service ID that can be used to generate IAM tokens. You can use the tokens to authenticate the namespace with other IBM Cloud services. The API key is provided to actions as the environment variable __OW_IAM_NAMESPACE_API_KEY.

You can view a list of service IDs by running the following command.

ibmcloud iam service ids

You can also check the API keys that are associated with a service ID by running the following command.

ibmcloud iam service-api-keys <ServiceID-12345678-1234-abcd-1234-123456789abc>

Tip: Do not delete IDs or API keys.

You can see a list of your Cloud Functions namespace by running the following command.

ibmcloud fn namespace list

The command output includes all of the namespaces in the currently selected region and also lists whether the namespace is a Cloud Foundry-based or an IAM-based namespace. Note that this command is scoped to the targeted region and resource group that you set.

You can see a list your IAM-based namespaces by using the following command. This command lists Cloud Functions namespace of all regions.

Creating an IAM-based namespace

Use the following command to create an IAM-managed namespace with the CLI.

ibmcloud fn namespace create <namespace_name> [--description <"description of yur namespace">]
  • <namespace_name>: The display name for the IAM-based namespace.

  • -n <description>: Optional: Add a description to the namespace, such as which kind of actions or packages you plan to create. If your description is longer than one word, it must be in quotations.

  • --description <description>: Optional: Add a description to the namespace, such as which kind of actions or packages you plan to create. If your description is longer than one word, it must be in quotations.

Verify that your new namespace is created.

ibmcloud fn namespace get <namespace_name> --properties

Before you can create entities in the namespace, you must set your CLI context to the namespace by targeting it.

ibmcloud fn property set --namespace <namespace_name>

Note: After you set a property, it is retained until you manually unset it. If you want to switch between IAM namespaces or between cloud Foundry and IAM namespace, you must unset the namespace property and then reset it. For more information, see ibmcloud fn property set.

Targeting namespaces

Before you can work in Cloud Functions, you must target a namespace.

ibmcloud fn namespace target <namespace_name>

Accessing other resources from namespace:

Actions typically call other IBM Cloud resources and services that require appropriate authentication. If these services are IAM-enabled and accept IAM tokens, you can leverage the namespace’s functional ID for outbound communication.

For each namespace, a service ID is created that represents the namespace. You can grant access to other services and resources for this service ID by assigning the appropriate roles by using IAM policy management. For more information, see Creating and working with service IDs.

At runtime, Cloud Functions passes an API key of the namespace service ID to the action code as the environment variable _OW_IAM_NAMESPACE_API_KEY. The actions code can use this API key to generate an IAM token. Most of the supported Cloud Functions SDKs such as Cloudant and IBM Cloud Object Storage authenticate with the IAM key itself. For other IAM-managed services or resources that use a REST API, you can authenticate with the token that is derived from the IAM API key. For more information, see Create an IAM access token for a user or service ID.

Deploying quick start templates in Node runtime

These templates are a combination of actions, triggers, sequences. By using these templates, we can unserstand how IBM Cloud Functions entities work together and even use these entities as a basis for own projects.

IBM Cloudant Events template

This IBM Cloudant template creates a sequence of actions and trigger that invokes that sequence. The trigger is fired when a change is made in the connected IBM Cloudant NoSQL database.

Set up IBM Cloudant database

Before we begin, we have to set up an instance of IBM Cloudant before we deploy this quick start template. When you create your IBM Cloudant instance, you must select Use both legacy credentials and IAM as your available authentication methods.

  1. In your browser, log in to IBM Cloud and go the the Dashboard. Select Create resource.

  2. Search for IBM Cloudant, and select the service.

  3. For Available authentication methods, select Use both legacy credentials and IAM. You can leave the default settings for the other fields. Click Create to create the service.

  4. After your IBM Cloudant instance is provisioned, select the instance from the Resource list and then select Launch Cloudant Dashboard.

  5. From the IBM Cloudant Dashboard, click Create Database.

  6. Create a non-partitioned database called cats for this quick start template.

  7. Back to the IBM Cloudant instance, select Service credentials. If you do not have service credentials set up, click New Credentials to create them. These values are required for deploying this template.

Now that your instance of IBM Cloudant is provisioned and a sample database of cats set up, you are ready to deploy the IBM Cloudant Events template.

Deploying the IBM Cloudant Events template from the CLI

  1. Creat a new folder for this template Cloud Functions.

  2. Create a new JavaScript file inside this folder, you can name it as process-change.js. Paste the following snippet into it.

    function main(params) {
      const { name, color } = params;
      return new Promise((resolve, reject) => {
        if (!name || !color) {
          reject({
            error: 'Please make sure name and color are passed in as params.'
          });
        } else {
          const message = `A ${color} cat named ${name} was added`;
          console.log(message);
          resolve({
            change: message,
          });
        }
      });
    }
    exports.main = main;
    
  3. Create a manifest.yaml file with following content.

    # Wskdeploy manifest for openwhisk-cloudant-trigger
    # GitHub repo is located at https://github.com/IBM/openwhisk-cloudant-trigger
    # Installing openwhisk actions, triggers, and rules for OpenWhisk building block - Cloudant Trigger
       
    # Deployment using this manifest file creates following OpenWhisk components:
    #   Package:    openwhisk-cloudant
    #   Package:    $PACKAGE_NAME
    #   Action:     $PACKAGE_NAME/process-change.js
    #   Sequence:   $PACKAGE_NAME/process-change-cloudant-sequence
    #   Trigger:    $TRIGGER_NAME
    #   Rule:       $RULE_NAME
       
    # This manifest file reads following env. variables:
    #   CLOUDANT_HOSTNAME
    #   CLOUDANT_USERNAME
    #   CLOUDANT_PASSWORD
    #   CLOUDANT_DATABASE
    #   PACKAGE_NAME
    #   RULE_NAME
    #   TRIGGER_NAME
       
    project:
      namespace: _
      packages:
        $PACKAGE_NAME:
          dependencies:
              # binding cloudant package named openwhisk-cloudant
              openwhisk-cloudant:
                  location: /whisk.system/cloudant
                  inputs:
                      username: $CLOUDANT_USERNAME
                      password: $CLOUDANT_PASSWORD
                      host: $CLOUDANT_HOSTNAME
                      iamApiKey: $CLOUDANT_IAM_APIKEY
       
          triggers:
              # Trigger
              # Creating trigger to fire events when data is inserted into database
              $TRIGGER_NAME:
                  feed: openwhisk-cloudant/changes
                  inputs:
                      dbname: $CLOUDANT_DATABASE
          actions:
              # Action named "process-change"
              # Creating action that is printing data which is written to the database
              process-change:
                  function: ./process-change.js
                  runtime: nodejs:12
          sequences:
              # Sequence named "process-change-cloudant-sequence"
              # Creating sequence to connect the cloudant "read" action with the "process-change" action
              process-change-cloudant-sequence:
                  actions: openwhisk-cloudant/read, process-change
          rules:
              # Rule
              # Creating rule that maps database change trigger to sequence
              $RULE_NAME:
                  trigger: $TRIGGER_NAME
                  action: process-change-cloudant-sequence
    
  4. Now, deploy the Cloud Functions with the following environment variable.

    CLOUDANT_HOSTNAME=<host> CLOUDANT_USERNAME=<username> CLOUDANT_PASSWORD=<password> CLOUDANT_DATABASE=<database> PACKAGE_NAME=<name> RULE_NAME=<name> TRIGGER_NAME=<name> ibmcloud fn deploy -m manifest.yaml
    

    These environment variables are indicated in the manifest.yaml. We can set these fields in the web console, however, to deploy just using the CLI, we need to feed them in the deploy commands. Find these information in the web console by navigating to your IBM Cloudant instance and then selecting Service Credentials that we created in previous step. Also, you can find service credentials by running the following commands.

    a. Find the credentials for your IBM Cloudant instance.

    ibmcloud resource service-keys --instance-name <Cloudant-name>
    

    Example output:

    Name            State    Created At   
    template-test   active   Sun Oct 25 11:51:44 UTC 2020  
    

    b. Retrieve the details of the service key. Replace the template-test with the credentials that you retrieved in the previous step.

    ibmcloud resource service-key template-test
    

    Example output:

    Name:          template-test   
    ID:            crn:v1:bluemix:public:cloudantnosqldb:us-south:a/5294aaf79314431586303d483bd3f3e0:2e4c6f05-a971-433e-b9c3-f6afcb326b1e:resource-key:6030db27-9f3c-4ca9-9561-7cb813875fbe   
    Created At:    Sun Oct 25 11:51:44 UTC 2020   
    State:         active   
    Credentials:                                   
                   apikey:                   J2axYj49SRfXjfbErzGYWeeG-sgvAzuljt1ojykFRtf3      
                   host:                     a26bb3a6-e85d-4962-a734-a8d51e30e753-bluemix.cloudantnosqldb.appdomain.cloud      
                   iam_apikey_description:   Auto-generated for key 6030db27-9f3c-4ca9-9561-7cb813875fbe      
                   iam_apikey_name:          template-test      
                   iam_role_crn:             crn:v1:bluemix:public:iam::::serviceRole:Manager      
                   iam_serviceid_crn:        crn:v1:bluemix:public:iam-identity::a/5294aaf79314431586303d483bd3f3e0::serviceid:ServiceId-5a737aa4-c1fb-41aa-8952-057cfb2bff18      
                   password:                 152577110cc5f237e2862a23e6f4c84579d43c59fd8d9577a252059694d266c9      
                   port:                     443      
                   url:                      https://a26bb3a6-e85d-4962-a734-a8d51e30e753-bluemix:152577110cc5f237e2862a23e6f4c84579d43c59fd8d9577a252059694d266c9@a26bb3a6-e85d-4962-a734-a8d51e30e753-bluemix.cloudantnosqldb.appdomain.cloud      
                   username:                 a26bb3a6-e85d-4962-a734-a8d51e30e753-bluemix 
    

    Using the correct service credential information found the previous output, run the following command to deploy the Cloud Functions:

    CLOUDANT_HOSTNAME=a26bb3a6-e85d-4962-a734-a8d51e30e753-bluemix.cloudantnosqldb.appdomain.cloud CLOUDANT_USERNAME=a26bb3a6-e85d-4962-a734-a8d51e30e753-bluemix CLOUDANT_PASSWORD=152577110cc5f237e2862a23e6f4c84579d43c59fd8d9577a252059694d266c9 CLOUDANT_DATABASE=cats PACKAGE_NAME=my-package RULE_NAME=my-rule TRIGGER_NAME=my-trigger ibmcloud fn deploy -m ./manifest.yaml
    

    The CLOUDANT_DATABASE name with cats is created in previous step, the following environment variables can be customized, e.g. PACKAGE_NAME=awesome-package.

Testing your IBM Cloudant Events package

Now that you have your IBM Cloudant database and your IBM Cloudant events (action and trigger) ready, let’s try it out! The expected data item is a cat, with a name and a coloraturas defined. When a new cat is added to the cats database or a current cat item is edited, the data is logged to the console module for Node.js.

Before you make entries in the IBM Cloudant database, make sure that the process change action is working properly. Note that this entry does not update IBM Cloudant database, but instead of a method of testing the actions itself (by logging).

  1. Open the Action page from the Cloud Functions web console.

  2. From the events package (called my-package, if you accepted the default name), click on the process change action that will redirect you to the detail page.

  3. Click Invoke with parameters and enter a valid JSON entry for name and color, similar to the following example:

    {
      "name": "Tarball",
      "color": "Black"
    }
    
  4. Click Apply.

  5. Click Invoke. In the Activation pane, find output similar to the following example:

    Activation ID:
    6f446242d8804b07846242d880db0742
    Results:
       
    {
      "change": "A Black cat named Tarball was added"
    }
       
    Logs:
       
    [
      "2020-10-25T14:37:53.069453Z    stdout: A Black cat named Tarball was added"
    ]
    

Testing the Cloudant trigger

While you can test the process change action, you cannot test the trigger without making an entry to the IBM Cloudant database. Try the next.

  1. From the command line, run the following command to view a streaming, live list of activations for you namespace. It’s waiting for any changes in the IBM Cloudant database.

    ibmcloud fn activation poll
    
  2. From the IBM Cloud resource list, select your IBM Cloudant instance, in Manage > Dashboard > Databases, click Launch.

  3. Select your cats database that created previously.

  4. Click Create Document.

  5. Enter valid JSON and click Create Document (_id is automatically allocated).

    {
      "_id": "d10d48872d1b4ae28ec1103610ce97cd",
      "name": "Tom",
      "color": "Grey"
    }
    
  6. Back to your CLI terminal, check the activation poll to see whether the trigger fired.

    Example output:

    Activation: 'process-change' (c6c1e389c74049d581e389c74019d527)
    [
        "2020-10-25T14:56:13.945030Z    stdout: A Grey cat named Tom was added"
    ]
    

Summary

Now we finish the first quick start template deployment. The trigger comes from the IBM Cloudant database that invokes an action to log the changes in the database. Let’s move on to learn more.

Get HTTP resource template

The Get HTTP resource template creates an action, which takes a location parameter as input. The action is enabled as a web action, allowing it to be invoked with a URL, which is CORS enabled and does not need an authentication key, which is useful for building backend services for web applications.

Note: By default, the get-http-resource endpoint is publicly available to anyone who calls it.

Deploy the Get HTTP Resource template from the CLI

  1. Creat a new folder for this template Cloud Functions.

  2. Also create a new JavaScript file inside the folder, with following snippet.

    /**
     * main() will be invoked when you Run This Action.
     *
     * When enabled as a Web Action, use the following URL to invoke this action:
     * https://{APIHOST}/api/v1/web/{QUALIFIED ACTION NAME}?location=Austin
     *
     * For example:
     * https://openwhisk.ng.bluemix.net/api/v1/web/myusername@us.ibm.com_myspace/get-http-resource/location?location=Austin
     *
     * In this case, the params variable will look like:
     *     { "location": "Austin" }
     *
     */
       
    const needle = require('needle');
       
    async function main({
        location = 'Austin'
    }) {
        try {
            let response = await needle('get', `https://httpbin.org/anything?location=${location}`, {
                headers: {
                    'accept': 'application/json'
                }
            });
            return {
                statusCode: 200,
                headers: {
                    'Content-Type': 'application/json'
                },
                body: {
                    location: response.body.args.location
                },
            };
        } catch (err) {
            console.log(err)
            return Promise.reject({
                statusCode: 500,
                headers: {
                    'Content-Type': 'application/json'
                },
                body: {
                    message: err.message
                },
            });
        }
    }
    exports.main = main;
    
  3. Then create the manifest.yaml file with following content:

    # Wskdeploy manifest for Get External Resource - location
       
    # Deployment using this manifest file creates the following OpenWhisk components:
    #   Package:  get-http-resource
    #   Action:   get-http-resource/location.js
    #
    # The action can be invoked using:
    # curl https://us-south.functions.cloud.ibm.com/api/v1/web/<namespace>/get-http-resource/location.json?location=<location>
       
    project:
        namespace: _
        packages:
          $PACKAGE_NAME:
            version: 1.0
            license: Apache-2.0
            actions:
              location:
                web-export: true
                function: ./location.js
                runtime: nodejs:12
    
  4. Now deploy the template with a custom package name as an environment variable.

    PACKAGE_NAME=<name> ibmcloud fn deploy -m ./manifest.yaml
    

    I’m going to set the PACKAGE_NAME to my-package just as the previous template example, which will group all the actions under my-package together. So, the deploy command becomes

    PACKAGE_NAME=my-package ibmcloud fn deploy -m ./manifest.yaml
    

Invoking the Get HTTP Resource action

Test out the Get HTTP Resource action by using one of the following methods:

  1. From the Cloud Functions Actions page, select the action and then click Invoke.

    You can change the input parmeter by Invoke with parameters and entering valid JSON in the following format:

    {
      "location": "Siem Reap"
    }
    
  2. Opening the URL that given in the action’s Endpoints. You can also get the URL for any action using the CLI:

    ibmcloud fn action get <package_name>/<action_name> --url
    

    For example, to get the URL of the location action we just deployed:

    ibmcloud fn action get my-package/loaction --url
    

    Example output:

    https://us-south.functions.appdomain.cloud/api/v1/web/b0112440-ab69-4fbf-9a2e-bf30151e74dc/my-package/location
    

    Add a parameter for location by appending ?location=<city> to the end of the URL. For example:

    https://us-south.functions.appdomain.cloud/api/v1/web/b0112440-ab69-4fbf-9a2e-bf30151e74dc/my-package/location?location=London
    
  3. Curling the URL. For example:

    curl "https://us-south.functions.appdomain.cloud/api/v1/web/b0112440-ab69-4fbf-9a2e-bf30151e74dc/my-package/location?location=London"
    

Hello World template

This template is basic IBM Cloud Functions, which create a single action called helloworld. This action accepts a single parameter in JSON format, { "name": "xxxx" }.

Deploying the Hello World template from the CLI

  1. Create a folder for this template.

  2. Create a JavaScript file named helloworld.js with:

    /**
     *
     * main() will be invoked when you Run This Action.
     *
     * @param Cloud Functions actions accept a single parameter,
     *        which must be a JSON object.
     *
     * In this case, the params variable will look like:
     *     { "name": "xxxx" }
     *
     * @return which must be a JSON object.
     *         It will be the output of this action.
     *
     */
       
    function main(params) {
        if (params.name) {
            return {
                greeting: `Hello ${params.name}`
            };
        }
        return {
            greeting: 'Hello stranger!'
        };
    }
       
    exports.main = main;
    
  3. Now, let’s deploy the template without a manifest.yaml file with CLI.

    ibmcloud fn action create my-package/helloworld ./helloworld.js --kind nodejs:12
    

    Example output:

    ok: created action my-package/helloworld
    

Testing the Hello World action

From the Cloud Functions Actions page, select the action and then click Invoke or Invoke with parameters.

Note that this action is just return a JSON object, not web enabled nor return HTTP content.

Conclude

Well, just made my hand wet with IBM CLI to build Cloud Functions and have a rough feeling on how the Cloud Functions work. I’m considering to use IBM cloud to backend my website as the IBM Cloud free tier includes 400,000 GB-s :>.

Ads by Google

林宏

Frank Lin

Hey, there! This is Frank Lin (@flinhong), one of the 1.4 billion 🇨🇳. This 'inDev. Journal' site holds the exploration of my quirky thoughts and random adventures through life. Hope you enjoy reading and perusing my posts.

YOU MAY ALSO LIKE

Setup an IKEv2 server with StrongSwan

Tutorials

2020.01.09

Setup an IKEv2 server with StrongSwan

IKEv2, or Internet Key Exchange v2, is a protocol that allows for direct IPSec tunneling between two points. In IKEv2 implementations, IPSec provides encryption for the network traffic. IKEv2 is natively supported on some platforms (OS X 10.11+, iOS 9.1+, and Windows 10) with no additional applications necessary, and it handles client hiccups quite smoothly.

Using Liquid in Jekyll - Live with Demos

Web Notes

2016.08.20

Using Liquid in Jekyll - Live with Demos

Liquid is a simple templating language that Jekyll uses to process pages on your site. With Liquid you can output an modify variables, have logic statements inside your pages and loop over content.

Practising closures in JavaScript

JavaScript Notes

2018.12.17

Practising closures in JavaScript

JavaScript is a very function-oriented language. As we know, functions are first class objects and can be easily assigned to variables, passed as arguments, returned from another function invocation, or stored into data structures. A function can access variable outside of it. But what happens when an outer variable changes? Does a function get the most recent value or the one that existed when the function was created? Also, what happens when a function invoked in another place - does it get access to the outer variables of the new place?