Tuesday, 15 November 2016

Accessing the Cloud Foundry REST API from SpringBoot

Accessing the Cloud Foundry REST API is simple enough to do as shown in the example below using curl we can list all our organizations.

Cloud Foundry REST API - https://apidocs.cloudfoundry.org/246/

Below shows just the organizations name and I am filtering on that using JQ, if you wnat to see all the output then remove the PIPE or JQ. You have to be logged in to use "cf oauth-token"

pasapicella@pas-macbook:~/apps$ curl -k "https://api.run.pivotal.io/v2/organizations" -X GET -H "Authorization: `cf oauth-token`" | jq -r ".resources[].entity.name"

APJ
apples-pivotal-org
Suncorp

In the example below I will show how you would invoke this REST API using SpringBoot's RestTemplate.

1.  Firstly we need to retrieve our bearer token as we will need that for all API calls into the CF REST API. The code below will retrieve that for us using the RestTemplate

  
package com.pivotal.platform.pcf;

import org.apache.tomcat.util.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.client.RestTemplate;

import java.util.Arrays;
import java.util.Map;

public class Utils
{
    private final static String username = "papicella@pivotal.io";
    private final static String password = "PASSWORD";
    private static final Logger log = LoggerFactory.getLogger(Utils.class);
    private static final JsonParser parser = JsonParserFactory.getJsonParser();

    public static String getAccessToken ()
    {
        String uri = "https://login.run.pivotal.io/oauth/token";
        String data = "username=%s&password=%s&client_id=cf&grant_type=password&response_type=token";
        RestTemplate restTemplate = new RestTemplate();

        // HTTP POST call with data

        HttpHeaders headers = new HttpHeaders();

        headers.add("Authorization", "Basic " + encodePassword());
        headers.add("Content-Type", "application/x-www-form-urlencoded");

        headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));

        String postArgs = String.format(data, username, password);

        HttpEntity<String> requestEntity = new HttpEntity<String>(postArgs,headers);

        String response = restTemplate.postForObject(uri, requestEntity, String.class);

        Map<String, Object> jsonMap = parser.parseMap(response);

        String accessToken = (String) jsonMap.get("access_token");

        return accessToken;
    }

    private static String encodePassword()
    {
        String auth = "cf:";
        byte[] plainCredsBytes = auth.getBytes();
        byte[] base64CredsBytes = Base64.encodeBase64(plainCredsBytes);
        return new String(base64CredsBytes);
    }

}

To achieve the same thing as above using CURL would look as follows, I have stripped the actual bearer token as that is a lot of TEXT.

pasapicella@pas-macbook:~$ curl -v -XPOST -H "Application/json" -u "cf:" --data "username=papicella@pivotal.io&password=PASSWORD&client_id=cf&grant_type=password&response_type=token" https://login.run.pivotal.io/oauth/token

...

{"access_token":"YYYYYYYYYYY ....","token_type":"bearer","refresh_token":"3dd9a2b63f3640c38eb8220e2ae88dfc-r","expires_in":599,"scope":"openid uaa.user cloud_controller.read password.write cloud_controller.write","jti":"c3706c86e376445686a0dd289262bbfa"}

2. Once we have the bearer token we can then make calls to the CF REST API using the bearer token as shown below. The code below simply ensures we get the bearer token before we make the calls to the CF REST API and then we are free to output what we want to output. One method below simply returns the RAW JSON output as per the method "getAllApps" and the other method "getAllOrgs" to get Organizations strips out what we don't want and adds it to a list of POJO that define exactly what we want to return.
  
package com.pivotal.platform.pcf;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.pivotal.platform.pcf.beans.Organization;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.*;

@RestController
public class CFRestAPISpringBoot
{
    private RestTemplate restTemplate = new RestTemplate();
    private static final Logger log = LoggerFactory.getLogger(CFRestAPISpringBoot.class);
    private static final JsonParser parser = JsonParserFactory.getJsonParser();

