Note:

Manage Oracle Cloud Infrastructure REST APIs Programmatically using Shell and Java Code

Introduction

Oracle Cloud Infrastructure(OCI) provide cloud infrastructure to customers to host their mission critical applications on cloud. OCI also provides customer with the ways to access OCI services either through console, or through APIs. For connecting through APIs, customer can use SDKs based on the programming language fo their choice like Java, Python or Ruby. But their are few cases where customer can not use SDK, and can not use any programming language, but instead want to connect through APIs directly either from a tool, database or third party application where they can not install SDK. Here comes the usage of REST APIs comes handy. Customers can leverage REST APIs provided by OCI for almost all services, and use them in their tools directly by just updating the endpoints and taking care of authorization and authentication.

These days, every organization want to leverage the power of REST APIs in their applications and create an innovative offering for their customers without the need of installing and managing any SDK.

In this tutorial, we will discuss and understand how OCI can help organizations across the world to leverage the power of REST APIs with OCI. In this tutorial, we will demonstrate example of REST APIs based on shell scripting and Java code.

Objectives

Prerequisites

Task 1: Set up Oracle Cloud Infrastructure API Keys

  1. Log in to the OCI tenancy with the service account or the admin user and go to the User settings.

    Open User Settings

    Or

    Go to the user from the OCI IAM console. Ensure the user has all the required access for creating and updating infrastructure on OCI.

  2. Go to the Resources section, click API Keys and Add API Key. Select for either downloading the new public and private keys, upload your public key or paste your public key.

    Add API Key

  3. Save the keys at a convenient location, and save the configuration file as shown in the following page. You will need this configuration file and private key when connecting to your tenancy using REST APIs.

    Save Configuration File

    Note:

    • You will need same configuration parameters to connect to your OCI region and tenancy. You will replace the parameters in Java code and shell scripts accordingly as we dive deeper into the tutorial.

    • To call OCI services from OCI Compute instances, OCI provides a more secure way using instance principals. Avoid using user API keys as best practices. For more information, see Calling Services from an Instance.

Task 2: Create and Run Shell Script to Upload or Put a Binary File from OCI Object Storage using REST APIs

  1. Go to file system and create a shell file. For this example, file named as PUT_OBJECT_REST_API.sh.

  2. Open the file with your choice of editor and paste the following code in the shell file.

    • PUT_OBJECT_REST_API.sh:

      #!/bin/bash
      
      ########################## Fill these in with your values ##########################
      #OCID of the tenancy calls are being made in to
      tenancy_ocid="ocid1.tenancy.oc1..aaaaaaaackopa"
      
      # namespace of the tenancy
      namespace="ocitenancyname"
      
      # OCID of the user making the rest call
      user_ocid="ocid1.user.oc1..aaaaaaaafelqsoffcjh"
      
      # path to the private PEM format key for this user
      privateKeyPath="/home/lovelesh/.oci/oci_api_key.pem"
      
      # fingerprint of the private key for this user
      fingerprint="72:d4:6c:f8:89"
      
      #bucket name for uplaod/ put
      bucket="ocibucketname"
      
      #file name for upload/ put
      object="test_pdf.pdf"
      
      # The REST api you want to call, with any required paramters.
      rest_api="/n/$namespace/b/$bucket/o/$object"
      
      # The host you want to make the call against
      host="objectstorage.us-ashburn-1.oraclecloud.com"
      
      # the file containing the data you want to POST to the rest endpoint
      body="/home/lovelesh/.oci/test_pdf.pdf"
      ####################################################################################
      
      # extra headers required for a POST/PUT request
      method="put"
      content_type="application/octet-stream"
      body_arg=(--data-binary @${body})
      content_sha256="$(openssl dgst -binary -sha256 < $body | openssl enc -e -base64)";
      content_sha256_header="x-content-sha256: $content_sha256"
      content_length="$(wc -c < $body | xargs)";
      content_length_header="content-length: $content_length"
      headers="(request-target) date host"
      # add on the extra fields required for a POST/PUT
      headers=$headers" x-content-sha256 content-type content-length"
      content_type_header="content-type: $content_type";
      
      date=`date -u "+%a, %d %h %Y %H:%M:%S GMT"`
      date_header="date: $date"
      host_header="host: $host"
      request_target="(request-target): $method $rest_api"
      
      # note the order of items. The order in the signing_string matches the order in the headers, including the extra POST fields
      signing_string="$request_target\n$date_header\n$host_header"
      # add on the extra fields required for a POST/PUT
      signing_string="$signing_string\n$content_sha256_header\n$content_type_header\n$content_length_header"
      
      
      
      signature=`printf '%b' "$signing_string" | openssl dgst -sha256 -sign $privateKeyPath | openssl enc -e -base64 | tr -d '\n'`
      
      set -x
      curl -v -X $(echo $method | tr [:lower:] [:upper:]) --data-binary "@$body" -sS https://$host$rest_api -H "date: $date" -H "x-content-sha256: $content_sha256" -H "content-type: $content_type" -H "content-length: $content_length" -H "Authorization: Signature version=\"1\",keyId=\"$tenancy_ocid/$user_ocid/$fingerprint\",algorithm=\"rsa-sha256\",headers=\"$headers\",signature=\"$signature\""
      
  3. Now we have all the code pasted in shell file, let us work on updating the required values and understand their functionality.

    tenancy_ocid="ocid1.tenancy.oc1..aaaaaaaackopa"
    ...
    fingerprint="72:d4:6c:f8:89"
    

    All these values are responsible to make a secure connection with your OCI tenancy. Get these values from the config file.

    rest_api="/n/$namespace/b/$bucket/o/$object"
    host="objectstorage.us-ashburn-1.oraclecloud.com"
    

    This is the rest_api and host endpoint, prepare it as per your requirement as this will change for each and every service. The example shown here is endpoint for OCI Object Storage Service for ashburn region.

    headers=$headers" x-content-sha256 content-type content-length"
    content_type_header="content-type: $content_type";
    date=`date -u "+%a, %d %h %Y %H:%M:%S GMT"`
    date_header="date: $date"
    host_header="host: $host"
    request_target="(request-target): $method $rest_api"
    

    Here we have created the headers for REST API along with content type and other required parameters. Note that here we have definitions for method_type, date, and host headers as for post call we need detailed header set.

    signing_string="$signing_string\n$content_sha256_header\n$content_type_header\n$content_length_header"
    curl -v -X $(echo $method | tr [:lower:] [:upper:]) --data-binary "@$body" ...
    

    Finally, we are creating the signing string, and then making the REST API call using curl. Note that OCI need the API calls to be signed, and that’s why we are doing it here.

  4. After you have updated the values as per your configuration, run the shell script, you should see the script executing all good, and the file is pushed to OCI Object Storage.

    Note:

    • Make sure you have updated the shell file permission to executable by changing it to 755 or manage it yourself.

    • You can similarly also do this on Windows using powershell, or use the same script on Windows by installing a WSL(Windows Subsystem for Linux) like Ubuntu and run it from Ubuntu WSL on Windows. For more information, see Windows Subsystem for Linux.

