Repository: ansible/ansible

Issue:
eos eapi connection issue
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
`lib/ansible/plugins/action/eos.py`

##### ANSIBLE VERSION
```
ansible 2.3.0 (devel a60d358e56) last updated 2017/03/02 10:38:17 (GMT +100)
  config file = /Users/lynchg/git/arista/veos/ansible.cfg
  configured module search path = Default w/o overrides
```
also affects 2.2.

##### CONFIGURATION

##### OS / ENVIRONMENT
Mac OS Yosemite
Vagrant
Virtualbox

##### SUMMARY
The action plugin fails to correctly handle EAPI SSL settings

##### STEPS TO REPRODUCE

- Create a Vagrantfile

```
# -*- mode: ruby -*-
# vi: set ft=ruby :

# Specify minimum Vagrant version and Vagrant API version
Vagrant.require_version ">= 1.6.0"
VAGRANTFILE_API_VER = "2"

# Plugins we require
#required_plugins = %w(vai vagrant-vbguest vagrant-host-shell)
required_plugins = %w(vai)

##### START Helper functions
def install_ssh_key()
  puts "Adding ssh key to the ssh agent"
  puts "ssh-add #{Vagrant.source_root}/keys/vagrant"
  system "ssh-add #{Vagrant.source_root}/keys/vagrant"
end

def install_plugins(required_plugins)
  plugins_to_install = required_plugins.select { |plugin| not Vagrant.has_plugin? plugin }
  if not plugins_to_install.empty?
    puts "Installing plugins: #{plugins_to_install.join(' ')}"
    if system "vagrant plugin install #{plugins_to_install.join(' ')}"
      exec "vagrant #{ARGV.join(' ')}"
    else
      abort "Installation of one or more plugins has failed. Aborting."
    end
  end
end
##### END Helper functions

# Install ssh key
#
# Uncomment the next line if you're using ssh-agent
# install_ssh_key

# Check certain plugins are installed
install_plugins required_plugins

# Require YAML module
require 'yaml'

# Read YAML file with box details
vagrant_root = File.dirname(__FILE__)
topology = YAML.load_file(vagrant_root + '/topology.yml')

# Constants
RAM  = 2048
CPUS = 2

Vagrant.configure(VAGRANTFILE_API_VER) do |config|
  # config.vbguest.auto_update = false

  # Iterate through entries in YAML file
  topology['hosts'].each do |host|
    config.vm.define host["name"] do |srv|
      srv.vm.box = host["box"]

      srv.ssh.insert_key = false

      # Disable synced folders
      srv.vm.synced_folder ".", "/vagrant", disabled: true

      srv.vm.provider "virtualbox" do |v, override|
        # uncomment to set the name in virtualbox
        #v.name = host['name']
        v.memory = host['ram'] || RAM
        v.cpus = host['cpus'] || CPUS
      end

      if host.key?("forwarded_ports")
        host["forwarded_ports"].each do |port|
          srv.vm.network :forwarded_port, guest: port["guest"], host: port["host"], id: port["name"]
        end
      end

      if host.key?("links")
        host["links"].each do |link|
          ipaddr = "169.254.1.11"
          srv.vm.network "private_network", virtualbox__intnet: link["name"], ip: ipaddr, auto_config: false
        end
      end

      # Configure hostname, eAPI and Management interface
      $script = <<-SCRIPT
        FastCli -p 15 -c "configure
        hostname $1
        username eapiuser privilege 15 role network-admin secret icanttellyou
        management api http-commands
          no shutdown
          protocol https
          protocol http
          exit
        interface Management1
          ip address $2 secondary
        end
        copy running-config startup-config"
      SCRIPT

      srv.vm.provision "shell" do |s|
        s.inline = $script
        s.args = [host["name"], host['management']]
      end

    end
  end

  # Use the vai plugin to make inventory group_var management
  # massively easier.  Note that the vagrant ordering is outer=>in, so this will
  # run _before_ the internal shell / ansible provision
  config.vm.provision "vai" do |ansible|
    ansible.inventory_dir = 'inventory/vagrant'
    ansible.groups = topology['groups']
  end
end
```

- Create a topology.yml file

```
hosts:
  - name: leaf-1
    box: vEOS-lab-4.18.0
    management: 192.168.56.103/24
    forwarded_ports: 
      - name: http
        guest: 80
        host: 8080
      - name: https
        guest: 443
        host: 8443
    links:
      - name: L1L2
      - name: S1L1
      - name: S2L1

```

