Terraform 用の Ansible 認定コンテンツ コレクション(v1.0.0)が利用可能に

# 概要

Terraform 用の Ansible 認定コンテンツ コレクション(v1.0.0)が利用可能になった様なので触ってみました。

# 情報リンク

- twitter

https://twitter.com/ansible/status/1597247368679673856?s=20&t=2hZVhNpCSN3ZyUHSk1AG1g

Terraform 用の Ansible 認定コンテンツ コレクションが利用可能になったことを発表できることをうれしく思います。 Ansible Automation Platform サブスクリプションの一部として Automation Hub で利用できるようになりました

- redhat

https://console.redhat.com/ansible/automation-hub/repo/published/cloud/terraform

- github

https://github.com/ansible-collections/cloud.terraform

# 導入

```
$ ansible-galaxy collection install cloud.terraform
Starting galaxy collection install process
Process install dependency map
Starting collection install process
Downloading https://galaxy.ansible.com/download/cloud-terraform-1.0.0.tar.gz to /home/vagrant/.ansible/tmp/ansible-local-29742d9psx0sz/tmpulp0lvx7/cloud-terraform-1.0.0-_hacx70x
Installing 'cloud.terraform:1.0.0' to '/home/vagrant/.ansible/collections/ansible_collections/cloud/terraform'
cloud.terraform:1.0.0 was installed successfully
```

# 導入されたファイルツリー

```
$ tree ~/.ansible/collections/ansible_collections/ 
/home/vagrant/.ansible/collections/ansible_collections/
|-- cloud
|   `-- terraform
|       |-- CHANGELOG.rst
|       |-- FILES.json
|       |-- LICENSE
|       |-- MANIFEST.json
|       |-- README.md
|       |-- changelogs
|       |   |-- changelog.yaml
|       |   |-- config.yaml
|       |   `-- fragments
|       |       `-- 0001-initial.yml
|       |-- docs
|       |   `-- docsite
|       |       `-- links.yml
|       |-- meta
|       |   `-- runtime.yml
|       |-- plugins
|       |   |-- module_utils
|       |   |   `-- version.py
|       |   `-- modules
|       |       |-- terraform.py
|       |       `-- terraform_output.py
|       |-- requirements.txt
|       |-- requirements.yml
|       |-- roles
|       |   |-- git_plan
|       |   |   |-- README.md
|       |   |   |-- meta
|       |   |   |   `-- argument_specs.yml
|       |   |   `-- tasks
|       |   |       `-- main.yml
|       |   `-- inventory_from_outputs
|       |       |-- README.md
|       |       |-- meta
|       |       |   `-- argument_specs.yml
|       |       `-- tasks
|       |           `-- main.yml
|       |-- test-requirements.txt
|       `-- tests
|           |-- integration
|           |   `-- targets
|           |       |-- action_groups
|           |       |   |-- files
|           |       |   |   `-- nothing.tf
|           |       |   `-- tasks
|           |       |       `-- main.yml
|           |       |-- aws
|           |       |   |-- files
|           |       |   |   `-- cloud.tf
|           |       |   `-- tasks
|           |       |       `-- main.yml
|           |       |-- azure
|           |       |   |-- files
|           |       |   |   `-- cloud.tf
|           |       |   `-- tasks
|           |       |       `-- main.yml
|           |       |-- complex_variables
|           |       |   |-- files
|           |       |   |   |-- main.tf
|           |       |   |   `-- variables.tf
|           |       |   `-- tasks
|           |       |       `-- main.yml
|           |       |-- gcp
|           |       |   |-- files
|           |       |   |   `-- cloud.tf
|           |       |   `-- tasks
|           |       |       `-- main.yml
|           |       |-- list_vars_passthrough
|           |       |   |-- files
|           |       |   |   `-- main.tf
|           |       |   `-- tasks
|           |       |       `-- main.yml
|           |       |-- local
|           |       |   |-- files
|           |       |   |   `-- write_file.tf
|           |       |   `-- tasks
|           |       |       `-- main.yml
|           |       |-- provider_upgrade
|           |       |   |-- tasks
|           |       |   |   |-- main.yml
|           |       |   |   `-- test_provider_upgrade.yml
|           |       |   |-- templates
|           |       |   |   `-- main.tf.j2
|           |       |   `-- vars
|           |       |       `-- main.yml
|           |       |-- state_planned
|           |       |   |-- files
|           |       |   |   `-- write_file.tf
|           |       |   `-- tasks
|           |       |       `-- main.yml
|           |       |-- terraform_output
|           |       |   |-- files
|           |       |   |   `-- outputs.tf
|           |       |   `-- tasks
|           |       |       `-- main.yml
|           |       |-- test_git_plan
|           |       |   |-- files
|           |       |   |   `-- write_file.tf
|           |       |   `-- tasks
|           |       |       `-- main.yml
|           |       `-- test_inventory_from_outputs
|           |           |-- files
|           |           |   `-- create_inventory.tf
|           |           `-- tasks
|           |               `-- main.yml
|           `-- unit
|               |-- compat
|               |   |-- __init__.py
|               |   |-- builtins.py
|               |   |-- mock.py
|               |   `-- unittest.py
|               `-- plugins
|                   `-- modules
|                       |-- cloud
|                       |   `-- misc
|                       |       `-- test_terraform.py
|                       `-- utils.py
`-- cloud.terraform-1.0.0.info
    `-- GALAXY.yml

64 directories, 56 files
```





以下の環境で試す。

## 環境
- Red Hat Enterprise Linux 8.5 (Ootpa)
- ansible-6.3.0-1.el8.noarch
- ansible-core-2.13.3-1.el8.x86_64
- Terraform v1.3.6 on linux_amd64


# Playbook例

```yaml:site.yml
---
- hosts: localhost
  gather_facts: false

  vars:
    project_dir: terraform/    # 最後の/が必要

  tasks:

  - name: Basic deploy of a service
    cloud.terraform.terraform:
      project_path: "{{ project_dir }}"
      state: present
      force_init: true
```

```terraform:./terraform/output.tf
output "test" {
  value = "Hello Terraform!"
}
```

```:ansible.cfg
[defaults]
verbosity = 1
stdout_callback = ansible.posix.debug
```

ansible.posix.debug は、ansible-galaxy collection install ansible.posix にて追加導入



# Playbook実行

## 初回
```
$ ansible-playbook site.yml
Using /home/vagrant/repo/terraform-collection-test/ansible.cfg as config file
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
[WARNING]: An error occurred while calling ansible.utils.display.initialize_locale (unsupported locale setting). This may result in incorrectly calculated text widths that can cause Display
to print incorrect line lengths

PLAY [localhost] *****************************************************************************************************************************************************************************

TASK [Basic deploy of a service] *************************************************************************************************************************************************************
changed: [localhost] => {
    "changed": true,
    "command": "/usr/bin/terraform apply -no-color -input=false -auto-approve -lock=true /tmp/tmph6h9tryt.tfplan",
    "outputs": {
        "test": {
            "sensitive": false,
            "type": "string",
            "value": "Hello Terraform!"
        }
    },
    "state": "present",
    "workspace": "default"
}

STDOUT:


Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

test = "Hello Terraform!"


PLAY RECAP ***********************************************************************************************************************************************************************************
localhost                  : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
```

意図通り、outputsが得られた。
/tmp に /tmp/tmph6h9tryt.tfplan など tfplan ファイルが作成されていく。


    ```
$ tree -a .
.
├── ansible.cfg
├── site.yml
└── terraform
    ├── output.tf
    └── terraform.tfstate

1 directory, 5 files
```
今回はバックエンドを構成していないので、terraform.tfstateファイルが作成されている。

```json:./terraform/terraform.tfstate
{
  "version": 4,
  "terraform_version": "1.3.6",
  "serial": 1,
  "lineage": "32d997f1-7e25-9783-9ce9-c7d753ee5086",
  "outputs": {
    "test": {
      "value": "Hello Terraform!",
      "type": "string"
    }
  },
  "resources": [],
  "check_results": null
}
```



## 2回目

```
$ ansible-playbook site.yml
Using /home/vagrant/repo/terraform-collection-test/ansible.cfg as config file
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
[WARNING]: An error occurred while calling ansible.utils.display.initialize_locale (unsupported locale setting). This may result in incorrectly calculated text widths that can cause Display
to print incorrect line lengths

PLAY [localhost] *****************************************************************************************************************************************************************************

TASK [Basic deploy of a service] *************************************************************************************************************************************************************
ok: [localhost] => {
    "changed": false,
    "command": "/usr/bin/terraform apply -no-color -input=false -auto-approve -lock=true /tmp/tmpfnyqh4yc.tfplan",
    "outputs": {
        "test": {
            "sensitive": false,
            "type": "string",
            "value": "Hello Terraform!"
        }
    },
    "state": "present",
    "workspace": "default"
}

STDOUT:


No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration
and found no differences, so no changes are needed.


PLAY RECAP ***********************************************************************************************************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
```

レジストリー変数で取り出せる内容には outputs が含まれるが、変更がないためか、STDOUT には outputs が含まれなかった。
terraform.tfstateを削除して実行すると STDOUT に outputs が表示される。


# 参考: ansible-doc cloud.terraform.terraform

```
$ ansible-doc cloud.terraform.terraform
Using /home/hiro/repo/terraform-ansible/ansible.cfg as config file
> CLOUD.TERRAFORM.TERRAFORM    (/home/hiro/.ansible/collections/ansible_collections/cloud/terraform/plugins/modules/terraform.py)

        Provides support for deploying resources with Terraform and
        pulling resource information back into Ansible.

OPTIONS (= is mandatory):

- backend_config
        A group of key-values to provide at init stage to the
        -backend-config parameter.
        "default": !!null "null"
        "type": "dict"
        added in: version 1.0.0 of cloud.terraform


- backend_config_files
        The path to a configuration file to provide at init state to
        the -backend-config parameter. This can accept a list of paths
        to multiple configuration files.
        "default": !!null "null"
        "elements": "path"
        "type": "list"
        added in: version 1.0.0 of cloud.terraform


- binary_path
        The path of a terraform binary to use.
        "default": !!null "null"
        "type": "path"
        added in: version 1.0.0 of cloud.terraform


- check_destroy
        Apply only when no resources are destroyed. Note that this
        only prevents "destroy" actions, but not "destroy and re-
        create" actions. This option is ignored when `state=absent'.
        "default": !!bool "false"
        "type": "bool"
        added in: version 1.0.0 of cloud.terraform


- complex_vars
        Enable/disable capability to handle complex variable
        structures for `terraform'.
        If `true' the `variables' also accepts dictionaries, lists,
        and booleans to be passed to `terraform'. Strings that are
        passed are correctly quoted.
        When disabled, supports only simple variables (strings,
        integers, and floats), and passes them on unquoted.
        "default": !!bool "false"
        "type": "bool"
        added in: version 1.0.0 of cloud.terraform


- force_init
        To avoid duplicating infra, if a state file can't be found
        this will force a `terraform init'. Generally, this should be
        turned off unless you intend to provision an entirely new
        Terraform deployment.
        "default": !!bool "false"
        "type": "bool"
        added in: version 1.0.0 of cloud.terraform


- init_reconfigure
        Forces backend reconfiguration during init.
        "default": !!bool "false"
        "type": "bool"
        added in: version 1.0.0 of cloud.terraform


- lock
        Enable statefile locking, if you use a service that accepts
        locks (such as S3+DynamoDB) to store your statefile.
        "default": !!bool "true"
        "type": "bool"
        added in: version 1.0.0 of cloud.terraform


- lock_timeout
        How long to maintain the lock on the statefile, if you use a
        service that accepts locks (such as S3+DynamoDB).
        "default": !!null "null"
        "type": "int"
        added in: version 1.0.0 of cloud.terraform


- overwrite_init
        Run init even if `.terraform/terraform.tfstate' already exists
        in `project_path'.
        "default": !!bool "true"
        "type": "bool"
        added in: version 1.0.0 of cloud.terraform


- parallelism
        Restrict concurrent operations when Terraform applies the
        plan.
        "default": !!null "null"
        "type": "int"
        added in: version 1.0.0 of cloud.terraform


- plan_file
        The path to an existing Terraform plan file to apply. If this
        is not specified, Ansible will build a new TF plan and execute
        it.
        Note that this option is required if 'state' has the 'planned'
        value. In this case, the plan file is only generated, but not
        applied.
        "default": !!null "null"
        "type": "path"
        added in: version 1.0.0 of cloud.terraform


- plugin_paths
        List of paths containing Terraform plugin executable files.
        Plugin executables can be downloaded from
        https://releases.hashicorp.com/.
        When set, the plugin discovery and auto-download behavior of
        Terraform is disabled.
        The directory structure in the plugin path can be tricky. The
        Terraform docs
        https://learn.hashicorp.com/tutorials/terraform/automate-
        terraform#pre-installed-plugins show a simple directory of
        files, but actually, the directory structure has to follow the
        same structure you would see if Terraform auto-downloaded the
        plugins. See the examples below for a tree output of an
        example plugin directory.
        "default": !!null "null"
        "elements": "path"
        "type": "list"
        added in: version 1.0.0 of cloud.terraform


= project_path
        The path to the root of the Terraform directory with the
        vars.tf/main.tf/etc to use.
        "type": "path"
        added in: version 1.0.0 of cloud.terraform


- provider_upgrade
        Allows Terraform init to upgrade providers to versions
        specified in the project's version constraints.
        "default": !!bool "false"
        "type": "bool"
        added in: version 1.0.0 of cloud.terraform


- purge_workspace
        Only works with state = absent
        If true, the workspace will be deleted after the "terraform
        destroy" action.
        The 'default' workspace will not be deleted.
        "default": !!bool "false"
        "type": "bool"
        added in: version 1.0.0 of cloud.terraform


- state
        Goal state of given stage/project.
        Option `planned` is deprecated. Its function is equivalent to
        running the module in check mode.
        choices: ["present", "absent", "planned"]
        "default": "present"
        "type": "str"
        added in: version 1.0.0 of cloud.terraform


- state_file
        The path to an existing Terraform state file to use when
        building plan. If this is not specified, the default
        `terraform.tfstate' will be used.
        This option is ignored when plan is specified.
        "default": !!null "null"
        "type": "path"
        added in: version 1.0.0 of cloud.terraform


- targets
        A list of specific resources to target in this
        plan/application. The resources selected here will also auto-
        include any dependencies.
        "default": !!null "null"
        "elements": "str"
        "type": "list"
        added in: version 1.0.0 of cloud.terraform


- variables
        A group of key-values pairs to override template variables or
        those in variables files. By default, only string and number
        values are allowed, which are passed on unquoted.
        Support complex variable structures (lists, dictionaries,
        numbers, and booleans) to reflect terraform variable syntax
        when `complex_vars=true'.
        Ansible integers or floats are mapped to terraform numbers.
        Ansible strings are mapped to terraform strings.
        Ansible dictionaries are mapped to terraform objects.
        Ansible lists are mapped to terraform lists.
        Ansible booleans are mapped to terraform booleans.
        *Note* passwords passed as variables will be visible in the
        log output. Make sure to use `no_log=true' in production!
        "default": !!null "null"
        "type": "dict"
        added in: version 1.0.0 of cloud.terraform


- variables_files
        The path to a variables file for Terraform to fill into the TF
        configurations. This can accept a list of paths to multiple
        variables files.
        Up until Ansible 2.9, this option was usable as
        `variables_file'.
        aliases: ["variables_file"]
        "default": !!null "null"
        "elements": "path"
        "type": "list"
        added in: version 1.0.0 of cloud.terraform


- workspace
        The terraform workspace to work with.
        "default": "default"
        "type": "str"
        added in: version 1.0.0 of cloud.terraform



NOTES:
      * To just run a `terraform plan', use check mode.


REQUIREMENTS:  terraform

AUTHOR: Ryan Scott Brown (@ryansb)

EXAMPLES:

- name: Basic deploy of a service
  cloud.terraform.terraform:
    project_path: '{{ project_dir }}'
    state: present

- name: Define the backend configuration at init
  cloud.terraform.terraform:
    project_path: 'project/'
    state: "{{ state }}"
    force_init: true
    backend_config:
      region: "eu-west-1"
      bucket: "some-bucket"
      key: "random.tfstate"

- name: Define the backend configuration with one or more files at init
  cloud.terraform.terraform:
    project_path: 'project/'
    state: "{{ state }}"
    force_init: true
    backend_config_files:
      - /path/to/backend_config_file_1
      - /path/to/backend_config_file_2

- name: Disable plugin discovery and auto-download by setting plugin_paths
  cloud.terraform.terraform:
    project_path: 'project/'
    state: "{{ state }}"
    force_init: true
    plugin_paths:
      - /path/to/plugins_dir_1
      - /path/to/plugins_dir_2

- name: Complex variables example
  cloud.terraform.terraform:
    project_path: '{{ project_dir }}'
    state: present
    camplex_vars: true
    variables:
      vm_name: "{{ inventory_hostname }}"
      vm_vcpus: 2
      vm_mem: 2048
      vm_additional_disks:
        - label: "Third Disk"
          size: 40
          thin_provisioned: true
          unit_number: 2
        - label: "Fourth Disk"
          size: 22
          thin_provisioned: true
          unit_number: 3
    force_init: true

### Example directory structure for plugin_paths example
# $ tree /path/to/plugins_dir_1
# /path/to/plugins_dir_1/
# └── registry.terraform.io
#     └── hashicorp
#         └── vsphere
#             ├── 1.24.0
#             │   └── linux_amd64
#             │       └── terraform-provider-vsphere_v1.24.0_x4
#             └── 1.26.0
#                 └── linux_amd64
#                     └── terraform-provider-vsphere_v1.26.0_x4


RETURN VALUES:
- command
        Full `terraform' command built by this module, in case you
        want to re-run the command outside the module or debug a
        problem.
        "returned": "always"
        "sample": "terraform apply ..."
        "type": "str"

- outputs
        A dictionary of all the TF outputs by their assigned name. Use
        `.outputs.MyOutputName.value' to access the value.
        "returned": "on success"
        "sample": "{\"bukkit_arn\": {\"sensitive\": false, \"type\": \"string\", \"value\"\
          : \"arn:aws:s3:::tf-test-bukkit\"}"
        "type": "complex"

        CONTAINS:

        - sensitive
            Whether Terraform has marked this value as sensitive
            "returned": "always"
            "type": "bool"

        - type
            The type of the value (string, int, etc)
            "returned": "always"
            "type": "str"

        - value
            The value of the output as interpolated by Terraform
            "returned": "always"
            "type": "str"

- stdout
        Full `terraform' command stdout, in case you want to display
        it or examine the event log
        "returned": "always"
        "sample": ""
        "type": "str"

```