Skip to content

Commit

Permalink
Add salt backend
Browse files Browse the repository at this point in the history
  • Loading branch information
philpep committed May 17, 2015
1 parent 6776d8a commit 1fcf076
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 4 deletions.
2 changes: 2 additions & 0 deletions testinfra/backend/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

from testinfra.backend import local
from testinfra.backend import paramiko
from testinfra.backend import salt
from testinfra.backend import ssh


Expand All @@ -27,6 +28,7 @@ def get_backend(backend_type, *args, **kwargs):
"ssh": ssh.SshBackend,
"safe_ssh": ssh.SafeSshBackend,
"paramiko": paramiko.ParamikoBakend,
"salt": salt.SaltBackend,
}[backend_type]
except KeyError:
raise RuntimeError("Unknown backend '%s'" % (backend_type,))
Expand Down
3 changes: 2 additions & 1 deletion testinfra/backend/paramiko.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@

try:
import paramiko
HAS_PARAMIKO = True
except ImportError:
HAS_PARAMIKO = False
else:
HAS_PARAMIKO = True

logger = logging.getLogger("testinfra.backend")

Expand Down
51 changes: 51 additions & 0 deletions testinfra/backend/salt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# -*- coding: utf8 -*-
# Copyright © 2015 Philippe Pepiot
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import unicode_literals
from __future__ import absolute_import

from testinfra.backend import base

try:
import salt.client
except ImportError:
HAS_SALT = False
else:
HAS_SALT = True


class SaltBackend(base.BaseBackend):

def __init__(self, host, *args, **kwargs):
self.host = host
self._client = None
super(SaltBackend, self).__init__(*args, **kwargs)

@property
def client(self):
if self._client is None:
if not HAS_SALT:
raise RuntimeError(
"You must install salt package to use the salt backend")
self._client = salt.client.LocalClient()
return self._client

def run(self, command, *args):
command = self.quote(command, *args)
out = self.client.cmd(
self.host, 'cmd.run_all', [command],
)[self.host]
return base.CommandResult(
out['retcode'], out['stdout'], out['stderr'], command)
8 changes: 5 additions & 3 deletions testinfra/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@
def testinfra_backend(pytestconfig, _testinfra_host):
if _testinfra_host is not None:
backend_type = pytestconfig.option.connection or "paramiko"
kwargs = {}
if pytestconfig.option.ssh_config is not None:
kwargs["ssh_config"] = pytestconfig.option.ssh_config
testinfra.set_backend(
backend_type,
_testinfra_host,
ssh_config=pytestconfig.option.ssh_config,
)
**kwargs)
else:
testinfra.set_backend("local")

Expand All @@ -49,7 +51,7 @@ def pytest_addoption(parser):
"--connection",
action="store",
dest="connection",
help="Remote connection backend ssh|paramiko|safe_ssh",
help="Remote connection backend paramiko|ssh|safe_ssh|salt",
)
group._addoption(
"--hosts",
Expand Down

8 comments on commit 1fcf076

@daenney
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome. Do you have an example on how to use Salt and a use case in which it makes sense?

@philpep
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, the salt integration is a work in progress (no docs yet).
The salt backend allow testinfra to use salt communication channel to run commands on remote hosts (minions). This have to be run from the salt master, for example:

testinfra --connection=salt --hosts=minion1,minion2 test_file.py

Next I'm planning a Salt fixture (and maybe a SaltGrains too) that expose all the salt modules in testinfra, the syntax of the test file will be something like:

assert Salt("pkg.version", "nginx") == "1.6.2-5"

Under the hood testinfra will call the salt api (or run salt-call if not running under the salt backend).

The idea of salt integration in testinfra is that it is the most convenient way to write tests for salt based deployments and salt has a full and better os support than testinfra will never have :)

@daenney
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand. Looks very promising 😄.

@daenney
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you given any thought to using/supporting Ansible for this too?

@philpep
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes,. But the integration may be more difficult because most of ansible modules DO things and does not expose the current state. But at least the connection module (and configuration), the "setup" module and the check mode may be convenient to write testinfra based tests.

Do you known if puppet or chef have an API (at least CLI based), to get the actual state of servers ? Any integration with infrastructure tools is good to have :)

@daenney
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Puppet does. The Resource Abstraction Layer is accessible through puppet resource <type> <name> so you can do things like puppet resource package apt which will return this:

package { 'apt':
  ensure => '1.0.1ubuntu2',
}

Unfortunately it doesn't support a --yaml or --json switch like Facter does so we'd have to parse the output a bit. It's easy enough to do, just a bit more work than json.loads().

@philpep
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I've just installed puppet and run some puppet resource commands, looks very usefull to have as a "testinfra fixture" (and facter too).

@daenney
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Facter is very easy, all it takes is json.loads(subprocess.check_output(['facter', '--json']))

Please sign in to comment.