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

This commit is contained in:
xiaofei.sun 2016-09-10 09:06:21 +08:00
commit 58160ed6c0
57 changed files with 2013 additions and 892 deletions

View File

@ -4,17 +4,16 @@
# Because the location to this file must be explicitly declared when using it,
# its actual location on disk is up to the user.
#fedora_rs:
# - fedora1
# - fedora2
# - fedora3
# - fedora4
# - fedora5
fedora_rs:
- fedora1
- fedora2
- fedora3
- fedora4
- fedora5
ubuntu_rs:
- ubuntu1
- ubuntu2
- ubuntu3
- ubuntu4
- ubuntu5
#ubuntu_rs:
# - ubuntu1
# - ubuntu2
# - ubuntu3
# - ubuntu4
# - ubuntu5

View File

@ -1,23 +1,27 @@
base_ec2:
provider: my-ec2-config
image: ami-e565ba8c
size: t1.micro
script: python-bootstrap
minion:
cheese: edam
# This file may be used in addition to, or instead of, the files in the
# cloud.profiles.d/ directory. The format for this file, and all files in that
# directory, is identical.
ubuntu_rs:
provider: my-openstack-rackspace-config
image: Ubuntu 12.04 LTS
size: 256 server
script: Ubuntu
minion:
cheese: edam
#base_ec2:
# provider: my-ec2-config
# image: ami-e565ba8c
# size: t1.micro
# script: python-bootstrap
# minion:
# cheese: edam
fedora_rs:
provider: my-openstack-rackspace-config
image: Fedora 17
size: 256 server
script: Fedora
minion:
cheese: edam
#ubuntu_rs:
# provider: my-openstack-rackspace-config
# image: Ubuntu 12.04 LTS
# size: 256 server
# script: Ubuntu
# minion:
# cheese: edam
#fedora_rs:
# provider: my-openstack-rackspace-config
# image: Fedora 17
# size: 256 server
# script: Fedora
# minion:
# cheese: edam

View File

@ -2,115 +2,114 @@
# Arch Linux
# https://wiki.archlinux.org/index.php/Arch_Linux_AMIs_for_Amazon_Web_Services
arch_ec2:
provider: my-ec2-config
image: ami-6ee95107
size: t1.micro
ssh_username: root
location: us-east-1
minion:
grains:
cloud: ec2-us-east-1
#arch_ec2:
# provider: my-ec2-config
# image: ami-6ee95107
# size: t1.micro
# ssh_username: root
# location: us-east-1
# minion:
# grains:
# cloud: ec2-us-east-1
arch_cloud-init_ec2:
provider: my-ec2-config
image: ami-596de730
size: t1.micro
ssh_username: root
location: us-east-1
minion:
grains:
cloud: ec2-us-east-1
#arch_cloud-init_ec2:
# provider: my-ec2-config
# image: ami-596de730
# size: t1.micro
# ssh_username: root
# location: us-east-1
# minion:
# grains:
# cloud: ec2-us-east-1
# Centos 6, available from ec2 marketplace for no-charge
# http://wiki.centos.org/Cloud/AWS
centos_6:
provider: my-ec2-config
image: ami-86e15bef
size: t1.micro
ssh_username: root
location: us-east-1
minion:
grains:
cloud: ec2-us-east-1
#centos_6:
# provider: my-ec2-config
# image: ami-86e15bef
# size: t1.micro
# ssh_username: root
# location: us-east-1
# minion:
# grains:
# cloud: ec2-us-east-1
# official Debian, available at no-charge from ec2 marketplace:
# http://wiki.debian.org/Cloud/AmazonEC2Image
debian_squeeze_ec2:
provider: my-ec2-config
image: ami-a121a6c8
size: t1.micro
ssh_username: admin
location: us-east-1
minion:
grains:
cloud: ec2-us-east-1
#debian_squeeze_ec2:
# provider: my-ec2-config
# image: ami-a121a6c8
# size: t1.micro
# ssh_username: admin
# location: us-east-1
# minion:
# grains:
# cloud: ec2-us-east-1
# Fedora project cloud images
# https://fedoraproject.org/wiki/Cloud_images
fedora_17_ec2:
provider: my-ec2-config
image: ami-2ea50247
size: t1.micro
ssh_username: ec2-user
location: us-east-1
minion:
grains:
cloud: ec2-us-east-1
#fedora_17_ec2:
# provider: my-ec2-config
# image: ami-2ea50247
# size: t1.micro
# ssh_username: ec2-user
# location: us-east-1
# minion:
# grains:
# cloud: ec2-us-east-1
fedora_18_ec2:
provider: my-ec2-config
image: ami-6145cc08
size: t1.micro
ssh_username: ec2-user
location: us-east-1
minion:
grains:
cloud: ec2-us-east-1
#fedora_18_ec2:
# provider: my-ec2-config
# image: ami-6145cc08
# size: t1.micro
# ssh_username: ec2-user
# location: us-east-1
# minion:
# grains:
# cloud: ec2-us-east-1
# FreeBSD 9.1
# http://www.daemonology.net/freebsd-on-ec2/
# this t1.micro instance does not auto-populate SSH keys see above link
freebsd_91_ec2:
provider: my-ec2-config
image: ami-5339bb3a
size: t1.micro
ssh_username: ec2-user
location: us-east-1
minion:
grains:
cloud: ec2-us-east-1
#freebsd_91_ec2:
# provider: my-ec2-config
# image: ami-5339bb3a
# size: t1.micro
# ssh_username: ec2-user
# location: us-east-1
# minion:
# grains:
# cloud: ec2-us-east-1
freebsd_91_4XL_ec2:
provider: my-ec2-config
image: ami-79088510
size: Cluster Compute 4XL
ssh_username: ec2-user
location: us-east-1
minion:
grains:
cloud: ec2-us-east-1
#freebsd_91_4XL_ec2:
# provider: my-ec2-config
# image: ami-79088510
# size: Cluster Compute 4XL
# ssh_username: ec2-user
# location: us-east-1
# minion:
# grains:
# cloud: ec2-us-east-1
# Canonical Ubuntu LTS images
# http://cloud-images.ubuntu.com/releases/
ubuntu_lucid_ec2:
provider: my-ec2-config
image: ami-21e47148
size: t1.micro
ssh_username: ubuntu
location: us-east-1
minion:
grains:
cloud: ec2-us-east-1
ubuntu_precise_ec2:
provider: my-ec2-config
image: ami-0145d268
size: t1.micro
ssh_username: ubuntu
location: us-east-1
minion:
grains:
cloud: ec2-us-east-1
#ubuntu_lucid_ec2:
# provider: my-ec2-config
# image: ami-21e47148
# size: t1.micro
# ssh_username: ubuntu
# location: us-east-1
# minion:
# grains:
# cloud: ec2-us-east-1
#ubuntu_precise_ec2:
# provider: my-ec2-config
# image: ami-0145d268
# size: t1.micro
# ssh_username: ubuntu
# location: us-east-1
# minion:
# grains:
# cloud: ec2-us-east-1

View File

@ -2,105 +2,104 @@
# Arch Linux
# https://wiki.archlinux.org/index.php/Arch_Linux_AMIs_for_Amazon_Web_Services
arch_ec2:
provider: my-ec2-config
image: ami-337d5b76
size: t1.micro
ssh_username: root
location: us-west-1
minion:
grains:
cloud: ec2-us-west-1
#arch_ec2:
# provider: my-ec2-config
# image: ami-337d5b76
# size: t1.micro
# ssh_username: root
# location: us-west-1
# minion:
# grains:
# cloud: ec2-us-west-1
arch_cloud-init_ec2:
provider: my-ec2-config
image: ami-6a5f7c2f
size: t1.micro
ssh_username: root
location: us-west-1
minion:
grains:
cloud: ec2-us-west-1
#arch_cloud-init_ec2:
# provider: my-ec2-config
# image: ami-6a5f7c2f
# size: t1.micro
# ssh_username: root
# location: us-west-1
# minion:
# grains:
# cloud: ec2-us-west-1
# Centos 6, available from ec2 marketplace for no-charge
# http://wiki.centos.org/Cloud/AWS
centos_6:
provider: my-ec2-config
image: ami-f61630b3
size: t1.micro
ssh_username: root
location: us-west-1
minion:
grains:
cloud: ec2-us-west-1
#centos_6:
# provider: my-ec2-config
# image: ami-f61630b3
# size: t1.micro
# ssh_username: root
# location: us-west-1
# minion:
# grains:
# cloud: ec2-us-west-1
# official Debian, available at no-charge from ec2 marketplace:
# http://wiki.debian.org/Cloud/AmazonEC2Image
debian_squeeze_ec2:
provider: my-ec2-config
image: ami-2c735269
size: t1.micro
ssh_username: admin
location: us-west-1
minion:
grains:
cloud: ec2-us-west-1
#debian_squeeze_ec2:
# provider: my-ec2-config
# image: ami-2c735269
# size: t1.micro
# ssh_username: admin
# location: us-west-1
# minion:
# grains:
# cloud: ec2-us-west-1
# Fedora project cloud images
# https://fedoraproject.org/wiki/Cloud_images
fedora_17_ec2:
provider: my-ec2-config
image: ami-877e24c2
size: t1.micro
ssh_username: ec2-user
location: us-west-1
minion:
grains:
cloud: ec2-us-west-1
#fedora_17_ec2:
# provider: my-ec2-config
# image: ami-877e24c2
# size: t1.micro
# ssh_username: ec2-user
# location: us-west-1
# minion:
# grains:
# cloud: ec2-us-west-1
fedora_18_ec2:
provider: my-ec2-config
image: ami-0899b94d
size: t1.micro
ssh_username: ec2-user
location: us-west-1
minion:
grains:
cloud: ec2-us-west-1
#fedora_18_ec2:
# provider: my-ec2-config
# image: ami-0899b94d
# size: t1.micro
# ssh_username: ec2-user
# location: us-west-1
# minion:
# grains:
# cloud: ec2-us-west-1
# FreeBSD 9.1
# http://www.daemonology.net/freebsd-on-ec2/
# this t1.micro instance does not auto-populate SSH keys see above link
freebsd_91_ec2:
provider: my-ec2-config
image: ami-4c8baa09
size: t1.micro
ssh_username: ec2-user
location: us-west-1
minion:
grains:
cloud: ec2-us-west-1
#freebsd_91_ec2:
# provider: my-ec2-config
# image: ami-4c8baa09
# size: t1.micro
# ssh_username: ec2-user
# location: us-west-1
# minion:
# grains:
# cloud: ec2-us-west-1
# Canonical Ubuntu LTS images
# http://cloud-images.ubuntu.com/releases/
ubuntu_lucid_ec2:
provider: my-ec2-config
image: ami-e63013a3
size: t1.micro
ssh_username: ubuntu
location: us-west-1
minion:
grains:
cloud: ec2-us-west-1
ubuntu_precise_ec2:
provider: my-ec2-config
image: ami-3ed8fb7b
size: t1.micro
ssh_username: ubuntu
location: us-west-1
minion:
grains:
cloud: ec2-us-west-1
#ubuntu_lucid_ec2:
# provider: my-ec2-config
# image: ami-e63013a3
# size: t1.micro
# ssh_username: ubuntu
# location: us-west-1
# minion:
# grains:
# cloud: ec2-us-west-1
#ubuntu_precise_ec2:
# provider: my-ec2-config
# image: ami-3ed8fb7b
# size: t1.micro
# ssh_username: ubuntu
# location: us-west-1
# minion:
# grains:
# cloud: ec2-us-west-1

View File

@ -2,115 +2,114 @@
# Arch Linux
# https://wiki.archlinux.org/index.php/Arch_Linux_AMIs_for_Amazon_Web_Services
arch_ec2:
provider: my-ec2-config
image: ami-bcf77e8c
size: t1.micro
ssh_username: root
location: us-west-2
minion:
grains:
cloud: ec2-us-west-2
#arch_ec2:
# provider: my-ec2-config
# image: ami-bcf77e8c
# size: t1.micro
# ssh_username: root
# location: us-west-2
# minion:
# grains:
# cloud: ec2-us-west-2
arch_cloud-init_ec2:
provider: my-ec2-config
image: ami-6a5f7c2f
size: t1.micro
ssh_username: root
location: us-west-2
minion:
grains:
cloud: ec2-us-west-2
#arch_cloud-init_ec2:
# provider: my-ec2-config
# image: ami-6a5f7c2f
# size: t1.micro
# ssh_username: root
# location: us-west-2
# minion:
# grains:
# cloud: ec2-us-west-2
# Centos 6, available from ec2 marketplace for no-charge
# http://wiki.centos.org/Cloud/AWS
centos_6:
provider: my-ec2-config
image: ami-de5bd2ee
size: t1.micro
ssh_username: root
location: us-west-2
minion:
grains:
cloud: ec2-us-west-2
#centos_6:
# provider: my-ec2-config
# image: ami-de5bd2ee
# size: t1.micro
# ssh_username: root
# location: us-west-2
# minion:
# grains:
# cloud: ec2-us-west-2
# official Debian, available at no-charge from ec2 marketplace:
# http://wiki.debian.org/Cloud/AmazonEC2Image
debian_squeeze_ec2:
provider: my-ec2-config
image: ami-e4da52d4
size: t1.micro
ssh_username: admin
location: us-west-2
minion:
grains:
cloud: ec2-us-west-2
#debian_squeeze_ec2:
# provider: my-ec2-config
# image: ami-e4da52d4
# size: t1.micro
# ssh_username: admin
# location: us-west-2
# minion:
# grains:
# cloud: ec2-us-west-2
# Fedora project cloud images
# https://fedoraproject.org/wiki/Cloud_images
fedora_17_ec2:
provider: my-ec2-config
image: ami-8e69e5be
size: t1.micro
ssh_username: ec2-user
location: us-west-2
minion:
grains:
cloud: ec2-us-west-2
#fedora_17_ec2:
# provider: my-ec2-config
# image: ami-8e69e5be
# size: t1.micro
# ssh_username: ec2-user
# location: us-west-2
# minion:
# grains:
# cloud: ec2-us-west-2
fedora_18_ec2:
provider: my-ec2-config
image: ami-0266ed32
size: t1.micro
ssh_username: ec2-user
location: us-west-2
minion:
grains:
cloud: ec2-us-west-2
#fedora_18_ec2:
# provider: my-ec2-config
# image: ami-0266ed32
# size: t1.micro
# ssh_username: ec2-user
# location: us-west-2
# minion:
# grains:
# cloud: ec2-us-west-2
# FreeBSD 9.1
# http://www.daemonology.net/freebsd-on-ec2/
# this t1.micro instance does not auto-populate SSH keys see above link
freebsd_91_ec2:
provider: my-ec2-config
image: ami-aa09819a
size: t1.micro
ssh_username: ec2-user
location: us-west-2
minion:
grains:
cloud: ec2-us-west-2
#freebsd_91_ec2:
# provider: my-ec2-config
# image: ami-aa09819a
# size: t1.micro
# ssh_username: ec2-user
# location: us-west-2
# minion:
# grains:
# cloud: ec2-us-west-2
freebsd_91_4XL_ec2:
provider: my-ec2-config
image: ami-66169e56
size: Cluster Compute 4XL
ssh_username: ec2-user
location: us-west-2
minion:
grains:
cloud: ec2-us-west-2
#freebsd_91_4XL_ec2:
# provider: my-ec2-config
# image: ami-66169e56
# size: Cluster Compute 4XL
# ssh_username: ec2-user
# location: us-west-2
# minion:
# grains:
# cloud: ec2-us-west-2
# Canonical Ubuntu LTS images
# http://cloud-images.ubuntu.com/releases/
ubuntu_lucid_ec2:
provider: my-ec2-config
image: ami-6ec8425e
size: t1.micro
ssh_username: ubuntu
location: us-west-2
minion:
grains:
cloud: ec2-us-west-2
ubuntu_precise_ec2:
provider: my-ec2-config
image: ami-e0941ed0
size: t1.micro
ssh_username: ubuntu
location: us-west-2
minion:
grains:
cloud: ec2-us-west-2
#ubuntu_lucid_ec2:
# provider: my-ec2-config
# image: ami-6ec8425e
# size: t1.micro
# ssh_username: ubuntu
# location: us-west-2
# minion:
# grains:
# cloud: ec2-us-west-2
#ubuntu_precise_ec2:
# provider: my-ec2-config
# image: ami-e0941ed0
# size: t1.micro
# ssh_username: ubuntu
# location: us-west-2
# minion:
# grains:
# cloud: ec2-us-west-2

