Merge branch '2018.3' into jid

This commit is contained in:
Nicole Thomas 2018-08-05 15:16:41 -04:00 committed by GitHub
commit 084e7f0ef4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
195 changed files with 12502 additions and 1770 deletions

View File

@ -1,10 +1,23 @@
pipeline { pipeline {
agent { label 'docs' } agent { label 'docs' }
options {
timestamps()
ansiColor('xterm')
}
environment { environment {
PYENV_ROOT = "/usr/local/pyenv" PYENV_ROOT = "/usr/local/pyenv"
PATH = "$PYENV_ROOT/bin:$PATH" PATH = "$PYENV_ROOT/bin:$PATH"
PY_COLORS = 1
} }
stages { stages {
stage('github-pending') {
steps {
githubNotify credentialsId: 'test-jenkins-credentials',
description: 'Testing docs...',
status: 'PENDING',
context: "jenkins/pr/docs"
}
}
stage('setup') { stage('setup') {
steps { steps {
sh 'eval "$(pyenv init -)"; pyenv install 2.7.14 || echo "We already have this python."; pyenv local 2.7.14; pyenv shell 2.7.14' sh 'eval "$(pyenv init -)"; pyenv install 2.7.14 || echo "We already have this python."; pyenv local 2.7.14; pyenv shell 2.7.14'
@ -14,16 +27,24 @@ pipeline {
stage('build') { stage('build') {
steps { steps {
sh 'eval "$(pyenv init -)"; make -C doc clean html' sh 'eval "$(pyenv init -)"; make -C doc clean html'
archiveArtifacts artifacts: 'doc/_build/html'
} }
} }
} }
post { post {
always {
cleanWs()
}
success { success {
githubNotify description: "The docs job has passed, artifacts have been saved", status: "SUCCESS" githubNotify credentialsId: 'test-jenkins-credentials',
description: 'The docs job has passed',
status: 'SUCCESS',
context: "jenkins/pr/docs"
} }
failure { failure {
githubNotify description: "The docs job has failed", status: "FAILURE" githubNotify credentialsId: 'test-jenkins-credentials',
description: 'The docs job has failed',
status: 'FAILURE',
context: "jenkins/pr/docs"
} }
} }
} }

View File

@ -1,5 +1,9 @@
pipeline { pipeline {
agent { label 'kitchen-slave' } agent { label 'kitchen-slave' }
options {
timestamps()
ansiColor('xterm')
}
environment { environment {
SALT_KITCHEN_PLATFORMS = "/var/jenkins/workspace/platforms.yml" SALT_KITCHEN_PLATFORMS = "/var/jenkins/workspace/platforms.yml"
SALT_KITCHEN_DRIVER = "/var/jenkins/workspace/driver.yml" SALT_KITCHEN_DRIVER = "/var/jenkins/workspace/driver.yml"
@ -7,8 +11,17 @@ pipeline {
RBENV_VERSION = "2.4.2" RBENV_VERSION = "2.4.2"
TEST_SUITE = "py2" TEST_SUITE = "py2"
TEST_PLATFORM = "centos-7" TEST_PLATFORM = "centos-7"
PY_COLORS = 1
} }
stages { stages {
stage('github-pending') {
steps {
githubNotify credentialsId: 'test-jenkins-credentials',
description: "running ${TEST_SUITE}-${TEST_PLATFORM}...",
status: 'PENDING',
context: "jenkins/pr/${TEST_SUITE}-${TEST_PLATFORM}"
}
}
stage('setup') { stage('setup') {
steps { steps {
sh 'bundle install --with ec2 windows --without opennebula docker' sh 'bundle install --with ec2 windows --without opennebula docker'
@ -23,26 +36,36 @@ pipeline {
sh 'bundle exec kitchen verify $TEST_SUITE-$TEST_PLATFORM' sh 'bundle exec kitchen verify $TEST_SUITE-$TEST_PLATFORM'
} }
}} }}
archiveArtifacts artifacts: 'artifacts/xml-unittests-output/*.xml'
} }
post { post {
always { always {
script { withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', accessKeyVariable: 'AWS_ACCESS_KEY_ID', credentialsId: 'AWS_ACCESS_KEY_ID', secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) { script { withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', accessKeyVariable: 'AWS_ACCESS_KEY_ID', credentialsId: 'AWS_ACCESS_KEY_ID', secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) {
sshagent(credentials: ['jenkins-testing-ssh-key']) { sshagent(credentials: ['jenkins-testing-ssh-key']) {
sh 'ssh-add ~/.ssh/jenkins/jenkins-testing.pem' sh 'ssh-add ~/.ssh/jenkins-testing.pem'
sh 'bundle exec kitchen destroy $TEST_SUITE-$TEST_PLATFORM' sh 'bundle exec kitchen destroy $TEST_SUITE-$TEST_PLATFORM'
} }
}} }}
archiveArtifacts artifacts: 'artifacts/xml-unittests-output/*.xml'
} }
} }
} }
} }
post { post {
always {
junit 'artifacts/xml-unittests-output/*.xml'
cleanWs()
}
success { success {
githubNotify description: "The centos7-py2 job has passed", status: "SUCCESS" githubNotify credentialsId: 'test-jenkins-credentials',
description: "The ${TEST_SUITE}-${TEST_PLATFORM} job has passed",
status: 'SUCCESS',
context: "jenkins/pr/${TEST_SUITE}-${TEST_PLATFORM}"
} }
failure { failure {
githubNotify description: "The centos7-py2 job has failed", status: "FAILURE" githubNotify credentialsId: 'test-jenkins-credentials',
description: "The ${TEST_SUITE}-${TEST_PLATFORM} job has failed",
status: 'FAILURE',
context: "jenkins/pr/${TEST_SUITE}-${TEST_PLATFORM}"
} }
} }
} }

View File

@ -1,5 +1,9 @@
pipeline { pipeline {
agent { label 'kitchen-slave' } agent { label 'kitchen-slave' }
options {
timestamps()
ansiColor('xterm')
}
environment { environment {
SALT_KITCHEN_PLATFORMS = "/var/jenkins/workspace/platforms.yml" SALT_KITCHEN_PLATFORMS = "/var/jenkins/workspace/platforms.yml"
SALT_KITCHEN_DRIVER = "/var/jenkins/workspace/driver.yml" SALT_KITCHEN_DRIVER = "/var/jenkins/workspace/driver.yml"
@ -7,8 +11,17 @@ pipeline {
RBENV_VERSION = "2.4.2" RBENV_VERSION = "2.4.2"
TEST_SUITE = "py3" TEST_SUITE = "py3"
TEST_PLATFORM = "centos-7" TEST_PLATFORM = "centos-7"
PY_COLORS = 1
} }
stages { stages {
stage('github-pending') {
steps {
githubNotify credentialsId: 'test-jenkins-credentials',
description: "running ${TEST_SUITE}-${TEST_PLATFORM}...",
status: 'PENDING',
context: "jenkins/pr/${TEST_SUITE}-${TEST_PLATFORM}"
}
}
stage('setup') { stage('setup') {
steps { steps {
sh 'bundle install --with ec2 windows --without opennebula docker' sh 'bundle install --with ec2 windows --without opennebula docker'
@ -23,26 +36,36 @@ pipeline {
sh 'bundle exec kitchen verify $TEST_SUITE-$TEST_PLATFORM' sh 'bundle exec kitchen verify $TEST_SUITE-$TEST_PLATFORM'
} }
}} }}
archiveArtifacts artifacts: 'artifacts/xml-unittests-output/*.xml'
} }
post { post {
always { always {
script { withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', accessKeyVariable: 'AWS_ACCESS_KEY_ID', credentialsId: 'AWS_ACCESS_KEY_ID', secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) { script { withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', accessKeyVariable: 'AWS_ACCESS_KEY_ID', credentialsId: 'AWS_ACCESS_KEY_ID', secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) {
sshagent(credentials: ['jenkins-testing-ssh-key']) { sshagent(credentials: ['jenkins-testing-ssh-key']) {
sh 'ssh-add ~/.ssh/jenkins/jenkins-testing.pem' sh 'ssh-add ~/.ssh/jenkins-testing.pem'
sh 'bundle exec kitchen destroy $TEST_SUITE-$TEST_PLATFORM' sh 'bundle exec kitchen destroy $TEST_SUITE-$TEST_PLATFORM'
} }
}} }}
archiveArtifacts artifacts: 'artifacts/xml-unittests-output/*.xml'
} }
} }
} }
} }
post { post {
always {
junit 'artifacts/xml-unittests-output/*.xml'
cleanWs()
}
success { success {
githubNotify description: "The centos7-py3 job has passed", status: "SUCCESS" githubNotify credentialsId: 'test-jenkins-credentials',
description: "The ${TEST_SUITE}-${TEST_PLATFORM} job has passed",
status: 'SUCCESS',
context: "jenkins/pr/${TEST_SUITE}-${TEST_PLATFORM}"
} }
failure { failure {
githubNotify description: "The centos7-py3 job has failed", status: "FAILURE" githubNotify credentialsId: 'test-jenkins-credentials',
description: "The ${TEST_SUITE}-${TEST_PLATFORM} job has failed",
status: 'FAILURE',
context: "jenkins/pr/${TEST_SUITE}-${TEST_PLATFORM}"
} }
} }
} }

View File

@ -1,5 +1,9 @@
pipeline { pipeline {
agent { label 'kitchen-slave' } agent { label 'kitchen-slave' }
options {
timestamps()
ansiColor('xterm')
}
environment { environment {
SALT_KITCHEN_PLATFORMS = "/var/jenkins/workspace/platforms.yml" SALT_KITCHEN_PLATFORMS = "/var/jenkins/workspace/platforms.yml"
SALT_KITCHEN_DRIVER = "/var/jenkins/workspace/driver.yml" SALT_KITCHEN_DRIVER = "/var/jenkins/workspace/driver.yml"
@ -7,8 +11,17 @@ pipeline {
RBENV_VERSION = "2.4.2" RBENV_VERSION = "2.4.2"
TEST_SUITE = "py2" TEST_SUITE = "py2"
TEST_PLATFORM = "ubuntu-1604" TEST_PLATFORM = "ubuntu-1604"
PY_COLORS = 1
} }
stages { stages {
stage('github-pending') {
steps {
githubNotify credentialsId: 'test-jenkins-credentials',
description: "running ${TEST_SUITE}-${TEST_PLATFORM}...",
status: 'PENDING',
context: "jenkins/pr/${TEST_SUITE}-${TEST_PLATFORM}"
}
}
stage('setup') { stage('setup') {
steps { steps {
sh 'bundle install --with ec2 windows --without opennebula docker' sh 'bundle install --with ec2 windows --without opennebula docker'
@ -23,26 +36,36 @@ pipeline {
sh 'bundle exec kitchen verify $TEST_SUITE-$TEST_PLATFORM' sh 'bundle exec kitchen verify $TEST_SUITE-$TEST_PLATFORM'
} }
}} }}
archiveArtifacts artifacts: 'artifacts/xml-unittests-output/*.xml'
} }
post { post {
always { always {
script { withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', accessKeyVariable: 'AWS_ACCESS_KEY_ID', credentialsId: 'AWS_ACCESS_KEY_ID', secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) { script { withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', accessKeyVariable: 'AWS_ACCESS_KEY_ID', credentialsId: 'AWS_ACCESS_KEY_ID', secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) {
sshagent(credentials: ['jenkins-testing-ssh-key']) { sshagent(credentials: ['jenkins-testing-ssh-key']) {
sh 'ssh-add ~/.ssh/jenkins/jenkins-testing.pem' sh 'ssh-add ~/.ssh/jenkins-testing.pem'
sh 'bundle exec kitchen destroy $TEST_SUITE-$TEST_PLATFORM' sh 'bundle exec kitchen destroy $TEST_SUITE-$TEST_PLATFORM'
} }
}} }}
archiveArtifacts artifacts: 'artifacts/xml-unittests-output/*.xml'
} }
} }
} }
} }
post { post {
always {
junit 'artifacts/xml-unittests-output/*.xml'
cleanWs()
}
success { success {
githubNotify description: "The ubuntu-1604-py2 job has passed", status: "SUCCESS" githubNotify credentialsId: 'test-jenkins-credentials',
description: "The ${TEST_SUITE}-${TEST_PLATFORM} job has passed",
status: 'SUCCESS',
context: "jenkins/pr/${TEST_SUITE}-${TEST_PLATFORM}"
} }
failure { failure {
githubNotify description: "The ubuntu-1604-py2 job has failed", status: "FAILURE" githubNotify credentialsId: 'test-jenkins-credentials',
description: "The ${TEST_SUITE}-${TEST_PLATFORM} job has failed",
status: 'FAILURE',
context: "jenkins/pr/${TEST_SUITE}-${TEST_PLATFORM}"
} }
} }
} }

View File

@ -1,5 +1,9 @@
pipeline { pipeline {
agent { label 'kitchen-slave' } agent { label 'kitchen-slave' }
options {
timestamps()
ansiColor('xterm')
}
environment { environment {
SALT_KITCHEN_PLATFORMS = "/var/jenkins/workspace/platforms.yml" SALT_KITCHEN_PLATFORMS = "/var/jenkins/workspace/platforms.yml"
SALT_KITCHEN_DRIVER = "/var/jenkins/workspace/driver.yml" SALT_KITCHEN_DRIVER = "/var/jenkins/workspace/driver.yml"
@ -7,8 +11,17 @@ pipeline {
RBENV_VERSION = "2.4.2" RBENV_VERSION = "2.4.2"
TEST_SUITE = "py3" TEST_SUITE = "py3"
TEST_PLATFORM = "ubuntu-1604" TEST_PLATFORM = "ubuntu-1604"
PY_COLORS = 1
} }
stages { stages {
stage('github-pending') {
steps {
githubNotify credentialsId: 'test-jenkins-credentials',
description: "running ${TEST_SUITE}-${TEST_PLATFORM}...",
status: 'PENDING',
context: "jenkins/pr/${TEST_SUITE}-${TEST_PLATFORM}"
}
}
stage('setup') { stage('setup') {
steps { steps {
sh 'bundle install --with ec2 windows --without opennebula docker' sh 'bundle install --with ec2 windows --without opennebula docker'
@ -23,26 +36,36 @@ pipeline {
sh 'bundle exec kitchen verify $TEST_SUITE-$TEST_PLATFORM' sh 'bundle exec kitchen verify $TEST_SUITE-$TEST_PLATFORM'
} }
}} }}
archiveArtifacts artifacts: 'artifacts/xml-unittests-output/*.xml'
} }
post { post {
always { always {
script { withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', accessKeyVariable: 'AWS_ACCESS_KEY_ID', credentialsId: 'AWS_ACCESS_KEY_ID', secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) { script { withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', accessKeyVariable: 'AWS_ACCESS_KEY_ID', credentialsId: 'AWS_ACCESS_KEY_ID', secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) {
sshagent(credentials: ['jenkins-testing-ssh-key']) { sshagent(credentials: ['jenkins-testing-ssh-key']) {
sh 'ssh-add ~/.ssh/jenkins/jenkins-testing.pem' sh 'ssh-add ~/.ssh/jenkins-testing.pem'
sh 'bundle exec kitchen destroy $TEST_SUITE-$TEST_PLATFORM' sh 'bundle exec kitchen destroy $TEST_SUITE-$TEST_PLATFORM'
} }
}} }}
archiveArtifacts artifacts: 'artifacts/xml-unittests-output/*.xml'
} }
} }
} }
} }
post { post {
always {
junit 'artifacts/xml-unittests-output/*.xml'
cleanWs()
}
success { success {
githubNotify description: "The ubuntu-1604-py3 job has passed", status: "SUCCESS" githubNotify credentialsId: 'test-jenkins-credentials',
description: "The ${TEST_SUITE}-${TEST_PLATFORM} job has passed",
status: 'SUCCESS',
context: "jenkins/pr/${TEST_SUITE}-${TEST_PLATFORM}"
} }
failure { failure {
githubNotify description: "The ubuntu-1604-py3 job has failed", status: "FAILURE" githubNotify credentialsId: 'test-jenkins-credentials',
description: "The ${TEST_SUITE}-${TEST_PLATFORM} job has failed",
status: 'FAILURE',
context: "jenkins/pr/${TEST_SUITE}-${TEST_PLATFORM}"
} }
} }
} }

View File

