Merge branch 'develop' of https://github.com/saltstack/salt into develop

Increase syndic_master_publish_port parameters, to address the situation Top Master inconsistencies in nat port
This commit is contained in:
linxiao.jz 2014-03-19 22:56:28 +08:00
commit a7d2444a60
36 changed files with 1486 additions and 440 deletions

View File

@ -101,6 +101,24 @@ class LoadAuth(object):
time.sleep(0.001)
return False
def get_groups(self, load):
'''
Read in a load and return the groups a user is a member of
by asking the appropriate provider
'''
if 'eauth' not in load:
return False
fstr = '{0}.groups'.format(load['eauth'])
if fstr not in self.auth:
return False
fcall = salt.utils.format_call(self.auth[fstr], load)
try:
return self.auth[fstr](*fcall['args'])
except IndexError:
return False
except Exception:
return None
def mk_token(self, load):
'''
Run time_auth and create a token. Return False or the token

View File

@ -188,3 +188,7 @@ def auth(username, password):
)
)
return True
def groups(username, *args, **kwargs):
return None

View File

@ -23,6 +23,9 @@ from ctypes import CDLL, POINTER, Structure, CFUNCTYPE, cast, pointer, sizeof
from ctypes import c_void_p, c_uint, c_char_p, c_char, c_int
from ctypes.util import find_library
# Import Salt libs
from salt.utils import get_group_list
LIBPAM = CDLL(find_library('pam'))
LIBC = CDLL(find_library('c'))
@ -163,3 +166,12 @@ def auth(username, password, **kwargs):
Authenticate via pam
'''
return authenticate(username, password, kwargs.get('service', 'login'))
def groups(username, *args, **kwargs):
'''
Retreive groups for a given user for this auth provider
Uses system groups
'''
return get_group_list(username)

View File

@ -473,6 +473,13 @@ class ExecutorNix(ioflo.base.deeding.Deed):
'dst': (msg['route']['src'][0], None, 'remote_cmd')}
ret['cmd'] = '_return'
ret['id'] = self.opts['id']
try:
oput = self.modules.value[ret['fun']].__outputter__
except (KeyError, AttributeError, TypeError):
pass
else:
if isinstance(oput, str):
ret['out'] = oput
msg = {'route': route, 'load': ret}
ret_stack.transmit(msg, 'yard0')
ret_stack.serviceAll()

View File

