Please note that this guide is a work in progress. If you find any issues at all, please don't hesitate to report them either in the #KUBE100 Slack for beta testers, or on the GitHub repository.

Terraform is an industry-leading Infrastructure-as-code tool that allows you to use simple declarative statements to provision resources on cloud servers. This guide will guide you through the basic functionality of the Terraform Provider for Civo, allowing you to get started with provisioning Civo instances and services through Terraform files.

Install the provider

This guide will assume that you have Terraform installed for your particular operating system. Follow these installation instructions if you need to acquire Terraform before returning to this guide.

We will need to install the provider for your platform. Linux, Mac OS and Windows binaries can be downloaded from here. After you have downloaded and decompressed the binary, you'll need to place it in the Plugins folder for your specific operating system, like this:

├── .terraform
│   ├── plugins
│       ├── terraform-provider-civo_v0.9.0

In my case, as I use a Mac, the folder where plugins resides is in a hidden directory called .terraform in my home directory. Depending on your platform, your Plugins directory will reside in a different place. For example, for Linux, the directory name is .terraform.d/.

Once you have located the correct directory, copy the downloaded Civo Terraform provider file into it. Once you have copied the provider plugin to its folder, we need to declare the provider to make it usable. Start by creating a file called provider.tf in your ~/.terraform directory (i.e. one level above plugins) with this inside:

provider "civo" {
  token = "your_token"
}

You can find your API token in your Civo account under Security, or alternatively on the Civo API documentation page provided you are logged in to your account.

Save this file as provider.tf and you will be able to run terraform init. You should see a message like this:

Initializing the backend...

Initializing provider plugins...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.civo: version = "~> 0.9.0"

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

Quick Start: Working with the Civo Terraform provider

Instances

The provider is under active development. If you find any issues or discrepancies below, please report them in the github repository.

We will be starting with the civo_instances resource. In order to create a new instance you can create a file like instances.tf with the content based on the snippet below. For all documentation, refer to the help here

resource "civo_instance" "my-test-instance" {
    hostname = "foo.com"
    tags = ["python", "nginx"]
    notes = "this is a note for the server"
    size = element(data.civo_instances_size.small.sizes, 0).name
    template = element(data.civo_template.debian.templates, 0).id
}

The file above will need references for what the instance_size and template will be. For that, we'll need to create a file called data-source.tf with this content:

data "civo_instances_size" "small" {
    filter {
        key = "name"
        values = ["g2.small"]
    }
}

data "civo_template" "debian" {
   filter {
        key = "code"
        values = ["buster"]
   }
}

To see all options for this data source you can read the doc here for instance_size and here for the template.

Deploying a configuration

By running terraform plan, you will be able to see the configuration plan to be executed. For the above example, it would look something like this:

$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

data.civo_template.debian: Refreshing state...
data.civo_instances_size.medium: Refreshing state...

------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # civo_instance.my-test-instance will be created
  + resource "civo_instance" "my-test-instance" {
      + created_at       = (known after apply)
      + hostname         = "test-terraform"
      + id               = (known after apply)
      + initial_password = (known after apply)
      + initial_user     = "civo"
      + notes            = "Test note to the server"
      + private_ip       = (known after apply)
      + pseudo_ip        = (known after apply)
      + public_ip        = (known after apply)
      + size             = "g2.medium"
      + status           = (known after apply)
      + tags             = [
          + "nginx",
        ]
      + template         = "fffbe2e5-0dd8-476b-b480-cb7c9fccbe39"
    }

Plan: 1 to add, 0 to change, 0 to destroy.

------------------------------------------------------------------------

You will be able to execute the plan and watch your instance get deployed by running terraform apply:

$ terraform apply
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # civo_instance.my-test-instance will be created
  + resource "civo_instance" "my-test-instance" {
      + created_at       = (known after apply)
      + hostname         = "test-terraform"
      + id               = (known after apply)
      + initial_password = (known after apply)
      + initial_user     = "civo"
      + notes            = "Test note to the server"
      + private_ip       = (known after apply)
      + pseudo_ip        = (known after apply)
      + public_ip        = (known after apply)
      + size             = "g2.medium"
      + status           = (known after apply)
      + tags             = [
          + "nginx",
        ]
      + template         = "fffbe2e5-0dd8-476b-b480-cb7c9fccbe39"
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value:

DNS

Now, to manage the dns_domain_name and dns_domain_record resources, we need to create a file called dns.tf with this content altered for your personal domain details:

resource "civo_dns_domain_name" "main" {
  name = "mydomain.com"
}

resource "civo_dns_domain_record" "www" {
    domain_id = civo_dns_domain_name.main.id
    type = "a"
    name = "www"
    value = civo_instance.my-test-instance.public_ip
    ttl = 600
    depends_on = [civo_dns_domain_name.main, civo_instance.my-test-instance]
}

In this case we use depends_on because in order to create a domain record the domain name must exist and the instance must exist too. The depends_on option is the way to tell Terraform it needs to wait for the resource in the list to be created before creating any dependent resources. Details on DNS documentation can be found here and here

Firewall

Now, we will see how to create a firewall using the provider. For this, we'll need to create another file, in this case the name will be firewall.tf. To extend the functionality of our service, suppose you have more than one instance declared in Terraform, with the names vps01 and vps02. We want to make a firewall rule to allow access to a MySQL server:

resource "civo_firewall" "mysql" {
  name = "mysql"
}

resource "civo_firewall_rule" "mysql-inbound" {
  firewall_id = civo_firewall.mysql.id
  protocol = "tcp"
  start_port = "3306"
  end_port = "3306"
  cidr = [format("%s/%s",civo_instance.vps01.public_ip,"32"), format("%s/%s",civo_instance.vps02.public_ip,"32")]
  direction = "inbound"
  label = "server mysql"
  depends_on = [civo_firewall.mysql]
}

First we create a firewall with the name mysql, followed by a rule for that firewall with the name mysql-inbound. If you take a look at the cidr attribute, it is a list of two sets of civo_instance: vps01 and vps02 and for both we get the public_ip attribute.

Here the depends_on option works in the same way as above. Now, to add this rule to our MySQL instance simply add the line firewall_id = civo_firewall.mysql.id to the civo_instance that runs MySQL. But be careful - if you include this line and the firewall doesn't exist, an error will be raised. If you are creating all from scratch then you can use the depends_on option inside the instance resource like this depends_on = [civo_firewall.mysql].

You can read all documentation about firewalls and firewall rules here and here.

Load balancer

We will still be using the same two instances vps01 and vps02. These instances will stay connected to the MySQL instance, but we need a way to handle all traffic in front of vps01 and vps02. The way to do that is by creating a load balancer. Just like before, we need to create a file to define this service. Call a new file lb.tf and inside this file put the following code snippet. To see all options about load balancers you can read the doc here.

resource "civo_loadbalancer" "www-lb" {
    hostname = "www.mydomain.com"
    protocol = "http"
    port = 80
    max_request_size = 30
    policy = "round_robin"
    max_conns = 10
    fail_timeout = 40

    backend {
        instance_id = civo_instance.vps01.id
        protocol =  "http"
        port = 80
    }

    backend {
        instance_id = civo_instance.vps02.id
        protocol = "http"
        port = 80
    }
}

Conclusion

So far we have a functional service using the Terraform provider, with instances having traffic routed to them in turn through a load balancer, and a firewall rule allowing traffic between the instances and a mysql database. If you would like to know more and explore how all possible resources work, you can visit the GitHub repository of the project here. Let us know what you think by raising an issue, feature request or best of all, submit a pull request!