salt/tests/unit/config/schemas/test_ssh.py
rallytime 7dc1e770c4
Merge branch '2018.3' into 'develop'
Conflicts:
  - doc/topics/installation/freebsd.rst
  - salt/cloud/clouds/oneandone.py
  - salt/grains/core.py
  - salt/modules/napalm_ntp.py
  - salt/modules/win_update.py
  - salt/modules/win_wua.py
  - salt/modules/zabbix.py
  - salt/renderers/pass.py
  - salt/states/vault.py
  - tests/unit/states/test_win_update.py
2018-06-06 13:35:36 -04:00

318 lines
11 KiB
Python

# -*- coding: utf-8 -*-
'''
:codeauthor: Pedro Algarvio (pedro@algarvio.me)
tests.unit.config.schemas.test_ssh
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'''
# Import python libs
from __future__ import absolute_import, print_function, unicode_literals
# Import Salt Testing Libs
from tests.support.unit import TestCase, skipIf
# Import Salt Libs
from salt.config.schemas import ssh as ssh_schemas
from salt.config.schemas.minion import MinionConfiguration
import salt.utils.stringutils
from salt.utils.versions import LooseVersion as _LooseVersion
# Import 3rd-party libs
try:
import jsonschema
import jsonschema.exceptions
HAS_JSONSCHEMA = True
JSONSCHEMA_VERSION = _LooseVersion(jsonschema.__version__)
except ImportError:
HAS_JSONSCHEMA = False
JSONSCHEMA_VERSION = _LooseVersion('0')
class RosterEntryConfigTest(TestCase):
def test_config(self):
config = ssh_schemas.RosterEntryConfig()
expected = {
'$schema': 'http://json-schema.org/draft-04/schema#',
'title': 'Roster Entry',
'description': 'Salt SSH roster entry definition',
'type': 'object',
'properties': {
'host': {
'title': 'Host',
'description': 'The IP address or DNS name of the remote host',
'type': 'string',
'pattern': r'^((\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|([A-Za-z0-9][A-Za-z0-9\.\-]{1,255}))$',
'minLength': 1
},
'port': {
'description': 'The target system\'s ssh port number',
'title': 'Port',
'default': 22,
'maximum': 65535,
'minimum': 0,
'type': 'integer'
},
'user': {
'default': 'root',
'type': 'string',
'description': 'The user to log in as. Defaults to root',
'title': 'User',
'minLength': 1
},
'passwd': {
'title': 'Password',
'type': 'string',
'description': 'The password to log in with',
'format': 'secret',
'minLength': 1
},
'priv': {
'type': 'string',
'description': 'File path to ssh private key, defaults to salt-ssh.rsa',
'title': 'Private Key',
'minLength': 1
},
'priv_passwd': {
'type': 'string',
'description': 'Passphrase for private key file',
'title': 'Private Key passphrase',
'format': 'secret',
'minLength': 1,
},
'sudo': {
'default': False,
'type': 'boolean',
'description': 'run command via sudo. Defaults to False',
'title': 'Sudo'
},
'timeout': {
'type': 'integer',
'description': 'Number of seconds to wait for response when establishing an SSH connection',
'title': 'Timeout'
},
'thin_dir': {
'type': 'string',
'description': 'The target system\'s storage directory for Salt components. Defaults to /tmp/salt-<hash>.',
'title': 'Thin Directory'
},
# The actuall representation of the minion options would make this HUGE!
'minion_opts': ssh_schemas.DictItem(title='Minion Options',
description='Dictionary of minion options',
properties=MinionConfiguration()).serialize(),
},
'anyOf': [
{
'required': [
'passwd'
]
},
{
'required': [
'priv'
]
}
],
'required': [
'host',
'user',
],
'x-ordering': [
'host',
'port',
'user',
'passwd',
'priv',
'priv_passwd',
'sudo',
'timeout',
'thin_dir',
'minion_opts'
],
'additionalProperties': False
}
try:
self.assertDictContainsSubset(expected['properties'], config.serialize()['properties'])
self.assertDictContainsSubset(expected, config.serialize())
except AssertionError:
import salt.utils.json
print(salt.utils.json.dumps(config.serialize(), indent=4))
raise
@skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
def test_config_validate(self):
try:
jsonschema.validate(
{
'host': 'localhost',
'user': 'root',
'passwd': 'foo'
},
ssh_schemas.RosterEntryConfig.serialize(),
format_checker=jsonschema.FormatChecker()
)
except jsonschema.exceptions.ValidationError as exc:
self.fail('ValidationError raised: {0}'.format(exc))
try:
jsonschema.validate(
{
'host': '127.0.0.1',
'user': 'root',
'passwd': 'foo'
},
ssh_schemas.RosterEntryConfig.serialize(),
format_checker=jsonschema.FormatChecker()
)
except jsonschema.exceptions.ValidationError as exc:
self.fail('ValidationError raised: {0}'.format(exc))
try:
jsonschema.validate(
{
'host': '127.1.0.1',
'user': 'root',
'priv': 'foo',
'passwd': 'foo'
},
ssh_schemas.RosterEntryConfig.serialize(),
format_checker=jsonschema.FormatChecker()
)
except jsonschema.exceptions.ValidationError as exc:
self.fail('ValidationError raised: {0}'.format(exc))
try:
jsonschema.validate(
{
'host': '127.1.0.1',
'user': 'root',
'passwd': 'foo',
'sudo': False
},
ssh_schemas.RosterEntryConfig.serialize(),
format_checker=jsonschema.FormatChecker()
)
except jsonschema.exceptions.ValidationError as exc:
self.fail('ValidationError raised: {0}'.format(exc))
try:
jsonschema.validate(
{
'host': '127.1.0.1',
'user': 'root',
'priv': 'foo',
'passwd': 'foo',
'thin_dir': '/foo/bar'
},
ssh_schemas.RosterEntryConfig.serialize(),
format_checker=jsonschema.FormatChecker()
)
except jsonschema.exceptions.ValidationError as exc:
self.fail('ValidationError raised: {0}'.format(exc))
try:
jsonschema.validate(
{
'host': '127.1.0.1',
'user': 'root',
'passwd': 'foo',
'minion_opts': {
'interface': '0.0.0.0'
}
},
ssh_schemas.RosterEntryConfig.serialize(),
format_checker=jsonschema.FormatChecker()
)
except jsonschema.exceptions.ValidationError as exc:
self.fail('ValidationError raised: {0}'.format(exc))
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
jsonschema.validate(
{
'host': '127.1.0.1',
'user': '',
'passwd': 'foo',
},
ssh_schemas.RosterEntryConfig.serialize(),
format_checker=jsonschema.FormatChecker()
)
self.assertIn('is too short', excinfo.exception.message)
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
jsonschema.validate(
{
'host': '127.1.0.1',
'user': 'root',
'passwd': 'foo',
'minion_opts': {
'interface': 0
}
},
ssh_schemas.RosterEntryConfig.serialize(),
format_checker=jsonschema.FormatChecker()
)
self.assertIn('is not of type', excinfo.exception.message)
class RosterItemTest(TestCase):
def test_roster_config(self):
try:
self.assertDictContainsSubset(
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Roster Configuration",
"description": "Roster entries definition",
"type": "object",
"patternProperties": {
r"^([^:]+)$": ssh_schemas.RosterEntryConfig.serialize()
},
"additionalProperties": False
},
ssh_schemas.RosterItem.serialize()
)
except AssertionError:
import salt.utils.json
print(salt.utils.json.dumps(ssh_schemas.RosterItem.serialize(), indent=4))
raise
@skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
def test_roster_config_validate(self):
try:
jsonschema.validate(
{'target-1':
{
'host': 'localhost',
'user': 'root',
'passwd': 'foo'
}
},
ssh_schemas.RosterItem.serialize(),
format_checker=jsonschema.FormatChecker()
)
except jsonschema.exceptions.ValidationError as exc:
self.fail('ValidationError raised: {0}'.format(exc))
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
jsonschema.validate(
{salt.utils.stringutils.to_str('target-1:1'):
{
'host': 'localhost',
'user': 'root',
'passwd': 'foo'
}
},
ssh_schemas.RosterItem.serialize(),
format_checker=jsonschema.FormatChecker()
)
if JSONSCHEMA_VERSION < _LooseVersion('2.6.0'):
self.assertIn(
'Additional properties are not allowed (\'target-1:1\' was unexpected)',
excinfo.exception.message
)
else:
self.assertIn(
'\'target-1:1\' does not match any of the regexes',
excinfo.exception.message
)