@ -73,7 +73,7 @@ def clean_old_jobs(opts):
'''
if opts['keep_jobs'] != 0:
jid_root = os.path.join(opts['cachedir'], 'jobs')
cur = '{0:%Y%m%d%H}'.format(datetime.datetime.now())
cur = datetime.datetime.now()
if os.path.exists(jid_root):
for top in os.listdir(jid_root):
@ -82,15 +82,30 @@ def clean_old_jobs(opts):
f_path = os.path.join(t_path, final)
jid_file = os.path.join(f_path, 'jid')
if not os.path.isfile(jid_file):
continue
# No jid file means corrupted cache entry, scrub it
shutil.rmtree(f_path)
with salt.utils.fopen(jid_file, 'r') as fn_:
jid = fn_.read()
if len(jid) < 18:
# Invalid jid, scrub the dir
shutil.rmtree(f_path)
elif int(cur) - int(jid[:10]) > \
opts['keep_jobs']:
shutil.rmtree(f_path)
else:
# Parse the jid into a proper datetime object. We only
# parse down to the minute, since keep_jobs is measured
# in hours, so a minute difference is not important
try:
jidtime = datetime.datetime(int(jid[0:4]),
int(jid[4:6]),
int(jid[6:8]),
int(jid[8:10]),
int(jid[10:12]))
except ValueError as e:
# Invalid jid, scrub the dir
shutil.rmtree(f_path)
difference = cur - jidtime
hours_difference = difference.seconds / 3600.0
if hours_difference > opts['keep_jobs']:
shutil.rmtree(f_path)
def access_keys(opts):

View File

@ -1370,6 +1370,11 @@ def _smartos_zone_data():
# Provides:
# pkgsrcversion
# imageversion
# pkgsrcpath
# zonename
# zoneid
# hypervisor_uuid
# datacenter
if 'proxyminion' in __opts__:
return {}
@ -1378,6 +1383,7 @@ def _smartos_zone_data():
pkgsrcversion = re.compile('^release:\\s(.+)')
imageversion = re.compile('Image:\\s(.+)')
pkgsrcpath = re.compile('PKG_PATH=(.+)')
if os.path.isfile('/etc/pkgsrc_version'):
with salt.utils.fopen('/etc/pkgsrc_version', 'r') as fp_:
for line in fp_:
@ -1390,10 +1396,25 @@ def _smartos_zone_data():
match = imageversion.match(line)
if match:
grains['imageversion'] = match.group(1)
if os.path.isfile('/opt/local/etc/pkg_install.conf'):
with salt.utils.fopen('/opt/local/etc/pkg_install.conf', 'r') as fp_:
for line in fp_:
match = pkgsrcpath.match(line)
if match:
grains['pkgsrcpath'] = match.group(1)
if 'pkgsrcversion' not in grains:
grains['pkgsrcversion'] = 'Unknown'
if 'imageversion' not in grains:
grains['imageversion'] = 'Unknown'
if 'pkgsrcpath' not in grains:
grains['pkgsrcpath'] = 'Unknown'
grains['zonename'] = __salt__['cmd.run']('zonename')
grains['zoneid'] = __salt__['cmd.run']('zoneadm list -p | awk -F: \'{ print $1 }\'')
grains['hypervisor_uuid'] = __salt__['cmd.run']('mdata-get sdc:server_uuid')
grains['datacenter'] = __salt__['cmd.run']('mdata-get sdc:datacenter_name')
if "FAILURE" in grains['datacenter'] or "No metadata" in grains['datacenter']:
grains['datacenter'] = "Unknown"
return grains

View File

@ -2445,18 +2445,26 @@ class ClearFuncs(object):
'Authentication failure of type "eauth" occurred.'
)
return ''
except Exception as exc:
log.error(
'Exception occurred while authenticating: {0}'.format(exc)
)
return ''
auth_list = self.opts['external_auth'][extra['eauth']][name] if name in self.opts['external_auth'][extra['eauth']] else self.opts['external_auth'][extra['eauth']]['*']
# Auth has succeeded, get groups this user is a member of
groups = self.loadauth.get_groups(extra)
if groups:
auth_list = self.ckminions.gather_groups(self.opts['external_auth'][extra['eauth']], groups, auth_list)
good = self.ckminions.auth_check(
self.opts['external_auth'][extra['eauth']][name]
if name in self.opts['external_auth'][extra['eauth']]
else self.opts['external_auth'][extra['eauth']]['*'],
auth_list,
clear_load['fun'],
clear_load['tgt'],
clear_load.get('tgt_type', 'glob'))
clear_load.get('tgt_type', 'glob')
)
if not good:
# Accept find_job so the CLI will function cleanly
if clear_load['fun'] != 'saltutil.find_job':

View File

@ -160,14 +160,7 @@ def parse_args_and_kwargs(func, args, data=None):
invalid_kwargs = []
for arg in args:
# support old yamlify syntax
if isinstance(arg, string_types):
salt.utils.warn_until(
'Boron',
'This minion received a job where kwargs were passed as '
'string\'d args, which has been deprecated. This functionality will '
'be removed in Salt Boron.'
)
arg_name, arg_value = salt.utils.parse_kwarg(arg)
if arg_name:
if argspec.keywords or arg_name in argspec.args:
@ -186,7 +179,10 @@ def parse_args_and_kwargs(func, args, data=None):
for key, val in arg.iteritems():
if key == '__kwarg__':
continue
kwargs[key] = val
if isinstance(val, string_types):
kwargs[key] = yamlify_arg(val)
else:
kwargs[key] = val
continue
_args.append(yamlify_arg(arg))
if argspec.keywords and isinstance(data, dict):

136
salt/modules/etcd_mod.py Normal file
View File

@ -0,0 +1,136 @@
# -*- coding: utf-8 -*-
'''
Execution module to work with etcd
:depends: - python-etcd
In order to use an etcd server, a profile should be created in the master
configuration file:
.. code-block:: yaml
my_etd_config:
etcd.host: 127.0.0.1
etcd.port: 4001
It is technically possible to configure etcd without using a profile, but this
is not consided to be a best practice, especially when multiple etcd servers or
clusters are available.
.. code-block:: yaml
etcd.host: 127.0.0.1
etcd.port: 4001
'''
# Import python libs
import logging
# Import third party libs
try:
import salt.utils.etcd_util
HAS_LIBS = True
except Exception:
HAS_LIBS = False
__virtualname__ = 'etcd'
# Set up logging
log = logging.getLogger(__name__)
# Define a function alias in order not to shadow built-in's
__func_alias__ = {
'get_': 'get',
'set_': 'set',
'rm_': 'rm',
'ls_': 'ls'
}
def __virtual__():
'''
Only return if python-etcd is installed
'''
return __virtualname__ if HAS_LIBS else False
def get_(key, recurse=False, profile=None):
'''
Get a value from etcd, by direct path
CLI Examples:
salt myminion etcd.get /path/to/key
salt myminion etcd.get /path/to/key profile=my_etcd_config
salt myminion etcd.get /path/to/key recurse=True profile=my_etcd_config
'''
client = salt.utils.etcd_util.get_conn(__opts__, profile)
result = client.get(key)
if recurse:
return salt.utils.etcd_util.tree(client, key)
else:
return result.value
def set_(key, value, profile=None):
'''
Set a value in etcd, by direct path
CLI Example:
salt myminion etcd.set /path/to/key value
salt myminion etcd.set /path/to/key value profile=my_etcd_config
'''
client = salt.utils.etcd_util.get_conn(__opts__, profile)
return client.write(key, value)
def ls_(path='/', profile=None):
'''
Return all keys and dirs inside a specific path
CLI Example:
salt myminion etcd.ls /path/to/dir/
salt myminion etcd.ls /path/to/dir/ profile=my_etcd_config
'''
ret = {}
client = salt.utils.etcd_util.get_conn(__opts__, profile)
items = client.get(path)
for item in items.children:
if item.dir is True:
dir_name = '{0}/'.format(item.key)
ret[dir_name] = {}
else:
ret[item.key] = item.value
return {path: ret}
def rm_(key, recurse=False, profile=None):
'''
Delete a key from etcd
CLI Example:
salt myminion etcd.rm /path/to/key
salt myminion etcd.rm /path/to/key profile=my_etcd_config
salt myminion etcd.rm /path/to/dir recurse=True profile=my_etcd_config
'''
client = salt.utils.etcd_util.get_conn(__opts__, profile)
return client.delete(key, recursive=recurse)
def tree(path='/', profile=None):
'''
Recurse through etcd and return all values
CLI Example:
salt myminion etcd.tree
salt myminion etcd.tree profile=my_etcd_config
salt myminion etcd.tree /path/to/keys profile=my_etcd_config
'''
client = salt.utils.etcd_util.get_conn(__opts__, profile)
return salt.utils.etcd_util.tree(client, path)

View File

@ -166,7 +166,8 @@ def init(name,
[nic=nic_profile] [profile=lxc_profile] \\
[nic_opts=nic_opts] [start=(True|False)] \\
[seed=(True|False)] [install=(True|False)] \\
[config=minion_config]
[config=minion_config] [approve_key=(True|False) \\
[clone=original]
name
Name of the container.
@ -202,28 +203,48 @@ def init(name,
config
Optional config parameters. By default, the id is set to the name of the
container.
approve_key
Attempt to request key approval from the master. Default: ``True``
clone
Original from which to use a clone operation to create the container. Default: ``None``
'''
nicp = _nic_profile(nic)
start_ = kwargs.pop('start', False)
seed = kwargs.pop('seed', True)
install = kwargs.pop('install', True)
seed_cmd = kwargs.pop('seed_cmd', None)
config = kwargs.pop('config', None)
profile = _lxc_profile(profile)
def select(k, default=None):
kw = kwargs.pop(k, None)
p = profile.pop(k, default)
return kw or p
start_ = select('start', False)
seed = select('seed', True)
install = select('install', True)
seed_cmd = select('seed_cmd')
config = select('config')
approve_key = select('approve_key', True)
clone = select('clone')
with tempfile.NamedTemporaryFile() as cfile:
cfile.write(_gen_config(cpuset=cpuset, cpushare=cpushare,
memory=memory, nicp=nicp, nic_opts=nic_opts))
cfile.flush()
ret = create(name, config=cfile.name, profile=profile, **kwargs)
if not ret['created']:
if clone:
ret = __salt__['lxc.clone'](name, clone, config=cfile.name,
profile=profile, **kwargs)
else:
ret = __salt__['lxc.create'](name, config=cfile.name,
profile=profile, **kwargs)
if not (ret.get('created', False) or ret.get('cloned', False)):
return ret
rootfs = info(name)['rootfs']
if seed:
__salt__['seed.apply'](rootfs, id_=name, config=config,
install=install)
ret['seeded'] = __salt__['lxc.bootstrap'](
name, config=config, approve_key=approve_key, install=install)
elif seed_cmd:
__salt__[seed_cmd](rootfs, name, config)
if start_ and ret['created']:
ret['seeded'] = __salt__[seed_cmd](rootfs, name, config)
if start_:
ret['state'] = start(name)['state']
else:
ret['state'] = state(name)
@ -240,7 +261,7 @@ def create(name, config=None, profile=None, options=None, **kwargs):
salt 'minion' lxc.create name [config=config_file] \\
[profile=profile] [template=template_name] \\
[backing=backing_store] [ vgname=volume_group] \\
[backing=backing_store] [vgname=volume_group] \\
[size=filesystem_size] [options=template_options]
name
@ -280,7 +301,8 @@ def create(name, config=None, profile=None, options=None, **kwargs):
cmd = 'lxc-create -n {0}'.format(name)
profile = _lxc_profile(profile)
if not isinstance(profile, dict):
profile = _lxc_profile(profile)
def select(k, default=None):
kw = kwargs.pop(k, None)
@ -343,7 +365,9 @@ def clone(name,
.. code-block:: bash
salt 'minion' lxc.clone name ARGS
salt 'minion' lxc.clone name orig [snapshot=(True|False)] \\
[size=filesystem_size] [vgname=volume_group] \\
[profile=profile_name]
name
Name of the container.
@ -372,16 +396,25 @@ def clone(name,
'''
if exists(name):
return {'created': False, 'error': 'container already exists'}
if not exists(orig):
return {'created': False,
'error': 'original container does not exist'.format(orig)}
return {'cloned': False, 'error': 'container already exists'}
orig_state = state(orig)
if orig_state is None:
return {'cloned': False,
'error':
'original container \'{0}\' does not exist'.format(orig)}
elif orig_state != 'stopped':
return {'cloned': False,
'error': 'original container \'{0}\' is running'.format(orig)}
if not snapshot:
snapshot = ''
else:
snapshot = '-s'
cmd = 'lxc-clone {2} -o {0} -n {1}'.format(orig, name, snapshot)
profile = _lxc_profile(profile)
if not isinstance(profile, dict):
profile = _lxc_profile(profile)
def select(k, default=None):
kw = kwargs.pop(k, None)
@ -405,7 +438,7 @@ def clone(name,
cmd = 'lxc-destroy -n {0}'.format(name)
__salt__['cmd.retcode'](cmd)
log.warn('lxc-clone failed to create container')
return {'created': False, 'error':
return {'cloned': False, 'error':
'container could not be created: {0}'.format(ret['stderr'])}
@ -1037,12 +1070,12 @@ def bootstrap(name, config=None, approve_key=True, install=True):
if not infos:
return None
prior_state = _ensure_running(name)
__salt__['seed.apply'](infos['rootfs'], id_=name, config=config,
approve_key=approve_key, install=False,
prep_install=True)
prior_state = _ensure_running(name)
cmd = 'bash -c "if type salt-minion; then exit 0; '
if install:
cmd += 'else sh /tmp/bootstrap.sh -c /tmp; '

View File

@ -49,9 +49,15 @@ def add(name, gid=None, **kwargs):
raise SaltInvocationError(
'Salt will not create groups beginning with underscores'
)
if gid is not None and not isinstance(gid, int):
raise SaltInvocationError('gid must be an integer')
# check if gid is already in use
gid_list = _list_gids()
if str(gid) in gid_list:
raise CommandExecutionError(
'gid {0!r} already exists'.format(gid)
)
cmd = 'dseditgroup -o create '
if gid:
cmd += '-i {0} '.format(gid)
@ -59,6 +65,19 @@ def add(name, gid=None, **kwargs):
return __salt__['cmd.retcode'](cmd) == 0
def _list_gids():
'''
Return a list of gids in use
'''
cmd = __salt__['cmd.run']('dscacheutil -q group | grep gid:',
output_loglevel='quiet')
data_list = cmd.split()
for item in data_list:
if item == 'gid:':
data_list.remove(item)
return sorted(set(data_list))
def delete(name):
'''
Remove the named group

View File

@ -80,16 +80,15 @@ def version():
def build_rule(table=None, chain=None, command=None, position='', full=None, family='ipv4',
**kwargs):
'''
Build a well-formatted iptables rule based on kwargs. Long options must be
used (`--jump` instead of `-j`) because they will have the `--` added to
them. A `table` and `chain` are not required, unless `full` is True.
Build a well-formatted nftables rule based on kwargs.
A `table` and `chain` are not required, unless `full` is True.
If `full` is `True`, then `table`, `chain` and `command` are required.
`command` may be specified as either a short option ('I') or a long option
(`--insert`). This will return the iptables command, exactly as it would
`command` may be specified as either insert, append, or delete.
This will return the nftables command, exactly as it would
be used from the command line.
If a position is required (as with `-I` or `-D`), it may be specified as
If a position is required (as with `insert` or `delete`), it may be specified as
`position`. This will only be useful if `full` is True.
If `connstate` is passed in, it will automatically be changed to `state`.
@ -98,17 +97,17 @@ def build_rule(table=None, chain=None, command=None, position='', full=None, fam
.. code-block:: bash
salt '*' iptables.build_rule match=state \\
salt '*' nftables.build_rule match=state \\
connstate=RELATED,ESTABLISHED jump=ACCEPT
salt '*' iptables.build_rule filter INPUT command=I position=3 \\
full=True match=state state=RELATED,ESTABLISHED jump=ACCEPT
salt '*' nftables.build_rule filter input command=insert position=3 \\
full=True match=state state=related,established jump=accept
IPv6:
salt '*' iptables.build_rule match=state \\
connstate=RELATED,ESTABLISHED jump=ACCEPT \\
salt '*' nftables.build_rule match=state \\
connstate=related,established jump=accept \\
family=ipv6
salt '*' iptables.build_rule filter INPUT command=I position=3 \\
full=True match=state state=RELATED,ESTABLISHED jump=ACCEPT \\
salt '*' nftables.build_rule filter input command=insert position=3 \\
full=True match=state state=related,established jump=accept \\
family=ipv6
'''
@ -134,7 +133,6 @@ def build_rule(table=None, chain=None, command=None, position='', full=None, fam
del kwargs['of']
if 'proto' in kwargs:
#rule += '-p {0} '.format(kwargs['proto'])
proto = kwargs['proto']
if 'state' in kwargs:
@ -439,7 +437,7 @@ def check(table='filter', chain=None, rule=None, family='ipv4'):
def check_chain(table='filter', chain=None, family='ipv4'):
'''
.. versionadded:: 2014.1.0 (Hydrogen)
.. versionadded:: Helium
Check for the existence of a chain in the table
@ -493,7 +491,7 @@ def check_table(table=None, family='ipv4'):
def new_table(table, family='ipv4'):
'''
.. versionadded:: 2014.1.0 (Hydrogen)
.. versionadded:: Helium
Create new custom table.
@ -525,7 +523,7 @@ def new_table(table, family='ipv4'):
def delete_table(table, family='ipv4'):
'''
.. versionadded:: 2014.1.0 (Hydrogen)
.. versionadded:: Helium
Create new custom table.
@ -556,7 +554,7 @@ def delete_table(table, family='ipv4'):
def new_chain(table='filter', chain=None, table_type=None, hook=None, priority=None, family='ipv4'):
'''
.. versionadded:: 2014.1.0 (Hydrogen)
.. versionadded:: Helium
Create new chain to the specified table.
@ -612,7 +610,7 @@ def new_chain(table='filter', chain=None, table_type=None, hook=None, priority=N
def delete_chain(table='filter', chain=None, family='ipv4'):
'''
.. versionadded:: 2014.1.0 (Hydrogen)
.. versionadded:: Helium
Delete the chain from the specified table.

View File

@ -21,7 +21,7 @@ def __virtual__():
Only load the module if nginx is installed
'''
if __detect_os():
return 'nginx'
return True
return False

View File

@ -439,6 +439,37 @@ def running():
return ret
def cached():
'''
Return the data on all cached salt jobs on the minion
CLI Example:
.. code-block:: bash
salt '*' saltutil.cached
'''
ret = []
serial = salt.payload.Serial(__opts__)
proc_dir = os.path.join(__opts__['cachedir'], 'minion_jobs')
if not os.path.isdir(proc_dir):
return []
for fn_ in os.listdir(proc_dir):
path = os.path.join(proc_dir, fn_, 'return.p')
with salt.utils.fopen(path, 'rb') as fp_:
buf = fp_.read()
fp_.close()
if buf:
data = serial.loads(buf)
else:
continue
if not isinstance(data, dict):
# Invalid serial object
continue
ret.append(data)
return ret
def find_job(jid):
'''
Return the data for a specific job id
@ -455,6 +486,22 @@ def find_job(jid):
return {}
def find_cached_job(jid):
'''
Return the data for a specific cached job id
CLI Example:
.. code-block:: bash
salt '*' saltutil.find_cached_job <job id>
'''
for data in cached():
if data['jid'] == jid:
return data
return {}
def signal_job(jid, sig):
'''
Sends a signal to the named salt job's process

View File

@ -84,9 +84,7 @@ def apply_(path, id_=None, config=None, approve_key=True, install=True,
Install salt-minion, if absent. Default: true.
prep_install
Prepare the bootstrap script, but don't run it. The files needed for
installation (bootstrap.py, config, and keys) will be placed in /tmp
on the target path/device. Default: false
Prepare the bootstrap script, but don't run it. Default: false
'''
stats = __salt__['file.stats'](path, follow_symlinks=True)
if not stats:
@ -199,8 +197,16 @@ def _check_resolv(mpt):
def _check_install(root):
cmd = ('chroot {0} /bin/sh -c if ! type salt-minion; '
'then exit 1; fi').format(root)
sh_ = '/bin/sh'
if os.path.isfile(os.path.join(root, 'bin/bash')):
sh_ = '/bin/bash'
cmd = ('if ! type salt-minion; then exit 1; fi').format(root)
cmd = 'chroot {0} {1} -c {2!r}'.format(
root,
sh_,
cmd)
return not __salt__['cmd.retcode'](cmd, output_loglevel='quiet')

View File

@ -63,7 +63,15 @@ def __virtual__():
return False
def send_msg(recipient, message, subject='Message from Salt', sender=None, server=None, use_ssl='True', username=None, password=None, profile=None):
def send_msg(recipient,
message,
subject='Message from Salt',
sender=None,
server=None,
use_ssl='True',
username=None,
password=None,
profile=None):
'''
Send a message to an SMTP recipient. Designed for use in states.

View File

@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
'''
Use etcd data as a Pillar source
:depends: - python-etcd
In order to use an etcd server, a profile must be created in the master
configuration file:
.. code-block:: yaml
my_etd_config:
etcd.host: 127.0.0.1
etcd.port: 4001
After the profile is created, configure the external pillar system to use it.
Optionally, a root may be specified.
.. code-block:: yaml
ext_pillar:
- etcd: my_etcd_config
ext_pillar:
- etcd: my_etcd_config root=/salt
Using these configuration profiles, multiple etcd sources may also be used:
.. code-block:: yaml
ext_pillar:
- etcd: my_etcd_config
- etcd: my_other_etcd_config
'''
# Import python libs
import logging
# Import third party libs
try:
import salt.utils.etcd_util
HAS_LIBS = True
except Exception:
HAS_LIBS = False
__virtualname__ = 'etcd'
# Set up logging
log = logging.getLogger(__name__)
def __virtual__():
'''
Only return if python-etcd is installed
'''
return __virtualname__ if HAS_LIBS else False
def ext_pillar(minion_id, pillar, conf): # pylint: disable=W0613
'''
Check etcd for all data
'''
comps = conf.split()
profile = None
if comps[0]:
profile = comps[0]
client = salt.utils.etcd_util.get_conn(__opts__, profile)
path = '/'
if len(comps) > 1 and comps[1].startswith('root='):
path = comps[1].replace('root=', '')
return salt.utils.etcd_util.tree(client, path)

View File

@ -0,0 +1,166 @@
# -*- coding: utf-8 -*-
'''
Return data to an etcd server or cluster
:depends: - python-etcd
In order to return to an etcd server, a profile should be created in the master
configuration file:
.. code-block:: yaml
my_etd_config:
etcd.host: 127.0.0.1
etcd.port: 4001
It is technically possible to configure etcd without using a profile, but this
is not consided to be a best practice, especially when multiple etcd servers or
clusters are available.
.. code-block:: yaml
etcd.host: 127.0.0.1
etcd.port: 4001
Additionally, two more options must be specified in the top-level configuration
in order to use the etcd returner:
.. code-block:: yaml
etcd.returner: my_etcd_config
etcd.returner_root: /salt/return
The ``etcd.returner`` option specifies which configuration profile to use. The
``etcd.returner_root`` option specifies the path inside etcd to use as the root
of the returner system.
Once the etcd options are configured, the returner may be used:
CLI Example:
salt '*' test.ping --return etcd
'''
# Import python libs
import json
import logging
# Import salt libs
try:
import salt.utils.etcd_util
HAS_LIBS = True
except Exception:
HAS_LIBS = False
log = logging.getLogger(__name__)
# Define the module's virtual name
__virtualname__ = 'etcd'
def __virtual__():
'''
Only return if python-etcd is installed
'''
return __virtualname__ if HAS_LIBS else False
def _get_conn(opts):
'''
Establish a connection to etcd
'''
profile = opts.get('etcd.returner', None)
path = opts.get('etcd.returner_root', '/salt/return')
return salt.utils.etcd_util.get_conn(opts, profile), path
def returner(ret):
'''
Return data to an etcd server or cluster
'''
client, path = _get_conn(__opts__)
# Make a note of this minion for the external job cache
client.write(
'/'.join((path, 'minions', ret['id'])),
ret['jid'],
)
for field in ret.keys():
# Not using os.path.join because we're not dealing with file paths
dest = '/'.join((
path,
'jobs',
ret['jid'],
ret['id'],
field
))
client.write(dest, json.dumps(ret[field]))
def save_load(jid, load):
'''
Save the load to the specified jid
'''
client, path = _get_conn(__opts__)
client.write(
'/'.join((path, 'jobs', jid, '.load.p')),
json.dumps(load)
)
def get_load(jid):
'''
Return the load data that marks a specified jid
'''
client, path = _get_conn(__opts__)
return json.loads(client.get('/'.join(path, 'jobs', jid, '.load.p')))
def get_jid(jid):
'''
Return the information returned when the specified job id was executed
'''
client, path = _get_conn(__opts__)
jid_path = '/'.join((path, 'jobs', jid))
return salt.utils.etcd_util.tree(client, jid_path)
def get_fun(fun):
'''
Return a dict of the last function called for all minions
'''
ret = {}
client, path = _get_conn(__opts__)
items = client.get('/'.join((path, 'minions')))
for item in items.children:
comps = str(item.key).split('/')
ret[comps[-1]] = item.value
return ret
def get_jids():
'''
Return a list of all job ids
'''
ret = []
client, path = _get_conn(__opts__)
items = client.get('/'.join((path, 'jobs')))
for item in items.children:
if item.dir is True:
comps = str(item.key).split('/')
ret.append(comps[-1])
return ret
def get_minions():
'''
Return a list of minions
'''
ret = []
client, path = _get_conn(__opts__)
items = client.get('/'.join((path, 'minions')))
for item in items.children:
comps = str(item.key).split('/')
ret.append(comps[-1])
return ret

View File

@ -79,7 +79,8 @@ def init(name,
[nic=nic_profile] [profile=lxc_profile] \\
[nic_opts=nic_opts] [start=(true|false)] \\
[seed=(true|false)] [install=(true|false)] \\
[config=minion_config]
[config=minion_config] [clone=original] \\
[snapshot=(true|false)]
name
Name of the container.
@ -123,45 +124,52 @@ def init(name,
data = __salt__['lxc.list'](host, quiet=True)
for host, containers in data.items():
if name in sum(containers.values(), []):
print('Container "{0}" is already deployed'.format(name))
return 'fail'
print('Container \'{0}\' already exists on host \'{1}\''.format(
host))
return False
if host is None:
#TODO: Support selection of host based on available memory/cpu/etc.
print('A host must be provided.')
return 'fail'
print('A host must be provided')
return False
if host not in data:
print('Host "{0}" was not found'.format(host))
return 'fail'
print('Host \'{0}\' was not found'.format(host))
return False
seed = kwargs.get('seed', True)
if seed:
kw = dict((k, v) for k, v in kwargs.items() if not k.startswith('__'))
approve_key = kw.get('approve_key', True)
if approve_key:
kv = salt.utils.virt.VirtKey(host, name, __opts__)
if kv.authorize():
print('Minion will be preseeded.')
print('Container key will be preauthorized')
else:
print('Preauthorization failed.')
return 'fail'
print('Container key preauthorization failed')
return False
client = salt.client.LocalClient(__opts__['conf_file'])
print('Creating container {0} on host {1}.'.format(name, host))
args = [name]
print('Creating container \'{0}\' on host \'{1}\''.format(name, host))
args = [name]
cmd_ret = client.cmd_iter(host,
'lxc.init',
args,
kwarg=kwargs,
timeout=600)
ret = next(cmd_ret)
if not ret:
print('Container {0} was not initialized.'.format(name))
return 'fail'
if ret and host in ret:
ret = ret[host]['ret']
else:
ret = {}
print('Container {0} initialized on host {1}'.format(name, host))
return 'good'
if ret.get('created', False) or ret.get('cloned', False):
print('Container \'{0}\' initialized on host \'{1}\''.format(
name, host))
else:
error = ret.get('error', 'unknown error')
print('Container \'{0}\' was not initialized: {1}'.format(name, error))
return ret or None
def list_(host=None, quiet=False):

View File

@ -23,8 +23,8 @@ no disk space:
cmd.run:
- unless: echo 'foo' > /tmp/.test
Only run if the file specified by ``creates`` does not exist, in this case touch
/tmp/foo if it does not exist.
Only run if the file specified by ``creates`` does not exist, in this case
touch /tmp/foo if it does not exist.
.. code-block:: yaml
@ -118,8 +118,9 @@ it can also watch a git state for changes
- git: my-project
Should I use :mod:`cmd.run <salt.states.cmd.run>` or :mod:`cmd.wait <salt.states.cmd.wait>`?
--------------------------------------------------------------------------------------------
Should I use :mod:`cmd.run <salt.states.cmd.run>` or :mod:`cmd.wait
<salt.states.cmd.wait>`?
-------------------------------------------------------------------------------
These two states are often confused. The important thing to remember about them
is that :mod:`cmd.run <salt.states.cmd.run>` states are run each time the SLS
@ -147,10 +148,10 @@ executed when the state it is watching changes. Example:
- file: /usr/local/bin/postinstall.sh
How do I create a environment from a pillar map?
---------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------
The map that comes from a pillar cannot be directly consumed by the env option. To use it
one must convert it to a list. Example:
The map that comes from a pillar cannot be directly consumed by the env option.
To use it one must convert it to a list. Example:
.. code-block:: yaml
@ -279,28 +280,53 @@ def _run_check(cmd_kwargs, onlyif, unless, group, creates):
'result': False}
if onlyif is not None:
if not isinstance(onlyif, string_types):
if not onlyif:
if isinstance(onlyif, string_types):
if __salt__['cmd.retcode'](onlyif, **cmd_kwargs) != 0:
return {'comment': 'onlyif execution failed',
'result': True}
elif isinstance(onlyif, string_types):
if __salt__['cmd.retcode'](onlyif, **cmd_kwargs) != 0:
elif isinstance(onlyif, list):
if all([
__salt__['cmd.retcode'](
entry,
**cmd_kwargs
) != 0 for entry in onlyif
]):
return {'comment': 'onlyif execution failed',
'result': True}
elif not isinstance(onlyif, string_types):
if not onlyif:
return {'comment': 'onlyif execution failed',
'result': True}
if unless is not None:
if not isinstance(unless, string_types):
if unless:
if isinstance(unless, string_types):
if __salt__['cmd.retcode'](unless, **cmd_kwargs) == 0:
return {'comment': 'unless execution succeeded',
'result': True}
elif isinstance(unless, string_types):
if __salt__['cmd.retcode'](unless, **cmd_kwargs) == 0:
elif isinstance(unless, list):
if all([
__salt__['cmd.retcode'](
entry,
**cmd_kwargs
) == 0 for entry in unless
]):
return {'comment': 'unless execution succeeded',
'result': True}
elif not isinstance(unless, string_types):
if unless:
return {'comment': 'unless execution succeeded',
'result': True}
if isinstance(creates, string_types) and os.path.exists(creates):
return {'comment': '{0} exists'.format(creates),
'result': True}
elif isinstance(creates, list) and all([
os.path.exists(path) for path in creates
]):
return {'comment': 'All files in creates exist'.format(creates),
'result': True}
# No reason to stop, return True
return True
@ -550,7 +576,10 @@ def run(name,
'comment': ''}
if cwd and not os.path.isdir(cwd):
ret['comment'] = 'Desired working directory "{0}" is not available'.format(cwd)
ret['comment'] = (
'Desired working directory "{0}" '
'is not available'
).format(cwd)
return ret
if env:
@ -654,9 +683,9 @@ def script(name,
Download a script and execute it with specified arguments.
source
The location of the script to download. If the file is located on the master
in the directory named spam, and is called eggs, the source string is
salt://spam/eggs
The location of the script to download. If the file is located on the
master in the directory named spam, and is called eggs, the source
string is salt://spam/eggs
template
If this setting is applied then the named templating engine will be
@ -664,13 +693,16 @@ def script(name,
are supported
name
Either "cmd arg1 arg2 arg3..." (cmd is not used) or a source "salt://...".
Either "cmd arg1 arg2 arg3..." (cmd is not used) or a source
"salt://...".
onlyif
Run the named command only if the command passed to the ``onlyif`` option returns true
Run the named command only if the command passed to the ``onlyif``
option returns true
unless
Run the named command only if the command passed to the ``unless`` option returns false
Run the named command only if the command passed to the ``unless``
option returns false
cwd
The current working directory to execute the command in, defaults to
@ -701,8 +733,9 @@ def script(name,
args
String of command line args to pass to the script. Only used if no
args are specified as part of the `name` argument. To pass a string containing
spaces in YAML, you will need to doubly-quote it: "arg1 'arg two' arg3"
args are specified as part of the `name` argument. To pass a string
containing spaces in YAML, you will need to doubly-quote it: "arg1
'arg two' arg3"
creates
Only run if the file specified by ``creates`` does not exist.
@ -715,7 +748,10 @@ def script(name,
'result': False}
if cwd and not os.path.isdir(cwd):
ret['comment'] = 'Desired working directory "{0}" is not available'.format(cwd)
ret['comment'] = (
'Desired working directory "{0}" '
'is not available'
).format(cwd)
return ret
if isinstance(env, string_types):
@ -913,7 +949,9 @@ def mod_watch(name, **kwargs):
else:
return {'name': name,
'changes': {},
'comment': 'cmd.{0[sfun]} needs a named parameter func'.format(kwargs),
'comment': (
'cmd.{0[sfun]} needs a named parameter func'
).format(kwargs),
'result': False}
return {'name': name,

View File

@ -184,7 +184,7 @@ def user_present(name,
for role in roles[0][tenant_role]:
__salt__['keystone.user_role_add'](user=name,
role=role,
tenant_role=tenant_role,
tenant=tenant_role,
profile=profile,
**connection_args)
ret['comment'] = 'Keystone user {0} has been added'.format(name)

View File

@ -3,7 +3,7 @@
Management of nftables
======================
This is an iptables-specific module designed to manage Linux firewalls. It is
This is an nftables-specific module designed to manage Linux firewalls. It is
expected that this state module, and other system-specific firewall states, may
at some point be deprecated in favor of a more generic `firewall` state.
@ -115,7 +115,7 @@ def __virtual__():
def chain_present(name, table='filter', table_type=None, hook=None, priority=None, family='ipv4'):
'''
.. versionadded:: 2014.1.0 (Hydrogen)
.. versionadded:: Helium
Verify the chain is exist.
@ -169,7 +169,7 @@ def chain_present(name, table='filter', table_type=None, hook=None, priority=Non
def chain_absent(name, table='filter', family='ipv4'):
'''
.. versionadded:: 2014.1.0 (Hydrogen)
.. versionadded:: Helium
Verify the chain is absent.
@ -195,7 +195,7 @@ def chain_absent(name, table='filter', family='ipv4'):
if command is True:
ret['changes'] = {'locale': name}
ret['result'] = True
ret['comment'] = ('iptables {0} chain in {1} table delete success for {2}'
ret['comment'] = ('nftables {0} chain in {1} table delete success for {2}'
.format(name, table, family))
else:
ret['result'] = False
@ -226,7 +226,7 @@ def append(name, family='ipv4', **kwargs):
Network family, ipv4 or ipv6.
All other arguments are passed in with the same name as the long option
that would normally be used for iptables, with one exception: `--state` is
that would normally be used for nftables, with one exception: `--state` is
specified as `connstate` instead of `state` (not to be confused with
`ctstate`).
'''
@ -281,7 +281,7 @@ def append(name, family='ipv4', **kwargs):
def insert(name, family='ipv4', **kwargs):
'''
.. versionadded:: 2014.1.0 (Hydrogen)
.. versionadded:: Helium
Insert a rule into a chain
@ -293,7 +293,7 @@ def insert(name, family='ipv4', **kwargs):
Networking family, either ipv4 or ipv6
All other arguments are passed in with the same name as the long option
that would normally be used for iptables, with one exception: `--state` is
that would normally be used for nftables, with one exception: `--state` is
specified as `connstate` instead of `state` (not to be confused with
`ctstate`).
'''
@ -347,7 +347,7 @@ def insert(name, family='ipv4', **kwargs):
def delete(name, family='ipv4', **kwargs):
'''
.. versionadded:: 2014.1.0 (Hydrogen)
.. versionadded:: Helium
Delete a rule to a chain
@ -359,7 +359,7 @@ def delete(name, family='ipv4', **kwargs):
Networking family, either ipv4 or ipv6
All other arguments are passed in with the same name as the long option
that would normally be used for iptables, with one exception: `--state` is
that would normally be used for nftables, with one exception: `--state` is
specified as `connstate` instead of `state` (not to be confused with
`ctstate`).
'''
@ -424,64 +424,9 @@ def delete(name, family='ipv4', **kwargs):
return ret
def set_policy(name, family='ipv4', **kwargs):
'''
.. versionadded:: 2014.1.0 (Hydrogen)
Sets the default policy for iptables firewall tables
family
Networking family, either ipv4 or ipv6
'''
ret = {'name': name,
'changes': {},
'result': None,
'comment': ''}
for ignore in _STATE_INTERNAL_KEYWORDS:
if ignore in kwargs:
del kwargs[ignore]
if __salt__['iptables.get_policy'](
kwargs['table'],
kwargs['chain'],
family) == kwargs['policy']:
ret['result'] = True
ret['comment'] = ('iptables default policy for {0} for {1} already set to {2}'
.format(kwargs['table'], family, kwargs['policy']))
return ret
if not __salt__['iptables.set_policy'](
kwargs['table'],
kwargs['chain'],
kwargs['policy'],
family):
ret['changes'] = {'locale': name}
ret['result'] = True
ret['comment'] = 'Set default policy for {0} to {1} family {2}'.format(
kwargs['chain'],
kwargs['policy'],
family
)
if 'save' in kwargs:
if kwargs['save']:
__salt__['iptables.save'](filename=None, family=family)
ret['comment'] = 'Set and Saved default policy for {0} to {1} family {2}'.format(
kwargs['chain'],
kwargs['policy'],
family
)
return ret
else:
ret['result'] = False
ret['comment'] = 'Failed to set iptables default policy'
return ret
def flush(name, family='ipv4', **kwargs):
'''
.. versionadded:: 2014.1.0 (Hydrogen)
.. versionadded:: Helium
Flush current nftables state
@ -531,5 +476,5 @@ def flush(name, family='ipv4', **kwargs):
return ret
else:
ret['result'] = False
ret['comment'] = 'Failed to flush iptables rules'
ret['comment'] = 'Failed to flush nftables rules'
return ret

View File

@ -26,6 +26,7 @@ as either absent or present
# Import python libs
import logging
import os
# Import salt libs
import salt.utils
@ -51,6 +52,7 @@ def _changes(name,
optional_groups=None,
remove_groups=True,
home=None,
createhome=True,
password=None,
enforce_password=True,
shell=None,
@ -104,6 +106,9 @@ def _changes(name,
if home:
if lusr['home'] != home:
change['home'] = home
if createhome and not os.path.isdir(home):
change['homeDoesNotExist'] = home
if shell:
if lusr['shell'] != shell:
change['shell'] = shell
@ -327,6 +332,7 @@ def present(name,
present_optgroups,
remove_groups,
home,
createhome,
password,
enforce_password,
shell,
@ -360,6 +366,12 @@ def present(name,
if key == 'date':
__salt__['shadow.set_date'](name, date)
continue
if key == 'home' or key == 'homeDoesNotExist':
if createhome:
__salt__['user.chhome'](name, val, True)
else:
__salt__['user.chhome'](name, val, False)
continue
if key == 'mindays':
__salt__['shadow.set_mindays'](name, mindays)
continue
@ -404,6 +416,7 @@ def present(name,
present_optgroups,
remove_groups,
home,
createhome,
password,
enforce_password,
shell,

View File

@ -96,7 +96,7 @@ class TxHead(Head):
data['hl'] = hl
if self.packet.coat.size > raeting.MAX_MESSAGE_SIZE:
emsg = "Packet length of {0}, exceeds max of {1}".format(
emsg = "Packed message length of {0}, exceeds max of {1}".format(
self.packet.coat.size, raeting.MAX_MESSAGE_SIZE)
raise raeting.PacketError(emsg)
pl = hl + self.packet.coat.size + data['fl']
@ -411,7 +411,6 @@ class Packet(object):
emsg = "Unrecognizible packet kind."
raise raeting.PacketError(emsg)
self.data.update(pk=kind)
self.segments = None
@property
def size(self):
@ -420,13 +419,6 @@ class Packet(object):
'''
return len(self.packed)
@property
def segmented(self):
'''
Property is True if packet has at least one entry in .segments
'''
return (True if self.segments else False)
@property
def segmentive(self):
'''
@ -466,15 +458,9 @@ class TxPacket(Packet):
data = self.data
le = data['se']
if le == 0:
#host = data['sh']
#if host == '0.0.0.0':
#host = '127.0.0.1'
le = (data['sh'], data['sp'])
re = data['de']
if re == 0:
#host = data['dh']
#if host == '0.0.0.0':
#host = '127.0.0.1'
re = (data['dh'], data['dp'])
return ((data['cf'], le, re, data['si'], data['ti'], data['bf']))
@ -501,9 +487,10 @@ class TxPacket(Packet):
remote = self.stack.estates[self.data['de']]
return (remote.privee.encrypt(msg, remote.publee.key))
def pack(self):
def prepack(self):
'''
Pack the parts of the packet and then the full packet into .packed
Pre Pack the parts of the packet .packed but do not sign so can see
if needs to be segmented
'''
self.body.pack()
self.coat.pack()
@ -513,46 +500,18 @@ class TxPacket(Packet):
self.coat.packed,
self.foot.packed])
if self.size <= raeting.MAX_PACKET_SIZE:
self.sign()
else:
#print "****Segmentize**** packet size = {0}".format(self.size)
self.segmentize()
def segmentize(self):
def pack(self):
'''
Create packeted segments from existing packet
Pack the parts of the packet and then the full packet into .packed
'''
self.segments = odict()
fullsize = self.coat.size
full = self.coat.packed
extrasize = 0
if self.data['hk'] == raeting.headKinds.json:
extrasize = 32 # extra header size as a result of segmentation
hotelsize = self.head.size + extrasize + self.foot.size
haulsize = raeting.MAX_PACKET_SIZE - hotelsize
segcount = (fullsize // haulsize) + (1 if fullsize % haulsize else 0)
for i in range(segcount):
if i == segcount - 1: #last segment
haul = full[i * haulsize:]
else:
haul = full[i * haulsize: (i+1) * haulsize]
segment = TxPacket( stack=self.stack,
data=self.data)
segment.data.update(sn=i, sc=segcount, ml=fullsize, sf=True)
segment.coat.packed = segment.body.packed = haul
segment.foot.pack()
segment.head.pack()
segment.packed = ''.join([ segment.head.packed,
segment.coat.packed,
self.foot.packed])
segment.sign()
self.segments[i] = segment
self.prepack()
if self.size > raeting.MAX_PACKET_SIZE:
emsg = "Packet length of {0}, exceeds max of {1}".format(
self.size, raeting.MAX_PACKET_SIZE)
raise raeting.PacketError(emsg)
self.sign()
class RxPacket(Packet):
'''
@ -567,7 +526,6 @@ class RxPacket(Packet):
self.body = RxBody(packet=self)
self.coat = RxCoat(packet=self)
self.foot = RxFoot(packet=self)
self.segments = odict() # desegmentize assumes odict()
self.packed = packed or ''
@property
@ -638,14 +596,6 @@ class RxPacket(Packet):
self.foot.parse() #foot unpacks itself
def parseSegment(self, packet):
'''
If packet is segmentive then adds to .segments at index of segment number
Assumes packet parseOuter has already happened
'''
if packet.segmentive:
self.segments[packet.data['sn']] = packet
def unpackInner(self, packed=None):
'''
Unpacks the body, and coat parts of .packed
@ -667,46 +617,155 @@ class RxPacket(Packet):
Result is .body.data and .data
Raises PacketError exception If failure
'''
if not self.segmented: #since head and foot are not valid
self.unpackInner()
self.unpackInner()
self.coat.parse()
self.body.parse()
def desegmentable(self):
'''
Return True of .segments is complete
'''
if not self.segmentive or not self.segmented:
return False
sc = self.data['sc']
for i in range(0, sc):
if i not in self.segments:
return False
if not self.segments[i]:
return False
return True
class Tray(object):
'''
Manages messages, segmentation when needed and the associated packets
'''
def __init__(self, stack=None, data=None, body=None, packed=None, **kwa):
'''
Setup instance
'''
self.stack = stack
self.packed = packed or ''
self.data = odict(raeting.PACKET_DEFAULTS)
if data:
self.data.update(data)
self.body = body #body data of message
@property
def size(self):
'''
Property is the length of the .packed
'''
return len(self.packed)
class TxTray(Tray):
'''
Manages an outgoing message and ites associated packet(s)
'''
def __init__(self, **kwa):
'''
Setup instance
'''
super(TxTray, self).__init__(**kwa)
self.packets = []
self.current = 0 #current packet to send
def pack(self, data=None, body=None):
'''
Convert message in .body into one or more packets
'''
if data:
self.data.update(data)
if body is not None:
self.body = body
self.current = 0
self.packets = []
packet = TxPacket(stack=self.stack,
kind=raeting.pcktKinds.message,
embody=self.body,
data=self.data)
packet.prepack()
if packet.size <= raeting.MAX_PACKET_SIZE:
packet.sign()
self.packets.append(packet)
else:
self.packed = packet.coat.packed
self.packetize(headsize=packet.head.size, footsize=packet.foot.size)
def packetize(self, headsize, footsize):
'''
Create packeted segments from .packed using headsize footsize
'''
extrasize = 0
if self.data['hk'] == raeting.headKinds.json:
extrasize = 32 # extra header size as a result of segmentation
hotelsize = headsize + extrasize + footsize
segsize = raeting.MAX_PACKET_SIZE - hotelsize
segcount = (self.size // segsize) + (1 if self.size % segsize else 0)
for i in range(segcount):
if i == segcount - 1: #last segment
segment = self.packed[i * segsize:]
else:
segment = self.packed[i * segsize: (i+1) * segsize]
packet = TxPacket( stack=self.stack,
data=self.data)
packet.data.update(sn=i, sc=segcount, ml=self.size, sf=True)
packet.coat.packed = packet.body.packed = segment
packet.foot.pack()
packet.head.pack()
packet.packed = ''.join([ packet.head.packed,
packet.coat.packed,
packet.foot.packed])
packet.sign()
self.packets.append(packet)
class RxTray(Tray):
'''
Manages segmentated messages and the associated packets
'''
def __init__(self, segments=None, **kwa):
'''
Setup instance
'''
super(RxTray, self).__init__(**kwa)
self.segments = segments if segments is not None else []
self.complete = False
def parse(self, packet):
'''
Process a given packet
'''
sc = packet.data['sc']
console.verbose("segment count = {0} tid={1}\n".format(sc, packet.data['ti']))
if not self.segments: #get data from first packet received
self.data.update(packet.data)
self.segments = [None] * sc
hl = packet.data['hl']
fl = packet.data['fl']
segment = packet.packed[hl:packet.size - fl]
sn = packet.data['sn']
self.segments[sn] = segment
if None in self.segments: #don't have all segments yet
return None
self.body = self.desegmentize()
return self.body
def desegmentize(self):
'''
Reassemble packet from segments
When done ready to call parseInner on self
Process message packet assumes already parsed outer so verified signature
and processed header data
'''
hauls = []
sc = self.data['sc']
for i in range(0, sc):
segment = self.segments[i]
hl = segment.data['hl']
fl = segment.data['fl']
haul = segment.packed[hl:segment.size - fl]
hauls.append(haul)
packed = "".join(hauls)
self.coat.packed = packed
self.packed = "".join(self.segments)
ml = self.data['ml']
if self.coat.size != ml:
emsg = ("Full segmented payload length '{0}' does not equal head field"
" '{1}'".format(self.cost.size, ml))
if sc > 1 and self.size != ml:
emsg = ("Full message payload length '{0}' does not equal head field"
" '{1}'".format(self.size, ml))
raise raeting.PacketError(emsg)
packet = RxPacket(stack = self.stack, data=self.data)
packet.coat.packed = self.packed
packet.coat.parse()
packet.body.parse()
self.complete = True
return packet.body.data

View File

@ -50,7 +50,6 @@ header data =
ti: Transaction ID (TID) Default 0
tk: Transaction Kind (TrnsKind)
dt: Datetime Stamp (Datetime) Default 0
oi: Order index (OrdrIndx) Default 0
@ -294,6 +293,17 @@ class PacketError(RaetError):
'''
pass
class PacketSizeError(PacketError):
'''
Packet too large error needs to be segmented
Usage:
emsg = "Packet size {0} too large needs to be segmented".format(size)
raise raeting.PacketSizeError(emsg)
'''
pass
class KeepError(RaetError):
'''
Exceptions in RAET keep processing

View File

@ -35,20 +35,25 @@ def test( bk = raeting.bodyKinds.json):
stuff = "".join(stuff)
data.update(bk=raeting.bodyKinds.raw)
packet0 = packeting.TxPacket(embody=stuff, data=data, )
packet0.pack()
print packet0.packed
packet1 = packeting.RxPacket(packed=packet0.packed)
packet1.parse()
print packet1.data
print packet1.body.data
try:
packet0.pack()
except raeting.PacketError as ex:
print ex
print "Need to use tray"
rejoin = []
if packet0.segmented:
for index, segment in packet0.segments.items():
print index, segment.packed
rejoin.append(segment.body.packed)
rejoin = "".join(rejoin)
print stuff == rejoin
tray0 = packeting.TxTray(data=data, body=stuff)
tray0.pack()
print tray0.packed
print tray0.packets
tray1 = packeting.RxTray()
for packet in tray0.packets:
tray1.parse(packet)
print tray1.data
print tray1.body
print stuff == tray1.body
#master stack
masterName = "master"
@ -106,110 +111,59 @@ def test( bk = raeting.bodyKinds.json):
print "\n___________Raw Body Test"
data.update(se=1, de=2, bk=raeting.bodyKinds.raw, fk=raeting.footKinds.nacl)
packet0 = packeting.TxPacket(stack=stack0, embody=stuff, data=data, )
packet0.pack()
print packet0.packed #not signed if segmented each segment is signed
tray0 = packeting.TxTray(stack=stack0, data=data, body=stuff)
tray0.pack()
print tray0.packed
print tray0.packets
rejoin = []
if packet0.segmented:
for index, segment in packet0.segments.items():
print index, segment.packed
rejoin.append(segment.coat.packed)
tray1 = packeting.RxTray(stack=stack1)
for packet in tray0.packets:
tray1.parse(packet)
rejoin = "".join(rejoin)
print stuff == rejoin
print tray1.data
print tray1.body
segmentage = None
if packet0.segmented:
for segment in packet0.segments.values():
packet = packeting.RxPacket(stack=stack1, packed=segment.packed)
packet.parseOuter()
if packet.segmentive:
if not segmentage:
segmentage = packeting.RxPacket(stack=packet.stack,
data=packet.data)
segmentage.parseSegment(packet)
if segmentage.desegmentable():
segmentage.desegmentize()
break
if segmentage:
if not stack1.parseInner(segmentage):
print "*******BAD SEGMENTAGE********"
return
print segmentage.body.packed
print segmentage.body.data
print segmentage.body.packed == packet0.body.packed
body = odict(stuff=stuff)
print body
print stuff == tray1.body
print "\n_____________ Packed Body Test"
data.update(se=1, de=2, bk=bk, fk=raeting.footKinds.nacl)
packet0 = packeting.TxPacket(stack=stack0, embody=body, data=data, )
packet0.pack()
print packet0.packed
segmentage = None
if packet0.segmented:
for segment in packet0.segments.values():
packet = packeting.RxPacket(stack=stack1, packed=segment.packed)
packet.parseOuter()
if packet.segmentive:
if not segmentage:
segmentage = packeting.RxPacket(stack=packet.stack,
data=packet.data)
segmentage.parseSegment(packet)
if segmentage.desegmentable():
segmentage.desegmentize()
break
if segmentage:
if not stack1.parseInner(segmentage):
print "*******BAD SEGMENTAGE********"
return
print segmentage.body.packed
print segmentage.body.data
print segmentage.body.packed == packet0.body.packed
body = odict(stuff=stuff)
print body
data.update(se=1, de=2, bk=bk, fk=raeting.footKinds.nacl)
tray0 = packeting.TxTray(stack=stack0, data=data, body=body)
tray0.pack()
print tray0.packed
print tray0.packets
tray1 = packeting.RxTray(stack=stack1)
for packet in tray0.packets:
tray1.parse(packet)
print tray1.data
print tray1.body
print body == tray1.body
print "\n___________ Encrypted Coat Test "
body = odict(stuff=stuff)
print body
data.update(se=1, de=2,
bk=raeting.bodyKinds.json,
ck=raeting.coatKinds.nacl,
fk=raeting.footKinds.nacl)
packet0 = packeting.TxPacket(stack=stack0, embody=body, data=data, )
packet0.pack()
print "Body"
print packet0.body.size, packet0.body.packed
print "Coat"
print packet0.coat.size, packet0.coat.packed
print "Head"
print packet0.head.size, packet0.head.packed
print "Foot"
print packet0.foot.size, packet0.foot.packed
print "Packet"
print packet0.size, packet0.packed
tray0 = packeting.TxTray(stack=stack0, data=data, body=body)
tray0.pack()
print tray0.packed
print tray0.packets
segmentage = None
if packet0.segmented:
for segment in packet0.segments.values():
packet = packeting.RxPacket(stack=stack1, packed=segment.packed)
packet.parseOuter()
if packet.segmentive:
if not segmentage:
segmentage = packeting.RxPacket(stack=packet.stack,
data=packet.data)
segmentage.parseSegment(packet)
if segmentage.desegmentable():
segmentage.desegmentize()
break
if segmentage:
if not stack1.parseInner(segmentage):
print "*******BAD SEGMENTAGE********"
print segmentage.body.packed
print segmentage.body.data
print segmentage.body.packed == packet0.body.packed
tray1 = packeting.RxTray(stack=stack1)
for packet in tray0.packets:
tray1.parse(packet)
print tray1.data
print tray1.body
print body == tray1.body
stack0.server.close()

View File

@ -884,7 +884,7 @@ class Allower(Initiator):
RAET protocol Allower Initiator class Dual of Allowent
CurveCP handshake
'''
Timeout = 2.0
Timeout = 4.0
RedoTimeoutMin = 0.25 # initial timeout
RedoTimeoutMax = 1.0 # max timeout
@ -959,7 +959,6 @@ class Allower(Initiator):
self.transmit(self.txPacket) # redo
console.concise("Allower Redo Ack Final at {0}\n".format(self.stack.store.stamp))
def prep(self):
'''
Prepare .txData
@ -1165,7 +1164,7 @@ class Allowent(Correspondent):
RAET protocol Allowent Correspondent class Dual of Allower
CurveCP handshake
'''
Timeout = 2.0
Timeout = 4.0
RedoTimeoutMin = 0.25 # initial timeout
RedoTimeoutMax = 1.0 # max timeout
@ -1517,13 +1516,22 @@ class Messenger(Initiator):
RAET protocol Messenger Initiator class Dual of Messengent
Generic messages
'''
def __init__(self, **kwa):
Timeout = 10.0
RedoTimeoutMin = 1.0 # initial timeout
RedoTimeoutMax = 3.0 # max timeout
def __init__(self, redoTimeoutMin=None, redoTimeoutMax=None, **kwa):
'''
Setup instance
'''
kwa['kind'] = raeting.trnsKinds.message
super(Messenger, self).__init__(**kwa)
self.segmentage = None # special packet to hold segments if any
self.redoTimeoutMax = redoTimeoutMax or self.RedoTimeoutMax
self.redoTimeoutMin = redoTimeoutMin or self.RedoTimeoutMin
self.redoTimer = aiding.StoreTimer(self.stack.store,
duration=self.redoTimeoutMin)
if self.reid is None:
self.reid = self.stack.estates.values()[0].eid # zeroth is channel master
remote = self.stack.estates[self.reid]
@ -1536,6 +1544,7 @@ class Messenger(Initiator):
self.sid = remote.sid
self.tid = remote.nextTid()
self.prep() # prepare .txData
self.tray = packeting.TxTray(stack=self.stack)
self.add(self.index)
def receive(self, packet):
@ -1546,10 +1555,32 @@ class Messenger(Initiator):
if packet.data['tk'] == raeting.trnsKinds.message:
if packet.data['pk'] == raeting.pcktKinds.ack:
self.done()
self.again()
elif packet.data['pk'] == raeting.pcktKinds.nack: # rejected
self.rejected()
def process(self):
'''
Perform time based processing of transaction
'''
if self.timeout > 0.0 and self.timer.expired:
self.remove()
console.concise("Messenger timed out at {0}\n".format(self.stack.store.stamp))
return
# need keep sending message until completed or timed out
if self.redoTimer.expired:
duration = min(
max(self.redoTimeoutMin,
self.redoTimer.duration) * 2.0,
self.redoTimeoutMin)
self.redoTimer.restart(duration=duration)
if self.txPacket:
if self.txPacket.data['pk'] == raeting.pcktKinds.message:
self.transmit(self.txPacket) # redo
console.concise("Messenger Redo Segment {0} at {1}\n".format(
self.tray.current, self.stack.store.stamp))
def prep(self):
'''
Prepare .txData
@ -1579,31 +1610,35 @@ class Messenger(Initiator):
self.remove()
return
if body is None:
body = odict()
if not self.tray.packets:
try:
self.tray.pack(data=self.txData, body=body)
except raeting.PacketError as ex:
console.terse(ex + '\n')
self.stack.incStat("packing_error")
self.remove()
return
packet = packeting.TxPacket(stack=self.stack,
kind=raeting.pcktKinds.message,
embody=body,
data=self.txData)
try:
packet.pack()
except raeting.PacketError as ex:
console.terse(ex + '\n')
self.stack.incStat("packing_error")
self.remove()
if self.tray.current >= len(self.tray.packets):
return
if packet.segmented:
self.segmentage = packet
for segment in self.segmentage.segments.values():
self.transmit(segment)
packet = self.tray.packets[self.tray.current]
self.transmit(packet)
self.stack.incStat("message_segment_tx")
console.concise("Messenger Do Message Segment {0} at {1}\n".format(
self.tray.current, self.stack.store.stamp))
self.tray.current += 1
def again(self):
'''
Process ack packet
'''
if self.tray.current >= len(self.tray.packets):
self.complete()
else:
self.transmit(packet)
self.message()
console.concise("Messenger Do Message at {0}\n".format(self.stack.store.stamp))
def done(self):
def complete(self):
'''
Complete transaction and remove
'''
@ -1627,7 +1662,11 @@ class Messengent(Correspondent):
RAET protocol Messengent Correspondent class Dual of Messenger
Generic Messages
'''
def __init__(self, **kwa):
Timeout = 10.0
RedoTimeoutMin = 1.0 # initial timeout
RedoTimeoutMax = 3.0 # max timeout
def __init__(self, redoTimeoutMin=None, redoTimeoutMax=None, **kwa):
'''
Setup instance
'''
@ -1636,7 +1675,12 @@ class Messengent(Correspondent):
emsg = "Missing required keyword argumens: '{0}'".format('reid')
raise TypeError(emsg)
super(Messengent, self).__init__(**kwa)
self.segmentage = None # special packet to hold segments if any
self.redoTimeoutMax = redoTimeoutMax or self.RedoTimeoutMax
self.redoTimeoutMin = redoTimeoutMin or self.RedoTimeoutMin
self.redoTimer = aiding.StoreTimer(self.stack.store,
duration=self.redoTimeoutMin)
remote = self.stack.estates[self.reid]
if not remote.allowed:
emsg = "Must be allowed first"
@ -1654,6 +1698,7 @@ class Messengent(Correspondent):
remote.rsid = self.sid #update last received rsid for estate
remote.rtid = self.tid #update last received rtid for estate
self.prep() # prepare .txData
self.tray = packeting.RxTray(stack=self.stack)
self.add(self.index)
def receive(self, packet):
@ -1669,6 +1714,30 @@ class Messengent(Correspondent):
elif packet.data['pk'] == raeting.pcktKinds.nack: # rejected
self.rejected()
def process(self):
'''
Perform time based processing of transaction
'''
if self.timeout > 0.0 and self.timer.expired:
self.nack()
console.concise("Messengent timed out at {0}\n".format(self.stack.store.stamp))
return
# need to include current segment in ack or resend
#if self.redoTimer.expired:
#duration = min(
#max(self.redoTimeoutMin,
#self.redoTimer.duration) * 2.0,
#self.redoTimeoutMax)
#self.redoTimer.restart(duration=duration)
#if self.txPacket:
#if self.txPacket.data['pk'] == raeting.pcktKinds.ack:
#self.transmit(self.txPacket) #redo
#console.concise("Messengent Redo Ack at {0}\n".format(self.stack.store.stamp))
def prep(self):
'''
Prepare .txData
@ -1690,27 +1759,23 @@ class Messengent(Correspondent):
'''
Process message packet
'''
console.verbose("segment count = {0} tid={1}\n".format(
self.rxPacket.data['sc'], self.tid))
if self.rxPacket.segmentive:
if not self.segmentage:
self.segmentage = packeting.RxPacket(stack=self.stack,
data=self.rxPacket.data)
self.segmentage.parseSegment(self.rxPacket)
if not self.segmentage.desegmentable():
return
self.segmentage.desegmentize()
if not self.stack.parseInner(self.segmentage):
return
body = self.segmentage.body.data
else:
if not self.stack.parseInner(self.rxPacket):
return
body = self.rxPacket.body.data
try:
body = self.tray.parse(self.rxPacket)
except raeting.PacketError as ex:
console.terse(ex + '\n')
self.incStat('parsing_message_error')
self.remove()
return
self.stack.rxMsgs.append(body)
self.ackMessage()
if self.tray.complete:
console.verbose("{0} received message body\n{1}\n".format(
self.stack.name, body))
self.stack.rxMsgs.append(body)
self.complete()
def ackMessage(self):
'''
Send ack to message
@ -1736,9 +1801,17 @@ class Messengent(Correspondent):
self.remove()
return
self.transmit(packet)
self.stack.incStat("message_segment_rx")
console.concise("Messengent Do Ack Segment at {0}\n".format(
self.stack.store.stamp))
def complete(self):
'''
Complete transaction and remove
'''
self.remove()
console.concise("Messengent Do Ack at {0}\n".format(self.stack.store.stamp))
self.stack.incStat("message_correspond_complete")
console.concise("Messengent Complete at {0}\n".format(self.stack.store.stamp))
self.stack.incStat("messagent_correspond_complete")
def rejected(self):
'''

View File

@ -2104,6 +2104,7 @@ def get_group_list(user=None, include_default=True):
is a member.
'''
group_names = None
ugroups = set()
if not isinstance(user, string_types):
raise Exception
if hasattr(os, 'getgrouplist'):
@ -2111,7 +2112,6 @@ def get_group_list(user=None, include_default=True):
log.trace('Trying os.getgrouplist for {0!r}'.format(user))
try:
group_names = list(os.getgrouplist(user, pwd.getpwnam(user).pw_gid))
log.trace('os.getgrouplist for user {0!r}: {1!r}'.format(user, group_names))
except Exception:
pass
else:
@ -2120,7 +2120,6 @@ def get_group_list(user=None, include_default=True):
try:
import pysss
group_names = list(pysss.getgrouplist(user))
log.trace('pysss.getgrouplist for user {0!r}: {1!r}'.format(user, group_names))
except Exception:
pass
if group_names is None:
@ -2136,7 +2135,7 @@ def get_group_list(user=None, include_default=True):
except KeyError:
# If for some reason the user does not have a default group
pass
log.trace('Generic group list for user {0!r}: {1!r}'.format(user, group_names))
ugroups.update(group_names)
if include_default is False:
# Historically, saltstack code for getting group lists did not
# include the default group. Some things may only want
@ -2144,11 +2143,12 @@ def get_group_list(user=None, include_default=True):
# default group.
try:
default_group = grp.getgrgid(pwd.getpwnam(user).pw_gid).gr_name
group_names.remove(default_group)
ugroups.remove(default_group)
except KeyError:
# If for some reason the user does not have a default group
pass
return sorted(group_names)
log.trace('Group list for user {0!r}: {1!r}'.format(user, sorted(ugroups)))
return sorted(ugroups)
def get_group_dict(user=None, include_default=True):
@ -2170,4 +2170,4 @@ def get_gid_list(user=None, include_default=True):
is a member.
'''
gid_list = [gid for (group, gid) in salt.utils.get_group_dict(user, include_default=include_default).items()]
return gid_list
return sorted(set(gid_list))

79
salt/utils/etcd_util.py Normal file
View File

@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
'''
Utilities for working with etcd
:depends: - python-etcd
This library sets up a client object for etcd, using the configuration passed
into the client() function. Normally, this is __opts__. Optionally, a profile
may be passed in. The following configurations are both valid:
.. code-block:: yaml
# No profile name
etcd.host: 127.0.0.1
etcd.port: 4001
# One or more profiles defined
my_etcd_config:
etcd.host: 127.0.0.1
etcd.port: 4001
Once configured, the client() function is passed a set of opts, and optionally,
the name of a profile to be used.
.. code-block:: python
import salt.utils.etcd_utils
client = salt.utils.etcd_utils.client(__opts__, profile='my_etcd_config')
It should be noted that some usages of etcd require a profile to be specified,
rather than top-level configurations. This being the case, it is better to
always use a named configuration profile, as shown above.
'''
# Import python libs
import logging
# Import third party libs
try:
import etcd
HAS_LIBS = True
except Exception:
HAS_LIBS = False
# Set up logging
log = logging.getLogger(__name__)
def get_conn(opts, profile=None):
'''
Return a client object for accessing etcd
'''
if profile:
conf = opts.get(profile, {})
else:
conf = opts
host = conf.get('etcd.host', '127.0.0.1')
port = conf.get('etcd.port', 4001)
return etcd.Client(host, port)
def tree(client, path):
'''
Recurse through etcd and return all values
'''
ret = {}
items = client.get(path)
for item in items.children:
comps = str(item.key).split('/')
if item.dir is True:
if item.key == path:
continue
ret[comps[-1]] = tree(client, item.key)
else:
ret[comps[-1]] = item.value
return ret

View File

@ -528,7 +528,7 @@ class CkMinions(object):
fun,
form)
def auth_check(self, auth_list, funs, tgt, tgt_type='glob'):
def auth_check(self, auth_list, funs, tgt, tgt_type='glob', groups=None):
'''
Returns a bool which defines if the requested function is authorized.
Used to evaluate the standard structure under external master
@ -537,7 +537,6 @@ class CkMinions(object):
# compound commands will come in a list so treat everything as a list
if not isinstance(funs, list):
funs = [funs]
for fun in funs:
for ind in auth_list:
if isinstance(ind, str):
@ -564,6 +563,23 @@ class CkMinions(object):
return True
return False
def gather_groups(self, auth_provider, user_groups, auth_list):
'''
Returns the list of groups, if any, for a given authentication provider type
Groups are defined as any dict in which a key has a trailing '%'
'''
group_perm_keys = filter(lambda(item): item.endswith('%'), auth_provider)
groups = {}
if group_perm_keys:
for group_perm in group_perm_keys:
for matcher in auth_provider[group_perm]:
if group_perm[:-1] in user_groups:
groups[group_perm] = matcher
for item in groups.values():
auth_list.append(item)
return auth_list
def wheel_check(self, auth_list, fun):
'''
Check special API permissions

View File

@ -389,7 +389,10 @@ class InstallLib(install_lib):
inp = self.get_inputs()
out = self.get_outputs()
idx = inp.index('build/lib/salt/templates/git/ssh-id-wrapper')
#idx = inp.index('build/lib/salt/templates/git/ssh-id-wrapper')
for i, word in enumerate(inp):
if word.endswith('salt/templates/git/ssh-id-wrapper'):
idx = i
filename = out[idx]
os.chmod(filename, 0755)

View File

@ -37,6 +37,12 @@ def parse():
help=('State if this listener will attach to a master or a '
'minion daemon, pass "master" or "minion"'))
parser.add_option('-f',
'--func_count',
default='',
help=('Retun a count of the number of minons which have '
'replied to a job with a given func.'))
options, args = parser.parse_args()
opts = {}
@ -67,15 +73,27 @@ def listen(sock_dir, node, id=None):
id=id
)
print(event.puburi)
jid_counter = 0
found_minions = []
while True:
ret = event.get_event(full=True)
if ret is None:
continue
print('Event fired at {0}'.format(time.asctime()))
print('*' * 25)
print('Tag: {0}'.format(ret['tag']))
print('Data:')
pprint.pprint(ret['data'])
if opts['func_count']:
data = ret.get('data', False)
if data:
if 'id' in data.keys() and data.get('id', False) not in found_minions:
if data['fun'] == opts['func_count']:
jid_counter += 1
found_minions.append(data['id'])
print('Reply received from [{0}]. Total replies now: [{1}].'.format(ret['data']['id'], jid_counter))
continue
else:
print('Event fired at {0}'.format(time.asctime()))
print('*' * 25)
print('Tag: {0}'.format(ret['tag']))
print('Data:')
pprint.pprint(ret['data'])
if __name__ == '__main__':

View File

@ -0,0 +1,137 @@
# -*- coding: utf-8 -*-
'''
:codeauthor: :email:`Nicole Thomas <nicole@saltstack.com>`
'''
# Import Python Libs
import os
import random
import string
# Import Salt Testing Libs
from salttesting import skipIf
from salttesting.helpers import (
destructiveTest,
ensure_in_syspath,
requires_system_grains
)
ensure_in_syspath('../../')
# Import Salt Libs
import integration
from salt.exceptions import CommandExecutionError
def __random_string(size=6):
'''
Generates a random username
'''
return 'RS-' + ''.join(
random.choice(string.ascii_uppercase + string.digits)
for x in range(size)
)
# Create group name strings for tests
ADD_GROUP = __random_string()
DEL_GROUP = __random_string()
CHANGE_GROUP = __random_string()
class MacGroupModuleTest(integration.ModuleCase):
'''
Integration tests for the mac_group module
'''
def setUp(self):
'''
Sets up test requirements
'''
super(MacGroupModuleTest, self).setUp()
os_grain = self.run_function('grains.item', ['kernel'])
if os_grain['kernel'] not in 'Darwin':
self.skipTest(
'Test not applicable to \'{kernel}\' kernel'.format(
**os_grain
)
)
@destructiveTest
@skipIf(os.geteuid() != 0, 'You must be logged in as root to run this test')
@requires_system_grains
def test_mac_group_add(self, grains=None):
'''
Tests the add group function
'''
try:
self.run_function('group.add', [ADD_GROUP, 3456])
group_info = self.run_function('group.info', [ADD_GROUP])
self.assertEqual(group_info['name'], ADD_GROUP)
except CommandExecutionError:
self.run_function('group.delete', [ADD_GROUP])
raise
@destructiveTest
@skipIf(os.geteuid() != 0, 'You must be logged in as root to run this test')
@requires_system_grains
def test_mac_group_delete(self, grains=None):
'''
Tests the delete group function
'''
# Create a group to delete - If unsuccessful, skip the test
if self.run_function('group.add', [DEL_GROUP, 4567]) is not True:
self.run_function('group.delete', [DEL_GROUP])
self.skipTest('Failed to create a group to delete')
try:
# Now try to delete the added group
ret = self.run_function('group.delete', [DEL_GROUP])
self.assertTrue(ret)
except CommandExecutionError:
raise
@destructiveTest
@skipIf(os.getuid() != 0, 'You must be logged in as root to run this test')
@requires_system_grains
def test_mac_group_chgid(self, grains=None):
'''
Tests changing the group id
'''
# Create a group to delete - If unsuccessful, skip the test
if self.run_function('group.add', [CHANGE_GROUP, 5678]) is not True:
self.run_function('group.delete', [CHANGE_GROUP])
self.skipTest('Failed to create a group to manipulate')
try:
self.run_function('group.chgid', [CHANGE_GROUP, 6789])
group_info = self.run_function('group.info', [CHANGE_GROUP])
self.assertEqual(group_info['gid'], 6789)
except AssertionError:
self.run_function('group.delete', [CHANGE_GROUP])
raise
@destructiveTest
@skipIf(os.geteuid() != 0, 'You must be logged in as root to run this test')
@requires_system_grains
def tearDown(self, grains=None):
'''
Clean up after tests
'''
# Delete ADD_GROUP
add_info = self.run_function('group.info', [ADD_GROUP])
if add_info:
self.run_function('group.delete', [ADD_GROUP])
# Delete DEL_GROUP if something failed
del_info = self.run_function('group.info', [DEL_GROUP])
if del_info:
self.run_function('group.delete', [DEL_GROUP])
# Delete CHANGE_GROUP
change_info = self.run_function('group.info', [CHANGE_GROUP])
if change_info:
self.run_function('group.delete', [CHANGE_GROUP])
if __name__ == '__main__':
from integration import run_tests
run_tests(MacGroupModuleTest)

View File

@ -34,6 +34,7 @@ def __random_string(size=6):
# Create user strings for tests
ADD_USER = __random_string()
DEL_USER = __random_string()
CHANGE_USER = __random_string()
class MacUserModuleTest(integration.ModuleCase):
@ -79,6 +80,7 @@ class MacUserModuleTest(integration.ModuleCase):
# Create a user to delete - If unsuccessful, skip the test
if self.run_function('user.add', [DEL_USER]) is not True:
self.run_function('user.delete', [DEL_USER])
self.skipTest('Failed to create a user to delete')
try:
@ -88,6 +90,53 @@ class MacUserModuleTest(integration.ModuleCase):
except CommandExecutionError:
raise
@destructiveTest
@skipIf(os.geteuid() != 0, 'You must be logged in as root to run this test')
@requires_system_grains
def test_mac_user_changes(self, grains=None):
'''
Tests mac_user functions that change user properties
'''
# Create a user to manipulate - if unsuccessful, skip the test
if self.run_function('user.add', [CHANGE_USER]) is not True:
self.run_function('user.delete', [CHANGE_USER])
self.skipTest('Failed to create a user')
try:
# Test mac_user.chudi
self.run_function('user.chuid', [CHANGE_USER, 4376])
uid_info = self.run_function('user.info', [CHANGE_USER])
self.assertEqual(uid_info['uid'], 4376)
# Test mac_user.chgid
self.run_function('user.chgid', [CHANGE_USER, 4376])
gid_info = self.run_function('user.info', [CHANGE_USER])
self.assertEqual(gid_info['gid'], 4376)
# Test mac.user.chshell
self.run_function('user.chshell', [CHANGE_USER, '/bin/zsh'])
shell_info = self.run_function('user.info', [CHANGE_USER])
self.assertEqual(shell_info['shell'], '/bin/zsh')
# Test mac_user.chhome
self.run_function('user.chhome', [CHANGE_USER, '/Users/foo'])
home_info = self.run_function('user.info', [CHANGE_USER])
self.assertEqual(home_info['home'], '/Users/foo')
# Test mac_user.chfullname
self.run_function('user.chfullname', [CHANGE_USER, 'Foo Bar'])
fullname_info = self.run_function('user.info', [CHANGE_USER])
self.assertEqual(fullname_info['fullname'], 'Foo Bar')
# Test mac_user.chgroups
self.run_function('user.chgroups', [CHANGE_USER, 'wheel'])
groups_info = self.run_function('user.info', [CHANGE_USER])
self.assertEqual(groups_info['groups'], ['wheel'])
except AssertionError:
self.run_function('user.delete', [CHANGE_USER])
raise
@destructiveTest
@skipIf(os.geteuid() != 0, 'You must be logged in as root to run this test')
@requires_system_grains
@ -96,16 +145,21 @@ class MacUserModuleTest(integration.ModuleCase):
Clean up after tests
'''
# Delete add_user
# Delete ADD_USER
add_info = self.run_function('user.info', [ADD_USER])
if add_info:
self.run_function('user.delete', [ADD_USER])
# Delete del_user if something failed
# Delete DEL_USER if something failed
del_info = self.run_function('user.info', [DEL_USER])
if del_info:
self.run_function('user.delete', [DEL_USER])
# Delete CHANGE_USER
change_info = self.run_function('user.info', [CHANGE_USER])
if change_info:
self.run_function('user.delete', [CHANGE_USER])
if __name__ == '__main__':
from integration import run_tests

View File

@ -114,6 +114,68 @@ class FileTest(integration.ModuleCase, integration.SaltReturnAssertsMixIn):
self.assertEqual(master_data, minion_data)
self.assertSaltTrueReturn(ret)
def test_managed_file_mode(self):
'''
file.managed, correct file permissions
'''
desired_mode = 504 # 0770 octal
name = os.path.join(integration.TMP, 'grail_scene33')
ret = self.run_state(
'file.managed', name=name, mode='0770', source='salt://grail/scene33'
)
resulting_mode = stat.S_IMODE(
os.stat(name).st_mode
)
self.assertEqual(oct(desired_mode), oct(resulting_mode))
self.assertSaltTrueReturn(ret)
def test_managed_file_mode_file_exists_replace(self):
'''
file.managed, existing file with replace=True, change permissions
'''
initial_mode = 504 # 0770 octal
desired_mode = 384 # 0600 octal
name = os.path.join(integration.TMP, 'grail_scene33')
ret = self.run_state(
'file.managed', name=name, mode=oct(initial_mode), source='salt://grail/scene33'
)
resulting_mode = stat.S_IMODE(
os.stat(name).st_mode
)
self.assertEqual(oct(initial_mode), oct(resulting_mode))
name = os.path.join(integration.TMP, 'grail_scene33')
ret = self.run_state(
'file.managed', name=name, replace=True, mode=oct(desired_mode), source='salt://grail/scene33'
)
resulting_mode = stat.S_IMODE(
os.stat(name).st_mode
)
self.assertEqual(oct(desired_mode), oct(resulting_mode))
self.assertSaltTrueReturn(ret)
def test_managed_file_mode_file_exists_noreplace(self):
'''
file.managed, existing file with replace=False, change permissions
'''
initial_mode = 504 # 0770 octal
desired_mode = 384 # 0600 octal
name = os.path.join(integration.TMP, 'grail_scene33')
ret = self.run_state(
'file.managed', name=name, replace=True, mode=oct(initial_mode), source='salt://grail/scene33'
)
ret = self.run_state(
'file.managed', name=name, replace=False, mode=oct(desired_mode), source='salt://grail/scene33'
)
resulting_mode = stat.S_IMODE(
os.stat(name).st_mode
)
self.assertEqual(oct(desired_mode), oct(resulting_mode))
self.assertSaltTrueReturn(ret)
def test_managed_dir_mode(self):
'''
Tests to ensure that file.managed creates directories with the

View File

@ -26,7 +26,7 @@ class MacGroupTestCase(TestCase):
mock_group = {'passwd': '*', 'gid': 0, 'name': 'test', 'members': ['root']}
mock_getgrall = [grp.struct_group(('foo', '*', 20, ['test']))]
# 'add' function tests: 5
# 'add' function tests: 6
@patch('salt.modules.mac_group.info', MagicMock(return_value=mock_group))
def test_add_group_exists(self):
@ -57,6 +57,15 @@ class MacGroupTestCase(TestCase):
self.assertRaises(SaltInvocationError, mac_group.add, 'foo', 'foo')
@patch('salt.modules.mac_group.info', MagicMock(return_value={}))
@patch('salt.modules.mac_group._list_gids', MagicMock(return_value=['3456']))
def test_add_gid_exists(self):
'''
Tests if the gid is already in use or not
'''
self.assertRaises(CommandExecutionError, mac_group.add, 'foo', 3456)
@patch('salt.modules.mac_group.info', MagicMock(return_value={}))
@patch('salt.modules.mac_group._list_gids', MagicMock(return_value=[]))
def test_add(self):
'''
Tests if specified group was added