Automation through infrastructure as code is the name of the game.
And I am a player in that game.
In this article, I’ll introduce the GitHub repo in which I want to collect my handy and reusable plan files.
Introduction
In the last few weeks, I’ve spent a lot of time on creating resources on Oracle Cloud Infrastructure using the console, Terraform and the Resource Manager and Stacks with the CLI and with the OCI DevOps Build, and Deployment Pipelines.
I am trying to use Terraform more and more. Using the console for the initial creation perhaps, then using Resource Discovery with the OCI Provider for Terraform (see for example this Medium article) or the Stack export option in the OCI Console for creating the Terraform plan files for the resources I just created. From that point, I continue with those plan files and the command line — typically with OCI Cloud Shell with Terraform preinstalled and configured.
It seemed like a good idea to start building a collection of Terraform plan files for common constellations of resources. And I am well aware that I am far from the first to create such a collection. I will happily use similar offerings from my friends and peers and add some composites that are not yet available (or at least I did not find or like them).
The Composite
I’m happy to share with you my first and only OCI composite so far: Create, Build, and Invoke a Function. Functions on OCI are based on the Project Fn framework. They are created as container images that are pushed to the OCI Container Registry. A function is created in an application with a reference to the container image that provides the implementation.
In my first little collection of plan files, a function’s container image is built from the sources included in the repository (a Python function that does very little). Of course, this is just an example and you can easily replace these sources with real ones. The container image is pushed to OCI Container Image Registry to a repository that is created by these plans specifically for the function.
The function itself is subsequently created in the context of an application that already exists. The name of the application as well as the name of the function are defined in the variables.tf file — as are the region, the tenancy_ocid, the target compartment ocid, the Docker Registry credentials and the test request with which the function is to be invoked.
Use this sample set of Terraform plans to create your own plans.
Some “tricks” in these plans:
- use data sources to query existing OCI resources, such as the application based on its name (in datasources.tf)
- use local variables derived from data source results as well as input variables
- use provisioner “local-exec” to execute Linux Bash Shell command on the server running Terraform (to interact with fn and terraform tools)
- use Terraform function tomap() to create a key-value map structure (or JSON object) for one of the complex properties
- use Terraform output to report on what is going on or went on (like a
console.logor adbms_output.put_lineorSystem.out.println) - use resource “time_sleep” to introduce a little wait (10 seconds), in this case between creating and invoking the function (without the wait time, I ran into 404-NotAuthorizedOrNotFound errors)
The most important Terraform resources in these plans are shown here:
### Repository in the Container Image Registry for the container images underpinning the function resource "oci_artifacts_container_repository" "container_repository_for_function" { # note: repository = store for all images versions of a specific container image - so it included the function name compartment_id = var.compartment_ocid display_name = "${local.ocir_repo_name}/${var.function_name}" is_immutable = false is_public = false }resource "null_resource" "Login2OCIR" { depends_on = [ oci_artifacts_container_repository.container_repository_for_function]provisioner "local-exec" { command = "echo '${var.ocir_user_password}' | docker login ${local.ocir_docker_repository} --username ${local.ocir_namespace}/${var.ocir_user_name} --password-stdin" } }### build the function into a container image and push that image to the repository in the OCI Container Image Registry resource "null_resource" "FnPush2OCIR" { depends_on = [null_resource.Login2OCIR, oci_artifacts_container_repository.container_repository_for_function]# remove function image (if it exists) from local container registry provisioner "local-exec" { command = "image=$(docker images | grep ${local.app_name_lower} | awk -F ' ' '{print $3}') ; docker rmi -f $image &> /dev/null ; echo $image" working_dir = "functions/fake-fun" }# remove fake-fun image (if it exists) from local container registry provisioner "local-exec" { command = "image=$(docker images | grep fake-fun | awk -F ' ' '{print $3}') ; docker rmi -f $image &> /dev/null ; echo $image" working_dir = "functions/fake-fun" }# build the function; this results in an image called fake-fun (because of the name attribnute in the func.yaml file) provisioner "local-exec" { command = "fn build --verbose" working_dir = "functions/fake-fun" }# tag the container image with the proper name - based on the actual name of the function provisioner "local-exec" { command = "image=$(docker images | grep fake-fun | awk -F ' ' '{print $3}') ; docker tag $image ${local.ocir_docker_repository}/${local.ocir_namespace}/${local.ocir_repo_name}/${var.function_name}:0.0.1" working_dir = "functions/fake-fun" }# create a container image based on fake-fun (hello world), tagged for the designated function name provisioner "local-exec" { command = "docker push ${local.ocir_docker_repository}/${local.ocir_namespace}/${local.ocir_repo_name}/${var.function_name}:0.0.1" working_dir = "functions/fake-fun" }}resource "oci_functions_function" "new_function" { depends_on = [null_resource.FnPush2OCIR] application_id = "${local.application_id}" display_name = "${var.function_name}" image = "${local.ocir_docker_repository}/${local.ocir_namespace}/${local.ocir_repo_name}/${var.function_name}:0.0.1" memory_in_mbs = "128" config = tomap({ DUMMY_CONFIG_PARAM = "no value required" }) }### wait a little while before the function is ready to be invoked ## I got the following errors without this wait introduced into the plan ## Error: 404-NotAuthorizedOrNotFound ## Error Message: Authorization failed or requested resource not found ##│Suggestion: Either the resource has been deleted or service Functions Invoke Function need policy to access this resource. resource "time_sleep" "wait_for_function_to_be_ready" { depends_on = [oci_functions_function.new_function] create_duration = "10s" }resource "oci_functions_invoke_function" "test_invoke_new_function" { depends_on = [time_sleep.wait_for_function_to_be_ready] #Required function_id = oci_functions_function.new_function.id#Optional invoke_function_body = var.test_invoke_function_body fn_intent = "httprequest" fn_invoke_type = "sync" base64_encode_content = false }output "function_response" { value = oci_functions_invoke_function.test_invoke_new_function.content }
Resources
Oracle Terraform Modules — https://github.com/oracle-terraform-modules
Terraform on OCI — articles by That Finnish Guy (Simo Vilmunen) : https://www.thatfinnishguy.blog/tag/terraform/
Terraform docs on Sleep — https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep
Terraform Docs on OCI Provider — https://registry.terraform.io/providers/hashicorp/oci/latest/docs
Join the conversation!
If you’re curious about the goings-on of Oracle Developers in their natural habitat, come join us on our public Slack channel! We don’t mind being your fish bowl 🐠