@ -1,15 +1,27 @@
pipeline { pipeline {
agent { label 'pr-lint-slave' } agent { label 'pr-lint-slave' }
options {
timestamps()
ansiColor('xterm')
}
environment { environment {
PYENV_ROOT = "/usr/local/pyenv" PYENV_ROOT = "/usr/local/pyenv"
PATH = "$PYENV_ROOT/bin:$PATH" PATH = "$PYENV_ROOT/bin:$PATH"
PY_COLORS = 1
} }
stages { stages {
stage('github-pending') {
steps {
githubNotify credentialsId: 'test-jenkins-credentials',
description: 'Testing lint...',
status: 'PENDING',
context: "jenkins/pr/lint"
}
}
stage('setup') { stage('setup') {
steps { steps {
sh 'eval "$(pyenv init -)"; pyenv install 2.7.14 || echo "We already have this python."; pyenv local 2.7.14; pyenv shell 2.7.14' sh 'eval "$(pyenv init -)"; pyenv install 2.7.14 || echo "We already have this python."; pyenv local 2.7.14; pyenv shell 2.7.14'
sh 'eval "$(pyenv init -)"; pip install pylint SaltPyLint' sh 'eval "$(pyenv init -)"; pip install tox'
sh 'eval "$(pyenv init -)"; which pylint; pylint --version'
} }
} }
stage('linting') { stage('linting') {
@ -17,13 +29,13 @@ pipeline {
parallel { parallel {
stage('salt linting') { stage('salt linting') {
steps { steps {
sh 'eval "$(pyenv init -)"; pylint --rcfile=.testing.pylintrc --disable=W1307,str-format-in-logging setup.py salt/ | tee pylint-report.xml' sh 'eval "$(pyenv init - --no-rehash)"; tox -e pylint-salt $(find salt/ -name "*.py" -exec git diff --name-only "origin/$CHANGE_TARGET" "origin/$BRANCH_NAME" setup.py {} +) | tee pylint-report.xml'
archiveArtifacts artifacts: 'pylint-report.xml' archiveArtifacts artifacts: 'pylint-report.xml'
} }
} }
stage('test linting') { stage('test linting') {
steps { steps {
sh 'eval "$(pyenv init -)"; pylint --rcfile=.testing.pylintrc --disable=W0232,E1002,W1307,str-format-in-logging tests/ | tee pylint-report-tests.xml' sh 'eval "$(pyenv init - --no-rehash)"; tox -e pylint-tests $(find tests/ -name "*.py" -exec git diff --name-only "origin/$CHANGE_TARGET" "origin/$BRANCH_NAME" {} +) | tee pylint-report-tests.xml'
archiveArtifacts artifacts: 'pylint-report-tests.xml' archiveArtifacts artifacts: 'pylint-report-tests.xml'
} }
} }
@ -31,11 +43,30 @@ pipeline {
} }
} }
post { post {
always {
step([$class: 'WarningsPublisher',
parserConfigurations: [[
parserName: 'PyLint',
pattern: 'pylint-report*.xml'
]],
failedTotalAll: '0',
useDeltaValues: false,
canRunOnFailed: true,
usePreviousBuildAsReference: true
])
cleanWs()
}
success { success {
githubNotify description: "The lint job has passed", status: "SUCCESS" githubNotify credentialsId: 'test-jenkins-credentials',
description: 'The lint job has passed',
status: 'SUCCESS',
context: "jenkins/pr/lint"
} }
failure { failure {
githubNotify description: "The lint job has failed", status: "FAILURE" githubNotify credentialsId: 'test-jenkins-credentials',
description: 'The lint job has failed',
status: 'FAILURE',
context: "jenkins/pr/lint"
} }
} }
} }

1
.github/CODEOWNERS vendored
View File

@ -48,6 +48,7 @@ salt/spm/* @saltstack/team-spm
# Team SSH # Team SSH
salt/cli/ssh.py @saltstack/team-ssh salt/cli/ssh.py @saltstack/team-ssh
salt/client/ssh/* @saltstack/team-ssh salt/client/ssh/* @saltstack/team-ssh
salt/roster/* @saltstack/team-ssh
salt/runners/ssh.py @saltstack/team-ssh salt/runners/ssh.py @saltstack/team-ssh
salt/**/thin.py @saltstack/team-ssh salt/**/thin.py @saltstack/team-ssh

View File

@ -1,6 +1,6 @@
--- ---
<% vagrant = system('gem list -i kitchen-vagrant 2>/dev/null >/dev/null') %> <% vagrant = system('gem list -i kitchen-vagrant 2>/dev/null >/dev/null') %>
<% version = '2017.7.4' %> <% version = '2017.7.6' %>
<% platformsfile = ENV['SALT_KITCHEN_PLATFORMS'] || '.kitchen/platforms.yml' %> <% platformsfile = ENV['SALT_KITCHEN_PLATFORMS'] || '.kitchen/platforms.yml' %>
<% driverfile = ENV['SALT_KITCHEN_DRIVER'] || '.kitchen/driver.yml' %> <% driverfile = ENV['SALT_KITCHEN_DRIVER'] || '.kitchen/driver.yml' %>
<% verifierfile = ENV['SALT_KITCHEN_VERIFIER'] || '.kitchen/verifier.yml' %> <% verifierfile = ENV['SALT_KITCHEN_VERIFIER'] || '.kitchen/verifier.yml' %>
@ -31,7 +31,7 @@ provisioner:
salt_version: latest salt_version: latest
salt_bootstrap_url: https://bootstrap.saltstack.com salt_bootstrap_url: https://bootstrap.saltstack.com
salt_bootstrap_options: -X -p rsync stable <%= version %> salt_bootstrap_options: -X -p rsync stable <%= version %>
log_level: debug log_level: info
sudo: true sudo: true
require_chef: false require_chef: false
retry_on_exit_code: retry_on_exit_code:

View File

@ -138,7 +138,7 @@
<span class="icon-bar"></span> <span class="icon-bar"></span>
<span class="icon-bar"></span> <span class="icon-bar"></span>
</button> </button>
<a href="http://saltstack.com/" target="_blank"><img src="{{ pathto('_static/images/saltstack.svg', 1) }}" class="nolightbox" height="40px" width="170px"></a> <a href="http://saltstack.com/" target="_blank"><img src="{{ pathto('_static/images/SaltStack_white.svg', 1) }}" class="nolightbox" height="40px" width="170px"></a>
</div> </div>
<!-- Collect the nav links, forms, and other content for toggling --> <!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="navbarCollapse"> <div class="collapse navbar-collapse" id="navbarCollapse">
@ -252,13 +252,11 @@
<p>© {{ copyright }} SaltStack. All Rights Reserved, SaltStack Inc. | <a href="http://saltstack.com/privacy-policy" target="_blank">Privacy Policy</a></p> <p>© {{ copyright }} SaltStack. All Rights Reserved, SaltStack Inc. | <a href="http://saltstack.com/privacy-policy" target="_blank">Privacy Policy</a></p>
</div> </div>
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
<!--
<a href="https://saltstack.com/saltstack-enterprise/" target="_blank"><img class="nolightbox footer-banner center" src="{{ pathto('_static/images/enterprise_ad.jpg', 1) }}"/></a> <a href="https://saltstack.com/saltstack-enterprise/" target="_blank"><img class="nolightbox footer-banner center" src="{{ pathto('_static/images/enterprise_ad.jpg', 1) }}"/></a>
-->
<a href="http://saltconf.com" target="_blank"><img class="nolightbox footer-banner center" src="{{ pathto('_static/images/DOCBANNER.jpg', 1) }}"/></a> <a href="http://saltconf.com" target="_blank"><img class="nolightbox footer-banner center" src="{{ pathto('_static/images/DOCBANNER.jpg', 1) }}"/></a>
</div> </div>
{% endif %} {% endif %}
</div> </div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 240 KiB

After

Width:  |  Height:  |  Size: 589 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 301.57 44.47"><defs><style>.cls-1{fill:#fff;}</style></defs><title>Asset 2</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M50.63,6.05h8.79L72.58,43.24H64.15L61.7,35.6H48l-2.52,7.64H37.35Zm-.51,23.14h9.52l-4.7-14.64Z"/><path class="cls-1" d="M76.12,6.05h7.77V36.56h18.58v6.68H76.12Z"/><path class="cls-1" d="M127.07,6.05v6.58H115.94V43.24h-7.82V12.63H96.94V6.05Z"/><path class="cls-1" d="M190.53,6.05v6.58H179.41V43.24h-7.83V12.63H160.4V6.05Z"/><path class="cls-1" d="M200.41,6.05h8.8l13.16,37.19h-8.43l-2.45-7.64h-13.7l-2.52,7.64h-8.13Zm-.51,23.14h9.53l-4.7-14.64Z"/><path class="cls-1" d="M227.91,10.17a15.34,15.34,0,0,1,11.34-4.83q9.28-.2,13.71,5.87a13.46,13.46,0,0,1,2.69,6.89l-7.77.16a10.78,10.78,0,0,0-2-4A7.68,7.68,0,0,0,239.68,12a7.59,7.59,0,0,0-6.48,3.56Q230.88,19,231,25.25t2.73,9.32a7.91,7.91,0,0,0,6.49,3,7.27,7.27,0,0,0,6-2.8,11.86,11.86,0,0,0,1.84-4.35l7.72-.17a16.79,16.79,0,0,1-4.9,10,14.83,14.83,0,0,1-10.44,4q-7.94.16-12.59-4.88t-4.84-14.09Q222.85,15.56,227.91,10.17Z"/><path class="cls-1" d="M260.39,6.05H268V21.37L282.4,6.05h10.05L277.18,21.34l16.05,21.9h-10L271.78,27,268,30.8V43.24h-7.64Z"/><path class="cls-1" d="M15.65,12.47a9.05,9.05,0,0,1,4.6-1.05,10.15,10.15,0,0,1,4.63.94,5.29,5.29,0,0,1,3,4.77h7.47a11.06,11.06,0,0,0-4.4-9A16.57,16.57,0,0,0,20.79,5a23,23,0,0,0-5.14.54Z"/><path class="cls-1" d="M33.21,24.77a16.6,16.6,0,0,0-6.43-2.7l-6.53-1.54A22.45,22.45,0,0,1,15.31,19a3.18,3.18,0,0,1-1.81-3c0-.12,0-.22,0-.34H6.35c0,.3,0,.59,0,.9q0,5.47,3.76,8.05,2.22,1.54,8.15,2.85l4,.89a17.48,17.48,0,0,1,5.17,1.74A3.17,3.17,0,0,1,29,33c0,2.1-1.1,3.54-3.32,4.31a14.08,14.08,0,0,1-4.56.61c-3.13,0-5.33-.77-6.62-2.3a7.48,7.48,0,0,1-1.42-3.81H5.7a11,11,0,0,0,4.21,9.12q4.22,3.33,11.56,3.32,7.19,0,11.13-3.37a10.64,10.64,0,0,0,3.94-8.46A9.1,9.1,0,0,0,33.21,24.77Z"/><path class="cls-1" d="M137.55,12.73a9,9,0,0,1,4.6-1.05,10.18,10.18,0,0,1,4.63.94,5.33,5.33,0,0,1,3,4.77h7.47a11,11,0,0,0-4.41-9,16.54,16.54,0,0,0-10.15-3.12,23,23,0,0,0-5.14.54Z"/><path class="cls-1" d="M155.11,25a16.83,16.83,0,0,0-6.43-2.7l-6.53-1.54a21.71,21.71,0,0,1-4.94-1.54,3.17,3.17,0,0,1-1.81-3c0-.13,0-.23,0-.35h-7.19c0,.3-.05.6-.05.9,0,3.65,1.26,6.34,3.77,8.05,1.48,1,4.19,2,8.15,2.85l4,.89a17.29,17.29,0,0,1,5.16,1.74,3.16,3.16,0,0,1,1.63,2.85q0,3.15-3.32,4.32a14.39,14.39,0,0,1-4.56.6c-3.13,0-5.34-.76-6.62-2.3A7.48,7.48,0,0,1,135,32H127.6a11,11,0,0,0,4.21,9.13c2.8,2.21,6.66,3.32,11.55,3.32s8.51-1.13,11.14-3.37a10.7,10.7,0,0,0,3.94-8.47A9.1,9.1,0,0,0,155.11,25Z"/><rect class="cls-1" width="9.85" height="9.85"/><path class="cls-1" d="M299.8,40.44a.94.94,0,0,0,.16-.57,1.06,1.06,0,0,0-.08-.45.82.82,0,0,0-.24-.32,1,1,0,0,0-.38-.19,1.72,1.72,0,0,0-.52-.07h-1.2v3.4h.59V40.93h.66l.68,1.31h.63v0l-.76-1.42A1,1,0,0,0,299.8,40.44Zm-.59-.14a.52.52,0,0,1-.19.12,1.18,1.18,0,0,1-.28,0h-.61V39.32h.61a.79.79,0,0,1,.28,0,.55.55,0,0,1,.2.11.76.76,0,0,1,.12.19.85.85,0,0,1,0,.23.56.56,0,0,1,0,.23A.36.36,0,0,1,299.21,40.3Z"/><path class="cls-1" d="M298.7,37.68a2.88,2.88,0,1,0,2.87,2.87A2.88,2.88,0,0,0,298.7,37.68Zm0,5.27a2.4,2.4,0,1,1,2.39-2.4A2.41,2.41,0,0,1,298.7,43Z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -250,8 +250,8 @@ on_saltstack = 'SALT_ON_SALTSTACK' in os.environ
project = 'Salt' project = 'Salt'
version = salt.version.__version__ version = salt.version.__version__
latest_release = '2018.3.1' # latest release latest_release = '2018.3.2' # latest release
previous_release = '2017.7.6' # latest release from previous branch previous_release = '2017.7.7' # latest release from previous branch
previous_release_dir = '2017.7' # path on web server for previous branch previous_release_dir = '2017.7' # path on web server for previous branch
next_release = '' # next release next_release = '' # next release
next_release_dir = '' # path on web server for next release branch next_release_dir = '' # path on web server for next release branch

View File

@ -148,22 +148,23 @@ Why aren't my custom modules/states/etc. available on my Minions?
----------------------------------------------------------------- -----------------------------------------------------------------
Custom modules are synced to Minions when Custom modules are synced to Minions when
:mod:`saltutil.sync_modules <salt.modules.saltutil.sync_modules>`, :py:func:`saltutil.sync_modules <salt.modules.saltutil.sync_modules>`,
or :mod:`saltutil.sync_all <salt.modules.saltutil.sync_all>` is run. or :py:func:`saltutil.sync_all <salt.modules.saltutil.sync_all>` is run.
Custom modules are also synced by :mod:`state.apply` when run without
any arguments.
Similarly, custom states are synced to Minions when :py:func:`saltutil.sync_states
<salt.modules.saltutil.sync_states>`, or :py:func:`saltutil.sync_all
<salt.modules.saltutil.sync_all>` is run.
Similarly, custom states are synced to Minions They are both also synced when a :ref:`highstate <running-highstate>` is
when :mod:`state.apply <salt.modules.state.apply_>`, triggered.
:mod:`saltutil.sync_states <salt.modules.saltutil.sync_states>`, or
:mod:`saltutil.sync_all <salt.modules.saltutil.sync_all>` is run.
Custom states are also synced by :mod:`state.apply<salt.modules.state.apply_>` As of the Fluorine release, as well as 2017.7.7 and 2018.3.2 in their
when run without any arguments. respective release cycles, the ``sync`` argument to :py:func:`state.apply
<salt.modules.state.apply_>`/:py:func:`state.sls <salt.modules.state.sls>` can
be used to sync custom types when running individual SLS files.
Other custom types (renderers, outputters, etc.) have similar behavior, see the Other custom types (renderers, outputters, etc.) have similar behavior, see the
documentation for the :mod:`saltutil <salt.modules.saltutil>` module for more documentation for the :py:func:`saltutil <salt.modules.saltutil>` module for more
information. information.
:ref:`This reactor example <minion-start-reactor>` can be used to automatically :ref:`This reactor example <minion-start-reactor>` can be used to automatically

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "SALT-API" "1" "May 09, 2018" "2018.3.1" "Salt" .TH "SALT-API" "1" "Jun 14, 2018" "2018.3.2" "Salt"
.SH NAME .SH NAME
salt-api \- salt-api Command salt-api \- salt-api Command
. .

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "SALT-CALL" "1" "May 09, 2018" "2018.3.1" "Salt" .TH "SALT-CALL" "1" "Jun 14, 2018" "2018.3.2" "Salt"
.SH NAME .SH NAME
salt-call \- salt-call Documentation salt-call \- salt-call Documentation
. .

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "SALT-CLOUD" "1" "May 09, 2018" "2018.3.1" "Salt" .TH "SALT-CLOUD" "1" "Jun 14, 2018" "2018.3.2" "Salt"
.SH NAME .SH NAME
salt-cloud \- Salt Cloud Command salt-cloud \- Salt Cloud Command
. .

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "SALT-CP" "1" "May 09, 2018" "2018.3.1" "Salt" .TH "SALT-CP" "1" "Jun 14, 2018" "2018.3.2" "Salt"
.SH NAME .SH NAME
salt-cp \- salt-cp Documentation salt-cp \- salt-cp Documentation
. .

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "SALT-KEY" "1" "May 09, 2018" "2018.3.1" "Salt" .TH "SALT-KEY" "1" "Jun 14, 2018" "2018.3.2" "Salt"
.SH NAME .SH NAME
salt-key \- salt-key Documentation salt-key \- salt-key Documentation
. .

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "SALT-MASTER" "1" "May 09, 2018" "2018.3.1" "Salt" .TH "SALT-MASTER" "1" "Jun 14, 2018" "2018.3.2" "Salt"
.SH NAME .SH NAME
salt-master \- salt-master Documentation salt-master \- salt-master Documentation
. .

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "SALT-MINION" "1" "May 09, 2018" "2018.3.1" "Salt" .TH "SALT-MINION" "1" "Jun 14, 2018" "2018.3.2" "Salt"
.SH NAME .SH NAME
salt-minion \- salt-minion Documentation salt-minion \- salt-minion Documentation
. .

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "SALT-PROXY" "1" "May 09, 2018" "2018.3.1" "Salt" .TH "SALT-PROXY" "1" "Jun 14, 2018" "2018.3.2" "Salt"
.SH NAME .SH NAME
salt-proxy \- salt-proxy Documentation salt-proxy \- salt-proxy Documentation
. .

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "SALT-RUN" "1" "May 09, 2018" "2018.3.1" "Salt" .TH "SALT-RUN" "1" "Jun 14, 2018" "2018.3.2" "Salt"
.SH NAME .SH NAME
salt-run \- salt-run Documentation salt-run \- salt-run Documentation
. .

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "SALT-SSH" "1" "May 09, 2018" "2018.3.1" "Salt" .TH "SALT-SSH" "1" "Jun 14, 2018" "2018.3.2" "Salt"
.SH NAME .SH NAME
salt-ssh \- salt-ssh Documentation salt-ssh \- salt-ssh Documentation
. .

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "SALT-SYNDIC" "1" "May 09, 2018" "2018.3.1" "Salt" .TH "SALT-SYNDIC" "1" "Jun 14, 2018" "2018.3.2" "Salt"
.SH NAME .SH NAME
salt-syndic \- salt-syndic Documentation salt-syndic \- salt-syndic Documentation
. .

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "SALT-UNITY" "1" "May 09, 2018" "2018.3.1" "Salt" .TH "SALT-UNITY" "1" "Jun 14, 2018" "2018.3.2" "Salt"
.SH NAME .SH NAME
salt-unity \- salt-unity Command salt-unity \- salt-unity Command
. .

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "SALT" "1" "May 09, 2018" "2018.3.1" "Salt" .TH "SALT" "1" "Jun 14, 2018" "2018.3.2" "Salt"
.SH NAME .SH NAME
salt \- salt salt \- salt
. .

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "SPM" "1" "May 09, 2018" "2018.3.1" "Salt" .TH "SPM" "1" "Jun 14, 2018" "2018.3.2" "Salt"
.SH NAME .SH NAME
spm \- Salt Package Manager Command spm \- Salt Package Manager Command
. .

View File

@ -5054,6 +5054,21 @@ https://github.com/ytoolshed/range/wiki/%22yamlfile%22-module-file-spec
Include Configuration Include Configuration
===================== =====================
Configuration can be loaded from multiple files. The order in which this is
done is:
1. The master config file itself
2. The files matching the glob in :conf_master:`default_include`
3. The files matching the glob in :conf_master:`include` (if defined)
Each successive step overrides any values defined in the previous steps.
Therefore, any config options defined in one of the
:conf_master:`default_include` files would override the same value in the
master config file, and any options defined in :conf_master:`include` would
override both.
.. conf_master:: default_include .. conf_master:: default_include
``default_include`` ``default_include``

View File

@ -2928,7 +2928,22 @@ at the moment a single state fails
Include Configuration Include Configuration
===================== =====================
.. conf_minion:: include Configuration can be loaded from multiple files. The order in which this is
done is:
1. The minion config file itself
2. The files matching the glob in :conf_minion:`default_include`
3. The files matching the glob in :conf_minion:`include` (if defined)
Each successive step overrides any values defined in the previous steps.
Therefore, any config options defined in one of the
:conf_minion:`default_include` files would override the same value in the
minion config file, and any options defined in :conf_minion:`include` would
override both.
.. conf_minion:: default_include
``default_include`` ``default_include``
------------------- -------------------
@ -2946,6 +2961,7 @@ file.
files are prefixed with an underscore. A common example of this is the files are prefixed with an underscore. A common example of this is the
``_schedule.conf`` file. ``_schedule.conf`` file.
.. conf_minion:: include
``include`` ``include``
----------- -----------

View File

@ -21,11 +21,21 @@ SaltStack has its own coding style guide that informs contributors on various co
approaches. Please review the :ref:`Salt Coding Style <coding-style>` documentation approaches. Please review the :ref:`Salt Coding Style <coding-style>` documentation
for information about Salt's particular coding patterns. for information about Salt's particular coding patterns.
Within the :ref:`Salt Coding Style <coding-style>` documentation, there is a section Within the :ref:`Salt Coding Style <coding-style>` documentation, there is a
about running Salt's ``.pylintrc`` file. SaltStack recommends running the ``.pylintrc`` section about running Salt's ``.testing.pylintrc`` file. SaltStack recommends
file on any files you are changing with your code contribution before submitting a running the ``.testing.pylintrc`` file on any files you are changing with your
pull request to Salt's repository. Please see the :ref:`Linting<pylint-instructions>` code contribution before submitting a pull request to Salt's repository. Please
documentation for more information. see the :ref:`Linting<pylint-instructions>` documentation for more information.
.. note::
There are two pylint files in the ``salt`` directory. One is the
``.pylintrc`` file and the other is the ``.testing.pylintrc`` file. The
tests that run in Jenkins against GitHub Pull Requests use
``.testing.pylintrc``. The ``testing.pylintrc`` file is a little less
strict than the ``.pylintrc`` and is used to make it easier for contributors
to submit changes. The ``.pylintrc`` file can be used for linting, but the
``testing.pylintrc`` is the source of truth when submitting pull requests.
.. _github-pull-request: .. _github-pull-request:

View File

@ -22,21 +22,31 @@ improve Salt)!!
Linting Linting
======= =======
Most Salt style conventions are codified in Salt's ``.pylintrc`` file. Salt's Most Salt style conventions are codified in Salt's ``.testing.pylintrc`` file.
pylint file has two dependencies: pylint_ and saltpylint_. You can install Salt's pylint file has two dependencies: pylint_ and saltpylint_. You can
these dependencies with ``pip``: install these dependencies with ``pip``:
.. code-block:: bash .. code-block:: bash
pip install pylint pip install pylint
pip install saltpylint pip install saltpylint
The ``.pylintrc`` file is found in the root of the Salt project and can be passed The ``.testing.pylintrc`` file is found in the root of the Salt project and can
as an argument to the pylint_ program as follows: be passed as an argument to the pylint_ program as follows:
.. code-block:: bash .. code-block:: bash
pylint --rcfile=/path/to/salt/.pylintrc salt/dir/to/lint pylint --rcfile=/path/to/salt/.testing.pylintrc salt/dir/to/lint
.. note::
There are two pylint files in the ``salt`` directory. One is the
``.pylintrc`` file and the other is the ``.testing.pylintrc`` file. The
tests that run in Jenkins against GitHub Pull Requests use
``.testing.pylintrc``. The ``testing.pylintrc`` file is a little less
strict than the ``.pylintrc`` and is used to make it easier for contributors
to submit changes. The ``.pylintrc`` file can be used for linting, but the
``testing.pylintrc`` is the source of truth when submitting pull requests.
.. _pylint: http://www.pylint.org .. _pylint: http://www.pylint.org
.. _saltpylint: https://github.com/saltstack/salt-pylint .. _saltpylint: https://github.com/saltstack/salt-pylint

View File

@ -1,3 +1,5 @@
.. _event-system:
============ ============
Event System Event System
============ ============
@ -12,8 +14,8 @@ Event Bus
The event system is comprised of a two primary components, which make up the The event system is comprised of a two primary components, which make up the
concept of an Event Bus: concept of an Event Bus:
* The event sockets which publishes events. - The event sockets, which publish events
* The event library which can listen to events and send events into the salt system. - The event library, which can listen to events and send events into the salt system
Events are published onto the event bus and event bus subscribers listen for the Events are published onto the event bus and event bus subscribers listen for the
published events. published events.

View File

@ -886,6 +886,10 @@ Example:
encoding (usually a ``unicode`` type). This filter was incorrectly-named encoding (usually a ``unicode`` type). This filter was incorrectly-named
when it was added. ``json_decode_list`` will be supported until the Neon when it was added. ``json_decode_list`` will be supported until the Neon
release. release.
.. deprecated:: 2018.3.3,Fluorine
The :jinja_ref:`tojson` filter accomplishes what this filter was designed
to do, making this filter redundant.
Recursively encodes all string elements of the list to bytes. Recursively encodes all string elements of the list to bytes.
@ -915,6 +919,9 @@ Returns:
encoding (usually a ``unicode`` type). This filter was incorrectly-named encoding (usually a ``unicode`` type). This filter was incorrectly-named
when it was added. ``json_decode_dict`` will be supported until the Neon when it was added. ``json_decode_dict`` will be supported until the Neon
release. release.
.. deprecated:: 2018.3.3,Fluorine
The :jinja_ref:`tojson` filter accomplishes what this filter was designed
to do, making this filter redundant.
Recursively encodes all string items in the dictionary to bytes. Recursively encodes all string items in the dictionary to bytes.
@ -934,6 +941,22 @@ Returns:
{'a': '\xd0\x94'} {'a': '\xd0\x94'}
.. jinja_ref:: tojson
``tojson``
----------
.. versionadded:: 2018.3.3,Fluorine
Dumps a data structure to JSON.
This filter was added to provide this functionality to hosts which have a
Jinja release older than version 2.9 installed. If Jinja 2.9 or newer is
installed, then the upstream version of the filter will be used. See the
`upstream docs`__ for more information.
.. __: http://jinja.pocoo.org/docs/2.10/templates/#tojson
.. jinja_ref:: random_hash .. jinja_ref:: random_hash
``random_hash`` ``random_hash``
@ -1125,6 +1148,40 @@ Returns:
}' }'
} }
.. jinja_ref:: traverse
``traverse``
------------
.. versionadded:: 2018.3.3
Traverse a dict or list using a colon-delimited target string.
The target 'foo:bar:0' will return data['foo']['bar'][0] if this value exists,
and will otherwise return the provided default value.
Example:
.. code-block:: jinja
{{ {'a1': {'b1': {'c1': 'foo'}}, 'a2': 'bar'} | traverse('a1:b1', 'default') }}
Returns:
.. code-block:: python
{'c1': 'foo'}
.. code-block:: jinja
{{ {'a1': {'b1': {'c1': 'foo'}}, 'a2': 'bar'} | traverse('a2:b2', 'default') }}
Returns:
.. code-block:: python
'default'
.. _`builtin filters`: http://jinja.pocoo.org/docs/templates/#builtin-filters .. _`builtin filters`: http://jinja.pocoo.org/docs/templates/#builtin-filters
.. _`timelib`: https://github.com/pediapress/timelib/ .. _`timelib`: https://github.com/pediapress/timelib/

View File

@ -15,6 +15,23 @@ Statistics
- Contributors: **52** (`Ch3LL`_, `DmitryKuzmenko`_, `GwiYeong`_, `L4rS6`_, `SteffenKockel`_, `The-Loeki`_, `amendlik`_, `andreaspe`_, `angeloudy`_, `aphor`_, `bdrung`_, `cebe`_, `ciiqr`_, `damon-atkins`_, `danlsgiga`_, `ddoh94`_, `dmurphy18`_, `dwoz`_, `eliasp`_, `frogunder`_, `garethgreenaway`_, `gclinch`_, `gtmanfred`_, `jfindlay`_, `kstreee`_, `marccardinal`_, `mcalmer`_, `mchugh19`_, `meaksh`_, `michelsen`_, `nullify005`_, `oarmstrong`_, `oeuftete`_, `philpep`_, `racker-markh`_, `rallytime`_, `redbaron4`_, `roaldnefs`_, `rongshengfang`_, `rongzeng54`_, `rrroo`_, `samilaine`_, `samodid`_, `skizunov`_, `terminalmage`_, `tintoy`_, `twangboy`_, `viktordaniel`_, `vutny`_, `while0pass`_, `whytewolf`_, `zer0def`_) - Contributors: **52** (`Ch3LL`_, `DmitryKuzmenko`_, `GwiYeong`_, `L4rS6`_, `SteffenKockel`_, `The-Loeki`_, `amendlik`_, `andreaspe`_, `angeloudy`_, `aphor`_, `bdrung`_, `cebe`_, `ciiqr`_, `damon-atkins`_, `danlsgiga`_, `ddoh94`_, `dmurphy18`_, `dwoz`_, `eliasp`_, `frogunder`_, `garethgreenaway`_, `gclinch`_, `gtmanfred`_, `jfindlay`_, `kstreee`_, `marccardinal`_, `mcalmer`_, `mchugh19`_, `meaksh`_, `michelsen`_, `nullify005`_, `oarmstrong`_, `oeuftete`_, `philpep`_, `racker-markh`_, `rallytime`_, `redbaron4`_, `roaldnefs`_, `rongshengfang`_, `rongzeng54`_, `rrroo`_, `samilaine`_, `samodid`_, `skizunov`_, `terminalmage`_, `tintoy`_, `twangboy`_, `viktordaniel`_, `vutny`_, `while0pass`_, `whytewolf`_, `zer0def`_)
Changes to :py:func:`file.blockreplace <salt.states.file.blockreplace>` State
=============================================================================
The ``append_newline`` argument was added to this state. Additionally, to
improve idempotence, if the string represented by ``marker_end`` is found in
the middle of the line, the content preceding the marker will be removed when
the block is replaced. This allows one to remove ``append_newline: False`` from
the SLS and have the block properly replaced if the end of the content block is
immediately followed by the ``marker_end`` (i.e. no newline before the marker).
.. note::
This will require changes to your SLS if your ``marker_end`` does not
include the very beginning of the content you want to keep.
See the :py:func:`file.blockreplace <salt.states.file.blockreplace>` state
documentation for further information.
Changelog for v2017.7.4..v2017.7.5 Changelog for v2017.7.4..v2017.7.5
================================== ==================================

View File

@ -1,27 +1,67 @@
======================================== ===========================
In Progress: Salt 2017.7.7 Release Notes Salt 2017.7.7 Release Notes
======================================== ===========================
Version 2017.7.7 is an **unreleased** bugfix release for :ref:`2017.7.0 <release-2017-7-0>`. Version 2017.7.7 is a bugfix release for :ref:`2017.7.0 <release-2017-7-0>`.
This release is still in progress and has not been released yet.
The ``2017.7.7`` release contains only a single fix for Issue `#48038`_, which The ``2017.7.7`` release contains only a small number of fixes, which are detailed
is a critical bug that occurs in a multi-syndic setup where the same job is run below.
multiple times on a minion.
This release fixes two critical issues.
The first is Issue `#48038`_, which is a critical bug that occurs in a multi-syndic
setup where the same job is run multiple times on a minion.
The second issue is `#48130`_. This bug appears in certain setups where the Master
reports a Minion time-out, even though the job is still running on the Minion.
Both of these issues have been fixed with this release.
Statistics Statistics
========== ==========
- Total Merges: **1** - Total Merges: **5**
- Total Issue References: **1** - Total Issue References: **2**
- Total PR References: **2** - Total PR References: **6**
- Contributors: **2** (`garethgreenaway`_, `rallytime`_) - Contributors: **3** (`garethgreenaway`_, `gtmanfred`_, `rallytime`_)
Changelog for v2017.7.6..v2017.7.7 Changelog for v2017.7.6..v2017.7.7
================================== ==================================
*Generated at: 2018-06-14 15:43:34 UTC* *Generated at: 2018-06-17 19:26:52 UTC*
* **ISSUE** `#48130`_: (`rmarchei`_) Minion timeouts with 2018.3.1 (refs: `#48157`_)
* **PR** `#48157`_: (`gtmanfred`_) always listen when gathering job info
@ *2018-06-17 19:04:09 UTC*
* 8af4452134 Merge pull request `#48157`_ from gtmanfred/2017.7.7
* d8209e8a40 always listen when gathering job info
* **PR** `#48140`_: (`rallytime`_) Update man pages for 2017.7.7
@ *2018-06-14 21:22:43 UTC*
* b98c52ee51 Merge pull request `#48140`_ from rallytime/man-pages-2017.7.7
* 8893bf0d4c Update man pages for 2017.7.7
* **PR** `#48136`_: (`gtmanfred`_) [2017.7.7] bootstrap kitchen branch tests with 2017.7.6
@ *2018-06-14 21:20:16 UTC*
* baa0363336 Merge pull request `#48136`_ from gtmanfred/2017.7.7
* fce1c31146 bootstrap kitchen branch tests with 2017.7.6
* **PR** `#48134`_: (`rallytime`_) Add release notes file for 2017.7.7
@ *2018-06-14 16:31:34 UTC*
* b0ba08f4d9 Merge pull request `#48134`_ from rallytime/release-notes-2017.7.7
* 217005b8f1 Add missing `v` for tag reference
* d53569d1e3 Add release notes file for 2017.7.7
* **ISSUE** `#48038`_: (`austinpapp`_) jobs are not dedup'ing minion side (refs: `#48075`_) * **ISSUE** `#48038`_: (`austinpapp`_) jobs are not dedup'ing minion side (refs: `#48075`_)
@ -37,6 +77,13 @@ Changelog for v2017.7.6..v2017.7.7
.. _`#48038`: https://github.com/saltstack/salt/issues/48038 .. _`#48038`: https://github.com/saltstack/salt/issues/48038
.. _`#48075`: https://github.com/saltstack/salt/pull/48075 .. _`#48075`: https://github.com/saltstack/salt/pull/48075
.. _`#48098`: https://github.com/saltstack/salt/pull/48098 .. _`#48098`: https://github.com/saltstack/salt/pull/48098
.. _`#48130`: https://github.com/saltstack/salt/issues/48130
.. _`#48134`: https://github.com/saltstack/salt/pull/48134
.. _`#48136`: https://github.com/saltstack/salt/pull/48136
.. _`#48140`: https://github.com/saltstack/salt/pull/48140
.. _`#48157`: https://github.com/saltstack/salt/pull/48157
.. _`austinpapp`: https://github.com/austinpapp .. _`austinpapp`: https://github.com/austinpapp
.. _`garethgreenaway`: https://github.com/garethgreenaway .. _`garethgreenaway`: https://github.com/garethgreenaway
.. _`gtmanfred`: https://github.com/gtmanfred
.. _`rallytime`: https://github.com/rallytime .. _`rallytime`: https://github.com/rallytime
.. _`rmarchei`: https://github.com/rmarchei

View File

@ -43,6 +43,23 @@ replaced with ``--update-roster``, which will enable salt-ssh to add minions
to the flat roster file. This behavior can also be enabled by setting to the flat roster file. This behavior can also be enabled by setting
``ssh_update_roster: True`` in the master config file. ``ssh_update_roster: True`` in the master config file.
Changes to :py:func:`file.blockreplace <salt.states.file.blockreplace>` State
=============================================================================
The ``append_newline`` argument was added to this state. Additionally, to
improve idempotence, if the string represented by ``marker_end`` is found in
the middle of the line, the content preceding the marker will be removed when
the block is replaced. This allows one to remove ``append_newline: False`` from
the SLS and have the block properly replaced if the end of the content block is
immediately followed by the ``marker_end`` (i.e. no newline before the marker).
.. note::
This will require changes to your SLS if your ``marker_end`` does not
include the very beginning of the content you want to keep.
See the :py:func:`file.blockreplace <salt.states.file.blockreplace>` state
documentation for further information.
Changelog for v2018.3.0..v2018.3.1 Changelog for v2018.3.0..v2018.3.1
================================== ==================================

View File

@ -1,9 +1,8 @@
======================================== ===========================
In Progress: Salt 2018.3.2 Release Notes Salt 2018.3.2 Release Notes
======================================== ===========================
Version 2018.3.2 is an **unreleased** bugfix release for :ref:`2018.3.0 <release-2018-3-0>`. Version 2018.3.2 is a bugfix release for :ref:`2018.3.0 <release-2018-3-0>`.
This release is still in progress and has not been released yet.
The ``2018.3.2`` release contains only a small number of fixes, which are detailed The ``2018.3.2`` release contains only a small number of fixes, which are detailed
below. below.

View File

@ -14,3 +14,20 @@ Improves timezone detection by using the pytz module.
Adds ``timezone.list`` to list supported timezones in either Windows or Unix Adds ``timezone.list`` to list supported timezones in either Windows or Unix
format. format.
New Jinja Filter
================
The :jinja_ref:`tojson` filter (from Jinja 2.9 and later) has been ported to
Salt, and will be used when this filter is not available. This allows older LTS
releases such as CentOS 7 and Ubuntu 14.04 to use this filter.
You should use this filter any time you wish to dump a list or dictionary into
an SLS file, to ensure that the result is able to be loaded by the YAML
renderer. For example:
.. code-block:: jinja
foo:
bar.baz:
- some_arg: {{ mydict|tojson }}

View File

@ -332,6 +332,12 @@ Nested pillar values can also be set via the command line:
salt '*' state.sls my_sls_file pillar='{"foo": {"bar": "baz"}}' salt '*' state.sls my_sls_file pillar='{"foo": {"bar": "baz"}}'
Lists can be passed via command line pillar data as follows:
.. code-block:: bash
salt '*' state.sls my_sls_file pillar='{"some_list": ["foo", "bar", "baz"]}'
.. note:: .. note::
If a key is passed on the command line that already exists on the minion, If a key is passed on the command line that already exists on the minion,

View File

@ -139,13 +139,18 @@ where it is necessary to invoke the same function from a custom :ref:`outputter
<all-salt.output>`/returner, as well as an execution module. <all-salt.output>`/returner, as well as an execution module.
Utility modules placed in ``salt://_utils/`` will be synced to the minions when Utility modules placed in ``salt://_utils/`` will be synced to the minions when
any of the following Salt functions are called: a :ref:`highstate <running-highstate>` is run, as well as when any of the
following Salt functions are called:
* :mod:`state.apply <salt.modules.state.apply_>` * :py:func:`saltutil.sync_utils <salt.modules.saltutil.sync_utils>`
* :mod:`saltutil.sync_utils <salt.modules.saltutil.sync_utils>` * :py:func:`saltutil.sync_all <salt.modules.saltutil.sync_all>`
* :mod:`saltutil.sync_all <salt.modules.saltutil.sync_all>`
As of the Fluorine release, as well as 2017.7.7 and 2018.3.2 in their
respective release cycles, the ``sync`` argument to :py:func:`state.apply
<salt.modules.state.apply_>`/:py:func:`state.sls <salt.modules.state.sls>` can
be used to sync custom types when running individual SLS files.
To sync to the Master, use either of the following: To sync to the Master, use either of the following:
* :mod:`saltutil.sync_utils <salt.runners.saltutil.sync_utils>` * :py:func:`saltutil.sync_utils <salt.runners.saltutil.sync_utils>`
* :mod:`saltutil.sync_all <salt.runners.saltutil.sync_all>` * :py:func:`saltutil.sync_all <salt.runners.saltutil.sync_all>`

View File

@ -130,20 +130,18 @@ If Defined ProgramFiles(x86) (
) )
@echo. @echo.
@echo Copying VCRedist to Prerequisites
@echo ----------------------------------------------------------------------
:: Make sure the "prereq" directory exists :: Make sure the "prereq" directory exists
If NOT Exist "%PreDir%" mkdir "%PreDir%" If NOT Exist "%PreDir%" mkdir "%PreDir%"
:: Set the location of the vcredist to download :: Don't include the vcredist for Py3 installations
If %Python%==3 ( If %Python%==3 goto :vcredist_end
Set Url64="http://repo.saltstack.com/windows/dependencies/64/vcredist_x64_2015.exe"
Set Url32="http://repo.saltstack.com/windows/dependencies/32/vcredist_x86_2015.exe"
) Else ( @echo Copying VCRedist to Prerequisites
@echo ----------------------------------------------------------------------
:: Set the location of the vcredist to download
Set Url64="http://repo.saltstack.com/windows/dependencies/64/vcredist_x64_2008_mfc.exe" Set Url64="http://repo.saltstack.com/windows/dependencies/64/vcredist_x64_2008_mfc.exe"
Set Url32="http://repo.saltstack.com/windows/dependencies/32/vcredist_x86_2008_mfc.exe" Set Url32="http://repo.saltstack.com/windows/dependencies/32/vcredist_x86_2008_mfc.exe"
)
:: Check for 64 bit by finding the Program Files (x86) directory :: Check for 64 bit by finding the Program Files (x86) directory
If Defined ProgramFiles(x86) ( If Defined ProgramFiles(x86) (
@ -153,6 +151,8 @@ If Defined ProgramFiles(x86) (
) )
@echo. @echo.
:vcredist_end
:: Remove the fixed path in .exe files :: Remove the fixed path in .exe files
@echo Removing fixed path from .exe files @echo Removing fixed path from .exe files
@echo ---------------------------------------------------------------------- @echo ----------------------------------------------------------------------

View File

@ -415,26 +415,13 @@ Section -Prerequisites
Var /Global CheckVcRedist Var /Global CheckVcRedist
StrCpy $CheckVcRedist "False" StrCpy $CheckVcRedist "False"
# Visual C++ 2015 redist packages
!define PY3_VC_REDIST_NAME "VC_Redist_2015"
!define PY3_VC_REDIST_X64_GUID "{50A2BC33-C9CD-3BF1-A8FF-53C10A0B183C}"
!define PY3_VC_REDIST_X86_GUID "{BBF2AC74-720C-3CB3-8291-5E34039232FA}"
# Visual C++ 2008 SP1 MFC Security Update redist packages # Visual C++ 2008 SP1 MFC Security Update redist packages
!define PY2_VC_REDIST_NAME "VC_Redist_2008_SP1_MFC" !define PY2_VC_REDIST_NAME "VC_Redist_2008_SP1_MFC"
!define PY2_VC_REDIST_X64_GUID "{5FCE6D76-F5DC-37AB-B2B8-22AB8CEDB1D4}" !define PY2_VC_REDIST_X64_GUID "{5FCE6D76-F5DC-37AB-B2B8-22AB8CEDB1D4}"
!define PY2_VC_REDIST_X86_GUID "{9BE518E6-ECC6-35A9-88E4-87755C07200F}" !define PY2_VC_REDIST_X86_GUID "{9BE518E6-ECC6-35A9-88E4-87755C07200F}"
${If} ${PYTHON_VERSION} == 3 # VCRedist only needs to be installed for Python 2
StrCpy $VcRedistName ${PY3_VC_REDIST_NAME} ${If} ${PYTHON_VERSION} == 2
${If} ${CPUARCH} == "AMD64"
StrCpy $VcRedistGuid ${PY3_VC_REDIST_X64_GUID}
${Else}
StrCpy $VcRedistGuid ${PY3_VC_REDIST_X86_GUID}
${EndIf}
StrCpy $CheckVcRedist "True"
${Else}
StrCpy $VcRedistName ${PY2_VC_REDIST_NAME} StrCpy $VcRedistName ${PY2_VC_REDIST_NAME}
${If} ${CPUARCH} == "AMD64" ${If} ${CPUARCH} == "AMD64"

View File

@ -77,6 +77,7 @@ Function Get-Settings {
"SSLeay" = "ssleay32.dll" "SSLeay" = "ssleay32.dll"
"OpenSSLLic" = "OpenSSL_License.txt" "OpenSSLLic" = "OpenSSL_License.txt"
"msvcr" = "msvcr120.dll" "msvcr" = "msvcr120.dll"
"Libsodium" = "libsodium.dll"
} }
$ini.Add("64bitDLLs", $64bitDLLs) $ini.Add("64bitDLLs", $64bitDLLs)
@ -86,6 +87,7 @@ Function Get-Settings {
"SSLeay" = "ssleay32.dll" "SSLeay" = "ssleay32.dll"
"OpenSSLLic" = "OpenSSL_License.txt" "OpenSSLLic" = "OpenSSL_License.txt"
"msvcr" = "msvcr120.dll" "msvcr" = "msvcr120.dll"
"Libsodium" = "libsodium.dll"
} }
$ini.Add("32bitDLLs", $32bitDLLs) $ini.Add("32bitDLLs", $32bitDLLs)

View File

@ -9,34 +9,36 @@ salt for Windows with their corresponding licenses:
| certifi | ISC | | certifi | ISC |
| cffi | MIT | | cffi | MIT |
| CherryPy | BSD | | CherryPy | BSD |
| cryptography | BSD |
| enum34 | BSD | | enum34 | BSD |
| futures | BSD | | futures | BSD |
| gitdb | BSD | | gitdb | BSD |
| GitPython | BSD | | GitPython | BSD |
| idna | BSD-like | | idna | BSD-like |
| ioflo | Apache 2.0 |
| ioloop | MIT | | ioloop | MIT |
| ipaddress | PSF | | ipaddress | PSF |
| Jinja2 | BSD | | Jinja2 | BSD |
| libnacl | --- | | libnacl | Apache |
| lxml | BSD |
| Mako | MIT | | Mako | MIT |
| MarkupSafe | BSD | | MarkupSafe | BSD |
| msgpack-python | --- | | msgpack-python | Apache 2.0 |
| pip | MIT | | pip | MIT |
| psutil | BSD | | psutil | BSD |
| pyasn1 | BSD | | pyasn1 | BSD |
| pycparser | BSD | | pycparser | BSD |
| pycurl | LGPL + MIT | | pycurl | LGPL + MIT |
| PyMySQL | MIT | | PyMySQL | MIT |
| pypiwin32 | PSF | | PyOpenSSL | Apache 2.0 |
| python-certifi-win32 | BSD |
| python-dateutil | Simplified BSD | | python-dateutil | Simplified BSD |
| python-gnupg | BSD | | python-gnupg | BSD |
| pywin32 | PSF |
| PyYAML | MIT | | PyYAML | MIT |
| pyzmq | LGPL + BSD | | pyzmq | LGPL + BSD |
| requests | Apache 2.0 | | requests | Apache 2.0 |
| setuptools | MIT | | setuptools | MIT |
| singledispatch | MIT | | singledispatch | MIT |
| six | MIT |
| smmap | BSD | | smmap | BSD |
| timelib | ZLIB/PHP | | timelib | ZLIB/PHP |
| tornado | Apache 2.0 | | tornado | Apache 2.0 |

View File

@ -12,6 +12,7 @@ idna==2.5
ioloop==0.1a0 ioloop==0.1a0
ipaddress==1.0.18 ipaddress==1.0.18
Jinja2==2.9.6 Jinja2==2.9.6
libnacl==1.6.1 # required by the nacl module
lxml==3.7.3 lxml==3.7.3
Mako==1.0.6 Mako==1.0.6
MarkupSafe==1.0 MarkupSafe==1.0
@ -23,6 +24,7 @@ pycrypto==2.6.1
pycurl==7.43.0 pycurl==7.43.0
PyMySQL==0.7.11 PyMySQL==0.7.11
pyOpenSSL==17.5.0 pyOpenSSL==17.5.0
#python-certifi-win32==1.2
python-dateutil==2.6.1 python-dateutil==2.6.1
python-gnupg==0.4.1 python-gnupg==0.4.1
pythonnet==2.3.0 pythonnet==2.3.0

View File

@ -11,3 +11,4 @@ testinfra>=1.7.0
# satisfy other requirements, and httpretty 0.8.10 has bugs in setup.py that # satisfy other requirements, and httpretty 0.8.10 has bugs in setup.py that
# prevent it from being successfully installed (at least on Python 3.4). # prevent it from being successfully installed (at least on Python 3.4).
httpretty; python_version >= '3.4' httpretty; python_version >= '3.4'
pylint==1.6.5

View File

@ -22,6 +22,7 @@ psutil
pyvmomi pyvmomi
setproctitle setproctitle
cherrypy; sys.platform != 'win32' and sys.platform != 'darwin' cherrypy; sys.platform != 'win32' and sys.platform != 'darwin'
ldap; sys.platform != 'win32' and sys.platform != 'darwin'
pyinotify; sys.platform != 'win32' and sys.platform != 'darwin' pyinotify; sys.platform != 'win32' and sys.platform != 'darwin'
PyMySQL; sys.platform != 'win32' and sys.platform != 'darwin' PyMySQL; sys.platform != 'win32' and sys.platform != 'darwin'
jsonschema jsonschema
@ -34,3 +35,5 @@ dnspython
SaltTesting==2017.6.1 SaltTesting==2017.6.1
junos-eznc junos-eznc
jxmlease jxmlease
croniter
kazoo

View File

@ -1,4 +1,5 @@
-r base.txt -r base.txt
pycrypto>=2.6.1 pycrypto>=2.6.1
pyzmq>=2.2.0 pyzmq>=2.2.0,<17.1.0; python_version == '3.4' # pyzmq 17.1.0 stopped building wheels for python3.4
pyzmq>=2.2.0; python_version != '3.4'

View File

@ -83,13 +83,19 @@ def beacon(config):
it will override the previously defined threshold. it will override the previously defined threshold.
''' '''
parts = psutil.disk_partitions(all=False) parts = psutil.disk_partitions(all=True)
ret = [] ret = []
for mounts in config: for mounts in config:
mount = next(iter(mounts)) mount = next(iter(mounts))
# Because we're using regular expressions
# if our mount doesn't end with a $, insert one.
mount_re = mount
if not mount.endswith('$'):
mount_re = '{0}$'.format(mount)
for part in parts: for part in parts:
if re.match(mount, part.mountpoint): if re.match(mount_re, part.mountpoint):
_mount = part.mountpoint _mount = part.mountpoint
try: try:
@ -100,7 +106,7 @@ def beacon(config):
current_usage = _current_usage.percent current_usage = _current_usage.percent
monitor_usage = mounts[mount] monitor_usage = mounts[mount]
log.info('current_usage %s', current_usage) log.debug('current_usage %s', current_usage)
if '%' in monitor_usage: if '%' in monitor_usage:
monitor_usage = re.sub('%', '', monitor_usage) monitor_usage = re.sub('%', '', monitor_usage)
monitor_usage = float(monitor_usage) monitor_usage = float(monitor_usage)

View File

@ -11,6 +11,7 @@ import time
# Import salt libs # Import salt libs
import salt.utils.path import salt.utils.path
import salt.utils.stringutils
import salt.utils.vt import salt.utils.vt
__virtualname__ = 'sh' __virtualname__ = 'sh'
@ -81,14 +82,14 @@ def beacon(config):
stream_stdout=False, stream_stdout=False,
stream_stderr=False) stream_stderr=False)
__context__[pkey][pid]['user'] = ps_out[pid].get('user') __context__[pkey][pid]['user'] = ps_out[pid].get('user')
for pid in __context__[pkey]: for pid in list(__context__[pkey]):
out = '' out = ''
err = '' err = ''
while __context__[pkey][pid]['vt'].has_unread_data: while __context__[pkey][pid]['vt'].has_unread_data:
tout, terr = __context__[pkey][pid]['vt'].recv() tout, terr = __context__[pkey][pid]['vt'].recv()
if not terr: if not terr:
break break
out += tout out += salt.utils.stringutils.to_unicode(tout or '')
err += terr err += terr
for line in err.split('\n'): for line in err.split('\n'):
event = {'args': [], event = {'args': [],

View File

@ -172,8 +172,12 @@ def store(bank, key, data):
''' '''
_init_client() _init_client()
data = __context__['serial'].dumps(data) data = __context__['serial'].dumps(data)
query = "REPLACE INTO {0} (bank, etcd_key, data) values('{1}', '{2}', " \ query = b"REPLACE INTO {0} (bank, etcd_key, data) values('{1}', '{2}', " \
"'{3}')".format(_table_name, bank, key, data) b"'{3}')".format(_table_name,
bank,
key,
data)
cur, cnt = run_query(client, query) cur, cnt = run_query(client, query)
cur.close() cur.close()
if cnt not in (1, 2): if cnt not in (1, 2):

View File

@ -229,7 +229,7 @@ class LocalClient(object):
# Looks like the timeout is invalid, use config # Looks like the timeout is invalid, use config
return self.opts['timeout'] return self.opts['timeout']
def gather_job_info(self, jid, tgt, tgt_type, **kwargs): def gather_job_info(self, jid, tgt, tgt_type, listen=True, **kwargs):
''' '''
Return the information about a given job Return the information about a given job
''' '''
@ -241,6 +241,7 @@ class LocalClient(object):
arg=[jid], arg=[jid],
tgt_type=tgt_type, tgt_type=tgt_type,
timeout=timeout, timeout=timeout,
listen=listen,
**kwargs **kwargs
) )

View File

@ -350,7 +350,9 @@ class SSH(object):
return return
hostname = self.opts['tgt'].split('@')[-1] hostname = self.opts['tgt'].split('@')[-1]
needs_expansion = '*' not in hostname and salt.utils.network.is_reachable_host(hostname) needs_expansion = '*' not in hostname and \
salt.utils.network.is_reachable_host(hostname) and \
salt.utils.network.is_ip(hostname)
if needs_expansion: if needs_expansion:
hostname = salt.utils.network.ip_to_host(hostname) hostname = salt.utils.network.ip_to_host(hostname)
if hostname is None: if hostname is None:
@ -1245,7 +1247,10 @@ ARGS = {10}\n'''.format(self.minion_config,
shim_tmp_file.write(salt.utils.stringutils.to_bytes(cmd_str)) shim_tmp_file.write(salt.utils.stringutils.to_bytes(cmd_str))
# Copy shim to target system, under $HOME/.<randomized name> # Copy shim to target system, under $HOME/.<randomized name>
target_shim_file = '.{0}.{1}'.format(binascii.hexlify(os.urandom(6)), extension) target_shim_file = '.{0}.{1}'.format(
binascii.hexlify(os.urandom(6)).decode('ascii'),
extension
)
if self.winrm: if self.winrm:
target_shim_file = saltwinshell.get_target_shim_file(self, target_shim_file) target_shim_file = saltwinshell.get_target_shim_file(self, target_shim_file)
self.shell.send(shim_tmp_file.name, target_shim_file, makedirs=True) self.shell.send(shim_tmp_file.name, target_shim_file, makedirs=True)

View File

@ -167,7 +167,7 @@ def salt_refs(data, ret=None):
return ret return ret
def prep_trans_tar(opts, file_client, chunks, file_refs, pillar=None, id_=None, roster_grains=None): def prep_trans_tar(file_client, chunks, file_refs, pillar=None, id_=None, roster_grains=None):
''' '''
Generate the execution package from the saltenv file refs and a low state Generate the execution package from the saltenv file refs and a low state
data structure data structure

View File

@ -36,6 +36,59 @@ __func_alias__ = {
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def _ssh_state(chunks, st_kwargs,
kwargs, test=False):
'''
Function to run a state with the given chunk via salt-ssh
'''
file_refs = salt.client.ssh.state.lowstate_file_refs(
chunks,
_merge_extra_filerefs(
kwargs.get('extra_filerefs', ''),
__opts__.get('extra_filerefs', '')
)
)
# Create the tar containing the state pkg and relevant files.
trans_tar = salt.client.ssh.state.prep_trans_tar(
__context__['fileclient'],
chunks,
file_refs,
__pillar__,
st_kwargs['id_'])
trans_tar_sum = salt.utils.hashutils.get_hash(trans_tar, __opts__['hash_type'])
cmd = 'state.pkg {0}/salt_state.tgz test={1} pkg_sum={2} hash_type={3}'.format(
__opts__['thin_dir'],
test,
trans_tar_sum,
__opts__['hash_type'])
single = salt.client.ssh.Single(
__opts__,
cmd,
fsclient=__context__['fileclient'],
minion_opts=__salt__.minion_opts,
**st_kwargs)
single.shell.send(
trans_tar,
'{0}/salt_state.tgz'.format(__opts__['thin_dir']))
stdout, stderr, _ = single.cmd_block()
# Clean up our tar
try:
os.remove(trans_tar)
except (OSError, IOError):
pass
# Read in the JSON data and return the data structure
try:
return salt.utils.data.decode(salt.utils.json.loads(stdout, object_hook=salt.utils.data.encode_dict))
except Exception as e:
log.error("JSON Render failed for: %s\n%s", stdout, stderr)
log.error(str(e))
# If for some reason the json load fails, return the stdout
return salt.utils.data.decode(stdout)
def _set_retcode(ret, highstate=None): def _set_retcode(ret, highstate=None):
''' '''
Set the return code based on the data back from the state system Set the return code based on the data back from the state system
@ -165,7 +218,6 @@ def sls(mods, saltenv='base', test=None, exclude=None, **kwargs):
# Create the tar containing the state pkg and relevant files. # Create the tar containing the state pkg and relevant files.
_cleanup_slsmod_low_data(chunks) _cleanup_slsmod_low_data(chunks)
trans_tar = salt.client.ssh.state.prep_trans_tar( trans_tar = salt.client.ssh.state.prep_trans_tar(
opts,
__context__['fileclient'], __context__['fileclient'],
chunks, chunks,
file_refs, file_refs,
@ -310,7 +362,6 @@ def low(data, **kwargs):
# Create the tar containing the state pkg and relevant files. # Create the tar containing the state pkg and relevant files.
trans_tar = salt.client.ssh.state.prep_trans_tar( trans_tar = salt.client.ssh.state.prep_trans_tar(
__opts__,
__context__['fileclient'], __context__['fileclient'],
chunks, chunks,
file_refs, file_refs,
@ -401,7 +452,6 @@ def high(data, **kwargs):
# Create the tar containing the state pkg and relevant files. # Create the tar containing the state pkg and relevant files.
_cleanup_slsmod_low_data(chunks) _cleanup_slsmod_low_data(chunks)
trans_tar = salt.client.ssh.state.prep_trans_tar( trans_tar = salt.client.ssh.state.prep_trans_tar(
opts,
__context__['fileclient'], __context__['fileclient'],
chunks, chunks,
file_refs, file_refs,
@ -647,7 +697,6 @@ def highstate(test=None, **kwargs):
# Create the tar containing the state pkg and relevant files. # Create the tar containing the state pkg and relevant files.
_cleanup_slsmod_low_data(chunks) _cleanup_slsmod_low_data(chunks)
trans_tar = salt.client.ssh.state.prep_trans_tar( trans_tar = salt.client.ssh.state.prep_trans_tar(
opts,
__context__['fileclient'], __context__['fileclient'],
chunks, chunks,
file_refs, file_refs,
@ -730,7 +779,6 @@ def top(topfn, test=None, **kwargs):
# Create the tar containing the state pkg and relevant files. # Create the tar containing the state pkg and relevant files.
_cleanup_slsmod_low_data(chunks) _cleanup_slsmod_low_data(chunks)
trans_tar = salt.client.ssh.state.prep_trans_tar( trans_tar = salt.client.ssh.state.prep_trans_tar(
opts,
__context__['fileclient'], __context__['fileclient'],
chunks, chunks,
file_refs, file_refs,
@ -849,6 +897,7 @@ def sls_id(id_, mods, test=None, queue=False, **kwargs):
salt '*' state.sls_id my_state my_module,a_common_module salt '*' state.sls_id my_state my_module,a_common_module
''' '''
st_kwargs = __salt__.kwargs
conflict = _check_queue(queue, kwargs) conflict = _check_queue(queue, kwargs)
if conflict is not None: if conflict is not None:
return conflict return conflict
@ -861,13 +910,11 @@ def sls_id(id_, mods, test=None, queue=False, **kwargs):
if opts['saltenv'] is None: if opts['saltenv'] is None:
opts['saltenv'] = 'base' opts['saltenv'] = 'base'
try: st_ = salt.client.ssh.state.SSHHighState(
st_ = salt.state.HighState(opts, __opts__,
proxy=__proxy__, __pillar__,
initial_pillar=_get_initial_pillar(opts)) __salt__,
except NameError: __context__['fileclient'])
st_ = salt.state.HighState(opts,
initial_pillar=_get_initial_pillar(opts))
if not _check_pillar(kwargs, st_.opts['pillar']): if not _check_pillar(kwargs, st_.opts['pillar']):
__context__['retcode'] = 5 __context__['retcode'] = 5
@ -878,10 +925,7 @@ def sls_id(id_, mods, test=None, queue=False, **kwargs):
if isinstance(mods, six.string_types): if isinstance(mods, six.string_types):
split_mods = mods.split(',') split_mods = mods.split(',')
st_.push_active() st_.push_active()
try:
high_, errors = st_.render_highstate({opts['saltenv']: split_mods}) high_, errors = st_.render_highstate({opts['saltenv']: split_mods})
finally:
st_.pop_active()
errors += st_.state.verify_high(high_) errors += st_.state.verify_high(high_)
# Apply requisites to high data # Apply requisites to high data
high_, req_in_errors = st_.state.requisite_in(high_) high_, req_in_errors = st_.state.requisite_in(high_)
@ -893,17 +937,22 @@ def sls_id(id_, mods, test=None, queue=False, **kwargs):
__context__['retcode'] = 1 __context__['retcode'] = 1
return errors return errors
chunks = st_.state.compile_high_data(high_) chunks = st_.state.compile_high_data(high_)
ret = {} chunk = [x for x in chunks if x.get('__id__', '') == id_]
for chunk in chunks:
if chunk.get('__id__', '') == id_:
ret.update(st_.state.call_chunk(chunk, {}, chunks))
_set_retcode(ret, highstate=highstate) if not chunk:
if not ret:
raise SaltInvocationError( raise SaltInvocationError(
'No matches for ID \'{0}\' found in SLS \'{1}\' within saltenv ' 'No matches for ID \'{0}\' found in SLS \'{1}\' within saltenv '
'\'{2}\''.format(id_, mods, opts['saltenv']) '\'{2}\''.format(id_, mods, opts['saltenv'])
) )
ret = _ssh_state(chunk,
st_kwargs,
kwargs,
test=test)
_set_retcode(ret, highstate=highstate)
# Work around Windows multiprocessing bug, set __opts__['test'] back to
# value from before this function was run.
__opts__['test'] = orig_test
return ret return ret
@ -1093,7 +1142,6 @@ def single(fun, name, test=None, **kwargs):
# Create the tar containing the state pkg and relevant files. # Create the tar containing the state pkg and relevant files.
trans_tar = salt.client.ssh.state.prep_trans_tar( trans_tar = salt.client.ssh.state.prep_trans_tar(
opts,
__context__['fileclient'], __context__['fileclient'],
chunks, chunks,
file_refs, file_refs,

View File

@ -372,7 +372,8 @@ def create(vm_):
for iface_xml in domain_xml.findall('./devices/interface'): for iface_xml in domain_xml.findall('./devices/interface'):
iface_xml.remove(iface_xml.find('./mac')) iface_xml.remove(iface_xml.find('./mac'))
# enable IP learning, this might be a default behaviour... # enable IP learning, this might be a default behaviour...
if iface_xml.find("./filterref/parameter[@name='CTRL_IP_LEARNING']") is None: # Don't always enable since it can cause problems through libvirt-4.5
if ip_source == 'ip-learning' and iface_xml.find("./filterref/parameter[@name='CTRL_IP_LEARNING']") is None:
iface_xml.append(ElementTree.fromstring(IP_LEARNING_XML)) iface_xml.append(ElementTree.fromstring(IP_LEARNING_XML))
# If a qemu agent is defined we need to fix the path to its socket # If a qemu agent is defined we need to fix the path to its socket
@ -422,7 +423,7 @@ def create(vm_):
else: else:
raise SaltCloudExecutionFailure("Disk type '{0}' not supported".format(disk_type)) raise SaltCloudExecutionFailure("Disk type '{0}' not supported".format(disk_type))
clone_xml = ElementTree.tostring(domain_xml) clone_xml = salt.utils.stringutils.to_str(ElementTree.tostring(domain_xml))
log.debug("Clone XML '%s'", clone_xml) log.debug("Clone XML '%s'", clone_xml)
validate_flags = libvirt.VIR_DOMAIN_DEFINE_VALIDATE if validate_xml else 0 validate_flags = libvirt.VIR_DOMAIN_DEFINE_VALIDATE if validate_xml else 0
@ -615,7 +616,7 @@ def create_volume_xml(volume):
log.debug("Volume: %s", dir(volume)) log.debug("Volume: %s", dir(volume))
volume_xml.find('capacity').text = six.text_type(volume.info()[1]) volume_xml.find('capacity').text = six.text_type(volume.info()[1])
volume_xml.find('./target/path').text = volume.path() volume_xml.find('./target/path').text = volume.path()
xml_string = ElementTree.tostring(volume_xml) xml_string = salt.utils.stringutils.to_str(ElementTree.tostring(volume_xml))
log.debug("Creating %s", xml_string) log.debug("Creating %s", xml_string)
return xml_string return xml_string
@ -641,7 +642,7 @@ def create_volume_with_backing_store_xml(volume):
log.debug("volume: %s", dir(volume)) log.debug("volume: %s", dir(volume))
volume_xml.find('capacity').text = six.text_type(volume.info()[1]) volume_xml.find('capacity').text = six.text_type(volume.info()[1])
volume_xml.find('./backingStore/path').text = volume.path() volume_xml.find('./backingStore/path').text = volume.path()
xml_string = ElementTree.tostring(volume_xml) xml_string = salt.utils.stringutils.to_str(ElementTree.tostring(volume_xml))
log.debug("Creating %s", xml_string) log.debug("Creating %s", xml_string)
return xml_string return xml_string

View File

@ -206,8 +206,8 @@ def get_rsa_pub_key(path):
''' '''
log.debug('salt.crypt.get_rsa_pub_key: Loading public key') log.debug('salt.crypt.get_rsa_pub_key: Loading public key')
if HAS_M2: if HAS_M2:
with salt.utils.files.fopen(path) as f: with salt.utils.files.fopen(path, 'rb') as f:
data = f.read().replace(b'RSA ', '') data = f.read().replace(b'RSA ', b'')
bio = BIO.MemoryBuffer(data) bio = BIO.MemoryBuffer(data)
key = RSA.load_pub_key_bio(bio) key = RSA.load_pub_key_bio(bio)
else: else:
@ -608,6 +608,14 @@ class AsyncAuth(object):
error = SaltClientError('Detect mode is on') error = SaltClientError('Detect mode is on')
break break
if self.opts.get('caller'): if self.opts.get('caller'):
# We have a list of masters, so we should break
# and try the next one in the list.
if self.opts.get('local_masters', None):
error = SaltClientError('Minion failed to authenticate'
' with the master, has the '
'minion key been accepted?')
break
else:
print('Minion failed to authenticate with the master, ' print('Minion failed to authenticate with the master, '
'has the minion key been accepted?') 'has the minion key been accepted?')
sys.exit(2) sys.exit(2)

View File

@ -1,26 +1,84 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
''' '''
An engine that reads messages from Slack and can act on them. An engine that reads messages from Slack and can act on them
It has two major uses.
1. When the ``control`` parameter is set to ``True`` and a message is prefaced
with the ``trigger`` (which defaults to ``!``) then the engine will
validate that the user has permission, and if so will run the command
2. In addition, when the parameter ``fire_all`` is set (defaults to False),
all other messages (the messages that aren't control messages) will be
fired off to the salt event bus with the tag prefixed by the string
provided by the ``tag`` config option (defaults to ``salt/engines/slack``).
This allows for configuration to be gotten from either the engine config, or from
the saltmaster's minion pillar.
.. versionadded: 2016.3.0 .. versionadded: 2016.3.0
:configuration: Example configuration using only a 'default' group. The default :depends: `slackclient <https://pypi.org/project/slackclient/>`_ Python module
group is not special. In addition, other groups are being loaded from
pillars. .. important::
This engine requires a bot user. To create a bot user, first go to the
**Custom Integrations** page in your Slack Workspace. Copy and paste the
following URL, and replace ``myworkspace`` with the proper value for your
workspace:
``https://myworkspace.slack.com/apps/manage/custom-integrations``
Next, click on the ``Bots`` integration and request installation. Once
approved by an admin, you will be able to proceed with adding the bot user.
Once the bot user has been added, you can configure it by adding an avatar,
setting the display name, etc. You will also at this time have access to
your API token, which will be needed to configure this engine.
Finally, add this bot user to a channel by switching to the channel and
using ``/invite @mybotuser``. Keep in mind that this engine will process
messages from each channel in which the bot is a member, so it is
recommended to narrowly define the commands which can be executed, and the
Slack users which are allowed to run commands.
This engine has two boolean configuration parameters that toggle specific
features (both default to ``False``):
1. ``control`` - If set to ``True``, then any message which starts with the
trigger string (which defaults to ``!`` and can be overridden by setting the
``trigger`` option in the engine configuration) will be interpreted as a
Salt CLI command and the engine will attempt to run it. The permissions
defined in the various ``groups`` will determine if the Slack user is
allowed to run the command. The ``targets`` and ``default_target`` options
can be used to set targets for a given command, but the engine can also read
the following two keyword arguments:
- ``target`` - The target expression to use for the command
- ``tgt_type`` - The match type, can be one of ``glob``, ``list``,
``pcre``, ``grain``, ``grain_pcre``, ``pillar``, ``nodegroup``, ``range``,
``ipcidr``, or ``compound``. The default value is ``glob``.
Here are a few examples:
.. code-block:: text
!test.ping target=*
!state.apply foo target=os:CentOS tgt_type=grain
!pkg.version mypkg target=role:database tgt_type=pillar
2. ``fire_all`` - If set to ``True``, all messages which are not prefixed with
the trigger string will fired as events onto Salt's ref:`event bus
<event-system>`. The tag for these veents will be prefixed with the string
specified by the ``tag`` config option (default: ``salt/engines/slack``).
The ``groups_pillar_name`` config option can be used to pull group
configuration from the specified pillar key.
.. note::
In order to use ``groups_pillar_name``, the engine must be running as a
minion running on the master, so that the ``Caller`` client can be used to
retrieve that minions pillar data, because the master process does not have
pillar data.
Configuration Examples
======================
.. versionchanged:: 2017.7.0
Access control group support added
This example uses a single group called ``default``. In addition, other groups
are being loaded from pillar data. The group names do not have any
significance, it is the users and commands defined within them that are used to
determine whether the Slack user has permission to run the desired command.
.. code-block:: text .. code-block:: text
@ -33,7 +91,7 @@ the saltmaster's minion pillar.
groups: groups:
default: default:
users: users:
- * - '*'
commands: commands:
- test.ping - test.ping
- cmd.run - cmd.run
@ -55,12 +113,9 @@ the saltmaster's minion pillar.
target: saltmaster target: saltmaster
tgt_type: list tgt_type: list
:configuration: Example configuration using the 'default' group and a This example shows multiple groups applying to different users, with all users
non-default group and a pillar that will be merged in If the user is '*' having access to run test.ping. Keep in mind that when using ``*``, the value
(without the quotes) then the group's users or commands will match all must be quoted, or else PyYAML will fail to load the configuration.
users as appropriate
.. versionadded: 2017.7.0
.. code-block:: text .. code-block:: text
@ -74,9 +129,9 @@ the saltmaster's minion pillar.
groups_pillar_name: 'slack_engine:groups_pillar' groups_pillar_name: 'slack_engine:groups_pillar'
groups: groups:
default: default:
valid_users: users:
- * - '*'
valid_commands: commands:
- test.ping - test.ping
aliases: aliases:
list_jobs: list_jobs:
@ -87,16 +142,7 @@ the saltmaster's minion pillar.
users: users:
- garethgreenaway - garethgreenaway
commands: commands:
- * - '*'
:depends: slackclient
.. note:: groups_pillar_name
In order to use this, the engine must be running as a minion running on
the master, so that the ``Caller`` client can be used to retrieve that
minions pillar data, because the master process does not have pillars.
''' '''
@ -330,7 +376,6 @@ class SlackClient(object):
cmdlist.append(cmditem) cmdlist.append(cmditem)
return cmdlist return cmdlist
# m_data -> m_data, _text -> test, all_slack_users -> all_slack_users,
def control_message_target(self, slack_user_name, text, loaded_groups, trigger_string): def control_message_target(self, slack_user_name, text, loaded_groups, trigger_string):
'''Returns a tuple of (target, cmdline,) for the response '''Returns a tuple of (target, cmdline,) for the response

View File

@ -129,8 +129,6 @@ def serve_file(load, fnd):
with salt.utils.files.fopen(fpath, 'rb') as fp_: with salt.utils.files.fopen(fpath, 'rb') as fp_:
fp_.seek(load['loc']) fp_.seek(load['loc'])
data = fp_.read(__opts__['file_buffer_size']) data = fp_.read(__opts__['file_buffer_size'])
if data and six.PY3 and not salt.utils.files.is_binary(fpath):
data = data.decode(__salt_system_encoding__)
if gzip and data: if gzip and data:
data = salt.utils.gzip_util.compress(data, gzip) data = salt.utils.gzip_util.compress(data, gzip)
ret['gzip'] = gzip ret['gzip'] = gzip

View File

@ -82,6 +82,10 @@ if salt.utils.platform.is_windows():
'will be missing' 'will be missing'
) )
HAS_UNAME = True
if not hasattr(os, 'uname'):
HAS_UNAME = False
_INTERFACES = {} _INTERFACES = {}
@ -448,7 +452,7 @@ def _osx_memdata():
sysctl = salt.utils.path.which('sysctl') sysctl = salt.utils.path.which('sysctl')
if sysctl: if sysctl:
mem = __salt__['cmd.run']('{0} -n hw.memsize'.format(sysctl)) mem = __salt__['cmd.run']('{0} -n hw.memsize'.format(sysctl))
swap_total = __salt__['cmd.run']('{0} -n vm.swapusage'.format(sysctl)).split()[2] swap_total = __salt__['cmd.run']('{0} -n vm.swapusage'.format(sysctl)).split()[2].replace(',', '.')
if swap_total.endswith('K'): if swap_total.endswith('K'):
_power = 2**10 _power = 2**10
elif swap_total.endswith('M'): elif swap_total.endswith('M'):
@ -689,7 +693,7 @@ def _virtual(osdata):
# Quick backout for BrandZ (Solaris LX Branded zones) # Quick backout for BrandZ (Solaris LX Branded zones)
# Don't waste time trying other commands to detect the virtual grain # Don't waste time trying other commands to detect the virtual grain
if osdata['kernel'] == 'Linux' and 'BrandZ virtual linux' in os.uname(): if HAS_UNAME and osdata['kernel'] == 'Linux' and 'BrandZ virtual linux' in os.uname():
grains['virtual'] = 'zone' grains['virtual'] = 'zone'
return grains return grains
@ -947,7 +951,7 @@ def _virtual(osdata):
if os.path.isfile('/sys/devices/virtual/dmi/id/product_name'): if os.path.isfile('/sys/devices/virtual/dmi/id/product_name'):
try: try:
with salt.utils.files.fopen('/sys/devices/virtual/dmi/id/product_name', 'r') as fhr: with salt.utils.files.fopen('/sys/devices/virtual/dmi/id/product_name', 'r') as fhr:
output = salt.utils.stringutils.to_unicode(fhr.read()) output = salt.utils.stringutils.to_unicode(fhr.read(), errors='replace')
if 'VirtualBox' in output: if 'VirtualBox' in output:
grains['virtual'] = 'VirtualBox' grains['virtual'] = 'VirtualBox'
elif 'RHEV Hypervisor' in output: elif 'RHEV Hypervisor' in output:
@ -1597,7 +1601,7 @@ def os_data():
# my_init as pid1 # my_init as pid1
grains['init'] = 'runit' grains['init'] = 'runit'
else: else:
log.info( log.debug(
'Could not determine init system from command line: (%s)', 'Could not determine init system from command line: (%s)',
' '.join(init_cmdline) ' '.join(init_cmdline)
) )
@ -1780,7 +1784,10 @@ def os_data():
elif grains['kernel'] == 'SunOS': elif grains['kernel'] == 'SunOS':
if salt.utils.platform.is_smartos(): if salt.utils.platform.is_smartos():
# See https://github.com/joyent/smartos-live/issues/224 # See https://github.com/joyent/smartos-live/issues/224
if HAS_UNAME:
uname_v = os.uname()[3] # format: joyent_20161101T004406Z uname_v = os.uname()[3] # format: joyent_20161101T004406Z
else:
uname_v = os.name
uname_v = uname_v[uname_v.index('_')+1:] uname_v = uname_v[uname_v.index('_')+1:]
grains['os'] = grains['osfullname'] = 'SmartOS' grains['os'] = grains['osfullname'] = 'SmartOS'
# store a parsed version of YYYY.MM.DD as osrelease # store a parsed version of YYYY.MM.DD as osrelease
@ -1809,7 +1816,10 @@ def os_data():
else: else:
if development is not None: if development is not None:
osname = ' '.join((osname, development)) osname = ' '.join((osname, development))
if HAS_UNAME:
uname_v = os.uname()[3] uname_v = os.uname()[3]
else:
uname_v = os.name
grains['os'] = grains['osfullname'] = osname grains['os'] = grains['osfullname'] = osname
if osname in ['Oracle Solaris'] and uname_v.startswith(osmajorrelease): if osname in ['Oracle Solaris'] and uname_v.startswith(osmajorrelease):
# Oracla Solars 11 and up have minor version in uname # Oracla Solars 11 and up have minor version in uname
@ -2288,7 +2298,7 @@ def _hw_data(osdata):
if os.path.exists(contents_file): if os.path.exists(contents_file):
try: try:
with salt.utils.files.fopen(contents_file, 'r') as ifile: with salt.utils.files.fopen(contents_file, 'r') as ifile:
grains[key] = ifile.read().strip() grains[key] = salt.utils.stringutils.to_unicode(ifile.read().strip(), errors='replace')
if key == 'uuid': if key == 'uuid':
grains['uuid'] = grains['uuid'].lower() grains['uuid'] = grains['uuid'].lower()
except (IOError, OSError) as err: except (IOError, OSError) as err:

View File

@ -15,12 +15,14 @@ import inspect
import tempfile import tempfile
import functools import functools
import threading import threading
import traceback
import types import types
from collections import MutableMapping from collections import MutableMapping
from zipimport import zipimporter from zipimport import zipimporter
# Import salt libs # Import salt libs
import salt.config import salt.config
import salt.defaults.exitcodes
import salt.syspaths import salt.syspaths
import salt.utils.context import salt.utils.context
import salt.utils.data import salt.utils.data
@ -67,10 +69,10 @@ if USE_IMPORTLIB:
SUFFIXES = [] SUFFIXES = []
for suffix in importlib.machinery.EXTENSION_SUFFIXES: for suffix in importlib.machinery.EXTENSION_SUFFIXES:
SUFFIXES.append((suffix, 'rb', MODULE_KIND_EXTENSION)) SUFFIXES.append((suffix, 'rb', MODULE_KIND_EXTENSION))
for suffix in importlib.machinery.BYTECODE_SUFFIXES:
SUFFIXES.append((suffix, 'rb', MODULE_KIND_COMPILED))
for suffix in importlib.machinery.SOURCE_SUFFIXES: for suffix in importlib.machinery.SOURCE_SUFFIXES:
SUFFIXES.append((suffix, 'rb', MODULE_KIND_SOURCE)) SUFFIXES.append((suffix, 'rb', MODULE_KIND_SOURCE))
for suffix in importlib.machinery.BYTECODE_SUFFIXES:
SUFFIXES.append((suffix, 'rb', MODULE_KIND_COMPILED))
MODULE_KIND_MAP = { MODULE_KIND_MAP = {
MODULE_KIND_SOURCE: importlib.machinery.SourceFileLoader, MODULE_KIND_SOURCE: importlib.machinery.SourceFileLoader,
MODULE_KIND_COMPILED: importlib.machinery.SourcelessFileLoader, MODULE_KIND_COMPILED: importlib.machinery.SourcelessFileLoader,
@ -1480,6 +1482,17 @@ class LazyLoader(salt.utils.lazy.LazyDict):
self.missing_modules[name] = error self.missing_modules[name] = error
return False return False
except SystemExit as error: except SystemExit as error:
try:
fn_, _, caller, _ = traceback.extract_tb(sys.exc_info()[2])[-1]
except Exception:
pass
else:
tgt_fn = os.path.join('salt', 'utils', 'process.py')
if fn_.endswith(tgt_fn) and '_handle_signals' in caller:
# Race conditon, SIGTERM or SIGINT received while loader
# was in process of loading a module. Call sys.exit to
# ensure that the process is killed.
sys.exit(salt.defaults.exitcodes.EX_OK)
log.error( log.error(
'Failed to import %s %s as the module called exit()\n', 'Failed to import %s %s as the module called exit()\n',
self.tag, name, exc_info=True self.tag, name, exc_info=True

View File

@ -359,13 +359,11 @@ class FileserverUpdate(salt.utils.process.SignalHandlingMultiprocessingProcess):
self.__init__( self.__init__(
state['opts'], state['opts'],
log_queue=state['log_queue'], log_queue=state['log_queue'],
log_queue_level=state['log_queue_level']
) )
def __getstate__(self): def __getstate__(self):
return {'opts': self.opts, return {'opts': self.opts,
'log_queue': self.log_queue, 'log_queue': self.log_queue,
'log_queue_level': self.log_queue_level
} }
def fill_buckets(self): def fill_buckets(self):
@ -590,11 +588,19 @@ class Master(SMaster):
pass pass
if self.opts.get('git_pillar_verify_config', True): if self.opts.get('git_pillar_verify_config', True):
try:
git_pillars = [ git_pillars = [
x for x in self.opts.get('ext_pillar', []) x for x in self.opts.get('ext_pillar', [])
if 'git' in x if 'git' in x
and not isinstance(x['git'], six.string_types) and not isinstance(x['git'], six.string_types)
] ]
except TypeError:
git_pillars = []
critical_errors.append(
'Invalid ext_pillar configuration. It is likely that the '
'external pillar type was not specified for one or more '
'external pillars.'
)
if git_pillars: if git_pillars:
try: try:
new_opts = copy.deepcopy(self.opts) new_opts = copy.deepcopy(self.opts)

View File

@ -101,6 +101,7 @@ def cert(name,
server=None, server=None,
owner='root', owner='root',
group='root', group='root',
mode='0640',
certname=None): certname=None):
''' '''
Obtain/renew a certificate from an ACME CA, probably Let's Encrypt. Obtain/renew a certificate from an ACME CA, probably Let's Encrypt.
@ -113,8 +114,9 @@ def cert(name,
:param renew: True/'force' to force a renewal, or a window of renewal before expiry in days :param renew: True/'force' to force a renewal, or a window of renewal before expiry in days
:param keysize: RSA key bits :param keysize: RSA key bits
:param server: API endpoint to talk to :param server: API endpoint to talk to
:param owner: owner of private key :param owner: owner of the private key file
:param group: group of private key :param group: group of the private key file
:param mode: mode of the private key file
:param certname: Name of the certificate to save :param certname: Name of the certificate to save
:return: dict with 'result' True/False/None, 'comment' and certificate's expiry date ('not_after') :return: dict with 'result' True/False/None, 'comment' and certificate's expiry date ('not_after')
@ -170,27 +172,17 @@ def cert(name,
return {'result': False, 'comment': 'Certificate {0} renewal failed with:\n{1}'.format(name, res['stderr'])} return {'result': False, 'comment': 'Certificate {0} renewal failed with:\n{1}'.format(name, res['stderr'])}
if 'no action taken' in res['stdout']: if 'no action taken' in res['stdout']:
return {'result': None, comment = 'Certificate {0} unchanged'.format(cert_file)
'comment': 'No action taken on certificate {0}'.format(cert_file), elif renew:
'not_after': expires(name)}
if renew:
comment = 'Certificate {0} renewed'.format(name) comment = 'Certificate {0} renewed'.format(name)
else: else:
comment = 'Certificate {0} obtained'.format(name) comment = 'Certificate {0} obtained'.format(name)
ret = {'comment': comment, 'not_after': expires(name)}
res = __salt__['file.check_perms'](_cert_file(name, 'privkey'), {}, owner, group, '0600', follow_symlinks=True) ret = {'comment': comment, 'not_after': expires(name), 'changes': {}, 'result': True}
ret, _ = __salt__['file.check_perms'](_cert_file(name, 'privkey'),
if res is None: ret,
ret['result'] = False owner, group, mode,
ret['comment'] += ', but setting permissions failed.' follow_symlinks=True)
elif not res[0].get('result', False):
ret['result'] = False
ret['comment'] += ', but setting permissions failed with \n{0}'.format(res[0]['comment'])
else:
ret['result'] = True
ret['comment'] += '.'
return ret return ret

View File

@ -289,12 +289,14 @@ def raw_cron(user):
# Preserve line endings # Preserve line endings
lines = sdecode(__salt__['cmd.run_stdout'](cmd, lines = sdecode(__salt__['cmd.run_stdout'](cmd,
runas=user, runas=user,
ignore_retcode=True,
rstrip=False, rstrip=False,
python_shell=False)).splitlines(True) python_shell=False)).splitlines(True)
else: else:
cmd = 'crontab -u {0} -l'.format(user) cmd = 'crontab -u {0} -l'.format(user)
# Preserve line endings # Preserve line endings
lines = sdecode(__salt__['cmd.run_stdout'](cmd, lines = sdecode(__salt__['cmd.run_stdout'](cmd,
ignore_retcode=True,
rstrip=False, rstrip=False,
python_shell=False)).splitlines(True) python_shell=False)).splitlines(True)

View File

@ -393,20 +393,14 @@ def __within(within=None, errmsg=None, dtype=None):
def __space_delimited_list(value): def __space_delimited_list(value):
'''validate that a value contains one or more space-delimited values''' '''validate that a value contains one or more space-delimited values'''
valid, _value, errmsg = False, value, 'space-delimited string' if isinstance(value, str):
try: value = value.strip().split()
if hasattr(value, '__iter__'):
valid = True # TODO: if hasattr(value, '__iter__') and value != []:
return (True, value, 'space-delimited string')
else: else:
_value = value.split() return (False, value, '{0} is not a valid space-delimited value.\n'.format(value))
if _value == []:
raise ValueError
valid = True
except AttributeError:
pass
except ValueError:
pass
return (valid, _value, errmsg)
SALT_ATTR_TO_DEBIAN_ATTR_MAP = { SALT_ATTR_TO_DEBIAN_ATTR_MAP = {
'dns': 'dns-nameservers', 'dns': 'dns-nameservers',

View File

@ -6467,7 +6467,6 @@ def _prepare_trans_tar(name, sls_opts, mods=None, pillar=None):
refs = salt.client.ssh.state.lowstate_file_refs(chunks) refs = salt.client.ssh.state.lowstate_file_refs(chunks)
_mk_fileclient() _mk_fileclient()
trans_tar = salt.client.ssh.state.prep_trans_tar( trans_tar = salt.client.ssh.state.prep_trans_tar(
sls_opts,
__context__['cp.fileclient'], __context__['cp.fileclient'],
chunks, refs, pillar, name) chunks, refs, pillar, name)
return trans_tar return trans_tar

View File

@ -86,6 +86,9 @@ def fire_master(data, tag, preload=None):
channel = salt.transport.Channel.factory(__opts__, master_uri=master) channel = salt.transport.Channel.factory(__opts__, master_uri=master)
try: try:
channel.send(load) channel.send(load)
# channel.send was successful.
# Ensure ret is True.
ret = True
except Exception: except Exception:
ret = False ret = False
return ret return ret

View File

@ -61,6 +61,7 @@ import salt.utils.stringutils
import salt.utils.templates import salt.utils.templates
import salt.utils.url import salt.utils.url
import salt.utils.user import salt.utils.user
import salt.utils.data
from salt.exceptions import CommandExecutionError, MinionError, SaltInvocationError, get_error_message as _get_error_message from salt.exceptions import CommandExecutionError, MinionError, SaltInvocationError, get_error_message as _get_error_message
from salt.utils.files import HASHES, HASHES_REVMAP from salt.utils.files import HASHES, HASHES_REVMAP
@ -1718,18 +1719,19 @@ def _regex_to_static(src, regex):
return None return None
try: try:
src = re.search(regex, src, re.M) compiled = re.compile(regex, re.DOTALL)
src = [line for line in src if compiled.search(line) or line.count(regex)]
except Exception as ex: except Exception as ex:
raise CommandExecutionError("{0}: '{1}'".format(_get_error_message(ex), regex)) raise CommandExecutionError("{0}: '{1}'".format(_get_error_message(ex), regex))
return src and src.group().rstrip('\r') or regex return src and src or []
def _assert_occurrence(src, probe, target, amount=1): def _assert_occurrence(probe, target, amount=1):
''' '''
Raise an exception, if there are different amount of specified occurrences in src. Raise an exception, if there are different amount of specified occurrences in src.
''' '''
occ = src.count(probe) occ = len(probe)
if occ > amount: if occ > amount:
msg = 'more than' msg = 'more than'
elif occ < amount: elif occ < amount:
@ -1745,7 +1747,7 @@ def _assert_occurrence(src, probe, target, amount=1):
return occ return occ
def _get_line_indent(src, line, indent): def _set_line_indent(src, line, indent):
''' '''
Indent the line with the source line. Indent the line with the source line.
''' '''
@ -1758,7 +1760,36 @@ def _get_line_indent(src, line, indent):
break break
idt.append(c) idt.append(c)
return ''.join(idt) + line.strip() return ''.join(idt) + line.lstrip()
def _get_eol(line):
match = re.search('((?<!\r)\n|\r(?!\n)|\r\n)$', line)
return match and match.group() or ''
def _set_line_eol(src, line):
'''
Add line ending
'''
line_ending = _get_eol(src) or os.linesep
return line.rstrip() + line_ending
def _insert_line_before(idx, body, content, indent):
if not idx or (idx and _starts_till(body[idx - 1], content) < 0):
cnd = _set_line_indent(body[idx], content, indent)
body.insert(idx, cnd)
return body
def _insert_line_after(idx, body, content, indent):
# No duplicates or append, if "after" is the last line
next_line = idx + 1 < len(body) and body[idx + 1] or None
if next_line is None or _starts_till(next_line, content) < 0:
cnd = _set_line_indent(body[idx], content, indent)
body.insert(idx + 1, cnd)
return body
def line(path, content=None, match=None, mode=None, location=None, def line(path, content=None, match=None, mode=None, location=None,
@ -1889,132 +1920,110 @@ def line(path, content=None, match=None, mode=None, location=None,
match = content match = content
with salt.utils.files.fopen(path, mode='r') as fp_: with salt.utils.files.fopen(path, mode='r') as fp_:
body = salt.utils.stringutils.to_unicode(fp_.read()) body = salt.utils.data.decode_list(fp_.readlines())
body_before = hashlib.sha256(salt.utils.stringutils.to_bytes(body)).hexdigest() body_before = hashlib.sha256(salt.utils.stringutils.to_bytes(''.join(body))).hexdigest()
# Add empty line at the end if last line ends with eol.
# Allows simpler code
if body and _get_eol(body[-1]):
body.append('')
after = _regex_to_static(body, after) after = _regex_to_static(body, after)
before = _regex_to_static(body, before) before = _regex_to_static(body, before)
match = _regex_to_static(body, match) match = _regex_to_static(body, match)
if os.stat(path).st_size == 0 and mode in ('delete', 'replace'): if os.stat(path).st_size == 0 and mode in ('delete', 'replace'):
log.warning('Cannot find text to {0}. File \'{1}\' is empty.'.format(mode, path)) log.warning('Cannot find text to {0}. File \'{1}\' is empty.'.format(mode, path))
body = '' body = []
elif mode == 'delete': elif mode == 'delete' and match:
body = os.linesep.join([line for line in body.split(os.linesep) if line.find(match) < 0]) body = [line for line in body if line != match[0]]
elif mode == 'replace': elif mode == 'replace' and match:
body = os.linesep.join([(_get_line_indent(file_line, content, indent) idx = body.index(match[0])
if (file_line.find(match) > -1 and not file_line == content) else file_line) file_line = body.pop(idx)
for file_line in body.split(os.linesep)]) body.insert(idx, _set_line_indent(file_line, content, indent))
elif mode == 'insert': elif mode == 'insert':
if not location and not before and not after: if not location and not before and not after:
raise CommandExecutionError('On insert must be defined either "location" or "before/after" conditions.') raise CommandExecutionError('On insert must be defined either "location" or "before/after" conditions.')
if not location: if not location:
if before and after: if before and after:
_assert_occurrence(body, before, 'before') _assert_occurrence(before, 'before')
_assert_occurrence(body, after, 'after') _assert_occurrence(after, 'after')
out = [] out = []
lines = body.split(os.linesep)
in_range = False in_range = False
for line in lines: for line in body:
if line.find(after) > -1: if line == after[0]:
in_range = True in_range = True
elif line.find(before) > -1 and in_range: elif line == before[0] and in_range:
out.append(_get_line_indent(line, content, indent)) cnd = _set_line_indent(line, content, indent)
out.append(cnd)
out.append(line) out.append(line)
body = os.linesep.join(out) body = out
if before and not after: if before and not after:
_assert_occurrence(body, before, 'before') _assert_occurrence(before, 'before')
out = []
lines = body.split(os.linesep) idx = body.index(before[0])
for idx in range(len(lines)): body = _insert_line_before(idx, body, content, indent)
_line = lines[idx]
if _line.find(before) > -1:
cnd = _get_line_indent(_line, content, indent)
if not idx or (idx and _starts_till(lines[idx - 1], cnd) < 0): # Job for replace instead
out.append(cnd)
out.append(_line)
body = os.linesep.join(out)
elif after and not before: elif after and not before:
_assert_occurrence(body, after, 'after') _assert_occurrence(after, 'after')
out = []
lines = body.split(os.linesep) idx = body.index(after[0])
for idx, _line in enumerate(lines): body = _insert_line_after(idx, body, content, indent)
out.append(_line)
cnd = _get_line_indent(_line, content, indent)
# No duplicates or append, if "after" is the last line
if (_line.find(after) > -1 and
(lines[((idx + 1) < len(lines)) and idx + 1 or idx].strip() != cnd or
idx + 1 == len(lines))):
out.append(cnd)
body = os.linesep.join(out)
else: else:
if location == 'start': if location == 'start':
body = os.linesep.join((content, body)) if body:
body.insert(0, _set_line_eol(body[0], content))
else:
body.append(content + os.linesep)
elif location == 'end': elif location == 'end':
body = os.linesep.join((body, _get_line_indent(body[-1], content, indent) if body else content)) body.append(_set_line_indent(body[-1], content, indent) if body else content)
elif mode == 'ensure': elif mode == 'ensure':
after = after and after.strip()
before = before and before.strip()
if before and after: if before and after:
_assert_occurrence(body, before, 'before') _assert_occurrence(before, 'before')
_assert_occurrence(body, after, 'after') _assert_occurrence(after, 'after')
is_there = bool(body.count(content)) is_there = bool([l for l in body if l.count(content)])
if not is_there: if not is_there:
out = [] idx = body.index(after[0])
body = body.split(os.linesep) if idx < (len(body) - 1) and body[idx + 1] == before[0]:
for idx, line in enumerate(body): cnd = _set_line_indent(body[idx], content, indent)
out.append(line) body.insert(idx + 1, cnd)
if line.find(content) > -1: else:
is_there = True
if not is_there:
if idx < (len(body) - 1) and line.find(after) > -1 and body[idx + 1].find(before) > -1:
out.append(content)
elif line.find(after) > -1:
raise CommandExecutionError('Found more than one line between ' raise CommandExecutionError('Found more than one line between '
'boundaries "before" and "after".') 'boundaries "before" and "after".')
body = os.linesep.join(out)
elif before and not after: elif before and not after:
_assert_occurrence(body, before, 'before') _assert_occurrence(before, 'before')
body = body.split(os.linesep)
out = [] idx = body.index(before[0])
for idx in range(len(body)): body = _insert_line_before(idx, body, content, indent)
if body[idx].find(before) > -1:
prev = (idx > 0 and idx or 1) - 1
out.append(_get_line_indent(body[idx], content, indent))
if _starts_till(out[prev], content) > -1:
del out[prev]
out.append(body[idx])
body = os.linesep.join(out)
elif not before and after: elif not before and after:
_assert_occurrence(body, after, 'after') _assert_occurrence(after, 'after')
body = body.split(os.linesep)
skip = None
out = []
for idx in range(len(body)):
if skip != body[idx]:
out.append(body[idx])
if body[idx].find(after) > -1: idx = body.index(after[0])
next_line = idx + 1 < len(body) and body[idx + 1] or None body = _insert_line_after(idx, body, content, indent)
if next_line is not None and _starts_till(next_line, content) > -1:
skip = next_line
out.append(_get_line_indent(body[idx], content, indent))
body = os.linesep.join(out)
else: else:
raise CommandExecutionError("Wrong conditions? " raise CommandExecutionError("Wrong conditions? "
"Unable to ensure line without knowing " "Unable to ensure line without knowing "
"where to put it before and/or after.") "where to put it before and/or after.")
changed = body_before != hashlib.sha256(salt.utils.stringutils.to_bytes(body)).hexdigest() if body:
for idx, line in enumerate(body):
if not _get_eol(line) and idx+1 < len(body):
prev = idx and idx-1 or 1
body[idx] = _set_line_eol(body[prev], line)
# We do not need empty line at the end anymore
if '' == body[-1]:
body.pop()
changed = body_before != hashlib.sha256(salt.utils.stringutils.to_bytes(''.join(body))).hexdigest()
if backup and changed and __opts__['test'] is False: if backup and changed and __opts__['test'] is False:
try: try:
@ -2028,12 +2037,9 @@ def line(path, content=None, match=None, mode=None, location=None,
if changed: if changed:
if show_changes: if show_changes:
with salt.utils.files.fopen(path, 'r') as fp_: with salt.utils.files.fopen(path, 'r') as fp_:
path_content = [salt.utils.stringutils.to_unicode(x) path_content = salt.utils.data.decode_list(fp_.read().splitlines(True))
for x in fp_.read().splitlines(True)]
changes_diff = ''.join(difflib.unified_diff( changes_diff = ''.join(difflib.unified_diff(
path_content, path_content, body
[salt.utils.stringutils.to_unicode(x)
for x in body.splitlines(True)]
)) ))
if __opts__['test'] is False: if __opts__['test'] is False:
fh_ = None fh_ = None
@ -2041,12 +2047,12 @@ def line(path, content=None, match=None, mode=None, location=None,
# Make sure we match the file mode from salt.utils.files.fopen # Make sure we match the file mode from salt.utils.files.fopen
if six.PY2 and salt.utils.platform.is_windows(): if six.PY2 and salt.utils.platform.is_windows():
mode = 'wb' mode = 'wb'
body = salt.utils.stringutils.to_bytes(body) body = salt.utils.data.encode_list(body)
else: else:
mode = 'w' mode = 'w'
body = salt.utils.stringutils.to_str(body) body = salt.utils.data.decode_list(body, to_str=True)
fh_ = salt.utils.atomicfile.atomic_open(path, mode) fh_ = salt.utils.atomicfile.atomic_open(path, mode)
fh_.write(body) fh_.writelines(body)
finally: finally:
if fh_: if fh_:
fh_.close() fh_.close()
@ -2471,10 +2477,10 @@ def blockreplace(path,
final output final output
marker_end marker_end
The line content identifying a line as the end of the content block. The line content identifying the end of the content block. As of
Note that the whole line containing this marker will be considered, so versions 2017.7.5 and 2018.3.1, everything up to the text matching the
whitespace or extra content before or after the marker is included in marker will be replaced, so it's important to ensure that your marker
final output includes the beginning of the text you wish to replace.
content content
The content to be used between the two lines identified by marker_start The content to be used between the two lines identified by marker_start
@ -4463,27 +4469,6 @@ def check_perms(name, ret, user, group, mode, attrs=None, follow_symlinks=False)
if perms['lattrs']: if perms['lattrs']:
chattr(name, operator='remove', attributes=perms['lattrs']) chattr(name, operator='remove', attributes=perms['lattrs'])
# Mode changes if needed
if mode is not None:
# File is a symlink, ignore the mode setting
# if follow_symlinks is False
if os.path.islink(name) and not follow_symlinks:
pass
else:
mode = salt.utils.files.normalize_mode(mode)
if mode != perms['lmode']:
if __opts__['test'] is True:
ret['changes']['mode'] = mode
else:
set_mode(name, mode)
if mode != salt.utils.files.normalize_mode(get_mode(name)):
ret['result'] = False
ret['comment'].append(
'Failed to change mode to {0}'.format(mode)
)
else:
ret['changes']['mode'] = mode
# user/group changes if needed, then check if it worked # user/group changes if needed, then check if it worked
if user: if user:
if isinstance(user, int): if isinstance(user, int):
@ -4540,6 +4525,7 @@ def check_perms(name, ret, user, group, mode, attrs=None, follow_symlinks=False)
.format(user)) .format(user))
elif 'cuser' in perms and user != '': elif 'cuser' in perms and user != '':
ret['changes']['user'] = user ret['changes']['user'] = user
if group: if group:
if isinstance(group, int): if isinstance(group, int):
group = gid_to_group(group) group = gid_to_group(group)
@ -4560,18 +4546,32 @@ def check_perms(name, ret, user, group, mode, attrs=None, follow_symlinks=False)
elif 'cgroup' in perms and user != '': elif 'cgroup' in perms and user != '':
ret['changes']['group'] = group ret['changes']['group'] = group
if isinstance(orig_comment, six.string_types):
if orig_comment:
ret['comment'].insert(0, orig_comment)
ret['comment'] = '; '.join(ret['comment'])
if __opts__['test'] is True and ret['changes']:
ret['result'] = None
if not salt.utils.platform.is_windows() and not is_dir: if not salt.utils.platform.is_windows() and not is_dir:
# Replace attributes on file if it had been removed # Replace attributes on file if it had been removed
if perms.get('lattrs', ''): if perms.get('lattrs', ''):
chattr(name, operator='add', attributes=perms['lattrs']) chattr(name, operator='add', attributes=perms['lattrs'])
# Mode changes if needed
if mode is not None:
# File is a symlink, ignore the mode setting
# if follow_symlinks is False
if os.path.islink(name) and not follow_symlinks:
pass
else:
mode = salt.utils.files.normalize_mode(mode)
if mode != perms['lmode']:
if __opts__['test'] is True:
ret['changes']['mode'] = mode
else:
set_mode(name, mode)
if mode != salt.utils.files.normalize_mode(get_mode(name)):
ret['result'] = False
ret['comment'].append(
'Failed to change mode to {0}'.format(mode)
)
else:
ret['changes']['mode'] = mode
# Modify attributes of file if needed # Modify attributes of file if needed
if attrs is not None and not is_dir: if attrs is not None and not is_dir:
# File is a symlink, ignore the mode setting # File is a symlink, ignore the mode setting
@ -4598,6 +4598,18 @@ def check_perms(name, ret, user, group, mode, attrs=None, follow_symlinks=False)
else: else:
ret['changes']['attrs'] = attrs ret['changes']['attrs'] = attrs
# Only combine the comment list into a string
# after all comments are added above
if isinstance(orig_comment, six.string_types):
if orig_comment:
ret['comment'].insert(0, orig_comment)
ret['comment'] = '; '.join(ret['comment'])
# Set result to None at the very end of the function,
# after all changes have been recorded above
if __opts__['test'] is True and ret['changes']:
ret['result'] = None
return ret, perms return ret, perms
@ -4722,6 +4734,11 @@ def check_managed_changes(
defaults, defaults,
skip_verify, skip_verify,
**kwargs) **kwargs)
# Ensure that user-provided hash string is lowercase
if source_sum and ('hsum' in source_sum):
source_sum['hsum'] = source_sum['hsum'].lower()
if comments: if comments:
__clean_tmp(sfn) __clean_tmp(sfn)
return False, comments return False, comments
@ -5058,7 +5075,7 @@ def manage_file(name,
source source
file reference on the master file reference on the master
source_hash source_sum
sum hash for source sum hash for source
user user
@ -6196,6 +6213,16 @@ def grep(path,
''' '''
path = os.path.expanduser(path) path = os.path.expanduser(path)
# Backup the path in case the glob returns nothing
_path = path
path = glob.glob(path)
# If the list is empty no files exist
# so we revert back to the original path
# so the result is an error.
if not path:
path = _path
split_opts = [] split_opts = []
for opt in opts: for opt in opts:
try: try:
@ -6210,6 +6237,9 @@ def grep(path,
) )
split_opts.extend(split) split_opts.extend(split)
if isinstance(path, list):
cmd = ['grep'] + split_opts + [pattern] + path
else:
cmd = ['grep'] + split_opts + [pattern, path] cmd = ['grep'] + split_opts + [pattern, path]
try: try:
ret = __salt__['cmd.run_all'](cmd, python_shell=False) ret = __salt__['cmd.run_all'](cmd, python_shell=False)

View File

@ -29,15 +29,16 @@ def __virtual__():
return (False, 'glusterfs server is not installed') return (False, 'glusterfs server is not installed')
def _get_minor_version(): def _get_version():
# Set default version to 6 for tests # Set the default minor version to 6 for tests
version = 6 version = [3, 6]
cmd = 'gluster --version' cmd = 'gluster --version'
result = __salt__['cmd.run'](cmd).splitlines() result = __salt__['cmd.run'](cmd).splitlines()
for line in result: for line in result:
if line.startswith('glusterfs'): if line.startswith('glusterfs'):
version = int(line.split()[1].split('.')[1]) version = line.split()[-1].split('.')
return version version = [int(i) for i in version]
return tuple(version)
def _gluster_ok(xml_data): def _gluster_ok(xml_data):
@ -70,7 +71,7 @@ def _gluster_xml(cmd):
# We will pass the command string as stdin to allow for much longer # We will pass the command string as stdin to allow for much longer
# command strings. This is especially useful for creating large volumes # command strings. This is especially useful for creating large volumes
# where the list of bricks exceeds 128 characters. # where the list of bricks exceeds 128 characters.
if _get_minor_version() < 6: if _get_version() < (3, 6,):
result = __salt__['cmd.run']( result = __salt__['cmd.run'](
'script -q -c "gluster --xml --mode=script"', stdin="{0}\n\004".format(cmd) 'script -q -c "gluster --xml --mode=script"', stdin="{0}\n\004".format(cmd)
) )

View File

@ -58,22 +58,22 @@ def __virtual__():
'influxdb library not available.')) 'influxdb library not available.'))
def _client(user=None, password=None, host=None, port=None, **client_args): def _client(influxdb_user=None, influxdb_password=None, influxdb_host=None, influxdb_port=None, **client_args):
if not user: if not influxdb_user:
user = __salt__['config.option']('influxdb.user', 'root') influxdb_user = __salt__['config.option']('influxdb.user', 'root')
if not password: if not influxdb_password:
password = __salt__['config.option']('influxdb.password', 'root') influxdb_password = __salt__['config.option']('influxdb.password', 'root')
if not host: if not influxdb_host:
host = __salt__['config.option']('influxdb.host', 'localhost') influxdb_host = __salt__['config.option']('influxdb.host', 'localhost')
if not port: if not influxdb_port:
port = __salt__['config.option']('influxdb.port', 8086) influxdb_port = __salt__['config.option']('influxdb.port', 8086)
for ignore in _STATE_INTERNAL_KEYWORDS: for ignore in _STATE_INTERNAL_KEYWORDS:
if ignore in client_args: if ignore in client_args:
del client_args[ignore] del client_args[ignore]
return influxdb.InfluxDBClient(host=host, return influxdb.InfluxDBClient(host=influxdb_host,
port=port, port=influxdb_port,
username=user, username=influxdb_user,
password=password, password=influxdb_password,
**client_args) **client_args)

View File

@ -35,9 +35,9 @@ def __virtual__():
return __virtualname__ return __virtualname__
ini_regx = re.compile(r'^\s*\[(.+?)\]\s*$', flags=re.M) INI_REGX = re.compile(r'^\s*\[(.+?)\]\s*$', flags=re.M)
com_regx = re.compile(r'^\s*(#|;)\s*(.*)') COM_REGX = re.compile(r'^\s*(#|;)\s*(.*)')
indented_regx = re.compile(r'(\s+)(.*)') INDENTED_REGX = re.compile(r'(\s+)(.*)')
def set_option(file_name, sections=None, separator='='): def set_option(file_name, sections=None, separator='='):
@ -105,7 +105,13 @@ def get_option(file_name, section, option, separator='='):
salt '*' ini.get_option /path/to/ini section_name option_name salt '*' ini.get_option /path/to/ini section_name option_name
''' '''
inifile = _Ini.get_ini_file(file_name, separator=separator) inifile = _Ini.get_ini_file(file_name, separator=separator)
if section:
try:
return inifile.get(section, {}).get(option, None) return inifile.get(section, {}).get(option, None)
except AttributeError:
return None
else:
return inifile.get(option, None)
def remove_option(file_name, section, option, separator='='): def remove_option(file_name, section, option, separator='='):
@ -129,7 +135,10 @@ def remove_option(file_name, section, option, separator='='):
salt '*' ini.remove_option /path/to/ini section_name option_name salt '*' ini.remove_option /path/to/ini section_name option_name
''' '''
inifile = _Ini.get_ini_file(file_name, separator=separator) inifile = _Ini.get_ini_file(file_name, separator=separator)
if isinstance(inifile.get(section), (dict, OrderedDict)):
value = inifile.get(section, {}).pop(option, None) value = inifile.get(section, {}).pop(option, None)
else:
value = inifile.pop(option, None)
inifile.flush() inifile.flush()
return value return value
@ -182,9 +191,9 @@ def remove_section(file_name, section, separator='='):
salt '*' ini.remove_section /path/to/ini section_name salt '*' ini.remove_section /path/to/ini section_name
''' '''
inifile = _Ini.get_ini_file(file_name, separator=separator) inifile = _Ini.get_ini_file(file_name, separator=separator)
section = inifile.pop(section, {}) if section in inifile:
section = inifile.pop(section)
inifile.flush() inifile.flush()
ret = {} ret = {}
for key, value in six.iteritems(section): for key, value in six.iteritems(section):
@ -193,6 +202,44 @@ def remove_section(file_name, section, separator='='):
return ret return ret
def get_ini(file_name, separator='='):
'''
Retrieve whole structure from an ini file and return it as dictionary.
API Example:
.. code-block:: python
import salt
sc = salt.client.get_local_client()
sc.cmd('target', 'ini.get_ini',
[path_to_ini_file])
CLI Example:
.. code-block:: bash
salt '*' ini.get_ini /path/to/ini
'''
def ini_odict2dict(odict):
'''
Transform OrderedDict to regular dict recursively
:param odict: OrderedDict
:return: regular dict
'''
ret = {}
for key, val in six.iteritems(odict):
if key[0] != '#':
if isinstance(val, (dict, OrderedDict)):
ret.update({key: ini_odict2dict(val)})
else:
ret.update({key: val})
return ret
inifile = _Ini.get_ini_file(file_name, separator=separator)
return ini_odict2dict(inifile)
class _Section(OrderedDict): class _Section(OrderedDict):
def __init__(self, name, inicontents='', separator='=', commenter='#'): def __init__(self, name, inicontents='', separator='=', commenter='#'):
super(_Section, self).__init__(self) super(_Section, self).__init__(self)
@ -221,7 +268,7 @@ class _Section(OrderedDict):
self.pop(opt) self.pop(opt)
for opt_str in inicontents.split(os.linesep): for opt_str in inicontents.split(os.linesep):
# Match comments # Match comments
com_match = com_regx.match(opt_str) com_match = COM_REGX.match(opt_str)
if com_match: if com_match:
name = '#comment{0}'.format(comment_count) name = '#comment{0}'.format(comment_count)
self.com = com_match.group(1) self.com = com_match.group(1)
@ -229,7 +276,7 @@ class _Section(OrderedDict):
self.update({name: opt_str}) self.update({name: opt_str})
continue continue
# Add indented lines to the value of the previous entry. # Add indented lines to the value of the previous entry.
indented_match = indented_regx.match(opt_str) indented_match = INDENTED_REGX.match(opt_str)
if indented_match: if indented_match:
indent = indented_match.group(1).replace('\t', ' ') indent = indented_match.group(1).replace('\t', ' ')
if indent > curr_indent: if indent > curr_indent:
@ -318,7 +365,7 @@ class _Section(OrderedDict):
sections_dict = OrderedDict() sections_dict = OrderedDict()
for name, value in six.iteritems(self): for name, value in six.iteritems(self):
# Handle Comment Lines # Handle Comment Lines
if com_regx.match(name): if COM_REGX.match(name):
yield '{0}{1}'.format(value, os.linesep) yield '{0}{1}'.format(value, os.linesep)
# Handle Sections # Handle Sections
elif isinstance(value, _Section): elif isinstance(value, _Section):
@ -363,9 +410,6 @@ class _Section(OrderedDict):
class _Ini(_Section): class _Ini(_Section):
def __init__(self, name, inicontents='', separator='=', commenter='#'):
super(_Ini, self).__init__(name, inicontents, separator, commenter)
def refresh(self, inicontents=None): def refresh(self, inicontents=None):
if inicontents is None: if inicontents is None:
try: try:
@ -382,7 +426,7 @@ class _Ini(_Section):
# Remove anything left behind from a previous run. # Remove anything left behind from a previous run.
self.clear() self.clear()
inicontents = ini_regx.split(inicontents) inicontents = INI_REGX.split(inicontents)
inicontents.reverse() inicontents.reverse()
# Pop anything defined outside of a section (ie. at the top of # Pop anything defined outside of a section (ie. at the top of
# the ini file). # the ini file).

View File

@ -2268,22 +2268,22 @@ def _change_state(cmd,
# as te command itself mess with double forks; we must not # as te command itself mess with double forks; we must not
# communicate with it, but just wait for the exit status # communicate with it, but just wait for the exit status
pkwargs = {'python_shell': False, pkwargs = {'python_shell': False,
'redirect_stderr': True,
'with_communicate': with_communicate, 'with_communicate': with_communicate,
'use_vt': use_vt, 'use_vt': use_vt,
'stdin': stdin, 'stdin': stdin,
'stdout': stdout, 'stdout': stdout}
'stderr': stderr}
for i in [a for a in pkwargs]: for i in [a for a in pkwargs]:
val = pkwargs[i] val = pkwargs[i]
if val is _marker: if val is _marker:
pkwargs.pop(i, None) pkwargs.pop(i, None)
error = __salt__['cmd.run_stderr'](cmd, **pkwargs) _cmdout = __salt__['cmd.run_all'](cmd, **pkwargs)
if error: if _cmdout['retcode'] != 0:
raise CommandExecutionError( raise CommandExecutionError(
'Error changing state for container \'{0}\' using command ' 'Error changing state for container \'{0}\' using command '
'\'{1}\': {2}'.format(name, cmd, error) '\'{1}\': {2}'.format(name, cmd, _cmdout['stdout'])
) )
if expected is not None: if expected is not None:
# some commands do not wait, so we will # some commands do not wait, so we will

View File

@ -48,7 +48,7 @@ def _list_taps():
''' '''
List currently installed brew taps List currently installed brew taps
''' '''
cmd = 'brew tap' cmd = 'tap'
return _call_brew(cmd)['stdout'].splitlines() return _call_brew(cmd)['stdout'].splitlines()
@ -60,7 +60,7 @@ def _tap(tap, runas=None):
if tap in _list_taps(): if tap in _list_taps():
return True return True
cmd = 'brew tap {0}'.format(tap) cmd = 'tap {0}'.format(tap)
try: try:
_call_brew(cmd) _call_brew(cmd)
except CommandExecutionError: except CommandExecutionError:
@ -85,6 +85,7 @@ def _call_brew(cmd, failhard=True):
''' '''
user = __salt__['file.get_user'](_homebrew_bin()) user = __salt__['file.get_user'](_homebrew_bin())
runas = user if user != __opts__['user'] else None runas = user if user != __opts__['user'] else None
cmd = '{} {}'.format(salt.utils.path.which('brew'), cmd)
result = __salt__['cmd.run_all'](cmd, result = __salt__['cmd.run_all'](cmd,
runas=runas, runas=runas,
output_loglevel='trace', output_loglevel='trace',
@ -121,7 +122,7 @@ def list_pkgs(versions_as_list=False, **kwargs):
__salt__['pkg_resource.stringify'](ret) __salt__['pkg_resource.stringify'](ret)
return ret return ret
cmd = 'brew list --versions' cmd = 'list --versions'
ret = {} ret = {}
out = _call_brew(cmd)['stdout'] out = _call_brew(cmd)['stdout']
for line in out.splitlines(): for line in out.splitlines():
@ -230,7 +231,7 @@ def remove(name=None, pkgs=None, **kwargs):
targets = [x for x in pkg_params if x in old] targets = [x for x in pkg_params if x in old]
if not targets: if not targets:
return {} return {}
cmd = 'brew uninstall {0}'.format(' '.join(targets)) cmd = 'uninstall {0}'.format(' '.join(targets))
out = _call_brew(cmd) out = _call_brew(cmd)
if out['retcode'] != 0 and out['stderr']: if out['retcode'] != 0 and out['stderr']:
@ -263,7 +264,7 @@ def refresh_db():
''' '''
# Remove rtag file to keep multiple refreshes from happening in pkg states # Remove rtag file to keep multiple refreshes from happening in pkg states
salt.utils.pkg.clear_rtag(__opts__) salt.utils.pkg.clear_rtag(__opts__)
cmd = 'brew update' cmd = 'update'
if _call_brew(cmd)['retcode']: if _call_brew(cmd)['retcode']:
log.error('Failed to update') log.error('Failed to update')
return False return False
@ -286,7 +287,7 @@ def _info(*pkgs):
Caveat: If one of the packages does not exist, no packages will be Caveat: If one of the packages does not exist, no packages will be
included in the output. included in the output.
''' '''
cmd = 'brew info --json=v1 {0}'.format(' '.join(pkgs)) cmd = 'info --json=v1 {0}'.format(' '.join(pkgs))
brew_result = _call_brew(cmd) brew_result = _call_brew(cmd)
if brew_result['retcode']: if brew_result['retcode']:
log.error('Failed to get info about packages: %s', log.error('Failed to get info about packages: %s',
@ -382,9 +383,9 @@ def install(name=None, pkgs=None, taps=None, options=None, **kwargs):
_tap(tap) _tap(tap)
if options: if options:
cmd = 'brew install {0} {1}'.format(formulas, ' '.join(options)) cmd = 'install {0} {1}'.format(formulas, ' '.join(options))
else: else:
cmd = 'brew install {0}'.format(formulas) cmd = 'install {0}'.format(formulas)
out = _call_brew(cmd) out = _call_brew(cmd)
if out['retcode'] != 0 and out['stderr']: if out['retcode'] != 0 and out['stderr']:
@ -418,7 +419,7 @@ def list_upgrades(refresh=True, **kwargs): # pylint: disable=W0613
if refresh: if refresh:
refresh_db() refresh_db()
res = _call_brew(['brew', 'outdated', '--json=v1']) res = _call_brew('outdated --json=v1')
ret = {} ret = {}
try: try:
@ -478,7 +479,7 @@ def upgrade(refresh=True):
if salt.utils.data.is_true(refresh): if salt.utils.data.is_true(refresh):
refresh_db() refresh_db()
result = _call_brew('brew upgrade', failhard=False) result = _call_brew('upgrade', failhard=False)
__context__.pop('pkg.list_pkgs', None) __context__.pop('pkg.list_pkgs', None)
new = list_pkgs() new = list_pkgs()
ret = salt.utils.data.compare_dicts(old, new) ret = salt.utils.data.compare_dicts(old, new)

View File

@ -433,7 +433,6 @@ def disabled(name, runas=None, domain='system'):
disabled = launchctl('print-disabled', disabled = launchctl('print-disabled',
domain, domain,
return_stdout=True, return_stdout=True,
output_loglevel='trace',
runas=runas) runas=runas)
for service in disabled.split("\n"): for service in disabled.split("\n"):
if name in service: if name in service:

View File

@ -959,7 +959,7 @@ def alter_db(name, character_set=None, collate=None, **connection_args):
return [] return []
cur = dbc.cursor() cur = dbc.cursor()
existing = db_get(name, **connection_args) existing = db_get(name, **connection_args)
qry = 'ALTER DATABASE {0} CHARACTER SET {1} COLLATE {2};'.format( qry = 'ALTER DATABASE `{0}` CHARACTER SET {1} COLLATE {2};'.format(
name.replace('%', r'\%').replace('_', r'\_'), name.replace('%', r'\%').replace('_', r'\_'),
character_set or existing.get('character_set'), character_set or existing.get('character_set'),
collate or existing.get('collate')) collate or existing.get('collate'))
@ -1812,11 +1812,16 @@ def grant_exists(grant,
if not target_tokens: # Avoid the overhead of re-calc in loop if not target_tokens: # Avoid the overhead of re-calc in loop
target_tokens = _grant_to_tokens(target) target_tokens = _grant_to_tokens(target)
grant_tokens = _grant_to_tokens(grant) grant_tokens = _grant_to_tokens(grant)
grant_tokens_database = grant_tokens['database'].replace('"', '').replace('\\', '').replace('`', '')
target_tokens_database = target_tokens['database'].replace('"', '').replace('\\', '').replace('`', '') _grant_tokens = {}
if grant_tokens['user'] == target_tokens['user'] and \ _target_tokens = {}
grant_tokens_database == target_tokens_database and \ for item in ['user', 'database', 'host']:
grant_tokens['host'] == target_tokens['host'] and \ _grant_tokens[item] = grant_tokens[item].replace('"', '').replace('\\', '').replace('`', '')
_target_tokens[item] = target_tokens[item].replace('"', '').replace('\\', '').replace('`', '')
if _grant_tokens['user'] == _target_tokens['user'] and \
_grant_tokens['database'] == _target_tokens['database'] and \
_grant_tokens['host'] == _target_tokens['host'] and \
set(grant_tokens['grant']) >= set(target_tokens['grant']): set(grant_tokens['grant']) >= set(target_tokens['grant']):
return True return True
else: else:

View File

@ -98,20 +98,16 @@ def _space_delimited_list(value):
''' '''
validate that a value contains one or more space-delimited values validate that a value contains one or more space-delimited values
''' '''
valid, _value, errmsg = False, value, 'space-delimited string' if isinstance(value, str):
try: items = value.split(' ')
if hasattr(value, '__iter__'): valid = items and all(items)
valid = True
else: else:
_value = value.split() valid = hasattr(value, '__iter__') and (value != [])
if _value == []:
raise ValueError if valid:
valid = True return (True, 'space-delimited string')
except AttributeError: else:
errmsg = '{0} is not a valid list.\n'.format(value) return (False, '{0} is not a valid list.\n'.format(value))
except ValueError:
errmsg = '{0} is not a valid list.\n'.format(value)
return (valid, errmsg)
def _validate_ipv4(value): def _validate_ipv4(value):

View File

@ -162,7 +162,11 @@ def install(pkg=None,
env.update({'SUDO_UID': uid, 'SUDO_USER': ''}) env.update({'SUDO_UID': uid, 'SUDO_USER': ''})
cmd = ' '.join(cmd) cmd = ' '.join(cmd)
result = __salt__['cmd.run_all'](cmd, python_shell=True, cwd=dir, runas=runas, env=env) result = __salt__['cmd.run_all'](cmd,
python_shell=True,
cwd=dir,
runas=runas,
env=env)
if result['retcode'] != 0: if result['retcode'] != 0:
raise CommandExecutionError(result['stderr']) raise CommandExecutionError(result['stderr'])
@ -170,33 +174,9 @@ def install(pkg=None,
# npm >1.2.21 is putting the output to stderr even though retcode is 0 # npm >1.2.21 is putting the output to stderr even though retcode is 0
npm_output = result['stdout'] or result['stderr'] npm_output = result['stdout'] or result['stderr']
try: try:
return salt.utils.json.loads(npm_output) return salt.utils.json.find_json(npm_output)
except ValueError: except ValueError:
pass return npm_output
json_npm_output = _extract_json(npm_output)
return json_npm_output or npm_output
def _extract_json(npm_output):
lines = npm_output.splitlines()
log.error(lines)
# Strip all lines until JSON output starts
while lines and not lines[0].startswith('{') and not lines[0].startswith('['):
lines = lines[1:]
while lines and not lines[-1].startswith('}') and not lines[-1].startswith(']'):
lines = lines[:-1]
# macOS with fsevents includes the following line in the return
# when a new module is installed which is invalid JSON:
# [fsevents] Success: "..."
while lines and (lines[0].startswith('[fsevents]') or lines[0].startswith('Pass ')):
lines = lines[1:]
try:
return salt.utils.json.loads(''.join(lines))
except ValueError:
pass
return None
def uninstall(pkg, dir=None, runas=None, env=None): def uninstall(pkg, dir=None, runas=None, env=None):

View File

@ -25,13 +25,13 @@ def __virtual__():
''' '''
if salt.utils.platform.is_darwin() or salt.utils.platform.is_windows(): if salt.utils.platform.is_darwin() or salt.utils.platform.is_windows():
return True return True
return (False, 'Module proxy: module only works on Windows or MacOS systems') return False, 'Module proxy: module only works on Windows or MacOS systems'
def _get_proxy_osx(function, network_service): def _get_proxy_osx(cmd_function, network_service):
ret = {} ret = {}
out = __salt__['cmd.run']('networksetup -{0} {1}'.format(function, network_service)) out = __salt__['cmd.run']('networksetup -{0} {1}'.format(cmd_function, network_service))
match = re.match('Enabled: (.*)\nServer: (.*)\nPort: (.*)\n', out) match = re.match('Enabled: (.*)\nServer: (.*)\nPort: (.*)\n', out)
if match is not None: if match is not None:
g = match.groups() g = match.groups()
@ -41,8 +41,8 @@ def _get_proxy_osx(function, network_service):
return ret return ret
def _set_proxy_osx(function, server, port, user, password, network_service): def _set_proxy_osx(cmd_function, server, port, user, password, network_service):
cmd = 'networksetup -{0} {1} {2} {3}'.format(function, network_service, server, port) cmd = 'networksetup -{0} {1} {2} {3}'.format(cmd_function, network_service, server, port)
if user is not None and password is not None: if user is not None and password is not None:
cmd = cmd + ' On {0} {1}'.format(user, password) cmd = cmd + ' On {0} {1}'.format(user, password)
@ -58,12 +58,12 @@ def _get_proxy_windows(types=None):
if types is None: if types is None:
types = ['http', 'https', 'ftp'] types = ['http', 'https', 'ftp']
reg_val = __salt__['reg.read_value']('HKEY_CURRENT_USER', servers = __salt__['reg.read_value'](
r'SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings', hive='HKEY_CURRENT_USER',
'ProxyServer') key=r'SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings',
servers = reg_val['vdata'] vname='ProxyServer')['vdata']
if "=" in servers: if servers and "=" in servers:
split = servers.split(";") split = servers.split(";")
for s in split: for s in split:
if len(s) == 0: if len(s) == 0:
@ -87,16 +87,19 @@ def _get_proxy_windows(types=None):
del ret[key] del ret[key]
# Return enabled info # Return enabled info
reg_val = __salt__['reg.read_value']('HKEY_CURRENT_USER', ret['enabled'] = __salt__['reg.read_value'](
r'SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings', hive='HKEY_CURRENT_USER',
'ProxyEnable') key=r'SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings',
enabled = reg_val.get('vdata', 0) vname='ProxyEnable')['vdata'] == 1
ret['enabled'] = True if enabled == 1 else False
return ret return ret
def _set_proxy_windows(server, port, types=None, bypass_hosts=None, import_winhttp=True): def _set_proxy_windows(server,
port,
types=None,
bypass_hosts=None,
import_winhttp=True):
if types is None: if types is None:
types = ['http', 'https', 'ftp'] types = ['http', 'https', 'ftp']
@ -104,17 +107,27 @@ def _set_proxy_windows(server, port, types=None, bypass_hosts=None, import_winht
for t in types: for t in types:
server_str += '{0}={1}:{2};'.format(t, server, port) server_str += '{0}={1}:{2};'.format(t, server, port)
__salt__['reg.set_value']('HKEY_CURRENT_USER', r'SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings', __salt__['reg.set_value'](
'ProxyServer', server_str) hive='HKEY_CURRENT_USER',
key=r'SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings',
vname='ProxyServer',
vdata=server_str)
__salt__['reg.set_value']('HKEY_CURRENT_USER', r'SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings', __salt__['reg.set_value'](
'ProxyEnable', 1, vtype='REG_DWORD') hive='HKEY_CURRENT_USER',
key=r'SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings',
vname='ProxyEnable',
vdata=1,
vtype='REG_DWORD')
if bypass_hosts is not None: if bypass_hosts is not None:
bypass_hosts_str = '<local>;{0}'.format(';'.join(bypass_hosts)) bypass_hosts_str = '<local>;{0}'.format(';'.join(bypass_hosts))
__salt__['reg.set_value']('HKEY_CURRENT_USER', r'SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings', __salt__['reg.set_value'](
'ProxyOverride', bypass_hosts_str) hive='HKEY_CURRENT_USER',
key=r'SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings',
vname='ProxyOverride',
vdata=bypass_hosts_str)
if import_winhttp: if import_winhttp:
cmd = 'netsh winhttp import proxy source=ie' cmd = 'netsh winhttp import proxy source=ie'
@ -138,15 +151,22 @@ def get_http_proxy(network_service="Ethernet"):
salt '*' proxy.get_http_proxy Ethernet salt '*' proxy.get_http_proxy Ethernet
''' '''
if __grains__['os'] == 'Windows': if __grains__['os'] == 'Windows':
return _get_proxy_windows(['http']) return _get_proxy_windows(types=['http'])
return _get_proxy_osx("getwebproxy", network_service) return _get_proxy_osx(cmd_function="getwebproxy",
network_service=network_service)
def set_http_proxy(server, port, user=None, password=None, network_service="Ethernet", bypass_hosts=None): def set_http_proxy(server,
port,
user=None,
password=None,
network_service="Ethernet",
bypass_hosts=None):
''' '''
Sets the http proxy settings. Note: On Windows this will override any other proxy settings you have, Sets the http proxy settings. Note: On Windows this will override any other
the preferred method of updating proxies on windows is using set_proxy. proxy settings you have, the preferred method of updating proxies on windows
is using set_proxy.
server server
The proxy server to use The proxy server to use
@ -165,8 +185,8 @@ def set_http_proxy(server, port, user=None, password=None, network_service="Ethe
macOS macOS
bypass_hosts bypass_hosts
The hosts that are allowed to by pass the proxy. Only used on Windows for other OS's use The hosts that are allowed to by pass the proxy. Only used on Windows
set_proxy_bypass to edit the bypass hosts. for other OS's use set_proxy_bypass to edit the bypass hosts.
CLI Example: CLI Example:
@ -175,9 +195,17 @@ def set_http_proxy(server, port, user=None, password=None, network_service="Ethe
salt '*' proxy.set_http_proxy example.com 1080 user=proxy_user password=proxy_pass network_service=Ethernet salt '*' proxy.set_http_proxy example.com 1080 user=proxy_user password=proxy_pass network_service=Ethernet
''' '''
if __grains__['os'] == 'Windows': if __grains__['os'] == 'Windows':
return _set_proxy_windows(server, port, ['http'], bypass_hosts) return _set_proxy_windows(server=server,
port=port,
types=['http'],
bypass_hosts=bypass_hosts)
return _set_proxy_osx("setwebproxy", server, port, user, password, network_service) return _set_proxy_osx(cmd_function="setwebproxy",
server=server,
port=port,
user=user,
password=password,
network_service=network_service)
def get_https_proxy(network_service="Ethernet"): def get_https_proxy(network_service="Ethernet"):
@ -195,15 +223,22 @@ def get_https_proxy(network_service="Ethernet"):
salt '*' proxy.get_https_proxy Ethernet salt '*' proxy.get_https_proxy Ethernet
''' '''
if __grains__['os'] == 'Windows': if __grains__['os'] == 'Windows':
return _get_proxy_windows(['https']) return _get_proxy_windows(types=['https'])
return _get_proxy_osx("getsecurewebproxy", network_service) return _get_proxy_osx(cmd_function="getsecurewebproxy",
network_service=network_service)
def set_https_proxy(server, port, user=None, password=None, network_service="Ethernet", bypass_hosts=None): def set_https_proxy(server,
port,
user=None,
password=None,
network_service="Ethernet",
bypass_hosts=None):
''' '''
Sets the https proxy settings. Note: On Windows this will override any other proxy settings you have, Sets the https proxy settings. Note: On Windows this will override any other
the preferred method of updating proxies on windows is using set_proxy. proxy settings you have, the preferred method of updating proxies on windows
is using set_proxy.
server server
The proxy server to use The proxy server to use
@ -222,8 +257,8 @@ def set_https_proxy(server, port, user=None, password=None, network_service="Eth
macOS macOS
bypass_hosts bypass_hosts
The hosts that are allowed to by pass the proxy. Only used on Windows for other OS's use The hosts that are allowed to by pass the proxy. Only used on Windows
set_proxy_bypass to edit the bypass hosts. for other OS's use set_proxy_bypass to edit the bypass hosts.
CLI Example: CLI Example:
@ -232,9 +267,17 @@ def set_https_proxy(server, port, user=None, password=None, network_service="Eth
salt '*' proxy.set_https_proxy example.com 1080 user=proxy_user password=proxy_pass network_service=Ethernet salt '*' proxy.set_https_proxy example.com 1080 user=proxy_user password=proxy_pass network_service=Ethernet
''' '''
if __grains__['os'] == 'Windows': if __grains__['os'] == 'Windows':
return _set_proxy_windows(server, port, ['https'], bypass_hosts) return _set_proxy_windows(server=server,
port=port,
types=['https'],
bypass_hosts=bypass_hosts)
return _set_proxy_osx("setsecurewebproxy", server, port, user, password, network_service) return _set_proxy_osx(cmd_function="setsecurewebproxy",
server=server,
port=port,
user=user,
password=password,
network_service=network_service)
def get_ftp_proxy(network_service="Ethernet"): def get_ftp_proxy(network_service="Ethernet"):
@ -252,12 +295,18 @@ def get_ftp_proxy(network_service="Ethernet"):
salt '*' proxy.get_ftp_proxy Ethernet salt '*' proxy.get_ftp_proxy Ethernet
''' '''
if __grains__['os'] == 'Windows': if __grains__['os'] == 'Windows':
return _get_proxy_windows(['ftp']) return _get_proxy_windows(types=['ftp'])
return _get_proxy_osx("getftpproxy", network_service) return _get_proxy_osx(cmd_function="getftpproxy",
network_service=network_service)
def set_ftp_proxy(server, port, user=None, password=None, network_service="Ethernet", bypass_hosts=None): def set_ftp_proxy(server,
port,
user=None,
password=None,
network_service="Ethernet",
bypass_hosts=None):
''' '''
Sets the ftp proxy settings Sets the ftp proxy settings
@ -278,8 +327,8 @@ def set_ftp_proxy(server, port, user=None, password=None, network_service="Ether
macOS macOS
bypass_hosts bypass_hosts
The hosts that are allowed to by pass the proxy. Only used on Windows for other OS's use The hosts that are allowed to by pass the proxy. Only used on Windows
set_proxy_bypass to edit the bypass hosts. for other OS's use set_proxy_bypass to edit the bypass hosts.
CLI Example: CLI Example:
@ -288,9 +337,17 @@ def set_ftp_proxy(server, port, user=None, password=None, network_service="Ether
salt '*' proxy.set_ftp_proxy example.com 1080 user=proxy_user password=proxy_pass network_service=Ethernet salt '*' proxy.set_ftp_proxy example.com 1080 user=proxy_user password=proxy_pass network_service=Ethernet
''' '''
if __grains__['os'] == 'Windows': if __grains__['os'] == 'Windows':
return _set_proxy_windows(server, port, ['ftp'], bypass_hosts) return _set_proxy_windows(server=server,
port=port,
types=['ftp'],
bypass_hosts=bypass_hosts)
return _set_proxy_osx("setftpproxy", server, port, user, password, network_service) return _set_proxy_osx(cmd_function="setftpproxy",
server=server,
port=port,
user=user,
password=password,
network_service=network_service)
def get_proxy_bypass(network_service="Ethernet"): def get_proxy_bypass(network_service="Ethernet"):
@ -309,12 +366,16 @@ def get_proxy_bypass(network_service="Ethernet"):
''' '''
if __grains__['os'] == 'Windows': if __grains__['os'] == 'Windows':
reg_val = __salt__['reg.read_value']('HKEY_CURRENT_USER', reg_val = __salt__['reg.read_value'](
r'SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings', hive='HKEY_CURRENT_USER',
'ProxyOverride') key=r'SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings',
bypass_servers = reg_val['vdata'].replace("<local>", "").split(";") vname='ProxyOverride')['vdata']
return bypass_servers # `reg.read_value` returns None if the key doesn't exist
if reg_val is None:
return []
return reg_val.replace('<local>', '').split(';')
out = __salt__['cmd.run']('networksetup -getproxybypassdomains {0}'.format(network_service)) out = __salt__['cmd.run']('networksetup -getproxybypassdomains {0}'.format(network_service))
@ -357,7 +418,12 @@ def set_proxy_win(server, port, types=None, bypass_hosts=None):
The password to use if required by the server The password to use if required by the server
types types
The types of proxy connections should be setup with this server. Valid types are http and https. The types of proxy connections should be setup with this server. Valid
types are:
- ``http``
- ``https``
- ``ftp``
bypass_hosts bypass_hosts
The hosts that are allowed to by pass the proxy. The hosts that are allowed to by pass the proxy.
@ -369,7 +435,10 @@ def set_proxy_win(server, port, types=None, bypass_hosts=None):
salt '*' proxy.set_http_proxy example.com 1080 types="['http', 'https']" salt '*' proxy.set_http_proxy example.com 1080 types="['http', 'https']"
''' '''
if __grains__['os'] == 'Windows': if __grains__['os'] == 'Windows':
return _set_proxy_windows(server, port, types, bypass_hosts) return _set_proxy_windows(server=server,
port=port,
types=types,
bypass_hosts=bypass_hosts)
def get_proxy_win(): def get_proxy_win():

View File

@ -225,6 +225,7 @@ def list_users(runas=None):
runas = salt.utils.user.get_user() runas = salt.utils.user.get_user()
res = __salt__['cmd.run_all']( res = __salt__['cmd.run_all'](
[RABBITMQCTL, 'list_users', '-q'], [RABBITMQCTL, 'list_users', '-q'],
reset_system_locale=False,
runas=runas, runas=runas,
python_shell=False) python_shell=False)
@ -248,6 +249,7 @@ def list_vhosts(runas=None):
runas = salt.utils.user.get_user() runas = salt.utils.user.get_user()
res = __salt__['cmd.run_all']( res = __salt__['cmd.run_all'](
[RABBITMQCTL, 'list_vhosts', '-q'], [RABBITMQCTL, 'list_vhosts', '-q'],
reset_system_locale=False,
runas=runas, runas=runas,
python_shell=False) python_shell=False)
_check_response(res) _check_response(res)
@ -322,6 +324,7 @@ def add_user(name, password=None, runas=None):
res = __salt__['cmd.run_all']( res = __salt__['cmd.run_all'](
cmd, cmd,
reset_system_locale=False,
output_loglevel='quiet', output_loglevel='quiet',
runas=runas, runas=runas,
python_shell=python_shell) python_shell=python_shell)
@ -354,6 +357,7 @@ def delete_user(name, runas=None):
runas = salt.utils.user.get_user() runas = salt.utils.user.get_user()
res = __salt__['cmd.run_all']( res = __salt__['cmd.run_all'](
[RABBITMQCTL, 'delete_user', name], [RABBITMQCTL, 'delete_user', name],
reset_system_locale=False,
python_shell=False, python_shell=False,
runas=runas) runas=runas)
msg = 'Deleted' msg = 'Deleted'
@ -389,6 +393,7 @@ def change_password(name, password, runas=None):
cmd = [RABBITMQCTL, 'change_password', name, password] cmd = [RABBITMQCTL, 'change_password', name, password]
res = __salt__['cmd.run_all']( res = __salt__['cmd.run_all'](
cmd, cmd,
reset_system_locale=False,
runas=runas, runas=runas,
output_loglevel='quiet', output_loglevel='quiet',
python_shell=python_shell) python_shell=python_shell)
@ -411,6 +416,7 @@ def clear_password(name, runas=None):
runas = salt.utils.user.get_user() runas = salt.utils.user.get_user()
res = __salt__['cmd.run_all']( res = __salt__['cmd.run_all'](
[RABBITMQCTL, 'clear_password', name], [RABBITMQCTL, 'clear_password', name],
reset_system_locale=False,
runas=runas, runas=runas,
python_shell=False) python_shell=False)
msg = 'Password Cleared' msg = 'Password Cleared'
@ -436,7 +442,7 @@ def check_password(name, password, runas=None):
runas = salt.utils.user.get_user() runas = salt.utils.user.get_user()
try: try:
res = __salt__['cmd.run']([RABBITMQCTL, 'status'], runas=runas, python_shell=False) res = __salt__['cmd.run']([RABBITMQCTL, 'status'], reset_system_locale=False, runas=runas, python_shell=False)
server_version = re.search(r'\{rabbit,"RabbitMQ","(.+)"\}', res) server_version = re.search(r'\{rabbit,"RabbitMQ","(.+)"\}', res)
if server_version is None: if server_version is None:
@ -468,6 +474,7 @@ def check_password(name, password, runas=None):
res = __salt__['cmd.run_all']( res = __salt__['cmd.run_all'](
cmd, cmd,
reset_system_locale=False,
runas=runas, runas=runas,
output_loglevel='quiet', output_loglevel='quiet',
python_shell=python_shell) python_shell=python_shell)
@ -483,6 +490,7 @@ def check_password(name, password, runas=None):
res = __salt__['cmd.run_all']( res = __salt__['cmd.run_all'](
[RABBITMQCTL, 'eval', cmd], [RABBITMQCTL, 'eval', cmd],
reset_system_locale=False,
runas=runas, runas=runas,
output_loglevel='quiet', output_loglevel='quiet',
python_shell=False) python_shell=False)
@ -511,6 +519,7 @@ def add_vhost(vhost, runas=None):
runas = salt.utils.user.get_user() runas = salt.utils.user.get_user()
res = __salt__['cmd.run_all']( res = __salt__['cmd.run_all'](
[RABBITMQCTL, 'add_vhost', vhost], [RABBITMQCTL, 'add_vhost', vhost],
reset_system_locale=False,
runas=runas, runas=runas,
python_shell=False) python_shell=False)
@ -532,6 +541,7 @@ def delete_vhost(vhost, runas=None):
runas = salt.utils.user.get_user() runas = salt.utils.user.get_user()
res = __salt__['cmd.run_all']( res = __salt__['cmd.run_all'](
[RABBITMQCTL, 'delete_vhost', vhost], [RABBITMQCTL, 'delete_vhost', vhost],
reset_system_locale=False,
runas=runas, runas=runas,
python_shell=False) python_shell=False)
msg = 'Deleted' msg = 'Deleted'
@ -553,6 +563,7 @@ def set_permissions(vhost, user, conf='.*', write='.*', read='.*', runas=None):
res = __salt__['cmd.run_all']( res = __salt__['cmd.run_all'](
[RABBITMQCTL, 'set_permissions', '-p', [RABBITMQCTL, 'set_permissions', '-p',
vhost, user, conf, write, read], vhost, user, conf, write, read],
reset_system_locale=False,
runas=runas, runas=runas,
python_shell=False) python_shell=False)
msg = 'Permissions Set' msg = 'Permissions Set'
@ -573,6 +584,7 @@ def list_permissions(vhost, runas=None):
runas = salt.utils.user.get_user() runas = salt.utils.user.get_user()
res = __salt__['cmd.run_all']( res = __salt__['cmd.run_all'](
[RABBITMQCTL, 'list_permissions', '-q', '-p', vhost], [RABBITMQCTL, 'list_permissions', '-q', '-p', vhost],
reset_system_locale=False,
runas=runas, runas=runas,
python_shell=False) python_shell=False)
@ -593,6 +605,7 @@ def list_user_permissions(name, runas=None):
runas = salt.utils.user.get_user() runas = salt.utils.user.get_user()
res = __salt__['cmd.run_all']( res = __salt__['cmd.run_all'](
[RABBITMQCTL, 'list_user_permissions', name, '-q'], [RABBITMQCTL, 'list_user_permissions', name, '-q'],
reset_system_locale=False,
runas=runas, runas=runas,
python_shell=False) python_shell=False)
@ -616,6 +629,7 @@ def set_user_tags(name, tags, runas=None):
res = __salt__['cmd.run_all']( res = __salt__['cmd.run_all'](
[RABBITMQCTL, 'set_user_tags', name] + list(tags), [RABBITMQCTL, 'set_user_tags', name] + list(tags),
reset_system_locale=False,
runas=runas, runas=runas,
python_shell=False) python_shell=False)
msg = "Tag(s) set" msg = "Tag(s) set"
@ -636,6 +650,7 @@ def status(runas=None):
runas = salt.utils.user.get_user() runas = salt.utils.user.get_user()
res = __salt__['cmd.run_all']( res = __salt__['cmd.run_all'](
[RABBITMQCTL, 'status'], [RABBITMQCTL, 'status'],
reset_system_locale=False,
runas=runas, runas=runas,
python_shell=False) python_shell=False)
_check_response(res) _check_response(res)
@ -656,6 +671,7 @@ def cluster_status(runas=None):
runas = salt.utils.user.get_user() runas = salt.utils.user.get_user()
res = __salt__['cmd.run_all']( res = __salt__['cmd.run_all'](
[RABBITMQCTL, 'cluster_status'], [RABBITMQCTL, 'cluster_status'],
reset_system_locale=False,
runas=runas, runas=runas,
python_shell=False) python_shell=False)
_check_response(res) _check_response(res)
@ -680,7 +696,7 @@ def join_cluster(host, user='rabbit', ram_node=None, runas=None):
if runas is None and not salt.utils.platform.is_windows(): if runas is None and not salt.utils.platform.is_windows():
runas = salt.utils.user.get_user() runas = salt.utils.user.get_user()
stop_app(runas) stop_app(runas)
res = __salt__['cmd.run_all'](cmd, runas=runas, python_shell=False) res = __salt__['cmd.run_all'](cmd, reset_system_locale=False, runas=runas, python_shell=False)
start_app(runas) start_app(runas)
return _format_response(res, 'Join') return _format_response(res, 'Join')
@ -700,6 +716,7 @@ def stop_app(runas=None):
runas = salt.utils.user.get_user() runas = salt.utils.user.get_user()
res = __salt__['cmd.run_all']( res = __salt__['cmd.run_all'](
[RABBITMQCTL, 'stop_app'], [RABBITMQCTL, 'stop_app'],
reset_system_locale=False,
runas=runas, runas=runas,
python_shell=False) python_shell=False)
_check_response(res) _check_response(res)
@ -720,6 +737,7 @@ def start_app(runas=None):
runas = salt.utils.user.get_user() runas = salt.utils.user.get_user()
res = __salt__['cmd.run_all']( res = __salt__['cmd.run_all'](
[RABBITMQCTL, 'start_app'], [RABBITMQCTL, 'start_app'],
reset_system_locale=False,
runas=runas, runas=runas,
python_shell=False) python_shell=False)
_check_response(res) _check_response(res)
@ -740,6 +758,7 @@ def reset(runas=None):
runas = salt.utils.user.get_user() runas = salt.utils.user.get_user()
res = __salt__['cmd.run_all']( res = __salt__['cmd.run_all'](
[RABBITMQCTL, 'reset'], [RABBITMQCTL, 'reset'],
reset_system_locale=False,
runas=runas, runas=runas,
python_shell=False) python_shell=False)
_check_response(res) _check_response(res)
@ -760,6 +779,7 @@ def force_reset(runas=None):
runas = salt.utils.user.get_user() runas = salt.utils.user.get_user()
res = __salt__['cmd.run_all']( res = __salt__['cmd.run_all'](
[RABBITMQCTL, 'force_reset'], [RABBITMQCTL, 'force_reset'],
reset_system_locale=False,
runas=runas, runas=runas,
python_shell=False) python_shell=False)
_check_response(res) _check_response(res)
@ -780,7 +800,7 @@ def list_queues(runas=None, *args):
runas = salt.utils.user.get_user() runas = salt.utils.user.get_user()
cmd = [RABBITMQCTL, 'list_queues', '-q'] cmd = [RABBITMQCTL, 'list_queues', '-q']
cmd.extend(args) cmd.extend(args)
res = __salt__['cmd.run_all'](cmd, runas=runas, python_shell=False) res = __salt__['cmd.run_all'](cmd, reset_system_locale=False, runas=runas, python_shell=False)
_check_response(res) _check_response(res)
return _output_to_dict(res['stdout']) return _output_to_dict(res['stdout'])
@ -802,7 +822,7 @@ def list_queues_vhost(vhost, runas=None, *args):
runas = salt.utils.user.get_user() runas = salt.utils.user.get_user()
cmd = [RABBITMQCTL, 'list_queues', '-q', '-p', vhost] cmd = [RABBITMQCTL, 'list_queues', '-q', '-p', vhost]
cmd.extend(args) cmd.extend(args)
res = __salt__['cmd.run_all'](cmd, runas=runas, python_shell=False) res = __salt__['cmd.run_all'](cmd, reset_system_locale=False, runas=runas, python_shell=False)
_check_response(res) _check_response(res)
return _output_to_dict(res['stdout']) return _output_to_dict(res['stdout'])
@ -825,6 +845,7 @@ def list_policies(vhost="/", runas=None):
runas = salt.utils.user.get_user() runas = salt.utils.user.get_user()
res = __salt__['cmd.run_all']( res = __salt__['cmd.run_all'](
[RABBITMQCTL, 'list_policies', '-q', '-p', vhost], [RABBITMQCTL, 'list_policies', '-q', '-p', vhost],
reset_system_locale=False,
runas=runas, runas=runas,
python_shell=False) python_shell=False)
_check_response(res) _check_response(res)
@ -902,7 +923,7 @@ def set_policy(vhost,
if apply_to: if apply_to:
cmd.extend(['--apply-to', apply_to]) cmd.extend(['--apply-to', apply_to])
cmd.extend([name, pattern, definition]) cmd.extend([name, pattern, definition])
res = __salt__['cmd.run_all'](cmd, runas=runas, python_shell=False) res = __salt__['cmd.run_all'](cmd, reset_system_locale=False, runas=runas, python_shell=False)
log.debug('Set policy: %s', res['stdout']) log.debug('Set policy: %s', res['stdout'])
return _format_response(res, 'Set') return _format_response(res, 'Set')
@ -923,6 +944,7 @@ def delete_policy(vhost, name, runas=None):
runas = salt.utils.user.get_user() runas = salt.utils.user.get_user()
res = __salt__['cmd.run_all']( res = __salt__['cmd.run_all'](
[RABBITMQCTL, 'clear_policy', '-p', vhost, name], [RABBITMQCTL, 'clear_policy', '-p', vhost, name],
reset_system_locale=False,
runas=runas, runas=runas,
python_shell=False) python_shell=False)
log.debug('Delete policy: %s', res['stdout']) log.debug('Delete policy: %s', res['stdout'])
@ -960,7 +982,7 @@ def list_available_plugins(runas=None):
if runas is None and not salt.utils.platform.is_windows(): if runas is None and not salt.utils.platform.is_windows():
runas = salt.utils.user.get_user() runas = salt.utils.user.get_user()
cmd = [_get_rabbitmq_plugin(), 'list', '-m'] cmd = [_get_rabbitmq_plugin(), 'list', '-m']
ret = __salt__['cmd.run_all'](cmd, python_shell=False, runas=runas) ret = __salt__['cmd.run_all'](cmd, reset_system_locale=False, python_shell=False, runas=runas)
_check_response(ret) _check_response(ret)
return _output_to_list(ret['stdout']) return _output_to_list(ret['stdout'])
@ -978,7 +1000,7 @@ def list_enabled_plugins(runas=None):
if runas is None and not salt.utils.platform.is_windows(): if runas is None and not salt.utils.platform.is_windows():
runas = salt.utils.user.get_user() runas = salt.utils.user.get_user()
cmd = [_get_rabbitmq_plugin(), 'list', '-m', '-e'] cmd = [_get_rabbitmq_plugin(), 'list', '-m', '-e']
ret = __salt__['cmd.run_all'](cmd, python_shell=False, runas=runas) ret = __salt__['cmd.run_all'](cmd, reset_system_locale=False, python_shell=False, runas=runas)
_check_response(ret) _check_response(ret)
return _output_to_list(ret['stdout']) return _output_to_list(ret['stdout'])
@ -1011,7 +1033,7 @@ def enable_plugin(name, runas=None):
if runas is None and not salt.utils.platform.is_windows(): if runas is None and not salt.utils.platform.is_windows():
runas = salt.utils.user.get_user() runas = salt.utils.user.get_user()
cmd = [_get_rabbitmq_plugin(), 'enable', name] cmd = [_get_rabbitmq_plugin(), 'enable', name]
ret = __salt__['cmd.run_all'](cmd, runas=runas, python_shell=False) ret = __salt__['cmd.run_all'](cmd, reset_system_locale=False, runas=runas, python_shell=False)
return _format_response(ret, 'Enabled') return _format_response(ret, 'Enabled')
@ -1028,5 +1050,5 @@ def disable_plugin(name, runas=None):
if runas is None and not salt.utils.platform.is_windows(): if runas is None and not salt.utils.platform.is_windows():
runas = salt.utils.user.get_user() runas = salt.utils.user.get_user()
cmd = [_get_rabbitmq_plugin(), 'disable', name] cmd = [_get_rabbitmq_plugin(), 'disable', name]
ret = __salt__['cmd.run_all'](cmd, runas=runas, python_shell=False) ret = __salt__['cmd.run_all'](cmd, reset_system_locale=False, runas=runas, python_shell=False)
return _format_response(ret, 'Disabled') return _format_response(ret, 'Disabled')

View File

@ -19,6 +19,7 @@ Module to provide redis functionality to Salt
from __future__ import absolute_import, unicode_literals, print_function from __future__ import absolute_import, unicode_literals, print_function
from salt.ext.six.moves import zip from salt.ext.six.moves import zip
from salt.ext import six from salt.ext import six
from salt.utils import clean_kwargs
from datetime import datetime from datetime import datetime
# Import third party libs # Import third party libs
@ -395,7 +396,7 @@ def hmset(key, **fieldsvals):
database = fieldsvals.pop('db', None) database = fieldsvals.pop('db', None)
password = fieldsvals.pop('password', None) password = fieldsvals.pop('password', None)
server = _connect(host, port, database, password) server = _connect(host, port, database, password)
return server.hmset(key, **fieldsvals) return server.hmset(key, clean_kwargs(**fieldsvals))
def hset(key, field, value, host=None, port=None, db=None, password=None): def hset(key, field, value, host=None, port=None, db=None, password=None):

View File

@ -37,6 +37,7 @@ def __virtual__():
'Devuan', 'Devuan',
'Arch', 'Arch',
'Arch ARM', 'Arch ARM',
'Manjaro',
'ALT', 'ALT',
'SUSE Enterprise Server', 'SUSE Enterprise Server',
'SUSE', 'SUSE',

View File

@ -137,13 +137,15 @@ def renderer(path=None, string=None, default_renderer='jinja|yaml', **kwargs):
path_or_string = ':string:' path_or_string = ':string:'
kwargs['input_data'] = string kwargs['input_data'] = string
return salt.template.compile_template( ret = salt.template.compile_template(
path_or_string, path_or_string,
renderers, renderers,
default_renderer, default_renderer,
__opts__['renderer_blacklist'], __opts__['renderer_blacklist'],
__opts__['renderer_whitelist'], __opts__['renderer_whitelist'],
**kwargs) **kwargs
)
return ret.read() if __utils__['stringio.is_readable'](ret) else ret
def _get_serialize_fn(serializer, fn_name): def _get_serialize_fn(serializer, fn_name):

View File

@ -29,18 +29,13 @@ from salt.ext.six.moves import zip # pylint: disable=import-error,redefined-bui
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
DMIDECODER = salt.utils.path.which_bin(['dmidecode', 'smbios'])
def __virtual__(): def __virtual__():
''' '''
Only work when dmidecode is installed. Only work when dmidecode is installed.
''' '''
if DMIDECODER is None: return (bool(salt.utils.path.which_bin(['dmidecode', 'smbios'])),
log.debug('SMBIOS: neither dmidecode nor smbios found!') 'The smbios execution module failed to load: neither dmidecode nor smbios in the path.')
return (False, 'The smbios execution module failed to load: neither dmidecode nor smbios in the path.')
else:
return True
def get(string, clean=True): def get(string, clean=True):
@ -86,15 +81,11 @@ def get(string, clean=True):
val = _dmidecoder('-s {0}'.format(string)).strip() val = _dmidecoder('-s {0}'.format(string)).strip()
# Sometimes dmidecode delivers comments in strings. # Cleanup possible comments in strings.
# Don't.
val = '\n'.join([v for v in val.split('\n') if not v.startswith('#')]) val = '\n'.join([v for v in val.split('\n') if not v.startswith('#')])
if val.startswith('/dev/mem') or clean and not _dmi_isclean(string, val):
val = None
# handle missing /dev/mem
if val.startswith('/dev/mem'):
return None
if not clean or _dmi_isclean(string, val):
return val return val
@ -327,7 +318,11 @@ def _dmidecoder(args=None):
''' '''
Call DMIdecode Call DMIdecode
''' '''
if args is None: dmidecoder = salt.utils.path.which_bin(['dmidecode', 'smbios'])
return salt.modules.cmdmod._run_quiet(DMIDECODER)
if not args:
out = salt.modules.cmdmod._run_quiet(dmidecoder)
else: else:
return salt.modules.cmdmod._run_quiet('{0} {1}'.format(DMIDECODER, args)) out = salt.modules.cmdmod._run_quiet('{0} {1}'.format(dmidecoder, args))
return out

View File

@ -602,8 +602,7 @@ def template_str(tem, queue=False, **kwargs):
return ret return ret
def apply_(mods=None, def apply_(mods=None, **kwargs):
**kwargs):
''' '''
.. versionadded:: 2015.5.0 .. versionadded:: 2015.5.0
@ -743,6 +742,22 @@ def apply_(mods=None,
.. code-block:: bash .. code-block:: bash
salt '*' state.apply test localconfig=/path/to/minion.yml salt '*' state.apply test localconfig=/path/to/minion.yml
sync_mods
If specified, the desired custom module types will be synced prior to
running the SLS files:
.. code-block:: bash
salt '*' state.apply test sync_mods=states,modules
salt '*' state.apply test sync_mods=all
.. note::
This option is ignored when no SLS files are specified, as a
:ref:`highstate <running-highstate>` automatically syncs all custom
module types.
.. versionadded:: 2017.7.8,2018.3.3,Fluorine
''' '''
if mods: if mods:
return sls(mods, **kwargs) return sls(mods, **kwargs)
@ -1068,7 +1083,7 @@ def highstate(test=None, queue=False, **kwargs):
return ret return ret
def sls(mods, test=None, exclude=None, queue=False, **kwargs): def sls(mods, test=None, exclude=None, queue=False, sync_mods=None, **kwargs):
''' '''
Execute the states in one or more SLS files Execute the states in one or more SLS files
@ -1160,6 +1175,17 @@ def sls(mods, test=None, exclude=None, queue=False, **kwargs):
.. versionadded:: 2015.8.4 .. versionadded:: 2015.8.4
sync_mods
If specified, the desired custom module types will be synced prior to
running the SLS files:
.. code-block:: bash
salt '*' state.sls test sync_mods=states,modules
salt '*' state.sls test sync_mods=all
.. versionadded:: 2017.7.8,2018.3.3,Fluorine
CLI Example: CLI Example:
.. code-block:: bash .. code-block:: bash
@ -1223,6 +1249,28 @@ def sls(mods, test=None, exclude=None, queue=False, **kwargs):
'{0}.cache.p'.format(kwargs.get('cache_name', 'highstate')) '{0}.cache.p'.format(kwargs.get('cache_name', 'highstate'))
) )
if sync_mods is True:
sync_mods = ['all']
if sync_mods is not None:
sync_mods = salt.utils.args.split_input(sync_mods)
else:
sync_mods = []
if 'all' in sync_mods and sync_mods != ['all']:
# Prevent unnecessary extra syncing
sync_mods = ['all']
for module_type in sync_mods:
try:
__salt__['saltutil.sync_{0}'.format(module_type)](
saltenv=opts['saltenv']
)
except KeyError:
log.warning(
'Invalid custom module type \'%s\', ignoring',
module_type
)
try: try:
st_ = salt.state.HighState(opts, st_ = salt.state.HighState(opts,
pillar_override, pillar_override,
@ -1253,6 +1301,10 @@ def sls(mods, test=None, exclude=None, queue=False, **kwargs):
high_ = serial.load(fp_) high_ = serial.load(fp_)
return st_.state.call_high(high_, orchestration_jid) return st_.state.call_high(high_, orchestration_jid)
# If the state file is an integer, convert to a string then to unicode
if isinstance(mods, six.integer_types):
mods = salt.utils.stringutils.to_unicode(str(mods)) # future lint: disable=blacklisted-function
if isinstance(mods, six.string_types): if isinstance(mods, six.string_types):
mods = mods.split(',') mods = mods.split(',')

View File

@ -2010,7 +2010,7 @@ def snapshot(domain, name=None, suffix=None):
n_name = ElementTree.SubElement(doc, 'name') n_name = ElementTree.SubElement(doc, 'name')
n_name.text = name n_name.text = name
_get_domain(domain).snapshotCreateXML(ElementTree.tostring(doc)) _get_domain(domain).snapshotCreateXML(salt.utils.stringutils.to_str(ElementTree.tostring(doc)))
return {'name': name} return {'name': name}

View File

@ -58,8 +58,9 @@ from salt.modules.file import (check_hash, # pylint: disable=W0611
RE_FLAG_TABLE, blockreplace, prepend, seek_read, seek_write, rename, RE_FLAG_TABLE, blockreplace, prepend, seek_read, seek_write, rename,
lstat, path_exists_glob, write, pardir, join, HASHES, HASHES_REVMAP, lstat, path_exists_glob, write, pardir, join, HASHES, HASHES_REVMAP,
comment, uncomment, _add_flags, comment_line, _regex_to_static, comment, uncomment, _add_flags, comment_line, _regex_to_static,
_get_line_indent, apply_template_on_contents, dirname, basename, _set_line_indent, apply_template_on_contents, dirname, basename,
list_backups_dir, _assert_occurrence, _starts_till) list_backups_dir, _assert_occurrence, _starts_till, _set_line_eol, _get_eol,
_insert_line_after, _insert_line_before)
from salt.modules.file import normpath as normpath_ from salt.modules.file import normpath as normpath_
from salt.utils.functools import namespaced_function as _namespaced_function from salt.utils.functools import namespaced_function as _namespaced_function
@ -116,8 +117,9 @@ def __virtual__():
global blockreplace, prepend, seek_read, seek_write, rename, lstat global blockreplace, prepend, seek_read, seek_write, rename, lstat
global write, pardir, join, _add_flags, apply_template_on_contents global write, pardir, join, _add_flags, apply_template_on_contents
global path_exists_glob, comment, uncomment, _mkstemp_copy global path_exists_glob, comment, uncomment, _mkstemp_copy
global _regex_to_static, _get_line_indent, dirname, basename global _regex_to_static, _set_line_indent, dirname, basename
global list_backups_dir, normpath_, _assert_occurrence, _starts_till global list_backups_dir, normpath_, _assert_occurrence, _starts_till
global _insert_line_before, _insert_line_after, _set_line_eol, _get_eol
replace = _namespaced_function(replace, globals()) replace = _namespaced_function(replace, globals())
search = _namespaced_function(search, globals()) search = _namespaced_function(search, globals())
@ -173,7 +175,11 @@ def __virtual__():
uncomment = _namespaced_function(uncomment, globals()) uncomment = _namespaced_function(uncomment, globals())
comment_line = _namespaced_function(comment_line, globals()) comment_line = _namespaced_function(comment_line, globals())
_regex_to_static = _namespaced_function(_regex_to_static, globals()) _regex_to_static = _namespaced_function(_regex_to_static, globals())
_get_line_indent = _namespaced_function(_get_line_indent, globals()) _set_line_indent = _namespaced_function(_set_line_indent, globals())
_set_line_eol = _namespaced_function(_set_line_eol, globals())
_get_eol = _namespaced_function(_get_eol, globals())
_insert_line_after = _namespaced_function(_insert_line_after, globals())
_insert_line_before = _namespaced_function(_insert_line_before, globals())
_mkstemp_copy = _namespaced_function(_mkstemp_copy, globals()) _mkstemp_copy = _namespaced_function(_mkstemp_copy, globals())
_add_flags = _namespaced_function(_add_flags, globals()) _add_flags = _namespaced_function(_add_flags, globals())
apply_template_on_contents = _namespaced_function(apply_template_on_contents, globals()) apply_template_on_contents = _namespaced_function(apply_template_on_contents, globals())

View File

@ -2588,7 +2588,9 @@ class _policy_info(object):
userSid = '{1}\\{0}'.format(userSid[0], userSid[1]) userSid = '{1}\\{0}'.format(userSid[0], userSid[1])
else: else:
userSid = '{0}'.format(userSid[0]) userSid = '{0}'.format(userSid[0])
# TODO: This needs to be more specific
except Exception: except Exception:
log.exception('Handle this explicitly')
userSid = win32security.ConvertSidToStringSid(_sid) userSid = win32security.ConvertSidToStringSid(_sid)
usernames.append(userSid) usernames.append(userSid)
return usernames return usernames
@ -2607,7 +2609,9 @@ class _policy_info(object):
try: try:
sid = win32security.LookupAccountName('', _user)[0] sid = win32security.LookupAccountName('', _user)[0]
sids.append(sid) sids.append(sid)
# This needs to be more specific
except Exception as e: except Exception as e:
log.exception('Handle this explicitly')
raise CommandExecutionError(( raise CommandExecutionError((
'There was an error obtaining the SID of user "{0}". Error ' 'There was an error obtaining the SID of user "{0}". Error '
'returned: {1}' 'returned: {1}'
@ -2760,7 +2764,9 @@ def _processPolicyDefinitions(policy_def_path='c:\\Windows\\PolicyDefinitions',
except lxml.etree.XMLSyntaxError: except lxml.etree.XMLSyntaxError:
try: try:
xmltree = _remove_unicode_encoding(admfile) xmltree = _remove_unicode_encoding(admfile)
# TODO: This needs to be more specific
except Exception: except Exception:
log.exception('Handle this explicitly')
log.error('A error was found while processing admx ' log.error('A error was found while processing admx '
'file %s, all policies from this file will ' 'file %s, all policies from this file will '
'be unavailable via this module', admfile) 'be unavailable via this module', admfile)
@ -2845,7 +2851,9 @@ def _processPolicyDefinitions(policy_def_path='c:\\Windows\\PolicyDefinitions',
# see issue #38100 # see issue #38100
try: try:
xmltree = _remove_unicode_encoding(adml_file) xmltree = _remove_unicode_encoding(adml_file)
# TODO: This needs to be more specific
except Exception: except Exception:
log.exception('Handle this explicitly')
log.error('An error was found while processing ' log.error('An error was found while processing '
'adml file %s, all policy ' 'adml file %s, all policy '
'language data from this file will be ' 'language data from this file will be '
@ -2901,8 +2909,9 @@ def _findOptionValueInSeceditFile(option):
if _line.startswith(option): if _line.startswith(option):
return True, _line.split('=')[1].strip() return True, _line.split('=')[1].strip()
return True, 'Not Defined' return True, 'Not Defined'
except Exception as e: # TODO: This needs to be more specific
log.debug('error occurred while trying to get secedit data') except Exception:
log.exception('error occurred while trying to get secedit data')
return False, None return False, None
@ -2932,8 +2941,9 @@ def _importSeceditConfig(infdata):
if __salt__['file.file_exists'](_tInfFile): if __salt__['file.file_exists'](_tInfFile):
_ret = __salt__['file.remove'](_tInfFile) _ret = __salt__['file.remove'](_tInfFile)
return True return True
# TODO: This needs to be more specific
except Exception as e: except Exception as e:
log.debug('error occurred while trying to import secedit data') log.exception('error occurred while trying to import secedit data')
return False return False
@ -2995,8 +3005,9 @@ def _addAccountRights(sidObject, user_right):
user_rights_list = [user_right] user_rights_list = [user_right]
_ret = win32security.LsaAddAccountRights(_polHandle, sidObject, user_rights_list) _ret = win32security.LsaAddAccountRights(_polHandle, sidObject, user_rights_list)
return True return True
# TODO: This needs to be more specific
except Exception as e: except Exception as e:
log.error('Error attempting to add account right, exception was %s', log.exception('Error attempting to add account right, exception was %s',
e) e)
return False return False
@ -3011,8 +3022,7 @@ def _delAccountRights(sidObject, user_right):
_ret = win32security.LsaRemoveAccountRights(_polHandle, sidObject, False, user_rights_list) _ret = win32security.LsaRemoveAccountRights(_polHandle, sidObject, False, user_rights_list)
return True return True
except Exception as e: except Exception as e:
log.error('Error attempting to delete account right, ' log.exception('Error attempting to delete account right')
'exception was %s', e)
return False return False
@ -4180,7 +4190,7 @@ def _write_regpol_data(data_to_write,
try: try:
reg_pol_header = u'\u5250\u6765\x01\x00' reg_pol_header = u'\u5250\u6765\x01\x00'
if not os.path.exists(policy_file_path): if not os.path.exists(policy_file_path):
ret = __salt__['file.makedirs'](policy_file_path) __salt__['file.makedirs'](policy_file_path)
with salt.utils.files.fopen(policy_file_path, 'wb') as pol_file: with salt.utils.files.fopen(policy_file_path, 'wb') as pol_file:
if not data_to_write.startswith(reg_pol_header.encode('utf-16-le')): if not data_to_write.startswith(reg_pol_header.encode('utf-16-le')):
pol_file.write(reg_pol_header.encode('utf-16-le')) pol_file.write(reg_pol_header.encode('utf-16-le'))
@ -4188,11 +4198,12 @@ def _write_regpol_data(data_to_write,
try: try:
gpt_ini_data = '' gpt_ini_data = ''
if os.path.exists(gpt_ini_path): if os.path.exists(gpt_ini_path):
with salt.utils.files.fopen(gpt_ini_path, 'rb') as gpt_file: with salt.utils.files.fopen(gpt_ini_path, 'r') as gpt_file:
gpt_ini_data = gpt_file.read() gpt_ini_data = gpt_file.read()
if not _regexSearchRegPolData(r'\[General\]\r\n', gpt_ini_data): if not _regexSearchRegPolData(r'\[General\]\r\n', gpt_ini_data):
gpt_ini_data = '[General]\r\n' + gpt_ini_data gpt_ini_data = '[General]\r\n' + gpt_ini_data
if _regexSearchRegPolData(r'{0}='.format(re.escape(gpt_extension)), gpt_ini_data): if _regexSearchRegPolData(r'{0}='.format(re.escape(gpt_extension)),
gpt_ini_data):
# ensure the line contains the ADM guid # ensure the line contains the ADM guid
gpt_ext_loc = re.search(r'^{0}=.*\r\n'.format(re.escape(gpt_extension)), gpt_ext_loc = re.search(r'^{0}=.*\r\n'.format(re.escape(gpt_extension)),
gpt_ini_data, gpt_ini_data,
@ -4208,9 +4219,10 @@ def _write_regpol_data(data_to_write,
general_location = re.search(r'^\[General\]\r\n', general_location = re.search(r'^\[General\]\r\n',
gpt_ini_data, gpt_ini_data,
re.IGNORECASE | re.MULTILINE) re.IGNORECASE | re.MULTILINE)
gpt_ini_data = "{0}{1}={2}\r\n{3}".format( gpt_ini_data = '{0}{1}={2}\r\n{3}'.format(
gpt_ini_data[general_location.start():general_location.end()], gpt_ini_data[general_location.start():general_location.end()],
gpt_extension, gpt_extension_guid, gpt_extension,
gpt_extension_guid,
gpt_ini_data[general_location.end():]) gpt_ini_data[general_location.end():])
# https://technet.microsoft.com/en-us/library/cc978247.aspx # https://technet.microsoft.com/en-us/library/cc978247.aspx
if _regexSearchRegPolData(r'Version=', gpt_ini_data): if _regexSearchRegPolData(r'Version=', gpt_ini_data):
@ -4225,9 +4237,10 @@ def _write_regpol_data(data_to_write,
elif gpt_extension.lower() == 'gPCUserExtensionNames'.lower(): elif gpt_extension.lower() == 'gPCUserExtensionNames'.lower():
version_nums = (version_nums[0] + 1, version_nums[1]) version_nums = (version_nums[0] + 1, version_nums[1])
version_num = struct.unpack(b'>I', struct.pack(b'>2H', *version_nums))[0] version_num = struct.unpack(b'>I', struct.pack(b'>2H', *version_nums))[0]
gpt_ini_data = "{0}{1}={2}\r\n{3}".format( gpt_ini_data = '{0}{1}={2}\r\n{3}'.format(
gpt_ini_data[0:version_loc.start()], gpt_ini_data[0:version_loc.start()],
'Version', version_num, 'Version',
version_num,
gpt_ini_data[version_loc.end():]) gpt_ini_data[version_loc.end():])
else: else:
general_location = re.search(r'^\[General\]\r\n', general_location = re.search(r'^\[General\]\r\n',
@ -4237,20 +4250,26 @@ def _write_regpol_data(data_to_write,
version_nums = (0, 1) version_nums = (0, 1)
elif gpt_extension.lower() == 'gPCUserExtensionNames'.lower(): elif gpt_extension.lower() == 'gPCUserExtensionNames'.lower():
version_nums = (1, 0) version_nums = (1, 0)
gpt_ini_data = "{0}{1}={2}\r\n{3}".format( gpt_ini_data = '{0}{1}={2}\r\n{3}'.format(
gpt_ini_data[general_location.start():general_location.end()], gpt_ini_data[general_location.start():general_location.end()],
'Version', 'Version',
int("{0}{1}".format(six.text_type(version_nums[0]).zfill(4), six.text_type(version_nums[1]).zfill(4)), 16), int("{0}{1}".format(six.text_type(version_nums[0]).zfill(4),
six.text_type(version_nums[1]).zfill(4)),
16),
gpt_ini_data[general_location.end():]) gpt_ini_data[general_location.end():])
if gpt_ini_data: if gpt_ini_data:
with salt.utils.files.fopen(gpt_ini_path, 'wb') as gpt_file: with salt.utils.files.fopen(gpt_ini_path, 'w') as gpt_file:
gpt_file.write(salt.utils.stringutils.to_bytes(gpt_ini_data)) gpt_file.write(gpt_ini_data)
# TODO: This needs to be more specific
except Exception as e: except Exception as e:
msg = 'An error occurred attempting to write to {0}, the exception was {1}'.format( msg = 'An error occurred attempting to write to {0}, the exception was {1}'.format(
gpt_ini_path, e) gpt_ini_path, e)
log.exception(msg)
raise CommandExecutionError(msg) raise CommandExecutionError(msg)
# TODO: This needs to be more specific
except Exception as e: except Exception as e:
msg = 'An error occurred attempting to write to {0}, the exception was {1}'.format(policy_file_path, e) msg = 'An error occurred attempting to write to {0}, the exception was {1}'.format(policy_file_path, e)
log.exception(msg)
raise CommandExecutionError(msg) raise CommandExecutionError(msg)
@ -4648,8 +4667,9 @@ def _writeAdminTemplateRegPolFile(admtemplate_data,
policy_data.gpt_ini_path, policy_data.gpt_ini_path,
policy_data.admx_registry_classes[registry_class]['gpt_extension_location'], policy_data.admx_registry_classes[registry_class]['gpt_extension_location'],
policy_data.admx_registry_classes[registry_class]['gpt_extension_guid']) policy_data.admx_registry_classes[registry_class]['gpt_extension_guid'])
# TODO: This needs to be more specific or removed
except Exception: except Exception:
log.error('Unhandled exception %s occurred while attempting to write Adm Template Policy File') log.exception('Unhandled exception %s occurred while attempting to write Adm Template Policy File')
return False return False
return True return True
@ -4671,7 +4691,7 @@ def _getScriptSettingsFromIniFile(policy_info):
_existingData = deserialize(_existingData.decode('utf-16-le').lstrip('\ufeff')) _existingData = deserialize(_existingData.decode('utf-16-le').lstrip('\ufeff'))
log.debug('Have deserialized data %s', _existingData) log.debug('Have deserialized data %s', _existingData)
except Exception as error: except Exception as error:
log.error('An error occurred attempting to deserialize data for %s', policy_info['Policy']) log.exception('An error occurred attempting to deserialize data for %s', policy_info['Policy'])
raise CommandExecutionError(error) raise CommandExecutionError(error)
if 'Section' in policy_info['ScriptIni'] and policy_info['ScriptIni']['Section'].lower() in [z.lower() for z in _existingData.keys()]: if 'Section' in policy_info['ScriptIni'] and policy_info['ScriptIni']['Section'].lower() in [z.lower() for z in _existingData.keys()]:
if 'SettingName' in policy_info['ScriptIni']: if 'SettingName' in policy_info['ScriptIni']:
@ -5540,8 +5560,10 @@ def set_(computer_policy=None, user_policy=None,
_newModalSetData = dictupdate.update(_existingModalData, _modal_sets[_modal_set]) _newModalSetData = dictupdate.update(_existingModalData, _modal_sets[_modal_set])
log.debug('NEW MODAL SET = %s', _newModalSetData) log.debug('NEW MODAL SET = %s', _newModalSetData)
_ret = win32net.NetUserModalsSet(None, _modal_set, _newModalSetData) _ret = win32net.NetUserModalsSet(None, _modal_set, _newModalSetData)
except: # TODO: This needs to be more specific
except Exception:
msg = 'An unhandled exception occurred while attempting to set policy via NetUserModalSet' msg = 'An unhandled exception occurred while attempting to set policy via NetUserModalSet'
log.exception(msg)
raise CommandExecutionError(msg) raise CommandExecutionError(msg)
if _admTemplateData: if _admTemplateData:
_ret = False _ret = False

View File

@ -7,50 +7,56 @@ powercfg.
.. code-block:: bash .. code-block:: bash
# Set monitor to never turn off on Battery power
salt '*' powercfg.set_monitor_timeout 0 power=dc salt '*' powercfg.set_monitor_timeout 0 power=dc
# Set disk timeout to 120 minutes on AC power
salt '*' powercfg.set_disk_timeout 120 power=ac salt '*' powercfg.set_disk_timeout 120 power=ac
''' '''
# Import Python Libs # Import Python Libs
from __future__ import absolute_import, unicode_literals, print_function from __future__ import absolute_import, unicode_literals, print_function
import re import re
import logging import logging
# Import Salt Libs
import salt.utils.platform
import salt.utils.versions
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
__virtualname__ = "powercfg" __virtualname__ = 'powercfg'
def __virtual__(): def __virtual__():
''' '''
Only work on Windows Only work on Windows
''' '''
if __grains__['os'] == 'Windows': if not salt.utils.platform.is_windows():
return False, 'PowerCFG: Module only works on Windows'
return __virtualname__ return __virtualname__
return (False, 'Module only works on Windows.')
def _get_current_scheme(): def _get_current_scheme():
cmd = "powercfg /getactivescheme" cmd = 'powercfg /getactivescheme'
out = __salt__['cmd.run'](cmd, python_shell=False) out = __salt__['cmd.run'](cmd, python_shell=False)
matches = re.search(r"GUID: (.*) \(", out) matches = re.search(r'GUID: (.*) \(', out)
return matches.groups()[0].strip() return matches.groups()[0].strip()
def _get_powercfg_minute_values(scheme, guid, subguid, safe_name): def _get_powercfg_minute_values(scheme, guid, subguid, safe_name):
''' '''
Returns the AC/DC values in an array for a guid and subguid for a the given scheme Returns the AC/DC values in an dict for a guid and subguid for a the given
scheme
''' '''
if scheme is None: if scheme is None:
scheme = _get_current_scheme() scheme = _get_current_scheme()
if __grains__['osrelease'] == '7': if __grains__['osrelease'] == '7':
cmd = "powercfg /q {0} {1}".format(scheme, guid) cmd = 'powercfg /q {0} {1}'.format(scheme, guid)
else: else:
cmd = "powercfg /q {0} {1} {2}".format(scheme, guid, subguid) cmd = 'powercfg /q {0} {1} {2}'.format(scheme, guid, subguid)
out = __salt__['cmd.run'](cmd, python_shell=False) out = __salt__['cmd.run'](cmd, python_shell=False)
split = out.split("\r\n\r\n") split = out.split('\r\n\r\n')
if len(split) > 1: if len(split) > 1:
for s in split: for s in split:
if safe_name in s or subguid in s: if safe_name in s or subguid in s:
@ -59,172 +65,309 @@ def _get_powercfg_minute_values(scheme, guid, subguid, safe_name):
else: else:
out = split[0] out = split[0]
raw_settings = re.findall(r"Power Setting Index: ([0-9a-fx]+)", out) raw_settings = re.findall(r'Power Setting Index: ([0-9a-fx]+)', out)
return {"ac": int(raw_settings[0], 0) / 60, "dc": int(raw_settings[1], 0) / 60} return {'ac': int(raw_settings[0], 0) / 60,
'dc': int(raw_settings[1], 0) / 60}
def _set_powercfg_value(scheme, sub_group, setting_guid, power, value): def _set_powercfg_value(scheme, sub_group, setting_guid, power, value):
''' '''
Sets the value of a setting with a given power (ac/dc) to Sets the AC/DC values of a setting with the given power for the given scheme
the given scheme
''' '''
salt.utils.versions.warn_until(
'Fluorine',
'This function now expects the timeout value in minutes instead of '
'seconds as stated in the documentation. This warning will be removed '
'in Salt Fluorine.')
if scheme is None: if scheme is None:
scheme = _get_current_scheme() scheme = _get_current_scheme()
cmd = "powercfg /set{0}valueindex {1} {2} {3} {4}".format(power, scheme, sub_group, setting_guid, value) cmd = 'powercfg /set{0}valueindex {1} {2} {3} {4}' \
return __salt__['cmd.run'](cmd, python_shell=False) ''.format(power, scheme, sub_group, setting_guid, value * 60)
return __salt__['cmd.retcode'](cmd, python_shell=False) == 0
def set_monitor_timeout(timeout, power="ac", scheme=None): def set_monitor_timeout(timeout, power='ac', scheme=None):
''' '''
Set the monitor timeout in minutes for the given power scheme Set the monitor timeout in minutes for the given power scheme
Args:
timeout (int):
The amount of time in minutes before the monitor will timeout
power (str):
Set the value for AC or DC power. Default is ``ac``. Valid options
are:
- ``ac`` (AC Power)
- ``dc`` (Battery)
scheme (str):
The scheme to use, leave as ``None`` to use the current. Default is
``None``. This can be the GUID or the Alias for the Scheme. Known
Aliases are:
- ``SCHEME_BALANCED`` - Balanced
- ``SCHEME_MAX`` - Power saver
- ``SCHEME_MIN`` - High performance
Returns:
bool: ``True`` if successful, otherwise ``False``
CLI Example: CLI Example:
.. code-block:: bash .. code-block:: bash
salt '*' powercfg.set_monitor_timeout 30 power=ac # Sets the monitor timeout to 30 minutes
salt '*' powercfg.set_monitor_timeout 30
timeout
The amount of time in minutes before the monitor will timeout
power
Should we set the value for AC or DC (battery)? Valid options ac,dc.
scheme
The scheme to use, leave as None to use the current.
''' '''
return _set_powercfg_value(scheme, "SUB_VIDEO", "VIDEOIDLE", power, timeout) return _set_powercfg_value(
scheme=scheme,
sub_group='SUB_VIDEO',
setting_guid='VIDEOIDLE',
power=power,
value=timeout)
def get_monitor_timeout(scheme=None): def get_monitor_timeout(scheme=None):
''' '''
Get the current monitor timeout of the given scheme Get the current monitor timeout of the given scheme
Args:
scheme (str):
The scheme to use, leave as ``None`` to use the current. Default is
``None``. This can be the GUID or the Alias for the Scheme. Known
Aliases are:
- ``SCHEME_BALANCED`` - Balanced
- ``SCHEME_MAX`` - Power saver
- ``SCHEME_MIN`` - High performance
Returns:
dict: A dictionary of both the AC and DC settings
CLI Example: CLI Example:
.. code-block:: bash .. code-block:: bash
salt '*' powercfg.get_monitor_timeout salt '*' powercfg.get_monitor_timeout
scheme
The scheme to use, leave as None to use the current.
''' '''
return _get_powercfg_minute_values(scheme, "SUB_VIDEO", "VIDEOIDLE", "Turn off display after") return _get_powercfg_minute_values(
scheme=scheme,
guid='SUB_VIDEO',
subguid='VIDEOIDLE',
safe_name='Turn off display after')
def set_disk_timeout(timeout, power="ac", scheme=None): def set_disk_timeout(timeout, power='ac', scheme=None):
''' '''
Set the disk timeout in minutes for the given power scheme Set the disk timeout in minutes for the given power scheme
Args:
timeout (int):
The amount of time in minutes before the disk will timeout
power (str):
Set the value for AC or DC power. Default is ``ac``. Valid options
are:
- ``ac`` (AC Power)
- ``dc`` (Battery)
scheme (str):
The scheme to use, leave as ``None`` to use the current. Default is
``None``. This can be the GUID or the Alias for the Scheme. Known
Aliases are:
- ``SCHEME_BALANCED`` - Balanced
- ``SCHEME_MAX`` - Power saver
- ``SCHEME_MIN`` - High performance
Returns:
bool: ``True`` if successful, otherwise ``False``
CLI Example: CLI Example:
.. code-block:: bash .. code-block:: bash
# Sets the disk timeout to 30 minutes on battery
salt '*' powercfg.set_disk_timeout 30 power=dc salt '*' powercfg.set_disk_timeout 30 power=dc
timeout
The amount of time in minutes before the disk will timeout
power
Should we set the value for AC or DC (battery)? Valid options ac,dc.
scheme
The scheme to use, leave as None to use the current.
''' '''
return _set_powercfg_value(scheme, "SUB_DISK", "DISKIDLE", power, timeout) return _set_powercfg_value(
scheme=scheme,
sub_group='SUB_DISK',
setting_guid='DISKIDLE',
power=power,
value=timeout)
def get_disk_timeout(scheme=None): def get_disk_timeout(scheme=None):
''' '''
Get the current disk timeout of the given scheme Get the current disk timeout of the given scheme
Args:
scheme (str):
The scheme to use, leave as ``None`` to use the current. Default is
``None``. This can be the GUID or the Alias for the Scheme. Known
Aliases are:
- ``SCHEME_BALANCED`` - Balanced
- ``SCHEME_MAX`` - Power saver
- ``SCHEME_MIN`` - High performance
Returns:
dict: A dictionary of both the AC and DC settings
CLI Example: CLI Example:
.. code-block:: bash .. code-block:: bash
salt '*' powercfg.get_disk_timeout salt '*' powercfg.get_disk_timeout
scheme
The scheme to use, leave as None to use the current.
''' '''
return _get_powercfg_minute_values(scheme, "SUB_DISK", "DISKIDLE", "Turn off hard disk after") return _get_powercfg_minute_values(
scheme=scheme,
guid='SUB_DISK',
subguid='DISKIDLE',
safe_name='Turn off hard disk after')
def set_standby_timeout(timeout, power="ac", scheme=None): def set_standby_timeout(timeout, power='ac', scheme=None):
''' '''
Set the standby timeout in minutes for the given power scheme Set the standby timeout in minutes for the given power scheme
Args:
timeout (int):
The amount of time in minutes before the computer sleeps
power (str):
Set the value for AC or DC power. Default is ``ac``. Valid options
are:
- ``ac`` (AC Power)
- ``dc`` (Battery)
scheme (str):
The scheme to use, leave as ``None`` to use the current. Default is
``None``. This can be the GUID or the Alias for the Scheme. Known
Aliases are:
- ``SCHEME_BALANCED`` - Balanced
- ``SCHEME_MAX`` - Power saver
- ``SCHEME_MIN`` - High performance
Returns:
bool: ``True`` if successful, otherwise ``False``
CLI Example: CLI Example:
.. code-block:: bash .. code-block:: bash
# Sets the system standby timeout to 30 minutes on Battery
salt '*' powercfg.set_standby_timeout 30 power=dc salt '*' powercfg.set_standby_timeout 30 power=dc
timeout
The amount of time in minutes before the computer sleeps
power
Should we set the value for AC or DC (battery)? Valid options ac,dc.
scheme
The scheme to use, leave as None to use the current.
''' '''
return _set_powercfg_value(scheme, "SUB_SLEEP", "STANDBYIDLE", power, timeout) return _set_powercfg_value(
scheme=scheme,
sub_group='SUB_SLEEP',
setting_guid='STANDBYIDLE',
power=power,
value=timeout)
def get_standby_timeout(scheme=None): def get_standby_timeout(scheme=None):
''' '''
Get the current standby timeout of the given scheme Get the current standby timeout of the given scheme
scheme (str):
The scheme to use, leave as ``None`` to use the current. Default is
``None``. This can be the GUID or the Alias for the Scheme. Known
Aliases are:
- ``SCHEME_BALANCED`` - Balanced
- ``SCHEME_MAX`` - Power saver
- ``SCHEME_MIN`` - High performance
Returns:
dict: A dictionary of both the AC and DC settings
CLI Example: CLI Example:
.. code-block:: bash .. code-block:: bash
salt '*' powercfg.get_standby_timeout salt '*' powercfg.get_standby_timeout
scheme
The scheme to use, leave as None to use the current.
''' '''
return _get_powercfg_minute_values(scheme, "SUB_SLEEP", "STANDBYIDLE", "Sleep after") return _get_powercfg_minute_values(
scheme=scheme,
guid='SUB_SLEEP',
subguid='STANDBYIDLE',
safe_name='Sleep after')
def set_hibernate_timeout(timeout, power="ac", scheme=None): def set_hibernate_timeout(timeout, power='ac', scheme=None):
''' '''
Set the hibernate timeout in minutes for the given power scheme Set the hibernate timeout in minutes for the given power scheme
Args:
timeout (int):
The amount of time in minutes before the computer hibernates
power (str):
Set the value for AC or DC power. Default is ``ac``. Valid options
are:
- ``ac`` (AC Power)
- ``dc`` (Battery)
scheme (str):
The scheme to use, leave as ``None`` to use the current. Default is
``None``. This can be the GUID or the Alias for the Scheme. Known
Aliases are:
- ``SCHEME_BALANCED`` - Balanced
- ``SCHEME_MAX`` - Power saver
- ``SCHEME_MIN`` - High performance
Returns:
bool: ``True`` if successful, otherwise ``False``
CLI Example: CLI Example:
.. code-block:: bash .. code-block:: bash
salt '*' powercfg.set_hibernate_timeout 30 power=pc # Sets the hibernate timeout to 30 minutes on Battery
salt '*' powercfg.set_hibernate_timeout 30 power=dc
timeout
The amount of time in minutes before the computer hibernates
power
Should we set the value for AC or DC (battery)? Valid options ac,dc.
scheme
The scheme to use, leave as None to use the current.
''' '''
return _set_powercfg_value(scheme, "SUB_SLEEP", "HIBERNATEIDLE", power, timeout) return _set_powercfg_value(
scheme=scheme,
sub_group='SUB_SLEEP',
setting_guid='HIBERNATEIDLE',
power=power,
value=timeout)
def get_hibernate_timeout(scheme=None): def get_hibernate_timeout(scheme=None):
''' '''
Get the current hibernate timeout of the given scheme Get the current hibernate timeout of the given scheme
scheme (str):
The scheme to use, leave as ``None`` to use the current. Default is
``None``. This can be the GUID or the Alias for the Scheme. Known
Aliases are:
- ``SCHEME_BALANCED`` - Balanced
- ``SCHEME_MAX`` - Power saver
- ``SCHEME_MIN`` - High performance
Returns:
dict: A dictionary of both the AC and DC settings
CLI Example: CLI Example:
.. code-block:: bash .. code-block:: bash
salt '*' powercfg.get_hibernate_timeout salt '*' powercfg.get_hibernate_timeout
scheme
The scheme to use, leave as None to use the current.
''' '''
return _get_powercfg_minute_values(scheme, "SUB_SLEEP", "HIBERNATEIDLE", "Hibernate after") return _get_powercfg_minute_values(
scheme=scheme,
guid='SUB_SLEEP',
subguid='HIBERNATEIDLE',
safe_name='Hibernate after')

View File

@ -705,7 +705,7 @@ def modify(name,
win32service.SERVICE_QUERY_CONFIG) win32service.SERVICE_QUERY_CONFIG)
except pywintypes.error as exc: except pywintypes.error as exc:
raise CommandExecutionError( raise CommandExecutionError(
'Failed To Open {0}: {1}'.format(name, exc[2])) 'Failed To Open {0}: {1}'.format(name, exc))
config_info = win32service.QueryServiceConfig(handle_svc) config_info = win32service.QueryServiceConfig(handle_svc)

View File

@ -584,6 +584,8 @@ def latest_version(*names, **kwargs):
status = pkg_info.get('status', '').lower() status = pkg_info.get('status', '').lower()
if status.find('not installed') > -1 or status.find('out-of-date') > -1: if status.find('not installed') > -1 or status.find('out-of-date') > -1:
ret[name] = pkg_info.get('version') ret[name] = pkg_info.get('version')
else:
ret[name] = ''
# Return a string if only one package name passed # Return a string if only one package name passed
if len(names) == 1 and len(ret): if len(names) == 1 and len(ret):

View File

@ -203,8 +203,6 @@ import tornado.web
import tornado.gen import tornado.gen
from tornado.concurrent import Future from tornado.concurrent import Future
# pylint: enable=import-error # pylint: enable=import-error
import salt.utils
salt.utils.zeromq.install_zmq()
# salt imports # salt imports
import salt.ext.six as six import salt.ext.six as six
@ -214,6 +212,7 @@ import salt.utils.event
import salt.utils.jid import salt.utils.jid
import salt.utils.json import salt.utils.json
import salt.utils.yaml import salt.utils.yaml
import salt.utils.zeromq
from salt.utils.event import tagify from salt.utils.event import tagify
import salt.client import salt.client
import salt.runner import salt.runner
@ -224,6 +223,7 @@ from salt.exceptions import (
EauthAuthenticationError EauthAuthenticationError
) )
salt.utils.zeromq.install_zmq()
json = salt.utils.json.import_json() json = salt.utils.json.import_json()
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -933,9 +933,11 @@ class SaltAPIHandler(BaseSaltAPIHandler): # pylint: disable=W0223
# Generate jid before triggering a job to subscribe all returns from minions # Generate jid before triggering a job to subscribe all returns from minions
chunk['jid'] = salt.utils.jid.gen_jid(self.application.opts) chunk['jid'] = salt.utils.jid.gen_jid(self.application.opts)
# Subscribe returns from minions before firing a job # start listening for the event before we fire the job to avoid races
minions = set(self.ckminions.check_minions(chunk['tgt'], chunk.get('tgt_type', 'glob'))) events = [
future_minion_map = self.subscribe_minion_returns(chunk['jid'], minions) self.application.event_listener.get_event(self, tag='salt/job/'+chunk['jid']),
self.application.event_listener.get_event(self, tag='syndic/job/'+chunk['jid']),
]
f_call = self._format_call_run_job_async(chunk) f_call = self._format_call_run_job_async(chunk)
# fire a job off # fire a job off
@ -944,88 +946,92 @@ class SaltAPIHandler(BaseSaltAPIHandler): # pylint: disable=W0223
# if the job didn't publish, lets not wait around for nothing # if the job didn't publish, lets not wait around for nothing
# TODO: set header?? # TODO: set header??
if 'jid' not in pub_data: if 'jid' not in pub_data:
for future in future_minion_map: for future in events:
try: try:
future.set_result(None) future.set_result(None)
except Exception: except Exception:
pass pass
raise tornado.gen.Return('No minions matched the target. No command was sent, no jid was assigned.') raise tornado.gen.Return('No minions matched the target. No command was sent, no jid was assigned.')
# Map of minion_id -> returned for all minions we think we need to wait on
minions = {m: False for m in pub_data['minions']}
# minimum time required for return to complete. By default no waiting, if
# we are a syndic then we must wait syndic_wait at a minimum
min_wait_time = Future()
min_wait_time.set_result(True)
# wait syndic a while to avoid missing published events # wait syndic a while to avoid missing published events
if self.application.opts['order_masters']: if self.application.opts['order_masters']:
yield tornado.gen.sleep(self.application.opts['syndic_wait']) min_wait_time = tornado.gen.sleep(self.application.opts['syndic_wait'])
# To ensure job_not_running and all_return are terminated by each other, communicate using a future # To ensure job_not_running and all_return are terminated by each other, communicate using a future
is_finished = Future() is_finished = tornado.gen.sleep(self.application.opts['gather_job_timeout'])
job_not_running_future = self.job_not_running(pub_data['jid'], # ping until the job is not running, while doing so, if we see new minions returning
# that they are running the job, add them to the list
tornado.ioloop.IOLoop.current().spawn_callback(self.job_not_running, pub_data['jid'],
chunk['tgt'], chunk['tgt'],
f_call['kwargs']['tgt_type'], f_call['kwargs']['tgt_type'],
minions,
is_finished) is_finished)
minion_returns_future = self.sanitize_minion_returns(future_minion_map, pub_data['minions'], is_finished) def more_todo():
'''Check if there are any more minions we are waiting on returns from
yield job_not_running_future
raise tornado.gen.Return((yield minion_returns_future))
def subscribe_minion_returns(self, jid, minions):
# Subscribe each minion event
future_minion_map = {}
for minion in minions:
tag = tagify([jid, 'ret', minion], 'job')
minion_future = self.application.event_listener.get_event(self,
tag=tag,
matcher=EventListener.exact_matcher)
future_minion_map[minion_future] = minion
return future_minion_map
@tornado.gen.coroutine
def sanitize_minion_returns(self, future_minion_map, minions, is_finished):
''' '''
Return a future which will complete once all returns are completed return any(x is False for x in six.itervalues(minions))
(according to minions), or one of the passed in "finish_chunk_ret_future" completes
'''
if minions is None:
minions = []
# Remove redundant minions
redundant_minion_futures = [future for future in future_minion_map.keys() if future_minion_map[future] not in minions]
for redundant_minion_future in redundant_minion_futures:
try:
redundant_minion_future.set_result(None)
except Exception:
pass
del future_minion_map[redundant_minion_future]
# here we want to follow the behavior of LocalClient.get_iter_returns
# namely we want to wait at least syndic_wait (assuming we are a syndic)
# and that there are no more jobs running on minions. We are allowed to exit
# early if gather_job_timeout has been exceeded
chunk_ret = {} chunk_ret = {}
while True: while True:
f = yield Any(list(future_minion_map.keys()) + [is_finished]) to_wait = events+[is_finished]
if not min_wait_time.done():
to_wait += [min_wait_time]
def cancel_inflight_futures():
for event in to_wait:
if not event.done():
event.set_result(None)
f = yield Any(to_wait)
try: try:
# When finished entire routine, cleanup other futures and return result # When finished entire routine, cleanup other futures and return result
if f is is_finished: if f is is_finished:
for event in future_minion_map.keys(): cancel_inflight_futures()
if not event.done():
event.set_result(None)
raise tornado.gen.Return(chunk_ret) raise tornado.gen.Return(chunk_ret)
elif f is min_wait_time:
if not more_todo():
cancel_inflight_futures()
raise tornado.gen.Return(chunk_ret)
continue
f_result = f.result() f_result = f.result()
# if this is a start, then we need to add it to the pile
if f_result['tag'].endswith('/new'):
for minion_id in f_result['data']['minions']:
if minion_id not in minions:
minions[minion_id] = False
else:
chunk_ret[f_result['data']['id']] = f_result['data']['return'] chunk_ret[f_result['data']['id']] = f_result['data']['return']
# clear finished event future
minions[f_result['data']['id']] = True
# if there are no more minions to wait for, then we are done
if not more_todo() and min_wait_time.done():
cancel_inflight_futures()
raise tornado.gen.Return(chunk_ret)
except TimeoutException: except TimeoutException:
pass pass
# clear finished event future if f == events[0]:
try: events[0] = self.application.event_listener.get_event(self, tag='salt/job/'+chunk['jid'])
minions.remove(future_minion_map[f]) else:
del future_minion_map[f] events[1] = self.application.event_listener.get_event(self, tag='syndic/job/'+chunk['jid'])
except ValueError:
pass
if not minions:
if not is_finished.done():
is_finished.set_result(True)
raise tornado.gen.Return(chunk_ret)
@tornado.gen.coroutine @tornado.gen.coroutine
def job_not_running(self, jid, tgt, tgt_type, is_finished): def job_not_running(self, jid, tgt, tgt_type, minions, is_finished):
''' '''
Return a future which will complete once jid (passed in) is no longer Return a future which will complete once jid (passed in) is no longer
running on tgt running on tgt
@ -1051,8 +1057,6 @@ class SaltAPIHandler(BaseSaltAPIHandler): # pylint: disable=W0223
event = f.result() event = f.result()
except TimeoutException: except TimeoutException:
if not minion_running: if not minion_running:
if not is_finished.done():
is_finished.set_result(True)
raise tornado.gen.Return(True) raise tornado.gen.Return(True)
else: else:
ping_pub_data = yield self.saltclients['local'](tgt, ping_pub_data = yield self.saltclients['local'](tgt,
@ -1066,6 +1070,8 @@ class SaltAPIHandler(BaseSaltAPIHandler): # pylint: disable=W0223
# Minions can return, we want to see if the job is running... # Minions can return, we want to see if the job is running...
if event['data'].get('return', {}) == {}: if event['data'].get('return', {}) == {}:
continue continue
if event['data']['id'] not in minions:
minions[event['data']['id']] = False
minion_running = True minion_running = True
@tornado.gen.coroutine @tornado.gen.coroutine

View File

@ -1037,6 +1037,11 @@ class Pillar(object):
decrypt_errors = self.decrypt_pillar(pillar) decrypt_errors = self.decrypt_pillar(pillar)
if decrypt_errors: if decrypt_errors:
pillar.setdefault('_errors', []).extend(decrypt_errors) pillar.setdefault('_errors', []).extend(decrypt_errors)
# Reset the file_roots for the renderers
for mod_name in sys.modules:
if mod_name.startswith('salt.loaded.int.render.'):
sys.modules[mod_name].__opts__['file_roots'] = self.actual_file_roots
return pillar return pillar
def decrypt_pillar(self, pillar): def decrypt_pillar(self, pillar):

View File

@ -328,7 +328,7 @@ def chconfig(cmd, *args, **kwargs):
''' '''
# Strip the __pub_ keys...is there a better way to do this? # Strip the __pub_ keys...is there a better way to do this?
for k in kwargs: for k in list(kwargs):
if k.startswith('__pub_'): if k.startswith('__pub_'):
kwargs.pop(k) kwargs.pop(k)

View File

@ -200,7 +200,7 @@ def _walk(path, value, metrics, timestamp, skip):
to a float. Defaults to `False`. to a float. Defaults to `False`.
''' '''
log.trace( log.trace(
'Carbon return walking path: %s, value: %s, metrics: %s, ', 'Carbon return walking path: %s, value: %s, metrics: %s, '
'timestamp: %s', path, value, metrics, timestamp 'timestamp: %s', path, value, metrics, timestamp
) )
if isinstance(value, collections.Mapping): if isinstance(value, collections.Mapping):

View File

@ -192,14 +192,10 @@ def returner(ret):
logoption = logoption | getattr(syslog, opt) logoption = logoption | getattr(syslog, opt)
# Open syslog correctly based on options and tag # Open syslog correctly based on options and tag
try:
if 'tag' in _options: if 'tag' in _options:
syslog.openlog(ident=_options['tag'], logoption=logoption) syslog.openlog(ident=salt.utils.stringutils.to_str(_options['tag']), logoption=logoption)
else: else:
syslog.openlog(logoption=logoption) syslog.openlog(logoption=logoption)
except TypeError:
# Python 2.6 syslog.openlog does not accept keyword args
syslog.openlog(_options.get('tag', 'salt-minion'), logoption)
# Send log of given level and facility # Send log of given level and facility
syslog.syslog(facility | level, salt.utils.json.dumps(ret)) syslog.syslog(facility | level, salt.utils.json.dumps(ret))

View File

@ -164,7 +164,7 @@ def action(func=None,
instances, instances,
provider, provider,
instance, instance,
**salt.utils.args.clean_kwargs(**kwargs) salt.utils.args.clean_kwargs(**kwargs)
) )
except SaltCloudConfigError as err: except SaltCloudConfigError as err:
log.error(err) log.error(err)

View File

@ -153,7 +153,7 @@ def lookup_jid(jid,
try: try:
# Check if the return data has an 'out' key. We'll use that as the # Check if the return data has an 'out' key. We'll use that as the
# outputter in the absence of one being passed on the CLI. # outputter in the absence of one being passed on the CLI.
outputter = data[next(iter(data))].get('out') outputter = returns[next(iter(returns))].get('out')
except (StopIteration, AttributeError): except (StopIteration, AttributeError):
outputter = None outputter = None

View File

@ -72,7 +72,7 @@ def generate_token(minion_id, signature, impersonated_by_master=False):
payload = { payload = {
'policies': _get_policies(minion_id, config), 'policies': _get_policies(minion_id, config),
'num_uses': 1, 'num_uses': 1,
'metadata': audit_data 'meta': audit_data
} }
if payload['policies'] == []: if payload['policies'] == []:

View File

@ -172,7 +172,9 @@ def init(
start=True, start=True,
disk='default', disk='default',
saltenv='base', saltenv='base',
enable_vnc=False): enable_vnc=False,
seed_cmd='seed.apply',
enable_qcow=False):
''' '''
This routine is used to create a new virtual machine. This routines takes This routine is used to create a new virtual machine. This routines takes
a number of options to determine what the newly created virtual machine a number of options to determine what the newly created virtual machine
@ -194,14 +196,14 @@ def init(
on the salt fileserver, but http, https and ftp can also be used. on the salt fileserver, but http, https and ftp can also be used.
hypervisor hypervisor
The hypervisor to use for the new virtual machine. Default is 'kvm'. The hypervisor to use for the new virtual machine. Default is `kvm`.
host host
The host to use for the new virtual machine, if this is omitted The host to use for the new virtual machine, if this is omitted
Salt will automatically detect what host to use. Salt will automatically detect what host to use.
seed seed
Set to False to prevent Salt from seeding the new virtual machine. Set to `False` to prevent Salt from seeding the new virtual machine.
nic nic
The nic profile to use, defaults to the "default" nic profile which The nic profile to use, defaults to the "default" nic profile which
@ -217,6 +219,17 @@ def init(
saltenv saltenv
The Salt environment to use The Salt environment to use
enable_vnc
Whether a VNC screen is attached to resulting VM. Default is `False`.
seed_cmd
If seed is `True`, use this execution module function to seed new VM.
Default is `seed.apply`.
enable_qcow
Clone disk image as a copy-on-write qcow2 image, using downloaded
`image` as backing file.
''' '''
__jid_event__.fire_event({'message': 'Searching for hosts'}, 'progress') __jid_event__.fire_event({'message': 'Searching for hosts'}, 'progress')
data = query(host, quiet=True) data = query(host, quiet=True)
@ -262,20 +275,24 @@ def init(
[ [
name, name,
cpu, cpu,
mem, mem
image,
nic,
hypervisor,
start,
disk,
saltenv,
seed,
install,
pub_key,
priv_key,
enable_vnc,
], ],
timeout=600) timeout=600,
kwarg={
'image': image,
'nic': nic,
'hypervisor': hypervisor,
'start': start,
'disk': disk,
'saltenv': saltenv,
'seed': seed,
'install': install,
'pub_key': pub_key,
'priv_key': priv_key,
'seed_cmd': seed_cmd,
'enable_vnc': enable_vnc,
'enable_qcow': enable_qcow,
})
except SaltClientError as client_error: except SaltClientError as client_error:
# Fall through to ret error handling below # Fall through to ret error handling below
print(client_error) print(client_error)

Some files were not shown because too many files have changed in this diff Show More