- Create a play.yml file

```
- hosts: leaf-1
  connection: local
  gather_facts: no
  vars:
    eos_connection:
      host: "{{ ansible_ssh_host }}"
      port: 8080
      username: "eapiuser"
      password: "icanttellyou"
      transport: eapi
      use_ssl: True
      validate_certs: False

  tasks:
    - eos_command:
        commands: 'show inventory'
        provider: '{{ eos_connection }}'
      register: result

    - debug: var=result
```

- Run vagrant up

```
vagrant up leaf-1
```

- Try and run the playbook

```
ansible-playbook play.yml -vvv
```

##### EXPECTED RESULTS

In the connection details, I should see `'use_ssl': True`, and `'validate_certs': False`

##### ACTUAL RESULTS

They are unchanged.

```
ok: [leaf-1] => {
    "changed": false,
    "invocation": {
        "module_args": {
            "auth_pass": null,
            "authorize": false,
            "commands": [
                "show inventory"
            ],
            "host": "127.0.0.1",
            "interval": 1,
            "match": "all",
            "password": "icanttellyou",
            "port": 8080,
            "provider": {
                "auth_pass": null,
                "authorize": false,
                "host": "127.0.0.1",
                "password": "icanttellyou",
                "port": 8080,
                "timeout": 10,
                "transport": "eapi",
                "use_ssl": false,
                "username": "eapiuser",
                "validate_certs": true
            },
            "retries": 10,
            "ssh_keyfile": null,
            "timeout": 10,
            "transport": "eapi",
            "url_password": "icanttellyou",
            "url_username": "eapiuser",
            "use_ssl": false,
            "username": "eapiuser",
            "validate_certs": true,
            "wait_for": null
        }
    },
    ...
```

This appears to be related to these 2 lines - https://github.com/ansible/ansible/blob/devel/lib/ansible/plugins/action/eos.py#L98-L99

Which I believe should instead be

```
                'use_ssl': provider.get('use_ssl') or False,
                'validate_certs': provider.get('validate_certs') or True
```


Discussion:
Note that validate_certs is also undocumented, and doesn't appear to get overridden even with my above suggested change.  For example, if I change the `or True` to `or False` then has effect. i.e.

```
                'validate_certs': provider.get('validate_certs') or False
```

So I'm not sure how validate_certs is being passed or working.  My suggested change above for `use_ssl` does work from what I can tell.
cc @privateip 

pinging you because you appear to be the main contributor for this code.
@halberom Thank you for the analysis and repo steps, looks like you are right.

Adding to the 2.3 milestone

Resolving patch:
diff --git a/lib/ansible/plugins/action/eos.py b/lib/ansible/plugins/action/eos.py
--- a/lib/ansible/plugins/action/eos.py
+++ b/lib/ansible/plugins/action/eos.py
@@ -86,19 +86,33 @@ def run(self, tmp=None, task_vars=None):
             task_vars['ansible_socket'] = socket_path
 
         else:
-            provider_arg = {
-                'transport': 'eapi',
-                'host': provider.get('host') or self._play_context.remote_addr,
-                'port': provider.get('port'),
-                'username': provider.get('username') or self._play_context.connection_user,
-                'password': provider.get('password') or self._play_context.password,
-                'authorize': provider.get('authorize') or False,
-                'auth_pass': provider.get('auth_pass'),
-                'timeout': provider.get('timeout') or self._play_context.timeout,
-                'use_ssl': task_vars.get('eapi_use_ssl') or False,
-                'validate_certs': task_vars.get('eapi_validate_certs') or True
-            }
-            self._task.args['provider'] = provider_arg
+            provider['transport'] = 'eapi'
+
+            if provider.get('host') is None:
+                provider['host'] = self._play_context.remote_addr
+
+            if provider.get('port') is None:
+                provider['port'] = 443
+
+            if provider.get('timeout') is None:
+                provider['timeout'] = self._play_context.timeout
+
+            if provider.get('username') is None:
+                provider['username'] = self._play_context.connection_user
+
+            if provider.get('password') is None:
+                provider['password'] = self._play_context.password
+
+            if provider.get('authorize') is None:
+                provider['authorize'] = False
+
+            if provider.get('use_ssl') is None:
+                provider['use_ssl'] = True
+
+            if provider.get('validate_certs') is None:
+                provider['validate_certs'] = True
+
+            self._task.args['provider'] = provider
 
         if self._play_context.become_method == 'enable':
             self._play_context.become = False


Test patch: