ansible / terraform-provider-ansible

community terraform provider for ansible
https://registry.terraform.io/providers/ansible/ansible/latest
GNU General Public License v3.0
195 stars 43 forks source link

ansible_playbook not using ansible_user and ansible_host variables from ansible_host resource ? #37

Open jtognazzi opened 1 year ago

jtognazzi commented 1 year ago

I'm probably doing something wrong, but I'm currently struggling with the following case: I'm provisionning a VM from a template with terraform, the resulting VM is not known to the DNS and the IP is assigned dynamically via DHCP.

It seems to me that the below snippet does not work and the resource ansible_playbook exits with error code 4 because it does not use the provided ansible_user ans ansible_host

resource "vsphere_virtual_machine" "vm" {
  # Classic provisioning from template
}

resource "ansible_host" "myserver" {
  name                    = var.vm_hostname
  groups                  = ["dockervms"]
  variables = {
    ansible_user = "vagrant"
    ansible_host = vsphere_virtual_machine.vm.default_ip_address
  }
}

resource "ansible_playbook" "test" {
  playbook   = "test.yml"

  name       = var.vm_hostname
  check_mode = true
  replayable = true
  verbosity = 3
  ignore_playbook_failure = true
}

and this is the result from the tfstate file ansible_playbook_stdout

PLAYBOOK: test.yml ***************************************************
1 plays in test.yml
PLAY [Terraform debugging] *****************************************************
TASK [Gathering Facts] *********************************************************
...
[VTEZOL999] ESTABLISH SSH CONNECTION FOR USER: None
[VTEZOL999] SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o 'ControlPath=\"/home/user/.ansible/cp/6930ca647f\"' VTEZOL999 '/bin/sh -c '\"'\"'echo ~ \u0026\u0026 sleep 0'\"'\"''\u003cVTEZOL999\u003e (255, b'', b'ssh: Could not resolve hostname vtezol999: Name or service not known\\r\')
fatal: [VTEZOL999]: UNREACHABLE! =\u003e {    \"changed\": false,    \"msg\": \"Failed to connect to the host via ssh: ssh: Could not resolve hostname vtezol999: Name or service not known\",    \"unreachable\": true

For info, if I create an inventory file manually with:

---
plugin: cloud.terraform.terraform_provider

and run the playbook manually with it, it works.

but if I use the inventory file created dynamically in /tmp/.inventory-xxxx, which looks like this

[default]
VTEZOL999

I have the same error than with terraform.

Is it a bug ? Or am I doing something wrong ?

PS: are the GH issues the right channel for such user issue ?

fbjerggaard commented 1 year ago

It seems like the variables from the host resource is not used at all anywhere, which is a major bug if true

anazobec commented 1 year ago

HI, I've looked into your issue.To use ansible_host and ansible_playbook resources, you must manually create an inventory file with:

---
plugin: cloud.terraform.terraform_provider

This is the intended way if you wish to use those two resources together. The same goes for ansible_group + ansible_playbook. This is because those are separate resources. ansible_host and ansible_group are used with that terraform_provider plugin, while ansible_playbook is not. ansible_playbook, from provided hostname (name) creates its own basic temporary inventory file, with (in your case):

[default]
hostname_you_provided

If you were also to define groups in ansible_playbook resources, for example ["example_group"], that temporary inventory would look like this:

[example_group]
hostname_you_provided

And tihs is the inventory your playbook uses in ansible_playbook resource and doesn't need to be explicitly provided like you would have to for a custom inventory, such as the one you had to make manually. In this case, what you have to do is, use that manually created inventory file (let's say you named it inventory.yml) and pass it to your playbook using extra_vars. You terraform script would then look like this:

resource "vsphere_virtual_machine" "vm" {
  # Classic provisioning from template
}

resource "ansible_host" "myserver" {
  name                    = var.vm_hostname
  groups                  = ["dockervms"]
  variables = {
    ansible_user = "vagrant"
    ansible_host = vsphere_virtual_machine.vm.default_ip_address
  }
}

resource "ansible_playbook" "test" {
  playbook   = "test.yml"

  name       = var.vm_hostname  # <- keep this, since it's a required parameter
  check_mode = true
  replayable = true

  # --- the code to add
  extra_vars {
    ansible_host = var.vm_hostname 
    inventory_file = "path/to/inventory.yml"
  }
  # --- 

  verbosity = 3
  ignore_playbook_failure = true
}

In case you're unsure where I got the variables I put in the extra_vars, you can find info about this here.

luckpng commented 1 year ago

Hello!

I'm facing the same issue, but seems that when Ansible don't get the ansible_user var instead it defines the user as None as it follows:

PLAY [Install SQL Server Developer] ********************************************

        TASK [Adicionar chave SSH] *****************************************************
        task path: <path>/playbook.yml:10
        File lookup using <path>/.ssh/id_rsa.pub as file
        redirecting (type: modules) ansible.builtin.authorized_key to ansible.posix.authorized_key
        <> ESTABLISH SSH CONNECTION FOR USER: None
        <> SSH: ansible.cfg set ssh_args: (-C)(-o)(ControlMaster=auto)(-o)(ControlPersist=60s)
        <> SSH: ansible_password/ansible_ssh_password not set: (-o)(KbdInteractiveAuthentication=no)(-o)(PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey)(-o)(PasswordAuthentication=no)
        <> SSH: ANSIBLE_TIMEOUT/timeout set: (-o)(ConnectTimeout=10)
        <> SSH: Set ssh_common_args: ()
        <> SSH: Set ssh_extra_args: ()
        <> SSH: found only ControlPersist; added ControlPath: (-o)(ControlPath="/home/luckjpg/.ansible/cp/ed48d91177")
        <> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o 'ControlPath="/home/luckjpg/.ansible/cp/ed48d91177"' <IP>'/bin/sh -c '"'"'echo ~ && sleep 0'"'"''
        <>'', b'OpenSSH_8.9p1 Ubuntu-3, OpenSSL 3.0.2 15 Mar 2022\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug1: /etc/ssh/ssh_config line 19: include /etc/ssh/ssh_config.d/*.conf matched no files\r\ndebug1: /etc/ssh/ssh_config line 21: Applying options for *\r\ndebug2: resolve_canonicalize: hostname <IP> is address\r\ndebug3: expanded UserKnownHostsFile \'~/.ssh/known_hosts\' -> \'/home/luckjpg/.ssh/known_hosts\'\r\ndebug3: expanded UserKnownHostsFile \'~/.ssh/known_hosts2\' -> \'/home/luckjpg/.ssh/known_hosts2\'\r\ndebug1: auto-mux: Trying existing master\r\ndebug1: Control socket "/home/luckjpg/.ansible/cp/ed48d91177" does not exist\r\ndebug3: ssh_connect_direct: entering\r\ndebug1: Connecting to <IP> port 22.\r\ndebug3: set_sock_tos: set socket 3 IP_TOS 0x10\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug1: connect to address <IP> port 22: Connection refused\r\nssh: connect to host <IP> port 22: Connection refused\r\n')       
        fatal: [IP]: UNREACHABLE! => {
            "changed": false,
            "msg": "Failed to connect to the host via ssh: OpenSSH_8.9p1 Ubuntu-3, OpenSSL 3.0.2 15 Mar 2022\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug1: /etc/ssh/ssh_config line 19: include /etc/ssh/ssh_config.d/*.conf matched no files\r\ndebug1: /etc/ssh/ssh_config line 21: Applying options for *\r\ndebug2: resolve_canonicalize: hostname <IP> is address\r\ndebug3: expanded UserKnownHostsFile '~/.ssh/known_hosts' -> '/home/luckjpg/.ssh/known_hosts'\r\ndebug3: expanded UserKnownHostsFile '~/.ssh/known_hosts2' -> '/home/luckjpg/.ssh/known_hosts2'\r\ndebug1: auto-mux: Trying existing master\r\ndebug1: Control socket \"/home/luckjpg/.ansible/cp/ed48d91177\" does not exist\r\ndebug3: ssh_connect_direct: entering\r\ndebug1: Connecting to <IP>] port 22.\r\ndebug3: set_sock_tos: set socket 3 IP_TOS 0x10\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug1: connect to address <IP> port 22: Connection refused\r\nssh: connect to host <IP> port 22: Connection refused",
            "unreachable": true

Infos:

This is my main.tf:

terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "4.72.1"
    }
    ansible = {
      source = "ansible/ansible"
      version = "1.1.0"
    }
  }
}

provider "google" {
  credentials = file(var.google_credentials_path)
  project     = var.google_project
  region      = var.google_region

  }

resource "google_compute_instance" "vm_instance" {
  name         = var.instance_name
  machine_type = var.machine_type
  zone         = var.zone
  tags         = var.instance_tags

  boot_disk {
    initialize_params {
      image = "ubuntu-os-cloud/ubuntu-2004-lts"
    }
  }

  network_interface {
    network = "default"
    access_config {}
  }

  metadata = {
    ssh-keys = "${var.user}:${file(var.public_key_path)}"
  }

}

resource "ansible_host" "sql" {
  name   = google_compute_instance.vm_instance.network_interface[0].access_config[0].nat_ip
  groups = ["databases"]
  variables = {
    ansible_user                 = "lucas.matheus",
    ansible_ssh_private_key_file = "~/.ssh/id_rsa",
    ansible_python_interpreter   = "/usr/bin/python3"
  }
}