View File

@ -397,7 +397,17 @@
# access the master has to the minion.
#disable_modules: [cmd,test]
#disable_returners: []
#
# This is the reverse of disable_modules. The default, like disable_modules, is the empty list,
# but if this option is set to *anything* then *only* those modules will load.
# Note that this is a very large hammer and it can be quite difficult to keep the minion working
# the way you think it should since Salt uses many modules internally itself. At a bare minimum
# you need the following enabled or else the minion won't start.
#whitelist_modules:
# - cmdmod
# - test
# - config
# Modules can be loaded from arbitrary paths. This enables the easy deployment
# of third party modules. Modules for returners and minions can be loaded.
# Specify a list of extra directories to search for minion modules and

View File

@ -5,6 +5,11 @@ Salt 2016.3.3 Release Notes
Version 2016.3.3 is a bugfix release for :doc:`2016.3.0
</topics/releases/2016.3.0>`.
Known Issues
------------
:issue:`36055`: Salt Cloud events (``salt/cloud``) are not generated on the
master event bus when provisioning cloud systems.
Changes for v2016.3.2..2016.3.3
-------------------------------

View File

@ -91,7 +91,7 @@ the firewall tutorial is available :doc:`here </topics/tutorials/firewall>`.
Finding the Salt Master
~~~~~~~~~~~~~~~~~~~~~~~
When a minion starts, by default it searches for a system that resolves to the ``salt`` hostname`` on the network.
When a minion starts, by default it searches for a system that resolves to the ``salt`` hostname on the network.
If found, the minion initiates the handshake and key authentication process with the Salt master.
This means that the easiest configuration approach is to set internal DNS to resolve the name ``salt`` back to the Salt Master IP.

View File

@ -154,7 +154,10 @@ def prep_trans_tar(file_client, chunks, file_refs, pillar=None, id_=None):
for ref in file_refs[saltenv]:
for name in ref:
short = salt.utils.url.parse(name)[0]
try:
path = file_client.cache_file(name, saltenv, cachedir=cachedir)
except IOError:
path = ''
if path:
tgt = os.path.join(env_root, short)
tgt_dir = os.path.dirname(tgt)
@ -162,7 +165,10 @@ def prep_trans_tar(file_client, chunks, file_refs, pillar=None, id_=None):
os.makedirs(tgt_dir)
shutil.copy(path, tgt)
continue
try:
files = file_client.cache_dir(name, saltenv, cachedir=cachedir)
except IOError:
files = ''
if files:
for filename in files:
fn = filename[filename.find(short) + len(short):]

View File

@ -24,6 +24,21 @@ Set up the cloud configuration at ``/etc/salt/cloud.providers`` or
password: JHGhgsayu32jsa
driver: opennebula
This driver supports accessing new VM instances via DNS entry instead
of IP address. To enable this feature, in the provider or profile file
add `fqdn_base` with a value matching the base of your fully-qualified
domain name. Example:
.. code-block:: yaml
my-opennebula-config:
[...]
fqdn_base: <my.basedomain.com>
[...]
The driver will prepend the hostname to the fqdn_base and do a DNS lookup
to find the IP of the new VM.
.. note:
Whenever ``data`` is provided as a kwarg to a function and the
@ -61,10 +76,7 @@ from salt.exceptions import (
SaltCloudNotFound,
SaltCloudSystemExit
)
from salt.utils import is_true
# Import Salt Cloud Libs
import salt.utils.cloud
import salt.utils
# Import Third Party Libs
try:
@ -322,7 +334,7 @@ def list_nodes_select(call=None):
'The list_nodes_full function must be called with -f or --function.'
)
return salt.utils.cloud.list_nodes_select(
return __utils__['cloud.list_nodes_select'](
list_nodes_full('function'), __opts__['query.selection'], call,
)
@ -747,6 +759,27 @@ def get_template_id(kwargs=None, call=None):
return ret
def get_template(vm_):
r'''
Return the template id for a VM.
.. versionadded:: Carbon
vm\_
The VM dictionary for which to obtain a template.
'''
vm_template = str(config.get_cloud_config_value(
'template', vm_, __opts__, search_global=False
))
try:
return list_templates()[vm_template]['id']
except KeyError:
raise SaltCloudNotFound(
'The specified template, \'{0}\', could not be found.'.format(vm_template)
)
def get_vm_id(kwargs=None, call=None):
'''
Returns a virtual machine's ID from the given virtual machine's name.
@ -826,12 +859,28 @@ def create(vm_):
vm\_
The dictionary use to create a VM.
Optional vm_ dict options for overwriting template:
region_id
Optional - OpenNebula Zone ID
memory
Optional - In MB
cpu
Optional - Percent of host CPU to allocate
vcpu
Optional - Amount of vCPUs to allocate
CLI Example:
.. code-block:: bash
salt-cloud -p my-opennebula-profile vm_name
salt-cloud -p my-opennebula-profile vm_name memory=16384 cpu=2.5 vcpu=16
'''
try:
# Check for required profile parameters before sending any API calls.
@ -851,20 +900,23 @@ def create(vm_):
'event',
'starting create',
'salt/cloud/{0}/creating'.format(vm_['name']),
{
args={
'name': vm_['name'],
'profile': vm_['profile'],
'provider': vm_['driver'],
},
sock_dir=__opts__['sock_dir'],
transport=__opts__['transport']
)
log.info('Creating Cloud VM {0}'.format(vm_['name']))
kwargs = {
'name': vm_['name'],
'image_id': get_image(vm_),
'template_id': get_template(vm_),
'region_id': get_location(vm_),
}
if 'template' in vm_:
kwargs['image_id'] = get_template_id({'name': vm_['template']})
private_networking = config.get_cloud_config_value(
'private_networking', vm_, __opts__, search_global=False, default=None
@ -875,20 +927,41 @@ def create(vm_):
'event',
'requesting instance',
'salt/cloud/{0}/requesting'.format(vm_['name']),
{'kwargs': kwargs},
args={'kwargs': kwargs},
sock_dir=__opts__['sock_dir'],
)
region = ''
if kwargs['region_id'] is not None:
region = 'SCHED_REQUIREMENTS="ID={0}"'.format(kwargs['region_id'])
template = []
if kwargs.get('region_id'):
template.append('SCHED_REQUIREMENTS="ID={0}"'.format(kwargs.get('region_id')))
if vm_.get('memory'):
template.append('MEMORY={0}'.format(vm_.get('memory')))
if vm_.get('cpu'):
template.append('CPU={0}'.format(vm_.get('cpu')))
if vm_.get('vcpu'):
template.append('VCPU={0}'.format(vm_.get('vcpu')))
template_args = "\n".join(template)
try:
server, user, password = _get_xml_rpc()
auth = ':'.join([user, password])
server.one.template.instantiate(auth,
int(kwargs['image_id']),
cret = server.one.template.instantiate(auth,
int(kwargs['template_id']),
kwargs['name'],
False,
region)
template_args)
if not cret[0]:
log.error(
'Error creating {0} on OpenNebula\n\n'
'The following error was returned when trying to '
'instantiate the template: {1}'.format(
vm_['name'],
cret[1]
),
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG
)
return False
except Exception as exc:
log.error(
'Error creating {0} on OpenNebula\n\n'
@ -902,6 +975,10 @@ def create(vm_):
)
return False
fqdn = vm_.get('fqdn_base')
if fqdn is not None:
fqdn = '{0}.{1}'.format(vm_['name'], fqdn)
def __query_node_data(vm_name):
node_data = show_instance(vm_name, call='action')
if not node_data:
@ -913,7 +990,7 @@ def create(vm_):
return node_data
try:
data = salt.utils.cloud.wait_for_ip(
data = __utils__['cloud.wait_for_ip'](
__query_node_data,
update_args=(vm_['name'],),
timeout=config.get_cloud_config_value(
@ -940,10 +1017,15 @@ def create(vm_):
)
)
if fqdn:
vm_['ssh_host'] = fqdn
private_ip = '0.0.0.0'
else:
try:
private_ip = data['private_ips'][0]
except KeyError:
private_ip = data['template']['nic']['ip']
vm_['ssh_host'] = private_ip
ssh_username = config.get_cloud_config_value(
'ssh_username', vm_, __opts__, default='root'
@ -951,9 +1033,8 @@ def create(vm_):
vm_['username'] = ssh_username
vm_['key_filename'] = key_filename
vm_['ssh_host'] = private_ip
ret = salt.utils.cloud.bootstrap(vm_, __opts__)
ret = __utils__['cloud.bootstrap'](vm_, __opts__)
ret['id'] = data['id']
ret['image'] = vm_['image']
@ -974,11 +1055,12 @@ def create(vm_):
'event',
'created instance',
'salt/cloud/{0}/created'.format(vm_['name']),
{
args={
'name': vm_['name'],
'profile': vm_['profile'],
'provider': vm_['driver'],
},
sock_dir=__opts__['sock_dir'],
)
return ret
@ -1011,7 +1093,8 @@ def destroy(name, call=None):
'event',
'destroying instance',
'salt/cloud/{0}/destroying'.format(name),
{'name': name},
args={'name': name},
sock_dir=__opts__['sock_dir'],
)
server, user, password = _get_xml_rpc()
@ -1024,11 +1107,12 @@ def destroy(name, call=None):
'event',
'destroyed instance',
'salt/cloud/{0}/destroyed'.format(name),
{'name': name},
args={'name': name},
sock_dir=__opts__['sock_dir'],
)
if __opts__.get('update_cachedir', False) is True:
salt.utils.cloud.delete_minion_cachedir(
__utils__['cloud.delete_minion_cachedir'](
name,
__active_provider_name__.split(':')[0],
__opts__
@ -1373,7 +1457,7 @@ def image_persistent(call=None, kwargs=None):
server, user, password = _get_xml_rpc()
auth = ':'.join([user, password])
response = server.one.image.persistent(auth, int(image_id), is_true(persist))
response = server.one.image.persistent(auth, int(image_id), salt.utils.is_true(persist))
data = {
'action': 'image.persistent',
@ -1720,7 +1804,7 @@ def show_instance(name, call=None):
)
node = _get_node(name)
salt.utils.cloud.cache_node(node, __active_provider_name__, __opts__)
__utils__['cloud.cache_node'](node, __active_provider_name__, __opts__)
return node
@ -2586,7 +2670,7 @@ def vm_allocate(call=None, kwargs=None):
server, user, password = _get_xml_rpc()
auth = ':'.join([user, password])
response = server.one.vm.allocate(auth, data, is_true(hold))
response = server.one.vm.allocate(auth, data, salt.utils.is_true(hold))
ret = {
'action': 'vm.allocate',
@ -2816,7 +2900,7 @@ def vm_deploy(name, kwargs=None, call=None):
response = server.one.vm.deploy(auth,
int(vm_id),
int(host_id),
is_true(capacity_maintained),
salt.utils.is_true(capacity_maintained),
int(datastore_id))
data = {
@ -3285,8 +3369,8 @@ def vm_migrate(name, kwargs=None, call=None):
response = server.one.vm.migrate(auth,
vm_id,
int(host_id),
is_true(live_migration),
is_true(capacity_maintained),
salt.utils.is_true(live_migration),
salt.utils.is_true(capacity_maintained),
int(datastore_id))
data = {
@ -3404,7 +3488,7 @@ def vm_resize(name, kwargs=None, call=None):
server, user, password = _get_xml_rpc()
auth = ':'.join([user, password])
vm_id = int(get_vm_id(kwargs={'name': name}))
response = server.one.vm.resize(auth, vm_id, data, is_true(capacity_maintained))
response = server.one.vm.resize(auth, vm_id, data, salt.utils.is_true(capacity_maintained))
ret = {
'action': 'vm.resize',
@ -4336,8 +4420,7 @@ def _list_nodes(full=False):
for nic in vm.find('TEMPLATE').findall('NIC'):
try:
private_ips.append(nic.find('IP').text)
except AttributeError:
# There is no private IP; skip it
except Exception:
pass
vms[name]['id'] = vm.find('ID').text

View File

@ -1490,7 +1490,6 @@ def os_data():
osrelease_info[idx] = int(value)
grains['osrelease_info'] = tuple(osrelease_info)
grains['osmajorrelease'] = str(grains['osrelease_info'][0]) # This will be an integer in the two releases
salt.utils.warn_until('Nitrogen', 'The "osmajorrelease" will be a type of an integer.')
os_name = grains['os' if grains.get('os') in (
'FreeBSD', 'OpenBSD', 'NetBSD', 'Mac', 'Raspbian') else 'osfullname']
grains['osfinger'] = '{0}-{1}'.format(

View File

@ -1941,7 +1941,6 @@ class Minion(MinionBase):
log.debug('Forwarding salt error event tag={tag}'.format(tag=tag))
self._fire_master(data, tag)
elif package.startswith('salt/auth/creds'):
tag, data = salt.utils.event.MinionEvent.unpack(package)
key = tuple(data['key'])
log.debug('Updating auth data for {0}: {1} -> {2}'.format(
key, salt.crypt.AsyncAuth.creds_map.get(key), data['creds']))

View File

@ -303,6 +303,22 @@ def _run(cmd,
'Setting value to an empty string'.format(bad_env_key))
env[bad_env_key] = ''
if _check_loglevel(output_loglevel) is not None:
# Always log the shell commands at INFO unless quiet logging is
# requested. The command output is what will be controlled by the
# 'loglevel' parameter.
msg = (
'Executing command {0}{1}{0} {2}in directory \'{3}\'{4}'.format(
'\'' if not isinstance(cmd, list) else '',
cmd,
'as user \'{0}\' '.format(runas) if runas else '',
cwd,
'. Executing command in the background, no output will be '
'logged.' if bg else ''
)
)
log.info(log_callback(msg))
if runas and salt.utils.is_windows():
if not password:
msg = 'password is a required argument for runas on Windows'
@ -368,21 +384,6 @@ def _run(cmd,
)
)
if _check_loglevel(output_loglevel) is not None:
# Always log the shell commands at INFO unless quiet logging is
# requested. The command output is what will be controlled by the
# 'loglevel' parameter.
msg = (
'Executing command {0}{1}{0} {2}in directory \'{3}\'{4}'.format(
'\'' if not isinstance(cmd, list) else '',
cmd,
'as user \'{0}\' '.format(runas) if runas else '',
cwd,
' in the background, no output will be logged' if bg else ''
)
)
log.info(log_callback(msg))
if reset_system_locale is True:
if not salt.utils.is_windows():
# Default to C!
@ -680,6 +681,7 @@ def run(cmd,
saltenv='base',
use_vt=False,
bg=False,
password=None,
**kwargs):
r'''
Execute the passed command and return the output as a string
@ -699,8 +701,8 @@ def run(cmd,
:param str runas: User to run script as. If running on a Windows minion you
must also pass a password
:param str password: Windows only. Pass a password if you specify runas.
This parameter will be ignored for other OS's
:param str password: Windows only. Required when specifying ``runas``. This
parameter will be ignored on non-Windows platforms.
.. versionadded:: 2016.3.0
@ -848,7 +850,7 @@ def run(cmd,
pillarenv=kwargs.get('pillarenv'),
pillar_override=kwargs.get('pillar'),
use_vt=use_vt,
password=kwargs.get('password', None),
password=password,
bg=bg)
log_callback = _check_cb(log_callback)
@ -888,6 +890,7 @@ def shell(cmd,
saltenv='base',
use_vt=False,
bg=False,
password=None,
**kwargs):
'''
Execute the passed command and return the output as a string.
@ -906,15 +909,16 @@ def shell(cmd,
:param str runas: User to run script as. If running on a Windows minion you
must also pass a password
:param str password: Windows only. Pass a password if you specify runas.
This parameter will be ignored for other OS's
:param str password: Windows only. Required when specifying ``runas``. This
parameter will be ignored on non-Windows platforms.
.. versionadded:: 2016.3.0
:param int shell: Shell to execute under. Defaults to the system default
shell.
:param bool bg: If True, run command in background and do not await or deliver it's results
:param bool bg: If True, run command in background and do not await or
deliver its results
:param list env: A list of environment variables to be set prior to
execution.
@ -1053,6 +1057,7 @@ def shell(cmd,
use_vt=use_vt,
python_shell=python_shell,
bg=bg,
password=password,
**kwargs)
@ -1074,6 +1079,7 @@ def run_stdout(cmd,
ignore_retcode=False,
saltenv='base',
use_vt=False,
password=None,
**kwargs):
'''
Execute a command, and only return the standard out
@ -1090,8 +1096,8 @@ def run_stdout(cmd,
:param str runas: User to run script as. If running on a Windows minion you
must also pass a password
:param str password: Windows only. Pass a password if you specify runas.
This parameter will be ignored for other OS's
:param str password: Windows only. Required when specifying ``runas``. This
parameter will be ignored on non-Windows platforms.
.. versionadded:: 2016.3.0
@ -1212,6 +1218,7 @@ def run_stdout(cmd,
pillarenv=kwargs.get('pillarenv'),
pillar_override=kwargs.get('pillar'),
use_vt=use_vt,
password=password,
**kwargs)
log_callback = _check_cb(log_callback)
@ -1255,6 +1262,7 @@ def run_stderr(cmd,
ignore_retcode=False,
saltenv='base',
use_vt=False,
password=None,
**kwargs):
'''
Execute a command and only return the standard error
@ -1271,8 +1279,8 @@ def run_stderr(cmd,
:param str runas: User to run script as. If running on a Windows minion you
must also pass a password
:param str password: Windows only. Pass a password if you specify runas.
This parameter will be ignored for other OS's
:param str password: Windows only. Required when specifying ``runas``. This
parameter will be ignored on non-Windows platforms.
.. versionadded:: 2016.3.0
@ -1394,7 +1402,7 @@ def run_stderr(cmd,
saltenv=saltenv,
pillarenv=kwargs.get('pillarenv'),
pillar_override=kwargs.get('pillar'),
password=kwargs.get('password', None))
password=password)
log_callback = _check_cb(log_callback)
@ -1438,6 +1446,7 @@ def run_all(cmd,
saltenv='base',
use_vt=False,
redirect_stderr=False,
password=None,
**kwargs):
'''
Execute the passed command and return a dict of return data
@ -1454,8 +1463,8 @@ def run_all(cmd,
:param str runas: User to run script as. If running on a Windows minion you
must also pass a password
:param str password: Windows only. Pass a password if you specify runas.
This parameter will be ignored for other OS's
:param str password: Windows only. Required when specifying ``runas``. This
parameter will be ignored on non-Windows platforms.
.. versionadded:: 2016.3.0
@ -1587,7 +1596,7 @@ def run_all(cmd,
pillarenv=kwargs.get('pillarenv'),
pillar_override=kwargs.get('pillar'),
use_vt=use_vt,
password=kwargs.get('password', None))
password=password)
log_callback = _check_cb(log_callback)
@ -1629,6 +1638,7 @@ def retcode(cmd,
ignore_retcode=False,
saltenv='base',
use_vt=False,
password=None,
**kwargs):
'''
Execute a shell command and return the command's return code.
@ -1645,8 +1655,8 @@ def retcode(cmd,
:param str runas: User to run script as. If running on a Windows minion you
must also pass a password
:param str password: Windows only. Pass a password if you specify runas.
This parameter will be ignored for other OS's
:param str password: Windows only. Required when specifying ``runas``. This
parameter will be ignored on non-Windows platforms.
.. versionadded:: 2016.3.0
@ -1770,7 +1780,7 @@ def retcode(cmd,
pillarenv=kwargs.get('pillarenv'),
pillar_override=kwargs.get('pillar'),
use_vt=use_vt,
password=kwargs.get('password', None))
password=password)
log_callback = _check_cb(log_callback)
@ -1807,6 +1817,7 @@ def _retcode_quiet(cmd,
ignore_retcode=False,
saltenv='base',
use_vt=False,
password=None,
**kwargs):
'''
Helper for running commands quietly for minion startup.
@ -1829,6 +1840,7 @@ def _retcode_quiet(cmd,
ignore_retcode=ignore_retcode,
saltenv=saltenv,
use_vt=use_vt,
password=password,
**kwargs)
@ -1851,6 +1863,7 @@ def script(source,
saltenv='base',
use_vt=False,
bg=False,
password=None,
**kwargs):
'''
Download a script from a remote location and execute the script locally.
@ -1879,8 +1892,8 @@ def script(source,
:param str runas: User to run script as. If running on a Windows minion you
must also pass a password
:param str password: Windows only. Pass a password if you specify runas.
This parameter will be ignored for other OS's
:param str password: Windows only. Required when specifying ``runas``. This
parameter will be ignored on non-Windows platforms.
.. versionadded:: 2016.3.0
@ -2038,8 +2051,8 @@ def script(source,
pillarenv=kwargs.get('pillarenv'),
pillar_override=kwargs.get('pillar'),
use_vt=use_vt,
password=kwargs.get('password', None),
bg=bg)
bg=bg,
password=password)
_cleanup_tempfile(path)
return ret
@ -2061,6 +2074,7 @@ def script_retcode(source,
output_loglevel='debug',
log_callback=None,
use_vt=False,
password=None,
**kwargs):
'''
Download a script from a remote location and execute the script locally.
@ -2093,8 +2107,8 @@ def script_retcode(source,
:param str runas: User to run script as. If running on a Windows minion you
must also pass a password
:param str password: Windows only. Pass a password if you specify runas.
This parameter will be ignored for other OS's
:param str password: Windows only. Required when specifying ``runas``. This
parameter will be ignored on non-Windows platforms.
.. versionadded:: 2016.3.0
@ -2201,6 +2215,7 @@ def script_retcode(source,
output_loglevel=output_loglevel,
log_callback=log_callback,
use_vt=use_vt,
password=password,
**kwargs)['retcode']
@ -2348,10 +2363,10 @@ def run_chroot(root,
This function runs :mod:`cmd.run_all <salt.modules.cmdmod.run_all>` wrapped
within a chroot, with dev and proc mounted in the chroot
root:
root
Path to the root of the jail to use.
cmd:
cmd
The command to run. ex: 'ls -lart /home'
cwd
@ -2586,6 +2601,7 @@ def powershell(cmd,
ignore_retcode=False,
saltenv='base',
use_vt=False,
password=None,
**kwargs):
'''
Execute the passed PowerShell command and return the output as a string.
@ -2613,8 +2629,8 @@ def powershell(cmd,
:param str runas: User to run script as. If running on a Windows minion you
must also pass a password
:param str password: Windows only. Pass a password if you specify runas.
This parameter will be ignored for other OS's
:param str password: Windows only. Required when specifying ``runas``. This
parameter will be ignored on non-Windows platforms.
.. versionadded:: 2016.3.0
@ -2727,6 +2743,7 @@ def powershell(cmd,
saltenv=saltenv,
use_vt=use_vt,
python_shell=python_shell,
password=password,
**kwargs)
try:
@ -2749,7 +2766,9 @@ def run_bg(cmd,
output_loglevel='debug',
log_callback=None,
reset_system_locale=True,
ignore_retcode=False,
saltenv='base',
password=None,
**kwargs):
r'''
.. versionadded: 2016.3.0
@ -2771,6 +2790,11 @@ def run_bg(cmd,
:param str runas: User to run script as. If running on a Windows minion you
must also pass a password
:param str password: Windows only. Required when specifying ``runas``. This
parameter will be ignored on non-Windows platforms.
.. versionadded:: 2016.3.0
:param str shell: Shell to execute under. Defaults to the system default
shell.
@ -2896,12 +2920,11 @@ def run_bg(cmd,
log_callback=log_callback,
timeout=timeout,
reset_system_locale=reset_system_locale,
# ignore_retcode=ignore_retcode,
ignore_retcode=ignore_retcode,
saltenv=saltenv,
pillarenv=kwargs.get('pillarenv'),
pillar_override=kwargs.get('pillar'),
# password=kwargs.get('password', None),
)
password=password)
return {
'pid': res['pid']

View File

@ -609,11 +609,10 @@ def agent_join(consul_url=None, address=None, **kwargs):
query_params=query_params)
if res['res']:
ret['res'] = True
ret['message'] = ('Agent maintenance mode '
'{0}ed.'.format(kwargs['enable']))
ret['message'] = 'Agent joined the cluster'
else:
ret['res'] = False
ret['message'] = 'Unable to change maintenance mode for agent.'
ret['message'] = 'Unable to join the cluster.'
return ret

View File

@ -793,7 +793,7 @@ def push(path, keep_symlinks=False, upload_path=None, remove_source=False):
load_path_normal = os.path.normpath(load_path)
# If this is Windows and a drive letter is present, remove it
load_path_split_drive = os.path.splitdrive(load_path_normal)[1:]
load_path_split_drive = os.path.splitdrive(load_path_normal)[1]
# Finally, split the remaining path into a list for delivery to the master
load_path_list = os.path.split(load_path_split_drive)

File diff suppressed because it is too large Load Diff

View File

@ -41,8 +41,11 @@ def _check_systemd_salt_config():
sysctl_dir = os.path.split(conf)[0]
if not os.path.exists(sysctl_dir):
os.makedirs(sysctl_dir)
with salt.utils.fopen(conf, 'w'):
pass
try:
salt.utils.fopen(conf, 'w').close()
except (IOError, OSError):
msg = 'Could not create file: {0}'
raise CommandExecutionError(msg.format(conf))
return conf

View File

@ -12,7 +12,6 @@ A module to manage software on Windows
# Import python libs
from __future__ import absolute_import
import errno
import os
import locale
import logging
@ -28,7 +27,7 @@ except ImportError:
# pylint: enable=import-error
# Import salt libs
from salt.exceptions import CommandExecutionError, SaltRenderError
from salt.exceptions import SaltRenderError
import salt.utils
import salt.syspaths
from salt.exceptions import MinionError
@ -721,9 +720,14 @@ def install(name=None, refresh=False, pkgs=None, saltenv='base', **kwargs):
start_in=cache_path,
trigger_type='Once',
start_date='1975-01-01',
start_time='01:00')
start_time='01:00',
ac_only=False,
stop_if_on_batteries=False)
# Run Scheduled Task
__salt__['task.run_wait'](name='update-salt-software')
if not __salt__['task.run_wait'](name='update-salt-software'):
log.error('Failed to install {0}'.format(pkg_name))
log.error('Scheduled Task failed to run')
ret[pkg_name] = {'install status': 'failed'}
else:
# Build the install command
cmd = []
@ -957,9 +961,14 @@ def remove(name=None, pkgs=None, version=None, **kwargs):
start_in=cache_path,
trigger_type='Once',
start_date='1975-01-01',
start_time='01:00')
start_time='01:00',
ac_only=False,
stop_if_on_batteries=False)
# Run Scheduled Task
__salt__['task.run_wait'](name='update-salt-software')
if not __salt__['task.run_wait'](name='update-salt-software'):
log.error('Failed to remove {0}'.format(target))
log.error('Scheduled Task failed to run')
ret[target] = {'uninstall status': 'failed'}
else:
# Build the install command
cmd = []
@ -1051,6 +1060,9 @@ def get_repo_data(saltenv='base'):
# return __context__['winrepo.data']
repocache_dir = _get_local_repo_dir(saltenv=saltenv)
winrepo = 'winrepo.p'
if not os.path.exists(os.path.join(repocache_dir, winrepo)):
log.debug('No winrepo.p cache file. Refresh pkg db now.')
refresh_db(saltenv=saltenv)
try:
with salt.utils.fopen(
os.path.join(repocache_dir, winrepo), 'rb') as repofile:
@ -1061,12 +1073,6 @@ def get_repo_data(saltenv='base'):
log.exception(exc)
return {}
except IOError as exc:
if exc.errno == errno.ENOENT:
# File doesn't exist
raise CommandExecutionError(
'Windows repo cache doesn\'t exist, pkg.refresh_db likely '
'needed'
)
log.error('Not able to read repo file')
log.exception(exc)
return {}

View File

@ -125,7 +125,8 @@ Authentication
Authentication is performed by passing a session token with each request.
Tokens are generated via the :py:class:`Login` URL.
The token may be sent in one of two ways:
The token may be sent in one of two ways: as a custom header or as a session
cookie. The latter is far more convenient for clients that support cookies.
* Include a custom header named :mailheader:`X-Auth-Token`.
@ -178,54 +179,204 @@ The token may be sent in one of two ways:
Usage
-----
Commands are sent to a running Salt master via this module by sending HTTP
requests to the URLs detailed below.
This interface directly exposes Salt's :ref:`Python API <python-api>`.
Everything possible at the CLI is possible through the Python API. Commands are
executed on the Salt Master.
.. admonition:: Content negotiation
The root URL (``/``) is RPC-like in that it accepts instructions in the request
body for what Salt functions to execute, and the response contains the result
of those function calls.
This REST interface is flexible in what data formats it will accept as well
as what formats it will return (e.g., JSON, YAML, x-www-form-urlencoded).
For example:
* Specify the format of data in the request body by including the
:mailheader:`Content-Type` header.
* Specify the desired data format for the response body with the
:mailheader:`Accept` header.
.. code-block:: text
Data sent in :http:method:`post` and :http:method:`put` requests must be in
the format of a list of lowstate dictionaries. This allows multiple commands to
be executed in a single HTTP request. The order of commands in the request
corresponds to the return for each command in the response.
Lowstate, broadly, is a dictionary of values that are mapped to a function
call. This pattern is used pervasively throughout Salt. The functions called
from netapi modules are described in :ref:`Client Interfaces <netapi-clients>`.
The following example (in JSON format) causes Salt to execute two commands, a
command sent to minions as well as a runner function on the master::
[{
% curl -sSi https://localhost:8000 \
-H 'Content-type: application/json' \
-d '[{
"client": "local",
"tgt": "*",
"fun": "test.fib",
"arg": ["10"]
"fun": "test.ping"
}]'
HTTP/1.1 200 OK
Content-Type: application/json
[...snip...]
{"return": [{"jerry": true}]}
The request body must be an array of commands. Use this workflow to build a
command:
1. Choose a client interface.
2. Choose a function.
3. Fill out the remaining parameters needed for the chosen client.
The ``client`` field is a reference to the main Python classes used in Salt's
Python API. Read the full :ref:`client interfaces <netapi-clients>`
documentation, but in short:
* "local" uses :py:class:`LocalClient <salt.client.LocalClient>` which sends
commands to Minions. Equivalent to the ``salt`` CLI command.
* "runner" uses :py:class:`RunnerClient <salt.runner.RunnerClient>` which
invokes runner modules on the Master. Equivalent to the ``salt-run`` CLI
command.
* "wheel" uses :py:class:`WheelClient <salt.wheel.WheelClient>` which invokes
wheel modules on the Master. Wheel modules do not have a direct CLI
equivalent but they typically manage Master-side resources such as state
files, pillar files, the Salt config files, and the :py:mod:`key wheel module
<salt.wheel.key>` exposes similar functionality as the ``salt-key`` CLI
command.
Most clients have variants like synchronous or asyncronous execution as well as
others like batch execution. See the :ref:`full list of client interfaces
<netapi-clients>`.
Each client requires different arguments and sometimes has different syntax.
For example, ``LocalClient`` requires the ``tgt`` argument because it forwards
the command to Minions and the other client interfaces do not. ``LocalClient``
also takes ``arg`` (array) and ``kwarg`` (dictionary) arguments because these
values are sent to the Minions and used to execute the requested function
there. ``RunnerClient`` and ``WheelClient`` are executed directly on the Master
and thus do not need or accept those arguments.
Read the method signatures in the client documentation linked above, but
hopefully an example will help illustrate the concept. This example causes Salt
to execute two functions -- the :py:func:`test.arg execution function
<salt.modules.test.arg>` using ``LocalClient`` and the :py:func:`test.arg
runner function <salt.runners.test.arg>` using ``RunnerClient``; note the
different structure for each command. The results for both are combined and
returned as one response.
.. code-block:: text
% curl -b ~/cookies.txt -sSi localhost:8000 \
-H 'Content-type: application/json' \
-d '
[
{
"client": "local",
"tgt": "*",
"fun": "test.arg",
"arg": ["positional arg one", "positional arg two"],
"kwarg": {
"keyword arg one": "Hello from a minion",
"keyword arg two": "Hello again from a minion"
}
},
{
"client": "runner",
"fun": "jobs.lookup_jid",
"jid": "20130603122505459265"
}]
"fun": "test.arg",
"keyword arg one": "Hello from a master",
"keyword arg two": "Runners do not support positional args"
}
]
'
HTTP/1.1 200 OK
[...snip...]
{
"return": [
{
"jerry": {
"args": [
"positional arg one",
"positional arg two"
],
"kwargs": {
"keyword arg one": "Hello from a minion",
"keyword arg two": "Hello again from a minion",
[...snip...]
}
},
[...snip; other minion returns here...]
},
{
"args": [],
"kwargs": {
"keyword arg two": "Runners do not support positional args",
"keyword arg one": "Hello from a master"
}
}
]
}
.. admonition:: x-www-form-urlencoded
One more example, this time with more commonly used functions:
Sending JSON or YAML in the request body is simple and most flexible,
however sending data in urlencoded format is also supported with the
caveats below. It is the default format for HTML forms, many JavaScript
libraries, and the :command:`curl` command.
.. code-block:: text
For example, the equivalent to running ``salt '*' test.ping`` is sending
``fun=test.ping&arg&client=local&tgt=*`` in the HTTP request body.
curl -b /tmp/cookies.txt -sSi localhost:8000 \
-H 'Content-type: application/json' \
-d '
[
{
"client": "local",
"tgt": "*",
"fun": "state.sls",
"kwarg": {
"mods": "apache",
"pillar": {
"lookup": {
"wwwdir": "/srv/httpd/htdocs"
}
}
}
},
{
"client": "runner",
"fun": "cloud.create",
"provider": "my-ec2-provider",
"instances": "my-centos-6",
"image": "ami-1624987f",
"delvol_on_destroy", true
}
]
'
HTTP/1.1 200 OK
[...snip...]
{
"return": [
{
"jerry": {
"pkg_|-install_apache_|-httpd_|-installed": {
[...snip full state return here...]
}
}
[...snip other minion returns here...]
},
{
[...snip full salt-cloud output here...]
}
]
}
Caveats:
Content negotiation
-------------------
This REST interface is flexible in what data formats it will accept as well
as what formats it will return (e.g., JSON, YAML, urlencoded).
* Specify the format of data in the request body by including the
:mailheader:`Content-Type` header.
* Specify the desired data format for the response body with the
:mailheader:`Accept` header.
We recommend the JSON format for most HTTP requests. urlencoded data is simple
and cannot express complex data structures -- and that is often required for
some Salt commands, such as starting a state run that uses Pillar data. Salt's
CLI tool can reformat strings passed in at the CLI into complex data
structures, and that behavior also works via salt-api, but that can be brittle
and since salt-api can accept JSON it is best just to send JSON.
Here is an example of sending urlencoded data:
.. code-block:: bash
curl -sSik https://localhost:8000 \\
-b ~/cookies.txt \\
-d client=runner \\
-d fun='jobs.lookup_jid' \\
-d jid='20150129182456704682'
.. admonition:: urlencoded data caveats
* Only a single command may be sent per HTTP request.
* Repeating the ``arg`` parameter multiple times will cause those
@ -233,9 +384,23 @@ command sent to minions as well as a runner function on the master::
Note, some popular frameworks and languages (notably jQuery, PHP, and
Ruby on Rails) will automatically append empty brackets onto repeated
parameters. E.g., ``arg=one``, ``arg=two`` will be sent as ``arg[]=one``,
``arg[]=two``. This is not supported; send JSON or YAML instead.
query string parameters. E.g., ``?foo[]=fooone&foo[]=footwo``. This is
**not** supported; send ``?foo=fooone&foo=footwo`` instead, or send JSON
or YAML.
A note about ``curl``
The ``-d`` flag to curl does *not* automatically urlencode data which can
affect passwords and other data that contains characters that must be
encoded. Use the ``--data-urlencode`` flag instead. E.g.:
.. code-block:: bash
curl -ksi http://localhost:8000/login \\
-H "Accept: application/json" \\
-d username='myapiuser' \\
--data-urlencode password='1234+' \\
-d eauth='pam'
.. |req_token| replace:: a session token from :py:class:`~Login`.
.. |req_accept| replace:: the desired response format.
@ -248,21 +413,6 @@ command sent to minions as well as a runner function on the master::
.. |401| replace:: authentication required
.. |406| replace:: requested Content-Type not available
A Note About Curl
=================
When sending passwords and data that might need to be urlencoded, you must set
the ``-d`` flag to indicate the content type, and the ``--data-urlencode`` flag
to urlencode the input.
.. code-block:: bash
curl -ksi http://localhost:8000/login \\
-H "Accept: application/json" \\
-d username='myapiuser' \\
--data-urlencode password='1234+' \\
-d eauth='pam'
'''
# We need a custom pylintrc here...
# pylint: disable=W0212,E1101,C0103,R0201,W0221,W0613
@ -882,11 +1032,10 @@ class LowDataAdapter(object):
.. code-block:: bash
curl -sSik https://localhost:8000 \\
-b ~/cookies.txt \\
-H "Accept: application/x-yaml" \\
-H "X-Auth-Token: d40d1e1e<...snip...>" \\
-d client=local \\
-d tgt='*' \\
-d fun='test.ping' \\
-H "Content-type: application/json" \\
-d '[{"client": "local", "tgt": "*", "fun": "test.ping"}]'
.. code-block:: http
@ -894,10 +1043,9 @@ class LowDataAdapter(object):
Host: localhost:8000
Accept: application/x-yaml
X-Auth-Token: d40d1e1e
Content-Length: 36
Content-Type: application/x-www-form-urlencoded
Content-Type: application/json
fun=test.ping&client=local&tgt=*
[{"client": "local", "tgt": "*", "fun": "test.ping"}]
**Example response:**
@ -914,51 +1062,6 @@ class LowDataAdapter(object):
ms-2: true
ms-3: true
ms-4: true
**Other examples**:
.. code-block:: bash
# Sending multiple positional args with urlencoded:
curl -sSik https://localhost:8000 \\
-d client=local \\
-d tgt='*' \\
-d fun='cmd.run' \\
-d arg='du -sh .' \\
-d arg='/path/to/dir'
# Sending positional args and Keyword args with JSON:
echo '[
{
"client": "local",
"tgt": "*",
"fun": "cmd.run",
"arg": [
"du -sh .",
"/path/to/dir"
],
"kwarg": {
"shell": "/bin/sh",
"template": "jinja"
}
}
]' | curl -sSik https://localhost:8000 \\
-H 'Content-type: application/json' \\
-d@-
# Calling runner functions:
curl -sSik https://localhost:8000 \\
-d client=runner \\
-d fun='jobs.lookup_jid' \\
-d jid='20150129182456704682' \\
-d outputter=highstate
# Calling wheel functions:
curl -sSik https://localhost:8000 \\
-d client=wheel \\
-d fun='key.gen_accept' \\
-d id_=dave \\
-d keysize=4096
'''
return {
'return': list(self.exec_lowstate(
@ -1047,17 +1150,16 @@ class Minions(LowDataAdapter):
.. code-block:: bash
curl -sSi localhost:8000/minions \\
-b ~/cookies.txt \\
-H "Accept: application/x-yaml" \\
-d tgt='*' \\
-d fun='status.diskusage'
-d '[{"tgt": "*", "fun": "status.diskusage"}]'
.. code-block:: http
POST /minions HTTP/1.1
Host: localhost:8000
Accept: application/x-yaml
Content-Length: 26
Content-Type: application/x-www-form-urlencoded
Content-Type: application/json
tgt=*&fun=status.diskusage
@ -1473,20 +1575,25 @@ class Login(LowDataAdapter):
.. code-block:: bash
curl -si localhost:8000/login \\
-c ~/cookies.txt \\
-H "Accept: application/json" \\
-d username='saltuser' \\
-d password='saltpass' \\
-d eauth='pam'
-H "Content-type: application/json" \\
-d '{
"username": "saltuser",
"password": "saltuser",
"eauth": "auto"
}'
.. code-block:: http
POST / HTTP/1.1
Host: localhost:8000
Content-Length: 42
Content-Type: application/x-www-form-urlencoded
Content-Type: application/json
Accept: application/json
username=saltuser&password=saltpass&eauth=pam
{"username": "saltuser", "password": "saltuser", "eauth": "auto"}
**Example response:**
@ -1626,12 +1733,15 @@ class Run(LowDataAdapter):
curl -sS localhost:8000/run \\
-H 'Accept: application/x-yaml' \\
-d client='local' \\
-d tgt='*' \\
-d fun='test.ping' \\
-d username='saltdev' \\
-d password='saltdev' \\
-d eauth='pam'
-H 'Content-type: application/json' \\
-d '[{
"client": "local",
"tgt": "*",
"fun": "test.ping",
"username": "saltdev",
"password": "saltdev",
"eauth": "auto"
}]'
.. code-block:: http
@ -1639,9 +1749,9 @@ class Run(LowDataAdapter):
Host: localhost:8000
Accept: application/x-yaml
Content-Length: 75
Content-Type: application/x-www-form-urlencoded
Content-Type: application/json
client=local&tgt=*&fun=test.ping&username=saltdev&password=saltdev&eauth=pam
[{"client": "local", "tgt": "*", "fun": "test.ping", "username": "saltdev", "password": "saltdev", "eauth": "auto"}]
**Example response:**
@ -1658,12 +1768,14 @@ class Run(LowDataAdapter):
ms-3: true
ms-4: true
The /run enpoint can also be used to issue commands using the salt-ssh subsystem.
The /run enpoint can also be used to issue commands using the salt-ssh
subsystem.
When using salt-ssh, eauth credentials should not be supplied. Instad, authentication
should be handled by the SSH layer itself. The use of the salt-ssh client does not
require a salt master to be running. Instead, only a roster file must be present
in the salt configuration directory.
When using salt-ssh, eauth credentials should not be supplied. Instad,
authentication should be handled by the SSH layer itself. The use of
the salt-ssh client does not require a salt master to be running.
Instead, only a roster file must be present in the salt configuration
directory.
All SSH client requests are synchronous.
@ -2176,16 +2288,18 @@ class Webhook(object):
.. code-block:: bash
curl -sS localhost:8000/hook -d foo='Foo!' -d bar='Bar!'
curl -sS localhost:8000/hook \\
-H 'Content-type: application/json' \\
-d '{"foo": "Foo!", "bar": "Bar!"}'
.. code-block:: http
POST /hook HTTP/1.1
Host: localhost:8000
Content-Length: 16
Content-Type: application/x-www-form-urlencoded
Content-Type: application/json
foo=Foo&bar=Bar!
{"foo": "Foo!", "bar": "Bar!"}
**Example response**:

View File

@ -8,7 +8,9 @@ from __future__ import absolute_import, print_function
import os
import sys
import time
import signal
import logging
import functools
import threading
import traceback
from random import randint
@ -38,6 +40,29 @@ def _handle_interrupt(exc, original_exc, hardfail=False, trace=''):
raise exc
def _handle_signals(client, signum, sigframe):
trace = traceback.format_exc()
try:
hardcrash = client.options.hard_crash
except (AttributeError, KeyError):
hardcrash = False
_handle_interrupt(
SystemExit('\nExiting gracefully on Ctrl-c'),
Exception('\nExiting with hard crash Ctrl-c'),
hardcrash, trace=trace)
def _install_signal_handlers(client):
# Install the SIGINT/SIGTERM handlers if not done so far
if signal.getsignal(signal.SIGINT) is signal.SIG_DFL:
# No custom signal handling was added, install our own
signal.signal(signal.SIGINT, functools.partial(_handle_signals, client))
if signal.getsignal(signal.SIGTERM) is signal.SIG_DFL:
# No custom signal handling was added, install our own
signal.signal(signal.SIGINT, functools.partial(_handle_signals, client))
def salt_master():
'''
Start the salt master.
@ -101,7 +126,6 @@ def salt_minion():
Auto restart minion on error.
'''
import signal
import functools
import salt.utils.process
salt.utils.process.notify_systemd()
@ -298,20 +322,10 @@ def salt_key():
Manage the authentication keys with salt-key.
'''
import salt.cli.key
client = None
try:
client = salt.cli.key.SaltKey()
_install_signal_handlers(client)
client.run()
except KeyboardInterrupt as err:
trace = traceback.format_exc()
try:
hardcrash = client.options.hard_crash
except (AttributeError, KeyError):
hardcrash = False
_handle_interrupt(
SystemExit('\nExiting gracefully on Ctrl-c'),
err,
hardcrash, trace=trace)
except Exception as err:
sys.stderr.write("Error: {0}\n".format(err.message))
@ -322,20 +336,9 @@ def salt_cp():
master.
'''
import salt.cli.cp
client = None
try:
client = salt.cli.cp.SaltCPCli()
_install_signal_handlers(client)
client.run()
except KeyboardInterrupt as err:
trace = traceback.format_exc()
try:
hardcrash = client.options.hard_crash
except (AttributeError, KeyError):
hardcrash = False
_handle_interrupt(
SystemExit('\nExiting gracefully on Ctrl-c'),
err,
hardcrash, trace=trace)
def salt_call():
@ -346,20 +349,9 @@ def salt_call():
import salt.cli.call
if '' in sys.path:
sys.path.remove('')
client = None
try:
client = salt.cli.call.SaltCall()
_install_signal_handlers(client)
client.run()
except KeyboardInterrupt as err:
trace = traceback.format_exc()
try:
hardcrash = client.options.hard_crash
except (AttributeError, KeyError):
hardcrash = False
_handle_interrupt(
SystemExit('\nExiting gracefully on Ctrl-c'),
err,
hardcrash, trace=trace)
def salt_run():
@ -369,20 +361,9 @@ def salt_run():
import salt.cli.run
if '' in sys.path:
sys.path.remove('')
client = None
try:
client = salt.cli.run.SaltRun()
_install_signal_handlers(client)
client.run()
except KeyboardInterrupt as err:
trace = traceback.format_exc()
try:
hardcrash = client.options.hard_crash
except (AttributeError, KeyError):
hardcrash = False
_handle_interrupt(
SystemExit('\nExiting gracefully on Ctrl-c'),
err,
hardcrash, trace=trace)
def salt_ssh():
@ -392,20 +373,10 @@ def salt_ssh():
import salt.cli.ssh
if '' in sys.path:
sys.path.remove('')
client = None
try:
client = salt.cli.ssh.SaltSSH()
_install_signal_handlers(client)
client.run()
except KeyboardInterrupt as err:
trace = traceback.format_exc()
try:
hardcrash = client.options.hard_crash
except (AttributeError, KeyError):
hardcrash = False
_handle_interrupt(
SystemExit('\nExiting gracefully on Ctrl-c'),
err,
hardcrash, trace=trace)
except SaltClientError as err:
trace = traceback.format_exc()
try:
@ -436,20 +407,9 @@ def salt_cloud():
print('salt-cloud is not available in this system')
sys.exit(salt.defaults.exitcodes.EX_UNAVAILABLE)
client = None
try:
client = salt.cloud.cli.SaltCloud()
_install_signal_handlers(client)
client.run()
except KeyboardInterrupt as err:
trace = traceback.format_exc()
try:
hardcrash = client.options.hard_crash
except (AttributeError, KeyError):
hardcrash = False
_handle_interrupt(
SystemExit('\nExiting gracefully on Ctrl-c'),
err,
hardcrash, trace=trace)
def salt_api():
@ -472,20 +432,9 @@ def salt_main():
import salt.cli.salt
if '' in sys.path:
sys.path.remove('')
client = None
try:
client = salt.cli.salt.SaltCMD()
_install_signal_handlers(client)
client.run()
except KeyboardInterrupt as err:
trace = traceback.format_exc()
try:
hardcrash = client.options.hard_crash
except (AttributeError, KeyError):
hardcrash = False
_handle_interrupt(
SystemExit('\nExiting gracefully on Ctrl-c'),
err,
hardcrash, trace=trace)
def salt_spm():

View File

@ -1809,6 +1809,8 @@ class State(object):
tag = _gen_tag(low)
if (low.get('failhard', False) or self.opts['failhard']
and tag in running):
if running[tag]['result'] is None:
return False
return not running[tag]['result']
return False

View File

@ -95,6 +95,7 @@ def extracted(name,
if_missing=None,
keep=False,
trim_output=False,
skip_verify=False,
source_hash_update=None):
'''
.. versionadded:: 2014.1.0
@ -174,6 +175,13 @@ def extracted(name,
.. versionadded:: 2016.3.0
skip_verify:False
If ``True``, hash verification of remote file sources (``http://``,
``https://``, ``ftp://``) will be skipped, and the ``source_hash``
argument will be ignored.
.. versionadded:: 2016.3.4
archive_format
``tar``, ``zip`` or ``rar``
@ -324,6 +332,7 @@ def extracted(name,
source=source,
source_hash=source_hash,
makedirs=True,
skip_verify=skip_verify,
saltenv=__env__)
log.debug('file.managed: {0}'.format(file_result))
# get value of first key

View File

@ -487,7 +487,8 @@ def image_present(name,
load=None,
force=False,
insecure_registry=False,
client_timeout=CLIENT_TIMEOUT):
client_timeout=CLIENT_TIMEOUT,
**kwargs):
'''
Ensure that an image is present. The image can either be pulled from a
Docker registry, built from a Dockerfile, or loaded from a saved image.

View File

@ -105,7 +105,7 @@ def _get_branch_opts(branch, local_branch, all_local_branches,
return ret
def _get_local_rev_and_branch(target, user):
def _get_local_rev_and_branch(target, user, password):
'''
Return the local revision for before/after comparisons
'''
@ -113,6 +113,7 @@ def _get_local_rev_and_branch(target, user):
try:
local_rev = __salt__['git.revision'](target,
user=user,
password=password,
ignore_retcode=True)
except CommandExecutionError:
log.info('No local revision for {0}'.format(target))
@ -122,6 +123,7 @@ def _get_local_rev_and_branch(target, user):
try:
local_branch = __salt__['git.current_branch'](target,
user=user,
password=password,
ignore_retcode=True)
except CommandExecutionError:
log.info('No local branch for {0}'.format(target))
@ -205,6 +207,7 @@ def latest(name,
target=None,
branch=None,
user=None,
password=None,
update_head=True,
force_checkout=False,
force_clone=False,
@ -262,6 +265,12 @@ def latest(name,
.. versionadded:: 0.17.0
password
Windows only. Required when specifying ``user``. This parameter will be
ignored on non-Windows platforms.
.. versionadded:: 2016.3.4
update_head : True
If set to ``False``, then the remote repository will be fetched (if
necessary) to ensure that the commit to which ``rev`` points exists in
@ -503,6 +512,8 @@ def latest(name,
branch = str(branch)
if user is not None and not isinstance(user, six.string_types):
user = str(user)
if password is not None and not isinstance(password, six.string_types):
password = str(password)
if remote is not None and not isinstance(remote, six.string_types):
remote = str(remote)
if identity is not None:
@ -564,7 +575,7 @@ def latest(name,
return _fail(ret, ('\'rev\' is not compatible with the \'mirror\' and '
'\'bare\' arguments'))
run_check_cmd_kwargs = {'runas': user}
run_check_cmd_kwargs = {'runas': user, 'password': password}
if 'shell' in __grains__:
run_check_cmd_kwargs['shell'] = __grains__['shell']
@ -588,6 +599,7 @@ def latest(name,
heads=False,
tags=False,
user=user,
password=password,
identity=identity,
https_user=https_user,
https_pass=https_pass,
@ -682,13 +694,18 @@ def latest(name,
check = 'refs' if bare else '.git'
gitdir = os.path.join(target, check)
comments = []
if os.path.isdir(gitdir) or __salt__['git.is_worktree'](target):
if os.path.isdir(gitdir) or __salt__['git.is_worktree'](target,
user=user,
password=password):
# Target directory is a git repository or git worktree
try:
all_local_branches = __salt__['git.list_branches'](
target, user=user)
all_local_tags = __salt__['git.list_tags'](target, user=user)
local_rev, local_branch = _get_local_rev_and_branch(target, user)
target, user=user, password=password)
all_local_tags = __salt__['git.list_tags'](target,
user=user,
password=password)
local_rev, local_branch = \
_get_local_rev_and_branch(target, user, password)
if not bare and remote_rev is None and local_rev is not None:
return _fail(
@ -723,6 +740,7 @@ def latest(name,
target,
branch + '^{commit}',
user=user,
password=password,
ignore_retcode=True)
except CommandExecutionError as exc:
return _fail(
@ -734,12 +752,16 @@ def latest(name,
remotes = __salt__['git.remotes'](target,
user=user,
password=password,
redact_auth=False)
revs_match = _revs_equal(local_rev, remote_rev, remote_rev_type)
try:
local_changes = bool(
__salt__['git.diff'](target, 'HEAD', user=user)
__salt__['git.diff'](target,
'HEAD',
user=user,
password=password)
)
except CommandExecutionError:
# No need to capture the error and log it, the _git_run()
@ -767,6 +789,8 @@ def latest(name,
__salt__['git.rev_parse'](
target,
remote_rev + '^{commit}',
user=user,
password=password,
ignore_retcode=True)
except CommandExecutionError:
# Local checkout doesn't have the remote_rev
@ -787,6 +811,7 @@ def latest(name,
target,
desired_upstream,
user=user,
password=password,
ignore_retcode=True)
except CommandExecutionError:
pass
@ -806,6 +831,7 @@ def latest(name,
target,
rev + '^{commit}',
user=user,
password=password,
ignore_retcode=True)
except CommandExecutionError:
# Shouldn't happen if the tag exists
@ -875,6 +901,7 @@ def latest(name,
refs=[base_rev, remote_rev],
is_ancestor=True,
user=user,
password=password,
ignore_retcode=True)
if fast_forward is False:
@ -903,6 +930,7 @@ def latest(name,
base_branch + '@{upstream}',
opts=['--abbrev-ref'],
user=user,
password=password,
ignore_retcode=True)
except CommandExecutionError:
# There is a local branch but the rev-parse command
@ -970,6 +998,7 @@ def latest(name,
url=name,
remote=remote,
user=user,
password=password,
https_user=https_user,
https_pass=https_pass)
comments.append(
@ -1121,6 +1150,7 @@ def latest(name,
force=force_fetch,
refspecs=refspecs,
user=user,
password=password,
identity=identity,
saltenv=__env__)
except CommandExecutionError as exc:
@ -1136,6 +1166,8 @@ def latest(name,
__salt__['git.rev_parse'](
target,
remote_rev + '^{commit}',
user=user,
password=password,
ignore_retcode=True)
except CommandExecutionError as exc:
return _fail(
@ -1167,7 +1199,8 @@ def latest(name,
target,
refs=[base_rev, remote_rev],
is_ancestor=True,
user=user)
user=user,
password=password)
if fast_forward is False and not force_reset:
return _not_fast_forward(
@ -1207,7 +1240,8 @@ def latest(name,
checkout_rev,
force=force_checkout,
opts=checkout_opts,
user=user)
user=user,
password=password)
if '-b' in checkout_opts:
comments.append(
'New branch \'{0}\' was checked out, with {1} '
@ -1228,7 +1262,8 @@ def latest(name,
__salt__['git.reset'](
target,
opts=['--hard', remote_rev],
user=user
user=user,
password=password,
)
ret['changes']['forced update'] = True
comments.append(
@ -1239,7 +1274,8 @@ def latest(name,
__salt__['git.branch'](
target,
opts=branch_opts,
user=user)
user=user,
password=password)
comments.append(upstream_action)
# Fast-forward to the desired revision
@ -1255,6 +1291,8 @@ def latest(name,
if __salt__['git.symbolic_ref'](target,
'HEAD',
opts=['--quiet'],
user=user,
password=password,
ignore_retcode=True):
merge_rev = remote_rev if rev == 'HEAD' \
else desired_upstream
@ -1276,8 +1314,8 @@ def latest(name,
target,
rev=merge_rev,
opts=merge_opts,
user=user
)
user=user,
password=password)
comments.append(
'Repository was fast-forwarded to {0}'
.format(remote_loc)
@ -1295,8 +1333,8 @@ def latest(name,
target,
opts=['--hard',
remote_rev if rev == 'HEAD' else rev],
user=user
)
user=user,
password=password)
comments.append(
'Repository was reset to {0} (fast-forward)'
.format(rev)
@ -1311,6 +1349,7 @@ def latest(name,
'update',
opts=['--init', '--recursive'],
user=user,
password=password,
identity=identity,
saltenv=__env__)
except CommandExecutionError as exc:
@ -1332,6 +1371,7 @@ def latest(name,
force=force_fetch,
refspecs=refspecs,
user=user,
password=password,
identity=identity,
saltenv=__env__)
except CommandExecutionError as exc:
@ -1349,6 +1389,7 @@ def latest(name,
new_rev = __salt__['git.revision'](
cwd=target,
user=user,
password=password,
ignore_retcode=True)
except CommandExecutionError:
new_rev = None
@ -1447,6 +1488,7 @@ def latest(name,
__salt__['git.clone'](target,
name,
user=user,
password=password,
opts=clone_opts,
identity=identity,
https_user=https_user,
@ -1483,7 +1525,7 @@ def latest(name,
else:
if remote_rev_type == 'tag' \
and rev not in __salt__['git.list_tags'](
target, user=user):
target, user=user, password=password):
return _fail(
ret,
'Revision \'{0}\' does not exist in clone'
@ -1493,8 +1535,10 @@ def latest(name,
if branch is not None:
if branch not in \
__salt__['git.list_branches'](target,
user=user):
__salt__['git.list_branches'](
target,
user=user,
password=password):
if rev == 'HEAD':
checkout_rev = remote_rev
else:
@ -1504,7 +1548,8 @@ def latest(name,
__salt__['git.checkout'](target,
checkout_rev,
opts=['-b', branch],
user=user)
user=user,
password=password)
comments.append(
'Branch \'{0}\' checked out, with {1} '
'as a starting point'.format(
@ -1514,14 +1559,14 @@ def latest(name,
)
local_rev, local_branch = \
_get_local_rev_and_branch(target, user)
_get_local_rev_and_branch(target, user, password)
if not _revs_equal(local_rev, remote_rev, remote_rev_type):
__salt__['git.reset'](
target,
opts=['--hard', remote_rev],
user=user
)
user=user,
password=password)
comments.append(
'Repository was reset to {0}'.format(remote_loc)
)
@ -1532,6 +1577,7 @@ def latest(name,
local_branch + '@{upstream}',
opts=['--abbrev-ref'],
user=user,
password=password,
ignore_retcode=True)
except CommandExecutionError:
upstream = False
@ -1545,7 +1591,9 @@ def latest(name,
branch_opts = _get_branch_opts(
branch,
local_branch,
__salt__['git.list_branches'](target, user=user),
__salt__['git.list_branches'](target,
user=user,
password=password),
desired_upstream,
git_ver)
elif upstream and desired_upstream is False:
@ -1568,7 +1616,9 @@ def latest(name,
branch_opts = _get_branch_opts(
branch,
local_branch,
__salt__['git.list_branches'](target, user=user),
__salt__['git.list_branches'](target,
user=user,
password=password),
desired_upstream,
git_ver)
else:
@ -1578,7 +1628,8 @@ def latest(name,
__salt__['git.branch'](
target,
opts=branch_opts,
user=user)
user=user,
password=password)
comments.append(upstream_action)
if submodules and remote_rev:
@ -1587,6 +1638,7 @@ def latest(name,
'update',
opts=['--init', '--recursive'],
user=user,
password=password,
identity=identity)
except CommandExecutionError as exc:
return _failed_submodule_update(ret, exc, comments)
@ -1595,6 +1647,7 @@ def latest(name,
new_rev = __salt__['git.revision'](
cwd=target,
user=user,
password=password,
ignore_retcode=True)
except CommandExecutionError:
new_rev = None
@ -1624,7 +1677,8 @@ def present(name,
template=None,
separate_git_dir=None,
shared=None,
user=None):
user=None,
password=None):
'''
Ensure that a repository exists in the given directory
@ -1678,6 +1732,12 @@ def present(name,
.. versionadded:: 0.17.0
password
Windows only. Required when specifying ``user``. This parameter will be
ignored on non-Windows platforms.
.. versionadded:: 2016.3.4
.. _`git-init(1)`: http://git-scm.com/docs/git-init
.. _`worktree`: http://git-scm.com/docs/git-worktree
'''
@ -1689,7 +1749,7 @@ def present(name,
return ret
elif not bare and \
(os.path.isdir(os.path.join(name, '.git')) or
__salt__['git.is_worktree'](name)):
__salt__['git.is_worktree'](name, user=user, password=password)):
return ret
# Directory exists and is not a git repo, if force is set destroy the
# directory and recreate, otherwise throw an error
@ -1747,7 +1807,8 @@ def present(name,
template=template,
separate_git_dir=separate_git_dir,
shared=shared,
user=user)
user=user,
password=password)
actions = [
'Initialized {0}repository in {1}'.format(
@ -1773,6 +1834,7 @@ def detached(name,
target=None,
remote='origin',
user=None,
password=None,
force_clone=False,
force_checkout=False,
fetch_remote=True,
@ -1811,6 +1873,12 @@ def detached(name,
User under which to run git commands. By default, commands are run by
the user under which the minion is running.
password
Windows only. Required when specifying ``user``. This parameter will be
ignored on non-Windows platforms.
.. versionadded:: 2016.3.4
force_clone : False
If the ``target`` directory exists and is not a git repository, then
this state will fail. Set this argument to ``True`` to remove the
@ -1966,18 +2034,24 @@ def detached(name,
local_commit_id = None
gitdir = os.path.join(target, '.git')
if os.path.isdir(gitdir) or __salt__['git.is_worktree'](target):
if os.path.isdir(gitdir) \
or __salt__['git.is_worktree'](target, user=user, password=password):
# Target directory is a git repository or git worktree
local_commit_id = _get_local_rev_and_branch(target, user)[0]
local_commit_id = _get_local_rev_and_branch(target, user, password)[0]
if remote_ref_type is 'hash' and __salt__['git.describe'](target, ref):
if remote_ref_type is 'hash' \
and __salt__['git.describe'](target,
ref,
user=user,
password=password):
# The ref is a hash and it exists locally so skip to checkout
hash_exists_locally = True
else:
# Check that remote is present and set to correct url
remotes = __salt__['git.remotes'](target,
user=user,
password=password,
redact_auth=False)
if remote in remotes and name in remotes[remote]['fetch']:
@ -2002,6 +2076,7 @@ def detached(name,
url=name,
remote=remote,
user=user,
password=password,
https_user=https_user,
https_pass=https_pass)
comments.append(
@ -2073,6 +2148,7 @@ def detached(name,
__salt__['git.clone'](target,
name,
user=user,
password=password,
opts=clone_opts,
identity=identity,
https_user=https_user,
@ -2119,6 +2195,7 @@ def detached(name,
force=True,
refspecs=refspecs,
user=user,
password=password,
identity=identity,
saltenv=__env__)
except CommandExecutionError as exc:
@ -2135,7 +2212,7 @@ def detached(name,
#get refs and checkout
checkout_commit_id = ''
if remote_ref_type is 'hash':
if __salt__['git.describe'](target, ref):
if __salt__['git.describe'](target, ref, user=user, password=password):
checkout_commit_id = ref
else:
return _fail(
@ -2147,6 +2224,7 @@ def detached(name,
all_remote_refs = __salt__['git.remote_refs'](
target,
user=user,
password=password,
identity=identity,
https_user=https_user,
https_pass=https_pass,
@ -2179,8 +2257,8 @@ def detached(name,
__salt__['git.reset'](
target,
opts=['--hard', 'HEAD'],
user=user
)
user=user,
password=password)
comments.append(
'Repository was reset to HEAD before checking out ref'
)
@ -2202,7 +2280,8 @@ def detached(name,
__salt__['git.checkout'](target,
checkout_commit_id,
force=force_checkout,
user=user)
user=user,
password=password)
comments.append(
'Commit ID {0} was checked out at {1}'.format(
checkout_commit_id,
@ -2214,6 +2293,7 @@ def detached(name,
new_rev = __salt__['git.revision'](
cwd=target,
user=user,
password=password,
ignore_retcode=True)
except CommandExecutionError:
new_rev = None
@ -2223,6 +2303,7 @@ def detached(name,
'update',
opts=['--init', '--recursive'],
user=user,
password=password,
identity=identity)
comments.append(
'Submodules were updated'
@ -2244,6 +2325,7 @@ def config_unset(name,
value_regex=None,
repo=None,
user=None,
password=None,
**kwargs):
r'''
.. versionadded:: 2015.8.0
@ -2274,7 +2356,14 @@ def config_unset(name,
set. Required unless ``global`` is set to ``True``.
user
Optional name of a user as whom `git config` will be run
User under which to run git commands. By default, commands are run by
the user under which the minion is running.
password
Windows only. Required when specifying ``user``. This parameter will be
ignored on non-Windows platforms.
.. versionadded:: 2016.3.4
global : False
If ``True``, this will set a global git config option
@ -2349,6 +2438,7 @@ def config_unset(name,
key=key,
value_regex=value_regex,
user=user,
password=password,
ignore_retcode=True,
**{'global': global_}
)
@ -2397,6 +2487,7 @@ def config_unset(name,
key=key,
value_regex=None,
user=user,
password=password,
ignore_retcode=True,
**{'global': global_}
)
@ -2412,6 +2503,7 @@ def config_unset(name,
value_regex=value_regex,
all=all_,
user=user,
password=password,
**{'global': global_}
)
except CommandExecutionError as exc:
@ -2434,6 +2526,7 @@ def config_unset(name,
key=key,
value_regex=None,
user=user,
password=password,
ignore_retcode=True,
**{'global': global_}
)
@ -2453,6 +2546,7 @@ def config_unset(name,
key=key,
value_regex=value_regex,
user=user,
password=password,
ignore_retcode=True,
**{'global': global_}
)
@ -2474,6 +2568,7 @@ def config_set(name,
multivar=None,
repo=None,
user=None,
password=None,
**kwargs):
'''
.. versionadded:: 2014.7.0
@ -2504,7 +2599,14 @@ def config_set(name,
set. Required unless ``global`` is set to ``True``.
user
Optional name of a user as whom `git config` will be run
User under which to run git commands. By default, the commands are run
by the user under which the minion is running.
password
Windows only. Required when specifying ``user``. This parameter will be
ignored on non-Windows platforms.
.. versionadded:: 2016.3.4
global : False
If ``True``, this will set a global git config option
@ -2614,6 +2716,7 @@ def config_set(name,
cwd=repo,
key=name,
user=user,
password=password,
ignore_retcode=True,
**{'all': True, 'global': global_}
)
@ -2644,6 +2747,7 @@ def config_set(name,
value=value,
multivar=multivar,
user=user,
password=password,
**{'global': global_}
)
except CommandExecutionError as exc:
@ -2679,7 +2783,13 @@ def config_set(name,
return ret
def config(name, value=None, multivar=None, repo=None, user=None, **kwargs):
def config(name,
value=None,
multivar=None,
repo=None,
user=None,
password=None,
**kwargs):
'''
Pass through to git.config_set and display a deprecation warning
'''
@ -2693,6 +2803,7 @@ def config(name, value=None, multivar=None, repo=None, user=None, **kwargs):
multivar=multivar,
repo=repo,
user=user,
password=password,
**kwargs)

View File

@ -103,28 +103,38 @@ def mounted(name,
device otherwise.
extra_mount_invisible_options
A list of extra options that are not visible through the /proc/self/mountinfo
interface. If a option is not visible through this interface it will always
remount the device. This Option extends the builtin mount_invisible_options list.
A list of extra options that are not visible through the
``/proc/self/mountinfo`` interface.
If a option is not visible through this interface it will always remount
the device. This option extends the builtin ``mount_invisible_options``
list.
extra_mount_invisible_keys
A list of extra key options that are not visible through the /proc/self/mountinfo
interface. If a key option is not visible through this interface it will always
remount the device. This Option extends the builtin mount_invisible_keys list.
A good example for a key Option is the password Option:
A list of extra key options that are not visible through the
``/proc/self/mountinfo`` interface.
If a key option is not visible through this interface it will always
remount the device. This option extends the builtin
``mount_invisible_keys`` list.
A good example for a key option is the password option::
password=badsecret
extra_ignore_fs_keys
A dict of filesystem options which should not force a remount. This will update
the internal dictionary. The dict should look like this:
the internal dictionary. The dict should look like this::
{
'ramfs': ['size']
}
extra_mount_translate_options
A dict of mount options that gets translated when mounted. To prevent a remount
add additional Options to the default dictionary. This will update the internal
dictionary. The dictionary should look like this:
add additional options to the default dictionary. This will update the internal
dictionary. The dictionary should look like this::
{
'tcp': 'proto=tcp',
'udp': 'proto=udp'

View File

@ -2274,7 +2274,13 @@ def group_installed(name, skip=None, include=None, **kwargs):
if not isinstance(item, six.string_types):
include[idx] = str(item)
try:
diff = __salt__['pkg.group_diff'](name)
except CommandExecutionError as err:
ret['comment'] = ('An error was encountered while installing/updating '
'group \'{0}\': {1}.'.format(name, err))
return ret
mandatory = diff['mandatory']['installed'] + \
diff['mandatory']['not installed']

View File

@ -39,6 +39,9 @@ def present(dbname, name,
name
The name of the schema to manage
owner
The database user that will be the owner of the schema
db_user
database username if different from config or default
@ -99,7 +102,7 @@ def absent(dbname, name,
db_user=None, db_password=None,
db_host=None, db_port=None):
'''
Ensure that the named schema is absent
Ensure that the named schema is absent.
dbname
The database's name will work on

View File

@ -1213,7 +1213,7 @@ class Pygit2(GitProvider):
self.repo.config.set_multivar(
'http.sslVerify',
'',
self.ssl_verify
str(self.ssl_verify).lower()
)
except os.error:
# This exception occurs when two processes are trying to write

View File

@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
'''
Implements the ability to run processes as another user in Windows for salt
Run processes as a different user in Windows
Based on a solution from http://stackoverflow.com/questions/29566330
'''
from __future__ import absolute_import

View File

@ -108,6 +108,18 @@ def accept_dict(match, include_rejected=False, include_denied=False):
match
The dictionary of keys to accept.
include_rejected
To include rejected keys in the match along with pending keys, set this
to ``True``. Defaults to ``False``.
.. versionadded:: 2016.3.4
include_denied
To include denied keys in the match along with pending keys, set this
to ``True``. Defaults to ``False``.
.. versionadded:: 2016.3.4
Example to move a list of keys from the ``minions_pre`` (pending) directory
to the ``minions`` (accepted) directory:
@ -199,6 +211,18 @@ def reject_dict(match, include_accepted=False, include_denied=False):
match
The dictionary of keys to reject.
include_accepted
To include accepted keys in the match along with pending keys, set this
to ``True``. Defaults to ``False``.
.. versionadded:: 2016.3.4
include_denied
To include denied keys in the match along with pending keys, set this
to ``True``. Defaults to ``False``.
.. versionadded:: 2016.3.4
.. code-block:: python
>>> wheel.cmd_async({'fun': 'key.reject_dict',

Binary file not shown.

View File

@ -0,0 +1,110 @@
# -*- coding: utf-8 -*-
'''
Tests for the archive state
'''
# Import python libs
from __future__ import absolute_import
import os
import shutil
import threading
import tornado.ioloop
import tornado.web
# Import Salt Testing libs
from salttesting import TestCase
from salttesting.helpers import ensure_in_syspath
ensure_in_syspath('../../')
# Import salt libs
import integration
import salt.utils
STATE_DIR = os.path.join(integration.FILES, 'file', 'base')
if salt.utils.is_windows():
ARCHIVE_DIR = os.path.join("c:/", "tmp")
else:
ARCHIVE_DIR = '/tmp/archive/'
PORT = 9999
ARCHIVE_TAR_SOURCE = 'http://localhost:{0}/custom.tar.gz'.format(PORT)
UNTAR_FILE = ARCHIVE_DIR + 'custom/README'
ARCHIVE_TAR_HASH = 'md5=7643861ac07c30fe7d2310e9f25ca514'
STATE_DIR = os.path.join(integration.FILES, 'file', 'base')
class SetupWebServer(TestCase):
'''
Setup and Teardown of Web Server
Only need to set this up once not
before all tests
'''
@classmethod
def webserver(cls):
'''
method to start tornado
static web app
'''
application = tornado.web.Application([(r"/(.*)", tornado.web.StaticFileHandler,
{"path": STATE_DIR})])
application.listen(PORT)
tornado.ioloop.IOLoop.instance().start()
@classmethod
def setUpClass(cls):
cls.server_thread = threading.Thread(target=cls.webserver)
cls.server_thread.daemon = True
cls.server_thread.start()
@classmethod
def tearDownClass(cls):
tornado.ioloop.IOLoop.instance().stop()
cls.server_thread.join()
class ArchiveTest(SetupWebServer,
integration.ModuleCase,
integration.SaltReturnAssertsMixIn):
'''
Validate the archive state
'''
def _check_ext_remove(self, dir, file):
'''
function to check if file was extracted
and remove the directory.
'''
# check to see if it extracted
check_dir = os.path.isfile(file)
self.assertTrue(check_dir)
# wipe away dir. Can't do this in teardown
# because it needs to be wiped before each test
shutil.rmtree(dir)
def test_archive_extracted_skip_verify(self):
'''
test archive.extracted with skip_verify
'''
ret = self.run_state('archive.extracted', name=ARCHIVE_DIR,
source=ARCHIVE_TAR_SOURCE, archive_format='tar',
skip_verify=True)
self.assertSaltTrueReturn(ret)
self._check_ext_remove(ARCHIVE_DIR, UNTAR_FILE)
def test_archive_extracted_with_source_hash(self):
'''
test archive.extracted without skip_verify
only external resources work to check to
ensure source_hash is verified correctly
'''
ret = self.run_state('archive.extracted', name=ARCHIVE_DIR,
source=ARCHIVE_TAR_SOURCE, archive_format='tar',
source_hash=ARCHIVE_TAR_HASH)
self.assertSaltTrueReturn(ret)
self._check_ext_remove(ARCHIVE_DIR, UNTAR_FILE)
if __name__ == '__main__':
from integration import run_tests
run_tests(ArchiveTest)

View File

@ -596,6 +596,35 @@ class PkgTest(integration.ModuleCase,
'Package {0} is already up-to-date'.format(target)
)
@requires_system_grains
def test_group_installed_handle_missing_package_group(self, grains=None): # pylint: disable=unused-argument
'''
Tests that a CommandExecutionError is caught and the state returns False when
the package group is missing. Before this fix, the state would stacktrace.
See Issue #35819 for bug report.
'''
# Skip test if package manager not available
if not pkgmgr_avail(self.run_function, self.run_function('grains.items')):
self.skipTest('Package manager is not available')
# Group install not available message
grp_install_msg = 'pkg.group_install not available for this platform'
# Run the pkg.group_installed state with a fake package group
ret = self.run_state('pkg.group_installed', name='handle_missing_pkg_group',
skip='foo-bar-baz')
ret_comment = ret['pkg_|-handle_missing_pkg_group_|-handle_missing_pkg_group_|-group_installed']['comment']
# Not all package managers support group_installed. Skip this test if not supported.
if ret_comment == grp_install_msg:
self.skipTest(grp_install_msg)
# Test state should return False and should have the right comment
self.assertSaltFalseReturn(ret)
self.assertEqual(ret_comment, 'An error was encountered while installing/updating group '
'\'handle_missing_pkg_group\': Group \'handle_missing_pkg_group\' '
'not found.')
if __name__ == '__main__':
from integration import run_tests
run_tests(PkgTest)

View File

@ -8,7 +8,13 @@
# Import Python libs
from __future__ import absolute_import
import libcloud.security
try:
import libcloud.security
HAS_LIBCLOUD = True
except ImportError:
HAS_LIBCLOUD = False
import platform
import os
@ -44,7 +50,7 @@ ON_SUSE = True if 'SuSE' in platform.dist() else False
ON_MAC = True if 'Darwin' in platform.system() else False
if not os.path.exists('/etc/ssl/certs/YaST-CA.pem') and ON_SUSE:
if os.path.isfile('/etc/ssl/ca-bundle.pem'):
if os.path.isfile('/etc/ssl/ca-bundle.pem') and HAS_LIBCLOUD:
libcloud.security.CA_CERTS_PATH.append('/etc/ssl/ca-bundle.pem')
else:
HAS_CERTS = False

View File

@ -8,7 +8,13 @@
# Import Python libs
from __future__ import absolute_import
import libcloud.security
try:
import libcloud.security
HAS_LIBCLOUD = True
except ImportError:
HAS_LIBCLOUD = False
import platform
import os
@ -51,7 +57,7 @@ ON_SUSE = True if 'SuSE' in platform.dist() else False
ON_MAC = True if 'Darwin' in platform.system() else False
if not os.path.exists('/etc/ssl/certs/YaST-CA.pem') and ON_SUSE:
if os.path.isfile('/etc/ssl/ca-bundle.pem'):
if os.path.isfile('/etc/ssl/ca-bundle.pem') and HAS_LIBCLOUD:
libcloud.security.CA_CERTS_PATH.append('/etc/ssl/ca-bundle.pem')
else:
HAS_CERTS = False

View File

@ -20,6 +20,8 @@ from salt.exceptions import SaltCloudSystemExit, SaltCloudNotFound
# Global Variables
opennebula.__active_provider_name__ = ''
opennebula.__opts__ = {}
opennebula.__utils__ = {}
opennebula.__utils__['cloud.cache_node'] = MagicMock()
VM_NAME = 'my-vm'
@ -761,7 +763,6 @@ class OpenNebulaTestCase(TestCase):
@patch('salt.cloud.clouds.opennebula._get_node',
MagicMock(return_value={'my-vm': {'name': 'my-vm', 'id': 0}}))
@patch('salt.utils.cloud.cache_node', MagicMock())
def test_show_instance_success(self):
'''
Tests that the node was found successfully.

194
tests/unit/conf_test.py Normal file
View File

@ -0,0 +1,194 @@
# -*- coding: utf-8 -*-
'''
Unit tests for the files in the salt/conf directory.
'''
# Import Python libs
from __future__ import absolute_import
import os
# Import Salt Testing libs
from salttesting import skipIf, TestCase
from salttesting.helpers import ensure_in_syspath
from salttesting.mock import (
NO_MOCK,
NO_MOCK_REASON,
)
ensure_in_syspath('../')
# Import Salt libs
import salt.config
SAMPLE_CONF_DIR = os.path.dirname(os.path.realpath(__file__)).split('tests')[0] + 'conf/'
@skipIf(NO_MOCK, NO_MOCK_REASON)
class ConfTest(TestCase):
'''
Validate files in the salt/conf directory.
'''
def test_conf_master_sample_is_commented(self):
'''
The sample config file located in salt/conf/master must be completely
commented out. This test checks for any lines that are not commented or blank.
'''
master_config = SAMPLE_CONF_DIR + 'master'
ret = salt.config._read_conf_file(master_config)
self.assertEqual(
ret,
{},
'Sample config file \'{0}\' must be commented out.'.format(
master_config
)
)
def test_conf_minion_sample_is_commented(self):
'''
The sample config file located in salt/conf/minion must be completely
commented out. This test checks for any lines that are not commented or blank.
'''
minion_config = SAMPLE_CONF_DIR + 'minion'
ret = salt.config._read_conf_file(minion_config)
self.assertEqual(
ret,
{},
'Sample config file \'{0}\' must be commented out.'.format(
minion_config
)
)
def test_conf_cloud_sample_is_commented(self):
'''
The sample config file located in salt/conf/cloud must be completely
commented out. This test checks for any lines that are not commented or blank.
'''
cloud_config = SAMPLE_CONF_DIR + 'cloud'
ret = salt.config._read_conf_file(cloud_config)
self.assertEqual(
ret,
{},
'Sample config file \'{0}\' must be commented out.'.format(
cloud_config
)
)
def test_conf_cloud_profiles_sample_is_commented(self):
'''
The sample config file located in salt/conf/cloud.profiles must be completely
commented out. This test checks for any lines that are not commented or blank.
'''
cloud_profiles_config = SAMPLE_CONF_DIR + 'cloud.profiles'
ret = salt.config._read_conf_file(cloud_profiles_config)
self.assertEqual(
ret,
{},
'Sample config file \'{0}\' must be commented out.'.format(
cloud_profiles_config
)
)
def test_conf_cloud_providers_sample_is_commented(self):
'''
The sample config file located in salt/conf/cloud.providers must be completely
commented out. This test checks for any lines that are not commented or blank.
'''
cloud_providers_config = SAMPLE_CONF_DIR + 'cloud.providers'
ret = salt.config._read_conf_file(cloud_providers_config)
self.assertEqual(
ret,
{},
'Sample config file \'{0}\' must be commented out.'.format(
cloud_providers_config
)
)
def test_conf_proxy_sample_is_commented(self):
'''
The sample config file located in salt/conf/proxy must be completely
commented out. This test checks for any lines that are not commented or blank.
'''
proxy_config = SAMPLE_CONF_DIR + 'proxy'
ret = salt.config._read_conf_file(proxy_config)
self.assertEqual(
ret,
{},
'Sample config file \'{0}\' must be commented out.'.format(
proxy_config
)
)
def test_conf_roster_sample_is_commented(self):
'''
The sample config file located in salt/conf/roster must be completely
commented out. This test checks for any lines that are not commented or blank.
'''
roster_config = SAMPLE_CONF_DIR + 'roster'
ret = salt.config._read_conf_file(roster_config)
self.assertEqual(
ret,
{},
'Sample config file \'{0}\' must be commented out.'.format(
roster_config
)
)
def test_conf_cloud_profiles_d_files_are_commented(self):
'''
All cloud profile sample configs in salt/conf/cloud.profiles.d/* must be completely
commented out. This test loops through all of the files in that directory to check
for any lines that are not commented or blank.
'''
cloud_sample_files = os.listdir(SAMPLE_CONF_DIR + 'cloud.profiles.d/')
for conf_file in cloud_sample_files:
profile_conf = SAMPLE_CONF_DIR + 'cloud.profiles.d/' + conf_file
ret = salt.config._read_conf_file(profile_conf)
self.assertEqual(
ret,
{},
'Sample config file \'{0}\' must be commented out.'.format(
conf_file
)
)
def test_conf_cloud_providers_d_files_are_commented(self):
'''
All cloud profile sample configs in salt/conf/cloud.providers.d/* must be completely
commented out. This test loops through all of the files in that directory to check
for any lines that are not commented or blank.
'''
cloud_sample_files = os.listdir(SAMPLE_CONF_DIR + 'cloud.providers.d/')
for conf_file in cloud_sample_files:
provider_conf = SAMPLE_CONF_DIR + 'cloud.providers.d/' + conf_file
ret = salt.config._read_conf_file(provider_conf)
self.assertEqual(
ret,
{},
'Sample config file \'{0}\' must be commented out.'.format(
conf_file
)
)
def test_conf_cloud_maps_d_files_are_commented(self):
'''
All cloud profile sample configs in salt/conf/cloud.maps.d/* must be completely
commented out. This test loops through all of the files in that directory to check
for any lines that are not commented or blank.
'''
cloud_sample_files = os.listdir(SAMPLE_CONF_DIR + 'cloud.maps.d/')
for conf_file in cloud_sample_files:
map_conf = SAMPLE_CONF_DIR + 'cloud.maps.d/' + conf_file
ret = salt.config._read_conf_file(map_conf)
self.assertEqual(
ret,
{},
'Sample config file \'{0}\' must be commented out.'.format(
conf_file
)
)
if __name__ == '__main__':
from integration import run_tests
run_tests(ConfTest, needs_daemon=False)

View File

@ -103,6 +103,11 @@ if _has_required_boto():
StopLoggingTime=None)
@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
' or equal to version {0}'
.format(required_boto3_version))
@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoCloudTrailTestCaseBase(TestCase):
conn = None
@ -128,11 +133,6 @@ class BotoCloudTrailTestCaseMixin(object):
pass
@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
' or equal to version {0}'
.format(required_boto3_version))
@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoCloudTrailTestCase(BotoCloudTrailTestCaseBase, BotoCloudTrailTestCaseMixin):
'''
TestCase for salt.modules.boto_cloudtrail module

View File

@ -103,6 +103,11 @@ if _has_required_boto():
ruleDisabled=True)
@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
' or equal to version {0}'
.format(required_boto3_version))
@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoIoTTestCaseBase(TestCase):
conn = None
@ -128,11 +133,6 @@ class BotoIoTTestCaseMixin(object):
pass
@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
' or equal to version {0}'
.format(required_boto3_version))
@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoIoTPolicyTestCase(BotoIoTTestCaseBase, BotoIoTTestCaseMixin):
'''
TestCase for salt.modules.boto_iot module

View File

@ -109,6 +109,11 @@ def _has_required_boto():
return True
@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
' or equal to version {0}'
.format(required_boto3_version))
@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoLambdaTestCaseBase(TestCase):
conn = None
@ -145,11 +150,6 @@ class BotoLambdaTestCaseMixin(object):
pass
@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
' or equal to version {0}'
.format(required_boto3_version))
@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoLambdaFunctionTestCase(BotoLambdaTestCaseBase, BotoLambdaTestCaseMixin):
'''
TestCase for salt.modules.boto_lambda module

View File

@ -205,6 +205,11 @@ if _has_required_boto():
}
@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
' or equal to version {0}'
.format(required_boto3_version))
@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoS3BucketTestCaseBase(TestCase):
conn = None
@ -230,11 +235,6 @@ class BotoS3BucketTestCaseMixin(object):
pass
@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
' or equal to version {0}'
.format(required_boto3_version))
@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoS3BucketTestCase(BotoS3BucketTestCaseBase, BotoS3BucketTestCaseMixin):
'''
TestCase for salt.modules.boto_s3_bucket module

View File

@ -124,6 +124,13 @@ def _has_required_moto():
context = {}
@skipIf(NO_MOCK, NO_MOCK_REASON)
@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
@skipIf(HAS_MOTO is False, 'The moto module must be installed.')
@skipIf(_has_required_boto() is False, 'The boto module must be greater than'
' or equal to version {0}'
.format(required_boto_version))
@skipIf(_has_required_moto() is False, 'The moto version must be >= to version {0}'.format(required_moto_version))
class BotoVpcTestCaseBase(TestCase):
def setUp(self):
boto_vpc.__context__ = {}
@ -249,13 +256,6 @@ class BotoVpcTestCaseMixin(object):
return rtbl
@skipIf(NO_MOCK, NO_MOCK_REASON)
@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
@skipIf(HAS_MOTO is False, 'The moto module must be installed.')
@skipIf(_has_required_boto() is False, 'The boto module must be greater than'
' or equal to version {0}'
.format(required_boto_version))
@skipIf(_has_required_moto() is False, 'The moto version must be >= to version {0}'.format(required_moto_version))
class BotoVpcTestCase(BotoVpcTestCaseBase, BotoVpcTestCaseMixin):
'''
TestCase for salt.modules.boto_vpc module

View File

@ -84,14 +84,19 @@ class LinuxSysctlTestCase(TestCase):
self.assertEqual(linux_sysctl.assign(
'net.ipv4.ip_forward', 1), ret)
@patch('os.path.isfile', MagicMock(return_value=False))
def test_persist_no_conf_failure(self):
'''
Tests adding of config file failure
'''
asn_cmd = {'pid': 1337, 'retcode': 0,
'stderr': "sysctl: permission denied", 'stdout': ''}
mock_asn_cmd = MagicMock(return_value=asn_cmd)
cmd = "sysctl -w net.ipv4.ip_forward=1"
mock_cmd = MagicMock(return_value=cmd)
with patch.dict(linux_sysctl.__salt__, {'cmd.run_stdout': mock_cmd,
'cmd.run_all': mock_asn_cmd}):
with patch('salt.utils.fopen', mock_open()) as m_open:
helper_open = m_open()
helper_open.write.assertRaises(CommandExecutionError,
self.assertRaises(CommandExecutionError,
linux_sysctl.persist,
'net.ipv4.ip_forward',
1, config=None)

View File

@ -72,8 +72,8 @@ class DarwinSysctlTestCase(TestCase):
Tests adding of config file failure
'''
with patch('salt.utils.fopen', mock_open()) as m_open:
helper_open = m_open()
helper_open.write.assertRaises(CommandExecutionError,
m_open.side_effect = IOError(13, 'Permission denied', '/file')
self.assertRaises(CommandExecutionError,
mac_sysctl.persist,
'net.inet.icmp.icmplim',
50, config=None)

View File

@ -141,10 +141,10 @@ class MountTestCase(TestCase):
with patch.dict(mount.__grains__, {'kernel': ''}):
with patch.object(mount, 'fstab', mock_fstab):
with patch('salt.utils.fopen', mock_open()) as m_open:
helper_open = m_open()
helper_open.write.assertRaises(CommandExecutionError,
m_open.side_effect = IOError(13, 'Permission denied:', '/file')
self.assertRaises(CommandExecutionError,
mount.rm_fstab,
config=None)
'name', 'device')
def test_set_fstab(self):
'''
@ -180,11 +180,7 @@ class MountTestCase(TestCase):
mock = MagicMock(return_value={'name': 'name'})
with patch.object(mount, 'fstab', mock):
with patch('salt.utils.fopen', mock_open()) as m_open:
helper_open = m_open()
helper_open.write.assertRaises(CommandExecutionError,
mount.rm_automaster,
'name', 'device')
self.assertTrue(mount.rm_automaster('name', 'device'))
def test_set_automaster(self):
'''

View File

@ -11,7 +11,7 @@ from __future__ import absolute_import
# Import Salt Testing libs
from salttesting import skipIf, TestCase
from salttesting.helpers import ensure_in_syspath
from salttesting.mock import NO_MOCK, NO_MOCK_REASON
from salttesting.mock import NO_MOCK, NO_MOCK_REASON, MagicMock
ensure_in_syspath('../../')
# Import salt libs
@ -20,6 +20,10 @@ from salt.modules import portage_config
@skipIf(NO_MOCK, NO_MOCK_REASON)
class PortageConfigTestCase(TestCase):
class DummyAtom(object):
def __init__(self, atom):
self.cp, self.repo = atom.split("::") if "::" in atom else (atom, None)
def test_get_config_file_wildcards(self):
pairs = [
('*/*::repo', '/etc/portage/package.mask/repo'),
@ -29,7 +33,11 @@ class PortageConfigTestCase(TestCase):
('cat/pkg::repo', '/etc/portage/package.mask/cat/pkg'),
]
portage_config.portage = MagicMock()
for (atom, expected) in pairs:
dummy_atom = self.DummyAtom(atom)
portage_config.portage.dep.Atom = MagicMock(return_value=dummy_atom)
portage_config._p_to_cp = MagicMock(return_value=dummy_atom.cp)
self.assertEqual(portage_config._get_config_file('mask', atom), expected)
if __name__ == '__main__':

View File

@ -85,10 +85,12 @@ class PuppetTestCase(TestCase):
with patch('salt.utils.fopen', mock_open()):
self.assertTrue(puppet.disable())
try:
with patch('salt.utils.fopen', mock_open()) as m_open:
helper_open = m_open()
helper_open.write.assertRaises(CommandExecutionError,
puppet.disable)
m_open.side_effect = IOError(13, 'Permission denied:', '/file')
self.assertRaises(CommandExecutionError, puppet.disable)
except StopIteration:
pass
def test_status(self):
'''
@ -145,9 +147,8 @@ class PuppetTestCase(TestCase):
self.assertDictEqual(puppet.summary(), {'resources': 1})
with patch('salt.utils.fopen', mock_open()) as m_open:
helper_open = m_open()
helper_open.write.assertRaises(CommandExecutionError,
puppet.summary)
m_open.side_effect = IOError(13, 'Permission denied:', '/file')
self.assertRaises(CommandExecutionError, puppet.summary)
def test_plugin_sync(self):
'''

View File

@ -326,7 +326,7 @@ class UserAddTestCase(TestCase):
'''
Test the user information
'''
self.assertEqual(useradd.info('salt'), {})
self.assertEqual(useradd.info('username-that-doesnt-exist'), {})
mock = MagicMock(return_value=pwd.struct_passwd(('_TEST_GROUP',
'*',
@ -336,9 +336,7 @@ class UserAddTestCase(TestCase):
'/var/virusmails',
'/usr/bin/false')))
with patch.object(pwd, 'getpwnam', mock):
mock = MagicMock(return_value='Group Name')
with patch.object(useradd, 'list_groups', mock):
self.assertEqual(useradd.info('salt')['name'], '_TEST_GROUP')
self.assertEqual(useradd.info('username-that-doesnt-exist')['name'], '_TEST_GROUP')
# 'list_groups' function tests: 1

View File

@ -54,10 +54,18 @@ include('http')
extend_template = '''#!pyobjects
include('http')
from salt.utils.pyobjects import StateFactory
Service = StateFactory('service')
Service.running(extend('apache'), watch=[{'file': '/etc/file'}])
'''
map_template = '''#!pyobjects
from salt.utils.pyobjects import StateFactory
Service = StateFactory('service')
class Samba(Map):
__merge__ = 'samba:lookup'
@ -127,6 +135,9 @@ from salt://password.sls import password
'''
requisite_implicit_list_template = '''#!pyobjects
from salt.utils.pyobjects import StateFactory
Service = StateFactory('service')
with Pkg.installed("pkg"):
Service.running("service", watch=File("file"), require=Cmd("cmd"))
'''

View File

@ -104,6 +104,11 @@ if _has_required_boto():
StopLoggingTime=None)
@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
' or equal to version {0}'
.format(required_boto3_version))
@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoCloudTrailStateTestCaseBase(TestCase):
conn = None
@ -124,11 +129,6 @@ class BotoCloudTrailStateTestCaseBase(TestCase):
session_instance.client.return_value = self.conn
@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
' or equal to version {0}'
.format(required_boto3_version))
@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoCloudTrailTestCase(BotoCloudTrailStateTestCaseBase, BotoCloudTrailTestCaseMixin):
'''
TestCase for salt.modules.boto_cloudtrail state.module

View File

@ -103,6 +103,11 @@ if _has_required_boto():
principal = 'arn:aws:iot:us-east-1:1234:cert/21fc104aaaf6043f5756c1b57bda84ea8395904c43f28517799b19e4c42514'
@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
' or equal to version {0}'
.format(required_boto3_version))
@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoIoTStateTestCaseBase(TestCase):
conn = None
@ -123,11 +128,6 @@ class BotoIoTStateTestCaseBase(TestCase):
session_instance.client.return_value = self.conn
@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
' or equal to version {0}'
.format(required_boto3_version))
@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoIoTPolicyTestCase(BotoIoTStateTestCaseBase, BotoIoTTestCaseMixin):
'''
TestCase for salt.modules.boto_iot state.module

View File

@ -101,6 +101,11 @@ def _has_required_boto():
return True
@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
' or equal to version {0}'
.format(required_boto3_version))
@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoLambdaStateTestCaseBase(TestCase):
conn = None
@ -121,11 +126,6 @@ class BotoLambdaStateTestCaseBase(TestCase):
session_instance.client.return_value = self.conn
@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
' or equal to version {0}'
.format(required_boto3_version))
@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoLambdaFunctionTestCase(BotoLambdaStateTestCaseBase, BotoLambdaTestCaseMixin):
'''
TestCase for salt.modules.boto_lambda state.module

View File

@ -277,6 +277,11 @@ if _has_required_boto():
}
@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
' or equal to version {0}'
.format(required_boto3_version))
@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoS3BucketStateTestCaseBase(TestCase):
conn = None
@ -297,11 +302,6 @@ class BotoS3BucketStateTestCaseBase(TestCase):
session_instance.client.return_value = self.conn
@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
' or equal to version {0}'
.format(required_boto3_version))
@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoS3BucketTestCase(BotoS3BucketStateTestCaseBase, BotoS3BucketTestCaseMixin):
'''
TestCase for salt.modules.boto_s3_bucket state.module

View File

@ -20,6 +20,7 @@ def provision_state(module, fixture):
@skipIf(NO_MOCK, NO_MOCK_REASON)
@skipIf(True, 'Skipped: This module has been deprecated.')
class DockerStateTestCase(TestCase):
def test_docker_run_success(self):
from salt.states import dockerio

View File

@ -1367,26 +1367,6 @@ class FileTestCase(TestCase):
(name, source,
preserve=True), ret)
with patch.object(os.path, 'isdir', mock_t):
with patch.dict(filestate.__opts__, {'test': False}):
with patch.object(shutil, 'copy',
MagicMock(side_effect=[IOError,
True])):
comt = ('Failed to copy "{0}" to "{1}"'
.format(source, name))
ret.update({'comment': comt, 'result': False})
self.assertDictEqual(filestate.copy
(name, source,
preserve=True), ret)
comt = ('Copied "{0}" to "{1}"'.format(source,
name))
ret.update({'comment': comt, 'result': True,
'changes': {name: source}})
self.assertDictEqual(filestate.copy
(name, source,
preserve=True), ret)
# 'rename' function tests: 1
def test_rename(self):

View File

@ -150,16 +150,17 @@ class NetworkTestCase(TestCase):
interfaces = network._interfaces_ifconfig(SOLARIS)
self.assertEqual(interfaces,
{'ilbext0': {'inet': [{'address': '10.10.11.11',
'broadcast': '10.10.11.31',
'netmask': '255.255.255.224'},
{'address': '10.10.11.12',
'broadcast': '10.10.11.31',
'netmask': '255.255.255.224'}],
'inet6': [{'address': '::',
'prefixlen': '0'}],
'inet6': [],
'up': True},
'ilbint0': {'inet': [{'address': '10.6.0.11',
'broadcast': '10.6.0.255',
'netmask': '255.255.255.0'}],
'inet6': [{'address': '::',
'prefixlen': '0'}],
'inet6': [],
'up': True},
'lo0': {'inet': [{'address': '127.0.0.1',
'netmask': '255.0.0.0'}],
@ -174,8 +175,7 @@ class NetworkTestCase(TestCase):
'up': True},
'vpn0': {'inet': [{'address': '10.6.0.14',
'netmask': '255.0.0.0'}],
'inet6': [{'address': '::',
'prefixlen': '0'}],
'inet6': [],
'up': True}}
)

View File

@ -523,11 +523,6 @@ class UtilsTestCase(TestCase):
datetime.datetime.now.return_value = now
self.assertEqual(now, utils.date_cast(None))
self.assertEqual(now, utils.date_cast(now))
try:
ret = utils.date_cast('Mon Dec 23 10:19:15 MST 2013')
expected_ret = datetime.datetime(2013, 12, 23, 10, 19, 15)
self.assertEqual(ret, expected_ret)
except ImportError:
try:
ret = utils.date_cast('Mon Dec 23 10:19:15 MST 2013')
expected_ret = datetime.datetime(2013, 12, 23, 10, 19, 15)