Case Studies
        

March 19, 2024

Terraform Schema Converter

Customer’s Product Brief

 

Tanzu Mission Control (TMC) is a VMware product designed for administering Kubernetes clusters across cloud and on-premise environments. It features a user interface platform supported by a REST API that adheres to the Open API standard and is documented using Swagger.

In addition to the core product features, VMware provides a Terraform Provider to empower clients with Infrastructure as Code (IaC) capabilities. This facilitates the automation and scalable management of TMC’s infrastructure.

As part of our partnership with VMware, TeraSky was engaged to assist in developing the Terraform provider. Our responsibilities included creating new resources and data sources to extend the capabilities of the provider.

Throughout the project, during which the team delved into structure and operations, they identified a repetitive, tedious, and time-consuming task essential for small and large resources and data sources. The task involved writing extensive code to ensure the alignment of data representation in the Terraform data object with the API structure defined in Swagger. Further, making changes in the Terraform schema proved challenging, as it required modifications to both the ‘constructing’ and ‘flattening’ approaches. These approaches essentially involve constructing and flattening the Swagger model into the Terraform data object structure, respectively.

 

Technical Background

 

When using the Terraform IaC framework, the developer is required to compose a set of resources and modules to achieve a certain goal. With the Terraform code ready, the developer runs a command that will initiate the Terraform core to parse the Terraform files into a tree or a graph of resource dependencies to be built in the correct order.

Each resource or data source is mapped to its origin provider. Terraform core interprets the code, determines which providers to use, and communicates with each provider separately via RPC to instruct it on what to do—create, Read, Update, or Delete.

 

 

In the VMware Terraform provider context, the optimal approach was to develop the client library as part of the provider binary and not as an independent binary. Therefore, the implementation of the entire VMware Terraform provider consists of two major layers:

  • Terraform Provider Layer – includes the resources and data sources schema, CRUD operations implementation, and the provider linkage to these assets.
  • API Client Library Layer – includes Swagger-generated API Go struct models and the actual HTTPS API clients.

The Target API is, of course, the TMC platform in which customers manage their Kubernetes environments.

When developing Terraform resources and data sources using SDKv2, the developer is required to define a schema for the resource. The Terraform schema definition, in turn, has its own set of rules to represent the data in the Go object, mirroring the configuration filled by the IaC developer. Since an API is commonly implemented regardless of the existence of a Terraform provider, it doesn’t usually look the same. Additionally, the provider developer may not always align the resource schema definition to match the API structure exactly for various reasons, including user experience, naming conventions, hard-coded values, etc.
Finally, the Go object is not a primitive value that the provider developer can simply pass to the client library as if it was a JSON or a model object. Instead, it is an object that supports various functions, including getting or setting data to and from the resource configuration.

 

Challenges

 

Challenge 1 – Naming Convention

 

In the context of this project, the model objects were constructed by initializing a model struct object and filling it with data from the Terraform Go object. In terms of implementation, that meant extracting the data from the Terraform Go object via the functions supported, creating a new struct object and possibly a few inner ones, and casting the values extracted from the Terraform object to the type of the destination fields in the struct.

The models generated by Swagger included functionality for converting JSON objects to struct objects and vice versa, but all of the JSON tags for the struct fields were pre-coded in the Go camelcase naming convention to match the API structure, while the Terraform naming convention is lower case separated with underscores.

Solving this problem requires creating a kind of map between the fields to be able to construct the correct JSON object to seamlessly marshal to a struct object.

 

Challenge 2 – Repeatable Code

 

Eventually, it became clear that extracting data from the Terraform object and creating structs, inner structs, and arrays of structs was a repetitive task essential for any resource. This is due to the unique API requirements of each resource implementation, necessitating distinct model objects.

Recognizing this pattern encouraged the adoption of a versatile tool capable of dynamically constructing models. Such construction can be accomplished by integrating a robust reflection mechanism or harnessing the pre-existing marshal and unmarshal capabilities.
To get an idea of how much code it involves, view these examples for construction and flattening.

 

Challenge 3 – Terraform Block To Struct/JSON Object

 

As previously mentioned, to define a Terraform block, the provider developer must define a TypeList field, which is represented as a ‘[]interface{}’ with items of type ‘map[string]interface.’

The equivalent for a Terraform block in the model object’s structure can be one of the following:

  • If the Terraform Block is unrepeatable, it will be represented as a ‘[]interface{}’ with a single value of type ‘map[string]interface’ and the matching structure in the model’s object would be a struct type.
  • If the Terraform Block is repeatable, it will be represented as an ‘[]interface’ where each item is of type ‘map[string]interface’. The matching structure in the model’s object would be an array of structs.

Therefore, we must differentiate between repeatable and unrepeatable blocks when constructing a struct or a JSON object.

 

Challenge 4 – Unrepeatable Terraform Block To Structs/JSON Objects Array

 

In some cases, a provider developer would like to define an unrepeatable Terraform block that will be mapped to an array of structs or JSON objects. Although it might not seem challenging because both Terraform Block and structs/JSON objects are mapped to a ‘[]interface{},’ it is quite difficult to implement.

 

The challenge arises because these cases indicate that the API structure anticipates receiving an array of structs or JSON objects; each array item, however, can have one of several types of values, but it cannot contain multiple value types within the same item.

Consider the following structure:

 

 

In this example, it is expected that the Subscriptions array will be filled with Subscription objects that will either store Individual Customer data or Company Customer data but not both in the same item. Therefore, when defining an unrepeatable block to match such a field, the block repeatability should be delegated to the inner blocks as follows.

 

 

This behavior complicates the work of constructing and flattening even further because it requires the developer to build the model object’s data from a different level of the hierarchy in the Terraform resource object.

 

The Converter Solution

 

To solve these challenges, the team needed to devise a component to handle the construction and flatten operations and thereby offload the complexity from the provider developer when creating a new resource.

The full converter implementation can be found here.

 

How Does It Work?

 

To use the converter, the developer must instantiate a generic struct object – often referred to as the converter object – which receives a mapping object as an input and a type of a model as a generic type. Once the converter is instantiated, the developer can call the construct and flatten operations.

When the converter is called to construct a model, it runs through the mapping object and recursively builds the expected JSON representation that matches the model object. At the end of the process, the JSON object will be marshaled into a struct object. The process is similar when flattening a model object: the converter will run through the mapping object and fill the data in the Terraform object according to the existing model object.

 

 

The Solution Impact

 

As a result of developing the converter, building new resources and data sources became much easier and faster. The converter allowed the project developers to focus on implementing the actual resource logic rather than writing complex and repetitive code to convert the data representation. Further, the solution doesn’t use third-party libraries except for the Terraform SDKv2, making it safer and more reliable.

 

Want more info?

Tags:
VMware
VMware TMC
Tanzu Mission Control
Terraform Provider
golang
SDKv2
Terraform-provider-tanzu-mission-control
Share:

Next Articles

Case Studies
      

20 March, 2024

Reface: Reimagining the Selfie with Google Cloud
Read Case Study
Case Studies
      

27 February, 2024

Precision Scaling Success
Read Case Study
Case Studies
      

21 February, 2024

How TeraSky Conquered Developer Frustration with AWS’s Help
Read Case Study
Skip to content