appendix D Creating custom resources with the Shell provider

The Shell provider (https://github.com/scottwinkler/terraform-provider-shell) is a third-party provider proudly developed and maintained by yours truly. This provider is available on the Terraform Registry and allows you to create custom resources by invoking shell scripts, alleviating the need to create one-off Terraform providers for specific tasks. Many people find it useful for patching gaps in existing providers or creating specific utility functions. This appendix covers how to install the provider and goes through some examples of what can be done with it.

D.1 Installing the provider

To install a custom Terraform provider, you first have to declare that you want to use a custom Terraform provider. Each Terraform module must declare which providers it requires, and I usually put this information in versions.tf, as provider requirements are declared in the required_providers block of Terraform settings. This is used to source the provider from the Terraform Registry:

terraform {
  required_providers {
    shell = {
      source = "scottwinkler/shell"
      version = "~> 1.0"
    }
  }
}

Note Terraform first checks the local directory and ~/.terraform.d/plugins before it checks the Terraform Registry.

You can now install the third-party provider by running a normal terraform init:

$ terraform init
 
Initializing the backend...
 
Initializing provider plugins...
- Finding scottwinkler/shell versions matching "~> 1.0"...
- Installing scottwinkler/shell v1.7.3...
- Installed scottwinkler/shell v1.7.3 (self-signed, key ID 2CAB13AD54B7DF3D)
 
Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://www.terraform.io/docs/plugins/signing.html
 
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.

D.2 Using the provider

Once you have the Shell provider installed, you can access two new resources: a shell_script resource and a shell_script data source. These two resources allow you to create custom resources in Terraform by specifying commands that will be run during Terraform CRUD operations. You can also set computed attributes and reference them from Terraform. For example, the following listing shows a simple data source that can read the current logged in user with whoami:

Listing D.1 Shell script data source

terraform {
  required_providers {
    shell = {
      source = "scottwinkler/shell"
      version = "~> 1.0"
    }
  }
}
 
 
data "shell_script" "user" {
  lifecycle_commands {
    read = <<-EOF
        echo "{"user": "$(whoami)"}"               
    EOF
  }
}
 
output "user" {
    value = data.shell_script.user.output["user"]      
}

Sets the output of the custom data source

Reference output here

If you ran this, you would get the following:

$ terraform apply -auto-approve
data.shell_script.user: Refreshing state...
 
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
 
Outputs:
 
user = swinkler

TIP This pattern could also be used to read generic environment variables into Terraform variables.

I know what you might be thinking: a data source that calls external scripts is not particularly useful or interesting; the same thing could also be done with external data sources or the Null provider. What makes the Shell provider unique is its support for managed resources that implement the full lifecycle of Terraform resources. This example implementation gets the current weather in London and saves it to a local file.

Listing D.2 Shell script resource

terraform {
  required_providers {
    shell = {
      source = "scottwinkler/shell"
      version = "~> 1.0"
    }
  }
}
 
resource "shell_script" "weather" {
  lifecycle_commands {
    create = <<-EOF
      echo "{"London": "$(curl wttr.in/London?format="%l:+%c+%t")"}"  > 
state.json
      cat state.json 
    EOF
    delete = "rm state.json"
  }
}
 
output "weather" {
    value = shell_script.weather.output["London"]
}

Applying this queries the weather from wttr.in and saves it into a local file called state.json:

 

$ terraform apply -auto-approve
shell_script.weather: Creating...
shell_script.weather: Creation complete after 0s [id=bpcrf2dgrkri1bd7rgsg]
 
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
 
Outputs:
 
weather = London: ⛅️  +14°C

Since it’s a normal Terraform resource, it participates in the resource lifecycle by saving state to the state file:

$ terraform state show shell_script.weather
# shell_script.weather:
resource "shell_script" "weather" {
    dirty             = false
    id                = "btdk3gdgrkru9f4634h0"
    output            = {
        "London" = "London: ⛅️  +14°C"
    }
    working_directory = "."
 
    lifecycle_commands {
        create = <<~EOT
            echo "{"London": "$(curl wttr.in/London?format="%l:+%c+%t")"}"  > state.json
            cat state.json
        EOT
        delete = "rm state.json"
    }
}

Additionally, you can see that a new file has been created, state.json. This file stores the output of the command and represents a managed resource:

$ cat state.json
{"London": "London: ☁ +14°C"}

Calling terraform destroy ensures that the state.json file is deleted:

$ terraform destroy -force
shell_script.weather: Refreshing state... [id=bpcrg45grkri1sm1kf00]
shell_script.weather: Destroying... [id=bpcrg45grkri1sm1kf00]
shell_script.weather: Destruction complete after 0s
 
Destroy complete! Resources: 1 destroyed.
You can verify that it has been deleted by cat-ing it out once more:
$ cat state.json
cat: state.json: No such file or directory

D.3 Final thoughts

The Shell provider can be used for much more than what we have seen. It supports full CRUD resource lifecycle management and allows you to do almost anything that would normally only be possible by writing a custom Terraform provider. Because it stores stateful information like any other Terraform resource and supports read and update capabilities, it’s more versatile than Null resources with attached local-exec provisioners. To give you an idea of what is possible, here is an example of using the Shell provider to create a GitHub repository:

variable "oauth_token" {
  type = string
}
 
provider "shell" {
  sensitive_environment = {
    OAUTH_TOKEN = var.oauth_token
  }
}
 
resource "shell_script" "github_repository" {
  lifecycle_commands {
    create = file("${path.module}/scripts/create.sh")
    read   = file("${path.module}/scripts/read.sh")
    update = file("${path.module}/scripts/update.sh")
    delete = file("${path.module}/scripts/delete.sh")
  }
 
  environment = {
    NAME        = "My-Github-Repo-Name"
    DESCRIPTION = "some description"
  }
}

Note For the complete example, refer to the Shell provider documentation: http://mng.bz/VG8P.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.212.102.174