19

What is the best way to make REST API calls from Terraform? I'm currently using a null_resource with the local-exec provisioner to make a cURL call:

resource "null_resource" "cloudability-setup" {
  provisioner "local-exec" {
      command = <<EOT
        curl -s -X POST https://api.cloudability.com/v3/vendors/aws/accounts \
             -H 'Content-Type: application/json' \
             -u "$${CldAbltyAPIToken:?Missing Cloudability API Token Env Variable}:" \
             -d '{"vendorAccountId": "${data.aws_caller_identity.current.account_id}", "type": "aws_role" }'
EOT
  }

However, the cURL return code is successful for HTTP 200 and HTTP 400 responses. I'd like the resource to be marked as failed if the new account cannot be registered.

I've tried returning just the HTTP Response Code:

resource "null_resource" "cloudability-setup" {
  provisioner "local-exec" {
      command = <<EOT
        curl -s -o /dev/null -w "%{http_code}" \
             -X POST https://api.cloudability.com/v3/vendors/aws/accounts \
             -H 'Content-Type: application/json' \
             -u "$${CldAbltyAPIToken:?Missing Cloudability API Token Env Variable}:" \
             -d '{"vendorAccountId": "${data.aws_caller_identity.current.account_id}", "type": "aws_role" }'
EOT
  }

But then I lose the API response body, which contains valuable information. There are also times when a HTTP 400 code indicates the account already exists, which I consider a success from the overall setup standpoint.

skohrs
  • 689
  • 2
  • 6
  • 19

2 Answers2

12

I haven't used it myself, but this may be of use to you: https://github.com/Mastercard/terraform-provider-restapi

moebius
  • 2,061
  • 11
  • 20
12

This question has been viewed over 10,000 times and I realized I never posted my solution to the problem. I ended up writing a Python script to handle the various API responses and controlling the return codes to Terraform.

Terraform resource:

resource "null_resource" "cloudability-setup" {
  provisioner "local-exec" {
      command = "${path.module}/cloudability_setup.py -a ${data.aws_caller_identity.current.account_id} -t aws_role"
  }

  depends_on = ["aws_iam_role.cloudability-role"]
}

Python script:

import getopt
import json
import os
import requests
import sys

def print_help():
    print '''
Usage: cloudability_setup.py [options]

cloudability_setup -- Register new account with Cloudability

Options:
  -h, --help            Show this help message and exit
  -a <acct #>, --acctnum=<acct #>
                        Required argument: IaaS Account Number
  -t <type>, --type=<type>
                        Required argument: IaaS Account Type
'''

def register_acct(acctnum, type):

    url = 'https://api.cloudability.com/v3/vendors/aws/accounts'
    token = os.environ['CldAbltyAPIToken']
    headers = {'Content-Type': 'application/json'}
    data = '{"vendorAccountId": "' + acctnum + '", "type": "'+ type + '" }'

    response = requests.post(url, auth=(token,''), headers=headers, data=data)

    # If new account was registered successfully, update externalID:
    if response.status_code == requests.codes.created:
      update_acct(acctnum, type)

    # If account already exists, update externalID:
    elif str(response.status_code) == '409':
      update_acct(acctnum, type)

    else:
      print "Bad response from Cloudability API while registering new account."
      print "HTTP: " + str(response.status_code)
      sys.exit(3)


def update_acct(acctnum, type):

    url = 'https://api.cloudability.com/v3/vendors/aws/accounts/' + acctnum
    token = os.environ['CldAbltyAPIToken']
    headers = {'Content-Type': 'application/json'}
    data = '{"type": "' + type + '", "externalId": "XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX" }'

    response = requests.put(url, auth=(token,''), headers=headers, data=data)

    if response.status_code == requests.codes.ok:
      sys.exit()

    else:
      print "Bad response from Cloudability API while updating account."
      print "HTTP: " + str(response.status_code)
      sys.exit(3)


def main(argv=None):
    '''
    Main function: work with command line options and send an HTTPS request to the Cloudability API.
    '''

    try:
        opts, args = getopt.getopt(sys.argv[1:], 'ha:t:',
                                   ['help', 'acctnum=', 'type='])
    except getopt.GetoptError, err:
        # Print help information and exit:
        print str(err)
        print_help()
        sys.exit(2)

    # Initialize parameters
    acctnum = None
    type = None

    # Parse command line options
    for opt, arg in opts:
        if opt in ('-h', '--help'):
            print_help()
            sys.exit()
        elif opt in ('-a', '--acctnum'):
            acctnum = arg
        elif opt in ('-t', '--type'):
            type = arg

    # Enforce required arguments
    if not acctnum or not type:
      print_help()
      sys.exit(4)

    register_acct(acctnum, type)


if __name__ == '__main__':
    sys.exit(main())
skohrs
  • 689
  • 2
  • 6
  • 19
  • Hi skohrs, thank you for this solution. I have a related question but not exactly to the question here. What's the token that's being passed ? Is it the actual API Key, or the API Key with a colon in the end, or base64 encoded Token? I've been running into an Unauthorized error no matter what way I pass the token in. I was looking for Cloudability specific questions and found this. Thanks in advance ! – vivekveeramani Sep 09 '22 at 21:51