Task 3: Create and Run Java Code to Upload or Put a Binary File from OCI Object Storage using REST APIs

  1. Go to file system and create a java file. For this example, file named as PUT_OBJECT_REST_API.java.

  2. Open the file with your choice of editor and paste the following code in the shell file.

    • PUT_OBJECT_REST_API.java:

      import java.io.IOException;
      import java.net.URI;
      import java.net.http.HttpClient;
      import java.net.http.HttpRequest;
      import java.net.http.HttpResponse;
      import java.nio.file.Files;
      import java.nio.file.Paths;
      import java.security.KeyFactory;
      import java.security.NoSuchAlgorithmException;
      import java.security.PrivateKey;
      import java.security.Signature;
      import java.security.spec.InvalidKeySpecException;
      import java.security.spec.PKCS8EncodedKeySpec;
      import java.time.ZoneOffset;
      import java.time.ZonedDateTime;
      import java.time.format.DateTimeFormatter;
      import java.util.Base64;
      
      // NOTE - Feel free to remove logging as needed. Currently logs the signing string, host, date, and response. 
      
      public class PUT_OBJECT_REST_API {
      
          private static final String apiKeyFilePath = "C:\\Users\\User1\\Folder\\privatekey.pem"
          private static final String tenancyId = "ocid1.tenancy.oc1..aaaaaaaackopa27emaz4uteg4q7"; // OCI Tenancy ID
          private static final String userId = "ocid1.user.oc1..aaaaaaaafelqsoffcjq"; // OCI User ID
          private static final String keyFingerprint = "72:d4:6c:f8:89:74:aa"; // OCI fingerprint
          private static final String region = "us-ashburn-1"; // OCI Region
          private static final String namespace = "ocitenancyname"; // OCI bucket namespace
          private static final String bucketName = "ocibucketname"; // OCI bucket NAME
      
          public static void main(String[] args) {
              try {
                  String filePath = "D:\\file\\location\\test_pdf.pdf"; //args.length > 0 ? args[0] : ""; // File to upload (path)
                  PrivateKey privateKey = loadPrivateKey(apiKeyFilePath);
                  String fileName = Paths.get(filePath).getFileName().toString();
                  String targetUri = "https://objectstorage." + region + ".oraclecloud.com/n/" + namespace + "/b/" + bucketName + "/o/" + fileName;
                  HttpRequest request = buildRequest(targetUri, filePath, privateKey);
                  HttpClient client = HttpClient.newHttpClient();
                  HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
      
                  System.out.println("Response status code: " + response.statusCode());
                  System.out.println("Response body: " + response.body());
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
      
          private static PrivateKey loadPrivateKey(String filePath) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
              String key = new String(Files.readAllBytes(Paths.get(filePath)))
                  .replace("-----BEGIN PRIVATE KEY-----", "")
                  .replace("-----END PRIVATE KEY-----", "")
                  .replaceAll("\\s+", ""); // Remove all whitespace
      
              byte[] decodedKey = Base64.getDecoder().decode(key);
              PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decodedKey);
              KeyFactory kf = KeyFactory.getInstance("RSA");
              return kf.generatePrivate(spec);
          }
      
          private static HttpRequest buildRequest(String targetUri, String filePath, PrivateKey privateKey) throws Exception {
              String method = "put";
              String path = "/n/" + namespace + "/b/" + bucketName + "/o/" + Paths.get(filePath).getFileName();
              String host = "objectstorage." + region + ".oraclecloud.com";
              String date = DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC));
              byte[] fileContent = Files.readAllBytes(Paths.get(filePath)); // Read file content as byte array
      
              String signingString = "(request-target): " + method.toLowerCase() + " " + path + "\n" +
                                  "host: " + host + "\n" +
                                  "date: " + date;
              System.out.println("Signing String: \n" + signingString);
      
              String signature = signRequest(privateKey, signingString);
              String authorizationHeader = "Signature version=\"1\",headers=\"(request-target) host date\",keyId=\"" +
                  tenancyId + "/" + userId + "/" + keyFingerprint + "\",algorithm=\"rsa-sha256\",signature=\"" + signature + "\"";
      
              return HttpRequest.newBuilder()
                      .uri(URI.create(targetUri))
                      .header("Content-Type", "application/octet-stream")
                      .header("date", date)
                      .header("Authorization", authorizationHeader)
                      .PUT(HttpRequest.BodyPublishers.ofByteArray(fileContent))
                      .build();
          }
      
          private static String signRequest(PrivateKey privateKey, String dataToSign) throws Exception {
              Signature signatureInstance = Signature.getInstance("SHA256withRSA");
              signatureInstance.initSign(privateKey);
              signatureInstance.update(dataToSign.getBytes());
              byte[] signature = signatureInstance.sign();
              return Base64.getEncoder().encodeToString(signature);
          }
      }
      
  3. Now we have all the code pasted in java file, let us work on updating the required values and understand their functionality.

    private static final String apiKeyFilePath = "C:\\Users\\User1\\Folder\\privatekey.pem"
    private static final String tenancyId = "ocid1.tenancy.oc1..aaaaaaaackopa27emaz4uteg4q7"; // OCI Tenancy ID
    private static final String userId = "ocid1.user.oc1..aaaaaaaafelqsoffcjq"; // OCI User ID
    private static final String keyFingerprint = "72:d4:6c:f8:89:74:aa"; // OCI fingerprint
    private static final String region = "us-ashburn-1"; // OCI Region
    private static final String namespace = "ocitenancyname"; // OCI bucket namespace
    private static final String bucketName = "ocibucketname"; // OCI bucket NAME
    

    All these values are responsible to make a secure connection with your OCI tenancy. Get these values from the config file.

    String targetUri = "https://objectstorage." + region + ".oraclecloud.com/n/" + namespace + "/b/" + bucketName + "/o/" + fileName;
    HttpRequest request = buildRequest(targetUri, filePath, privateKey);
    

    This is the rest_api and host endpoint, prepare it as per your requirement as this will change for each and every service. The example shown here is endpoint for OCI Object Storage Service for ashburn region.

    private static HttpRequest buildRequest
    

    We have created the headers for REST API along with content type and other required parameters. Note that here we have definitions for method_type, date, and host headers as for post call we need detailed header set.

    private static String signRequest
    

    Finally, we are creating the signing string, and then making the REST API call. Note that OCI need the API calls to be signed, and that is why we are doing it here.

  4. After you have updated the values as per your configuration, run the java code, you should see the script executing all good, and the file is pushed to OCI Object Storage.

    Note: Make sure you have Java installed with all the required packages as needed by the code.

Acknowledgments

More Learning Resources

Explore other labs on docs.oracle.com/learn or access more free learning content on the Oracle Learning YouTube channel. Additionally, visit education.oracle.com/learning-explorer to become an Oracle Learning Explorer.

For product documentation, visit Oracle Help Center.