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.
I haven't used it myself, but this may be of use to you: https://github.com/Mastercard/terraform-provider-restapi
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())
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With