In the post Continuous Delivery on AWS with Terraform and Travis CI we have seen how Terraform can be used to manage your infrastructure as code and automate your deployments. When working on a project involving different infrastructure providers, Terraform can also be very helpful.
Besides managing popular cloud providers like Amazon Web Services, Google Cloud, or Microsoft Azure, Terraform supports a great set of additional official and community providers. If the desired provider happens to be missing, you can write your own custom provider and utilize Terraform’s flexible plugin mechanism to include it into your workflow.
The fact that Terraform is not tied to a specific infrastructure or cloud provider makes it a powerful tool in multi-provider deployments. You are able to manage all resources using the same set of configuration files, sharing variables or defining dependencies between resources across providers.
In this post we want to take a look at an example of how to provision resources from two providers. We will combine an AWS API Gateway deployment with a webhook subscription for the Enterprise Architecture Management (EAM) tool called LeanIX. I chose LeanIX in this example because we have recently been working with a customer who uses it for their EAM and it has a very developer-friendly API.
We are going to write our own custom provider for LeanIX because no community provider exists yet. The goal is for both the API and the webhook subscription to be integrated such that webhook is calling the AWS API. All resource configuration will be managed from within Terraform. The source code of the complete example as well as the custom provider is hosted on GitHub.
The remainder of the post is structured as follows. First we will quickly outline the solution architecture. The next section is going to discuss the implementation details, including the Terraform resource definitions, provider configuration, as well as the implementation of the custom LeanIX provider. We will conclude the post by summarizing and discussing the main findings.
The solution architecture involves two main components: LeanIX and the API Gateway. In LeanIX you can create a new webhook subscription in order to react to events happening within the system. This can be used, e.g., to build a Slack application that notifies you whenever a new user signs up.
In our example we will send the events towards a dummy API. API Gateway can integrate with arbitrary services to process incoming requests, e.g. AWS Lambda, but also just act as a reverse HTTP proxy. For the sake of simplicity we will only have a mock integration which does not actually process the requests. Let’s dive into the implementation details.
To implement the target architecture, we will define our resources in Terraform configuration files as usual. The AWS Provider is responsible for managing the API Gateway, while a custom LeanIX provider will manage the webhook subscription. The implementation of the custom provider will be explained in more detail in the second part of this section. First let’s look at the resource definitions.
An API Gateway deployment consists of a set of resources. Each resource can work with a set of methods. For each method we need to define what should happen if the API is called on this resource. This can be achieved by defining four building blocks: A method request, an integration request, an integration response, and a method response.
In our particular case we will have one resource called
events which accepts requests made with the
POST method. The definitions of method request/response and integration request/response are fairly simple, as we are using a mock endpoint. We configured the API to return status code 200 (OK) on every request. The following figure illustrates the final result after creating the API.
The method request defines the HTTP request format accepted by the API gateway. If your resource requires authentication and authorization, you need to define an authorizer inside the method request settings. The integration request defines how the API Gateway sends a request towards the integration endpoint. If you are integrating with a Lambda function, it will contain the original request wrapped inside a JSON event containing additional metadata. But you are also free to define your own request mapping depending on the API of the integration endpoint.
The integration response defines how to transform the back-end response before passing it on to the client. The status code must match to an existing method response. The method response then defines the form of the final HTTP response sent back to the client.
The listing below shows the Terraform file required to setup the API Gateway deployment. After creating all required resources we also have to create a deployment resource. Each deployment happens in a stage, allowing us to deploy the API multiple times, e.g. one for testing and one for production.
It is important to note that AWS requires you to set up the request and response definitions in order, following the arrows from the figure above. Unless you are defining this dependency implicitly by using variables, it is necessary to explicitly make the resources depend on each other.
Having the API that will accept incoming webhook events setup, we now need to create the webhook subscription.
In LeanIX, webhooks allow you to subscribe to events happening inside the application in near-real time. You can subscribe to many different event types, such as creation, modification or deletion of objects and user logins, for example.
To create a new webhook subscription we need to provide the following information:
- Identifier. The identifier is the name of the webhook that is shown on the UI.
- Target URL. The URL that the webhook is pushing events to.
- Target method. The HTTP method to use when sending a request to the target URL.
- Activity Status. Whether the webhook is active or not.
- Workspace ID. This associates the webhook with a specific LeanIX workspace, so it is visible and manageable through the workspace administration UI.
- Tag sets. Tag sets can be used to subscribe to specific event types. Every event that matches all of the tags in at least one of the tag sets will get forwarded.
The following figure shows a successfully created webhook subscription in LeanIX.
There are more configuration options available, but we are going to leave them out at this point. Please consult the official LeanIX API documentation for more information. The listing below illustrates the Terraform resource definition for the webhook subscription.
In this subscription we are only interested in getting notified when someone creates or deletes a fact sheet object. The service responsible for fact sheets is called pathfinder. We are generating the identifier from the LeanIX API key as it has to be unique across the system. The target URL is filled in by Terraform based on the API Gateway deployment.
In order to apply the changes and make Terraform create the webhook subscription, we need to initialize and configure the AWS and LeanIX providers. In the next section will look into the provider configuration as well as the basics on how the custom provider is implemented.
The AWS provider needs to have a valid access and secret key. There are multiple ways to provide AWS credentials and you are free to choose the one you prefer. In this case we are using a credentials file to keep the provider configuration clean. Thus we are only providing the region to use.
The LeanIX provider needs to have the URL of your LeanIX instance, as well as a valid API token. You can generate new API tokens directly from within LeanIX.
Next we can initialize Terraform and apply the changes. For convenience reasons, we even made Terraform generate a curl statement to immediately try the API.
Great! We have a working webhook subscription sending events towards our API hosted in AWS. The only open question is: how did we write the LeanIX Terraform provider?
LeanIX custom provider
Terraform knows two types of plugins: providers and provisioners. Providers enable you to manage resources provided by a specific service through an API. Provisioners are used to execute scripts on a local or remote machine as part of the resource lifecycle, e.g. bootstrapping a newly created virtual machine resource.
In our example we need to create a new custom provider to manage the LeanIX webhook subscription resource. Luckily, Terraform offers a comprehensive guide on creating custom providers. Here’s what we need to do:
Create new Go project. Terraform is written in Go. Terraform plugins are also written in Go. Each plugin is an executable that communicates with the Terraform core through remote procedure calls (RPC). Plugins are loaded dynamically and only have to be initialized once using
Create main method boilerplate. In order for the plugin to be usable it needs to have a main method that initializes the provider. It will be called by Terraform as soon as the provider is being used.
Create provider definition. The provider definition contains the provider schema, the available resources, as well as a function to bootstrap the provider given a user configuration. The provider schema defines which configuration options are available and required to use the provider.
Create resource definitions. A resource definition contains functions to create, read, update, and delete resources. This usually corresponds to API calls on the provider service, e.g. AWS or LeanIX. It also contains a schema definition which states the configuration options available and required for the Terraform resource.
It is recommended to decouple actual client logic from the resource definition, which is why I implemented a separate
LeanixClient type which contains all the logic including authentication and usage of the LeanIX API. Going into details regarding the actual resource implementation is beyond the scope of this post. But feel free to browse the source code and drop any question you might have in the comments.
In this post we have seen how Terraform can be used to deploy infrastructure involving more than one provider. By defining dependencies between resources and imputing variables across providers we are able to seamlessly integrate all infrastructure components.
Terraform resources can only be defined using configuration files, which is limiting the flexibility to some extent. Nevertheless, the simple, yet powerful plugin system allows to extend Terraform functionality as required. Terraform plugins have to be written in Go, but there is plenty of documentation and examples available to refer to.
Please do not forget to destroy your resources in case you were following along. Have you written your own custom provider before? Do you know any alternatives to Terraform when it comes to infrastructure as code involving multiple service providers? Let me know your thoughts in the comments.