resource "ansible_playbook" "playbook" {
  playbook   = "playbook.yml"
  name       = google_compute_instance.vm_instance.network_interface[0].access_config[0].nat_ip
  replayable = false
  verbosity = "6"

  check_mode = true

  ignore_playbook_failure = true

  extra_vars = {
    host_ip = google_compute_instance.vm_instance.network_interface[0].access_config[0].nat_ip
    ansible_host = google_compute_instance.vm_instance.network_interface[0].access_config[0].nat_ip
    inventory_file = "inventory.yml"
  }

}
rubencosta commented 1 year ago

Due to this limitation and others I ended up forking this repo. If you're just looking for a way to run ansible playbook from terraform check out https://registry.terraform.io/providers/rubencosta/ansible/

iambryancs commented 11 months ago

Hi @anazobec, I tried https://github.com/ansible/terraform-provider-ansible/issues/37#issuecomment-1628405287 but can't seem to get it working.

Using AWS EC2 instance, S3 tf backend and ff config:

resource "ansible_playbook" "provision_instance" { playbook = "playbook.yml"

Inventory configuration

Name of the host to use for inventory configuration

name = module.ec2_spot_instance.public_dns groups = ["default"]

check_mode = true

Connection configuration and other vars

extra_vars = { ansible_hostname = module.ec2_spot_instance.public_dns inventory_file = "inventory.yml" }

replayable = true

set the verbosity level of the debug output for this playbook

verbosity = 3 ignore_playbook_failure = true }

- `inventory.yml`
```yaml
---
plugin: cloud.terraform.terraform_provider
state_file: ""

I always get this:

...
host_list declined parsing /tmp/.inventory-268034303.ini as it did not pass its verify_file() method
script declined parsing /tmp/.inventory-268034303.ini as it did not pass its verify_file() method
auto declined parsing /tmp/.inventory-268034303.ini as it did not pass its verify_file() method
yaml declined parsing /tmp/.inventory-268034303.ini as it did not pass its verify_file() method
Parsed /tmp/.inventory-268034303.ini inventory source with ini plugin
Skipping callback 'default', as we already have a stdout callback.
Skipping callback 'minimal', as we already have a stdout callback.
Skipping callback 'oneline', as we already have a stdout callback.

PLAYBOOK: playbook.yml *********************************************************
1 plays in playbook.yml
Read vars_file 'var-file.yml'
Read vars_file 'var-file.yml'
Read vars_file 'var-file.yml'

PLAY [Build new release] *******************************************************
Read vars_file 'var-file.yml'

TASK [Gathering Facts] *********************************************************
task path: /home/bryan/test-project/playbook.yml:2
<ec2-xx-xx-xx-xx.ap-southeast-2.compute.amazonaws.com> ESTABLISH SSH CONNECTION FOR USER: None
<ec2-xx-xx-xx-xx.ap-southeast-2.compute.amazonaws.com> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o 'ControlPath="/home/bryancs/.ansible/cp/5f9e4b109b"' ec2-xx-xx-xx-xx.ap-southeast-2.compute.amazonaws.com '/bin/sh -c '"'"'echo ~ && sleep 0'"'"''
<ec2-xx-xx-xx-xx.ap-southeast-2.compute.amazonaws.com> (255, b'', b'bryan@ec2-xx-xx-xx-xx.ap-southeast-2.compute.amazonaws.com: Permission denied (publickey).\r\n')
fatal: [ec2-xx-xx-xx-xx.ap-southeast-2.compute.amazonaws.com]: UNREACHABLE! => {
    "changed": false,
    "msg": "Failed to connect to the host via ssh: bryan@ec2-xx-xx-xx-xx.ap-southeast-2.compute.amazonaws.com: Permission denied (publickey).",
    "unreachable": true
}

PLAY RECAP *********************************************************************
ec2-xx-xx-xx-xx.ap-southeast-2.compute.amazonaws.com : ok=0    changed=0    unreachable=1    failed=0    skipped=0    rescued=0    ignored=0

Also, I'm wondering why the aws example from the example directory does not use ansible_playbook but instead invoke ansible-playbook directly in a script. It would be great to have an example that uses ansible_playbook directly within terraform.

jwhite-ac commented 5 months ago

The reason the suggestion in https://github.com/ansible/terraform-provider-ansible/issues/37#issuecomment-1628405287 doesn't work is because inventory_file is not a variable that can be set by the user, as the link to the Ansible docs in the comment states.

It seems that it is impossible to use ansible_host and ansible_group with ansible_playbook, which is a major drawback to this provider. In my use case I am attempting to call a playbook from an internal collection for setting DNS records for the newly created VM, unfortunately becuase I need to set ansible_host for the new VM (because it is not in DNS yet), that then overrides ansible_host for all hosts, including a windows host that is set using add_host in the collection's playbook.

I feel that this provider was released as v1 a bit too prematurely, as there are just too many pieces missing to make it useful yet.

dasra commented 1 month ago

Any updates on this one. I am also exactly stucked on this step where ansible playbook runs agains the groups but ends up with no ssh connection.