    @RequestMapping(value = "/cf-apps", method = RequestMethod.GET, path = "/cf-apps")
    public String getAllApps ()
    {
        String uri = "https://api.run.pivotal.io/v2/apps";

        String accessToken = Utils.getAccessToken();

        // Make CF REST API call for Applications
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", String.format("Bearer %s", accessToken));
        headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));

        HttpEntity entity = new HttpEntity(headers);

        log.info("CF REST API Call - " + uri);

        HttpEntity<String> response = restTemplate.exchange(uri, HttpMethod.GET, entity, String.class);

        return response.getBody();
    }

    @RequestMapping(value = "/cf-orgs", method = RequestMethod.GET, path = "/cf-orgs")
    public List<Organization> getAllOrgs ()
    {
        String uri = "https://api.run.pivotal.io/v2/organizations";

        String accessToken = Utils.getAccessToken();

        // Make CF REST API call for Applications
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", String.format("Bearer %s", accessToken));
        headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));

        HttpEntity entity = new HttpEntity(headers);

        log.info("CF REST API Call - " + uri);
        HttpEntity<String> response = restTemplate.exchange(uri, HttpMethod.GET, entity, String.class);

        log.info(response.getBody());

        Map<String, Object> jsonMap = parser.parseMap(response.getBody());

        List<Object> resourcesList = (List<Object>) jsonMap.get("resources");
        ObjectMapper mapper = new ObjectMapper();
        ArrayList<Organization> orgs = new ArrayList<Organization>();

        for (Object item: resourcesList)
        {
            Map map = (Map) item;

            Iterator entries = map.entrySet().iterator();

            while (entries.hasNext())
            {
                Map.Entry thisEntry = (Map.Entry) entries.next();
                if (thisEntry.getKey().toString().equals("entity"))
                {
                    Map entityMap = (Map) thisEntry.getValue();
                    Organization org =
                            new Organization((String)entityMap.get("name"),
                                             (String)entityMap.get("status"),
                                             (String)entityMap.get("spaces_url"));
                    log.info(org.toString());
                    orgs.add(org);
                }

            }

        }

        return orgs;
    }
} 

3. Of course we have the standard SpringBoot main class which ensures we us an embedded tomcat server to server the REST end points
  
package com.pivotal.platform.pcf;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootCfRestApiApplication {

 public static void main(String[] args)
 {
  SpringApplication.run(SpringBootCfRestApiApplication.class, args);
 }
}

4. The POJO is as follows
  
package com.pivotal.platform.pcf.beans;

public final class Organization
{
    private String name;
    private String status;
    private String spacesUrl;

    public Organization()
    {
    }

    public Organization(String name, String status, String spacesUrl) {
        this.name = name;
        this.status = status;
        this.spacesUrl = spacesUrl;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public String getSpacesUrl() {
        return spacesUrl;
    }

    public void setSpacesUrl(String spacesUrl) {
        this.spacesUrl = spacesUrl;
    }

    @Override
    public String toString() {
        return "Organization{" +
                "name='" + name + '\'' +
                ", status='" + status + '\'' +
                ", spacesUrl='" + spacesUrl + '\'' +
                '}';
    }
} 

Once our Spring Boot application is running we can simply invoke one of the REST end points as follows and it will login as well as make the REST call using the CF REST API under the covers for us.

pasapicella@pas-macbook:~/apps$ curl http://localhost:8080/cf-orgs | jq -r
[
  {
    "name": "APJ",
    "status": "active",
    "spacesUrl": "/v2/organizations/b7ec654f-f7fd-40e2-a4f7-841379d396d7/spaces"
  },
  {
    "name": "apples-pivotal-org",
    "status": "active",
    "spacesUrl": "/v2/organizations/64c067c1-2e19-4d14-aa3f-38c07c46d552/spaces"
  },
  {
    "name": "Suncorp",
    "status": "active",
    "spacesUrl": "/v2/organizations/dd06618f-a062-4fbc-b8e9-7b829d9eaf37/spaces"
  }
]

More Information

1. Cloud Foundry REST API - https://apidocs.cloudfoundry.org/246/

2. RestTemplate - http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html



No comments: