Merge branch 'develop' into esxdatacenter-gh

This commit is contained in:
Alexandru Bleotu 2017-09-15 20:05:03 +01:00 committed by GitHub
commit f1bbb84efc
57 changed files with 2066 additions and 587 deletions

View File

@ -373,7 +373,7 @@
# interface: eth0
# cidr: '10.0.0.0/8'
# The number of seconds a mine update runs.
# The number of minutes between mine updates.
#mine_interval: 60
# Windows platforms lack posix IPC and must rely on slower TCP based inter-

View File

@ -706,7 +706,7 @@ Note these can be defined in the pillar for a minion as well.
Default: ``60``
The number of seconds a mine update runs.
The number of minutes between mine updates.
.. code-block:: yaml

View File

@ -118,3 +118,53 @@ has to be closed after every command.
.. code-block:: yaml
proxy_always_alive: False
``proxy_merge_pillar_in_opts``
------------------------------
.. versionadded:: 2017.7.3
Default: ``False``.
Wheter the pillar data to be merged into the proxy configuration options.
As multiple proxies can run on the same server, we may need different
configuration options for each, while there's one single configuration file.
The solution is merging the pillar data of each proxy minion into the opts.
.. code-block:: yaml
proxy_merge_pillar_in_opts: True
``proxy_deep_merge_pillar_in_opts``
-----------------------------------
.. versionadded:: 2017.7.3
Default: ``False``.
Deep merge of pillar data into configuration opts.
This option is evaluated only when :conf_proxy:`proxy_merge_pillar_in_opts` is
enabled.
``proxy_merge_pillar_in_opts_strategy``
---------------------------------------
.. versionadded:: 2017.7.3
Default: ``smart``.
The strategy used when merging pillar configuration into opts.
This option is evaluated only when :conf_proxy:`proxy_merge_pillar_in_opts` is
enabled.
``proxy_mines_pillar``
----------------------
.. versionadded:: 2017.7.3
Default: ``True``.
Allow enabling mine details using pillar data. This evaluates the mine
configuration under the pillar, for the following regular minion options that
are also equally available on the proxy minion: :conf_minion:`mine_interval`,
and :conf_minion:`mine_functions`.

View File

@ -51,6 +51,19 @@ New NaCl Renderer
A new renderer has been added for encrypted data.
New support for Cisco UCS Chassis
---------------------------------
The salt proxy minion now allows for control of Cisco USC chassis. See
the `cimc` modules for details.
New salt-ssh roster
-------------------
A new roster has been added that allows users to pull in a list of hosts
for salt-ssh targeting from a ~/.ssh configuration. For full details,
please see the `sshconfig` roster.
New GitFS Features
------------------

View File

@ -481,11 +481,17 @@ Alternatively the ``uninstaller`` can also simply repeat the URL of the msi file
:param bool allusers: This parameter is specific to `.msi` installations. It
tells `msiexec` to install the software for all users. The default is True.
:param bool cache_dir: If true, the entire directory where the installer resides
will be recursively cached. This is useful for installers that depend on
other files in the same directory for installation.
:param bool cache_dir: If true when installer URL begins with salt://, the
entire directory where the installer resides will be recursively cached.
This is useful for installers that depend on other files in the same
directory for installation.
.. note:: Only applies to salt: installer URLs.
:param str cache_file:
When installer URL begins with salt://, this indicates single file to copy
down for use with the installer. Copied to the same location as the
installer. Use this over ``cache_dir`` if there are many files in the
directory and you only need a specific file and don't want to cache
additional files that may reside in the installer directory.
Here's an example for a software package that has dependent files:

View File

@ -44,7 +44,7 @@ ${StrStrAdv}
!define CPUARCH "x86"
!endif
; Part of the Trim function for Strings
# Part of the Trim function for Strings
!define Trim "!insertmacro Trim"
!macro Trim ResultVar String
Push "${String}"
@ -61,27 +61,27 @@ ${StrStrAdv}
!define MUI_UNICON "salt.ico"
!define MUI_WELCOMEFINISHPAGE_BITMAP "panel.bmp"
; Welcome page
# Welcome page
!insertmacro MUI_PAGE_WELCOME
; License page
# License page
!insertmacro MUI_PAGE_LICENSE "LICENSE.txt"
; Configure Minion page
# Configure Minion page
Page custom pageMinionConfig pageMinionConfig_Leave
; Instfiles page
# Instfiles page
!insertmacro MUI_PAGE_INSTFILES
; Finish page (Customized)
# Finish page (Customized)
!define MUI_PAGE_CUSTOMFUNCTION_SHOW pageFinish_Show
!define MUI_PAGE_CUSTOMFUNCTION_LEAVE pageFinish_Leave
!insertmacro MUI_PAGE_FINISH
; Uninstaller pages
# Uninstaller pages
!insertmacro MUI_UNPAGE_INSTFILES
; Language files
# Language files
!insertmacro MUI_LANGUAGE "English"
@ -201,8 +201,8 @@ ShowInstDetails show
ShowUnInstDetails show
; Check and install Visual C++ redist packages
; See http://blogs.msdn.com/b/astebner/archive/2009/01/29/9384143.aspx for more info
# Check and install Visual C++ redist packages
# See http://blogs.msdn.com/b/astebner/archive/2009/01/29/9384143.aspx for more info
Section -Prerequisites
Var /GLOBAL VcRedistName
@ -211,12 +211,12 @@ Section -Prerequisites
Var /Global CheckVcRedist
StrCpy $CheckVcRedist "False"
; Visual C++ 2015 redist packages
# 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_X64_GUID "{5FCE6D76-F5DC-37AB-B2B8-22AB8CEDB1D4}"
!define PY2_VC_REDIST_X86_GUID "{9BE518E6-ECC6-35A9-88E4-87755C07200F}"
@ -239,7 +239,7 @@ Section -Prerequisites
StrCpy $VcRedistGuid ${PY2_VC_REDIST_X86_GUID}
${EndIf}
; VCRedist 2008 only needed on Windows Server 2008R2/Windows 7 and below
# VCRedist 2008 only needed on Windows Server 2008R2/Windows 7 and below
${If} ${AtMostWin2008R2}
StrCpy $CheckVcRedist "True"
${EndIf}
@ -255,20 +255,41 @@ Section -Prerequisites
"$VcRedistName is currently not installed. Would you like to install?" \
/SD IDYES IDNO endVcRedist
ClearErrors
; The Correct version of VCRedist is copied over by "build_pkg.bat"
# The Correct version of VCRedist is copied over by "build_pkg.bat"
SetOutPath "$INSTDIR\"
File "..\prereqs\vcredist.exe"
; /passive used by 2015 installer
; /qb! used by 2008 installer
; It just ignores the unrecognized switches...
ExecWait "$INSTDIR\vcredist.exe /qb! /passive"
IfErrors 0 endVcRedist
# If an output variable is specified ($0 in the case below),
# ExecWait sets the variable with the exit code (and only sets the
# error flag if an error occurs; if an error occurs, the contents
# of the user variable are undefined).
# http://nsis.sourceforge.net/Reference/ExecWait
# /passive used by 2015 installer
# /qb! used by 2008 installer
# It just ignores the unrecognized switches...
ClearErrors
ExecWait '"$INSTDIR\vcredist.exe" /qb! /passive /norestart' $0
IfErrors 0 CheckVcRedistErrorCode
MessageBox MB_OK \
"$VcRedistName failed to install. Try installing the package manually." \
/SD IDOK
Goto endVcRedist
CheckVcRedistErrorCode:
# Check for Reboot Error Code (3010)
${If} $0 == 3010
MessageBox MB_OK \
"$VcRedistName installed but requires a restart to complete." \
/SD IDOK
# Check for any other errors
${ElseIfNot} $0 == 0
MessageBox MB_OK \
"$VcRedistName failed with ErrorCode: $0. Try installing the package manually." \
/SD IDOK
${EndIf}
endVcRedist:
${EndIf}
${EndIf}
@ -294,12 +315,12 @@ Function .onInit
Call parseCommandLineSwitches
; Check for existing installation
# Check for existing installation
ReadRegStr $R0 HKLM \
"Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
"UninstallString"
StrCmp $R0 "" checkOther
; Found existing installation, prompt to uninstall
# Found existing installation, prompt to uninstall
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION \
"${PRODUCT_NAME} is already installed.$\n$\n\
Click `OK` to remove the existing installation." \
@ -307,12 +328,12 @@ Function .onInit
Abort
checkOther:
; Check for existing installation of full salt
# Check for existing installation of full salt
ReadRegStr $R0 HKLM \
"Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME_OTHER}" \
"UninstallString"
StrCmp $R0 "" skipUninstall
; Found existing installation, prompt to uninstall
# Found existing installation, prompt to uninstall
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION \
"${PRODUCT_NAME_OTHER} is already installed.$\n$\n\
Click `OK` to remove the existing installation." \
@ -321,22 +342,22 @@ Function .onInit
uninst:
; Get current Silent status
# Get current Silent status
StrCpy $R0 0
${If} ${Silent}
StrCpy $R0 1
${EndIf}
; Turn on Silent mode
# Turn on Silent mode
SetSilent silent
; Don't remove all directories
# Don't remove all directories
StrCpy $DeleteInstallDir 0
; Uninstall silently
# Uninstall silently
Call uninstallSalt
; Set it back to Normal mode, if that's what it was before
# Set it back to Normal mode, if that's what it was before
${If} $R0 == 0
SetSilent normal
${EndIf}
@ -350,7 +371,7 @@ Section -Post
WriteUninstaller "$INSTDIR\uninst.exe"
; Uninstall Registry Entries
# Uninstall Registry Entries
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" \
"DisplayName" "$(^Name)"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" \
@ -366,19 +387,19 @@ Section -Post
WriteRegStr HKLM "SYSTEM\CurrentControlSet\services\salt-minion" \
"DependOnService" "nsi"
; Set the estimated size
# Set the estimated size
${GetSize} "$INSTDIR\bin" "/S=OK" $0 $1 $2
IntFmt $0 "0x%08X" $0
WriteRegDWORD ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" \
"EstimatedSize" "$0"
; Commandline Registry Entries
# Commandline Registry Entries
WriteRegStr HKLM "${PRODUCT_CALL_REGKEY}" "" "$INSTDIR\salt-call.bat"
WriteRegStr HKLM "${PRODUCT_CALL_REGKEY}" "Path" "$INSTDIR\bin\"
WriteRegStr HKLM "${PRODUCT_MINION_REGKEY}" "" "$INSTDIR\salt-minion.bat"
WriteRegStr HKLM "${PRODUCT_MINION_REGKEY}" "Path" "$INSTDIR\bin\"
; Register the Salt-Minion Service
# Register the Salt-Minion Service
nsExec::Exec "nssm.exe install salt-minion $INSTDIR\bin\python.exe -E -s $INSTDIR\bin\Scripts\salt-minion -c $INSTDIR\conf -l quiet"
nsExec::Exec "nssm.exe set salt-minion Description Salt Minion from saltstack.com"
nsExec::Exec "nssm.exe set salt-minion Start SERVICE_AUTO_START"
@ -398,12 +419,12 @@ SectionEnd
Function .onInstSuccess
; If StartMinionDelayed is 1, then set the service to start delayed
# If StartMinionDelayed is 1, then set the service to start delayed
${If} $StartMinionDelayed == 1
nsExec::Exec "nssm.exe set salt-minion Start SERVICE_DELAYED_AUTO_START"
${EndIf}
; If start-minion is 1, then start the service
# If start-minion is 1, then start the service
${If} $StartMinion == 1
nsExec::Exec 'net start salt-minion'
${EndIf}
@ -413,10 +434,11 @@ FunctionEnd
Function un.onInit
; Load the parameters
# Load the parameters
${GetParameters} $R0
# Uninstaller: Remove Installation Directory
ClearErrors
${GetOptions} $R0 "/delete-install-dir" $R1
IfErrors delete_install_dir_not_found
StrCpy $DeleteInstallDir 1
@ -434,7 +456,7 @@ Section Uninstall
Call un.uninstallSalt
; Remove C:\salt from the Path
# Remove C:\salt from the Path
Push "C:\salt"
Call un.RemoveFromPath
@ -444,27 +466,27 @@ SectionEnd
!macro uninstallSalt un
Function ${un}uninstallSalt
; Make sure we're in the right directory
# Make sure we're in the right directory
${If} $INSTDIR == "c:\salt\bin\Scripts"
StrCpy $INSTDIR "C:\salt"
${EndIf}
; Stop and Remove salt-minion service
# Stop and Remove salt-minion service
nsExec::Exec 'net stop salt-minion'
nsExec::Exec 'sc delete salt-minion'
; Stop and remove the salt-master service
# Stop and remove the salt-master service
nsExec::Exec 'net stop salt-master'
nsExec::Exec 'sc delete salt-master'
; Remove files
# Remove files
Delete "$INSTDIR\uninst.exe"
Delete "$INSTDIR\nssm.exe"
Delete "$INSTDIR\salt*"
Delete "$INSTDIR\vcredist.exe"
RMDir /r "$INSTDIR\bin"
; Remove Registry entries
# Remove Registry entries
DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}"
DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY_OTHER}"
DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_CALL_REGKEY}"
@ -474,17 +496,17 @@ Function ${un}uninstallSalt
DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_MINION_REGKEY}"
DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_RUN_REGKEY}"
; Automatically close when finished
# Automatically close when finished
SetAutoClose true
; Prompt to remove the Installation directory
# Prompt to remove the Installation directory
${IfNot} $DeleteInstallDir == 1
MessageBox MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON2 \
"Would you like to completely remove $INSTDIR and all of its contents?" \
/SD IDNO IDNO finished
${EndIf}
; Make sure you're not removing Program Files
# Make sure you're not removing Program Files
${If} $INSTDIR != 'Program Files'
${AndIf} $INSTDIR != 'Program Files (x86)'
RMDir /r "$INSTDIR"
@ -526,7 +548,7 @@ FunctionEnd
Function Trim
Exch $R1 ; Original string
Exch $R1 # Original string
Push $R2
Loop:
@ -558,36 +580,36 @@ Function Trim
FunctionEnd
;------------------------------------------------------------------------------
; StrStr Function
; - find substring in a string
;
; Usage:
; Push "this is some string"
; Push "some"
; Call StrStr
; Pop $0 ; "some string"
;------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# StrStr Function
# - find substring in a string
#
# Usage:
# Push "this is some string"
# Push "some"
# Call StrStr
# Pop $0 ; "some string"
#------------------------------------------------------------------------------
!macro StrStr un
Function ${un}StrStr
Exch $R1 ; $R1=substring, stack=[old$R1,string,...]
Exch ; stack=[string,old$R1,...]
Exch $R2 ; $R2=string, stack=[old$R2,old$R1,...]
Push $R3 ; $R3=strlen(substring)
Push $R4 ; $R4=count
Push $R5 ; $R5=tmp
StrLen $R3 $R1 ; Get the length of the Search String
StrCpy $R4 0 ; Set the counter to 0
Exch $R1 # $R1=substring, stack=[old$R1,string,...]
Exch # stack=[string,old$R1,...]
Exch $R2 # $R2=string, stack=[old$R2,old$R1,...]
Push $R3 # $R3=strlen(substring)
Push $R4 # $R4=count
Push $R5 # $R5=tmp
StrLen $R3 $R1 # Get the length of the Search String
StrCpy $R4 0 # Set the counter to 0
loop:
StrCpy $R5 $R2 $R3 $R4 ; Create a moving window of the string that is
; the size of the length of the search string
StrCmp $R5 $R1 done ; Is the contents of the window the same as
; search string, then done
StrCmp $R5 "" done ; Is the window empty, then done
IntOp $R4 $R4 + 1 ; Shift the windows one character
Goto loop ; Repeat
StrCpy $R5 $R2 $R3 $R4 # Create a moving window of the string that is
# the size of the length of the search string
StrCmp $R5 $R1 done # Is the contents of the window the same as
# search string, then done
StrCmp $R5 "" done # Is the window empty, then done
IntOp $R4 $R4 + 1 # Shift the windows one character
Goto loop # Repeat
done:
StrCpy $R1 $R2 "" $R4
@ -595,7 +617,7 @@ Function ${un}StrStr
Pop $R4
Pop $R3
Pop $R2
Exch $R1 ; $R1=old$R1, stack=[result,...]
Exch $R1 # $R1=old$R1, stack=[result,...]
FunctionEnd
!macroend
@ -603,74 +625,74 @@ FunctionEnd
!insertmacro StrStr "un."
;------------------------------------------------------------------------------
; AddToPath Function
; - Adds item to Path for All Users
; - Overcomes NSIS ReadRegStr limitation of 1024 characters by using Native
; Windows Commands
;
; Usage:
; Push "C:\path\to\add"
; Call AddToPath
;------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# AddToPath Function
# - Adds item to Path for All Users
# - Overcomes NSIS ReadRegStr limitation of 1024 characters by using Native
# Windows Commands
#
# Usage:
# Push "C:\path\to\add"
# Call AddToPath
#------------------------------------------------------------------------------
!define Environ 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"'
Function AddToPath
Exch $0 ; Path to add
Push $1 ; Current Path
Push $2 ; Results of StrStr / Length of Path + Path to Add
Push $3 ; Handle to Reg / Length of Path
Push $4 ; Result of Registry Call
Exch $0 # Path to add
Push $1 # Current Path
Push $2 # Results of StrStr / Length of Path + Path to Add
Push $3 # Handle to Reg / Length of Path
Push $4 # Result of Registry Call
; Open a handle to the key in the registry, handle in $3, Error in $4
# Open a handle to the key in the registry, handle in $3, Error in $4
System::Call "advapi32::RegOpenKey(i 0x80000002, t'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', *i.r3) i.r4"
; Make sure registry handle opened successfully (returned 0)
# Make sure registry handle opened successfully (returned 0)
IntCmp $4 0 0 done done
; Load the contents of path into $1, Error Code into $4, Path length into $2
# Load the contents of path into $1, Error Code into $4, Path length into $2
System::Call "advapi32::RegQueryValueEx(i $3, t'PATH', i 0, i 0, t.r1, *i ${NSIS_MAX_STRLEN} r2) i.r4"
; Close the handle to the registry ($3)
# Close the handle to the registry ($3)
System::Call "advapi32::RegCloseKey(i $3)"
; Check for Error Code 234, Path too long for the variable
IntCmp $4 234 0 +4 +4 ; $4 == ERROR_MORE_DATA
# Check for Error Code 234, Path too long for the variable
IntCmp $4 234 0 +4 +4 # $4 == ERROR_MORE_DATA
DetailPrint "AddToPath Failed: original length $2 > ${NSIS_MAX_STRLEN}"
MessageBox MB_OK \
"You may add C:\salt to the %PATH% for convenience when issuing local salt commands from the command line." \
/SD IDOK
Goto done
; If no error, continue
IntCmp $4 0 +5 ; $4 != NO_ERROR
; Error 2 means the Key was not found
IntCmp $4 2 +3 ; $4 != ERROR_FILE_NOT_FOUND
# If no error, continue
IntCmp $4 0 +5 # $4 != NO_ERROR
# Error 2 means the Key was not found
IntCmp $4 2 +3 # $4 != ERROR_FILE_NOT_FOUND
DetailPrint "AddToPath: unexpected error code $4"
Goto done
StrCpy $1 ""
; Check if already in PATH
Push "$1;" ; The string to search
Push "$0;" ; The string to find
# Check if already in PATH
Push "$1;" # The string to search
Push "$0;" # The string to find
Call StrStr
Pop $2 ; The result of the search
StrCmp $2 "" 0 done ; String not found, try again with ';' at the end
; Otherwise, it's already in the path
Push "$1;" ; The string to search
Push "$0\;" ; The string to find
Pop $2 # The result of the search
StrCmp $2 "" 0 done # String not found, try again with ';' at the end
# Otherwise, it's already in the path
Push "$1;" # The string to search
Push "$0\;" # The string to find
Call StrStr
Pop $2 ; The result
StrCmp $2 "" 0 done ; String not found, continue (add)
; Otherwise, it's already in the path
Pop $2 # The result
StrCmp $2 "" 0 done # String not found, continue (add)
# Otherwise, it's already in the path
; Prevent NSIS string overflow
StrLen $2 $0 ; Length of path to add ($2)
StrLen $3 $1 ; Length of current path ($3)
IntOp $2 $2 + $3 ; Length of current path + path to add ($2)
IntOp $2 $2 + 2 ; Account for the additional ';'
; $2 = strlen(dir) + strlen(PATH) + sizeof(";")
# Prevent NSIS string overflow
StrLen $2 $0 # Length of path to add ($2)
StrLen $3 $1 # Length of current path ($3)
IntOp $2 $2 + $3 # Length of current path + path to add ($2)
IntOp $2 $2 + 2 # Account for the additional ';'
# $2 = strlen(dir) + strlen(PATH) + sizeof(";")
; Make sure the new length isn't over the NSIS_MAX_STRLEN
# Make sure the new length isn't over the NSIS_MAX_STRLEN
IntCmp $2 ${NSIS_MAX_STRLEN} +4 +4 0
DetailPrint "AddToPath: new length $2 > ${NSIS_MAX_STRLEN}"
MessageBox MB_OK \
@ -678,18 +700,18 @@ Function AddToPath
/SD IDOK
Goto done
; Append dir to PATH
# Append dir to PATH
DetailPrint "Add to PATH: $0"
StrCpy $2 $1 1 -1 ; Copy the last character of the existing path
StrCmp $2 ";" 0 +2 ; Check for trailing ';'
StrCpy $1 $1 -1 ; remove trailing ';'
StrCmp $1 "" +2 ; Make sure Path is not empty
StrCpy $0 "$1;$0" ; Append new path at the end ($0)
StrCpy $2 $1 1 -1 # Copy the last character of the existing path
StrCmp $2 ";" 0 +2 # Check for trailing ';'
StrCpy $1 $1 -1 # remove trailing ';'
StrCmp $1 "" +2 # Make sure Path is not empty
StrCpy $0 "$1;$0" # Append new path at the end ($0)
; We can use the NSIS command here. Only 'ReadRegStr' is affected
# We can use the NSIS command here. Only 'ReadRegStr' is affected
WriteRegExpandStr ${Environ} "PATH" $0
; Broadcast registry change to open programs
# Broadcast registry change to open programs
SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
done:
@ -702,16 +724,16 @@ Function AddToPath
FunctionEnd
;------------------------------------------------------------------------------
; RemoveFromPath Function
; - Removes item from Path for All Users
; - Overcomes NSIS ReadRegStr limitation of 1024 characters by using Native
; Windows Commands
;
; Usage:
; Push "C:\path\to\add"
; Call un.RemoveFromPath
;------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# RemoveFromPath Function
# - Removes item from Path for All Users
# - Overcomes NSIS ReadRegStr limitation of 1024 characters by using Native
# Windows Commands
#
# Usage:
# Push "C:\path\to\add"
# Call un.RemoveFromPath
#------------------------------------------------------------------------------
Function un.RemoveFromPath
Exch $0
@ -722,59 +744,59 @@ Function un.RemoveFromPath
Push $5
Push $6
; Open a handle to the key in the registry, handle in $3, Error in $4
# Open a handle to the key in the registry, handle in $3, Error in $4
System::Call "advapi32::RegOpenKey(i 0x80000002, t'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', *i.r3) i.r4"
; Make sure registry handle opened successfully (returned 0)
# Make sure registry handle opened successfully (returned 0)
IntCmp $4 0 0 done done
; Load the contents of path into $1, Error Code into $4, Path length into $2
# Load the contents of path into $1, Error Code into $4, Path length into $2
System::Call "advapi32::RegQueryValueEx(i $3, t'PATH', i 0, i 0, t.r1, *i ${NSIS_MAX_STRLEN} r2) i.r4"
; Close the handle to the registry ($3)
# Close the handle to the registry ($3)
System::Call "advapi32::RegCloseKey(i $3)"
; Check for Error Code 234, Path too long for the variable
IntCmp $4 234 0 +4 +4 ; $4 == ERROR_MORE_DATA
# Check for Error Code 234, Path too long for the variable
IntCmp $4 234 0 +4 +4 # $4 == ERROR_MORE_DATA
DetailPrint "AddToPath: original length $2 > ${NSIS_MAX_STRLEN}"
Goto done
; If no error, continue
IntCmp $4 0 +5 ; $4 != NO_ERROR
; Error 2 means the Key was not found
IntCmp $4 2 +3 ; $4 != ERROR_FILE_NOT_FOUND
# If no error, continue
IntCmp $4 0 +5 # $4 != NO_ERROR
# Error 2 means the Key was not found
IntCmp $4 2 +3 # $4 != ERROR_FILE_NOT_FOUND
DetailPrint "AddToPath: unexpected error code $4"
Goto done
StrCpy $1 ""
; Ensure there's a trailing ';'
StrCpy $5 $1 1 -1 ; Copy the last character of the path
StrCmp $5 ";" +2 ; Check for trailing ';', if found continue
StrCpy $1 "$1;" ; ensure trailing ';'
# Ensure there's a trailing ';'
StrCpy $5 $1 1 -1 # Copy the last character of the path
StrCmp $5 ";" +2 # Check for trailing ';', if found continue
StrCpy $1 "$1;" # ensure trailing ';'
; Check for our directory inside the path
Push $1 ; String to Search
Push "$0;" ; Dir to Find
# Check for our directory inside the path
Push $1 # String to Search
Push "$0;" # Dir to Find
Call un.StrStr
Pop $2 ; The results of the search
StrCmp $2 "" done ; If results are empty, we're done, otherwise continue
Pop $2 # The results of the search
StrCmp $2 "" done # If results are empty, we're done, otherwise continue
; Remove our Directory from the Path
# Remove our Directory from the Path
DetailPrint "Remove from PATH: $0"
StrLen $3 "$0;" ; Get the length of our dir ($3)
StrLen $4 $2 ; Get the length of the return from StrStr ($4)
StrCpy $5 $1 -$4 ; $5 is now the part before the path to remove
StrCpy $6 $2 "" $3 ; $6 is now the part after the path to remove
StrCpy $3 "$5$6" ; Combine $5 and $6
StrLen $3 "$0;" # Get the length of our dir ($3)
StrLen $4 $2 # Get the length of the return from StrStr ($4)
StrCpy $5 $1 -$4 # $5 is now the part before the path to remove
StrCpy $6 $2 "" $3 # $6 is now the part after the path to remove
StrCpy $3 "$5$6" # Combine $5 and $6
; Check for Trailing ';'
StrCpy $5 $3 1 -1 ; Load the last character of the string
StrCmp $5 ";" 0 +2 ; Check for ';'
StrCpy $3 $3 -1 ; remove trailing ';'
# Check for Trailing ';'
StrCpy $5 $3 1 -1 # Load the last character of the string
StrCmp $5 ";" 0 +2 # Check for ';'
StrCpy $3 $3 -1 # remove trailing ';'
; Write the new path to the registry
# Write the new path to the registry
WriteRegExpandStr ${Environ} "PATH" $3
; Broadcast the change to all open applications
# Broadcast the change to all open applications
SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
done:
@ -808,6 +830,7 @@ Function getMinionConfig
confFound:
FileOpen $0 "$INSTDIR\conf\minion" r
ClearErrors
confLoop:
FileRead $0 $1
IfErrors EndOfFile
@ -838,68 +861,69 @@ FunctionEnd
Function updateMinionConfig
ClearErrors
FileOpen $0 "$INSTDIR\conf\minion" "r" ; open target file for reading
GetTempFileName $R0 ; get new temp file name
FileOpen $1 $R0 "w" ; open temp file for writing
FileOpen $0 "$INSTDIR\conf\minion" "r" # open target file for reading
GetTempFileName $R0 # get new temp file name
FileOpen $1 $R0 "w" # open temp file for writing
loop: ; loop through each line
FileRead $0 $2 ; read line from target file
IfErrors done ; end if errors are encountered (end of line)
loop: # loop through each line
FileRead $0 $2 # read line from target file
IfErrors done # end if errors are encountered (end of line)
${If} $MasterHost_State != "" ; if master is empty
${AndIf} $MasterHost_State != "salt" ; and if master is not 'salt'
${StrLoc} $3 $2 "master:" ">" ; where is 'master:' in this line
${If} $3 == 0 ; is it in the first...
${OrIf} $3 == 1 ; or second position (account for comments)
StrCpy $2 "master: $MasterHost_State$\r$\n" ; write the master
${EndIf} ; close if statement
${EndIf} ; close if statement
${If} $MasterHost_State != "" # if master is empty
${AndIf} $MasterHost_State != "salt" # and if master is not 'salt'
${StrLoc} $3 $2 "master:" ">" # where is 'master:' in this line
${If} $3 == 0 # is it in the first...
${OrIf} $3 == 1 # or second position (account for comments)
StrCpy $2 "master: $MasterHost_State$\r$\n" # write the master
${EndIf} # close if statement
${EndIf} # close if statement
${If} $MinionName_State != "" ; if minion is empty
${AndIf} $MinionName_State != "hostname" ; and if minion is not 'hostname'
${StrLoc} $3 $2 "id:" ">" ; where is 'id:' in this line
${If} $3 == 0 ; is it in the first...
${OrIf} $3 == 1 ; or the second position (account for comments)
StrCpy $2 "id: $MinionName_State$\r$\n" ; change line
${EndIf} ; close if statement
${EndIf} ; close if statement
${If} $MinionName_State != "" # if minion is empty
${AndIf} $MinionName_State != "hostname" # and if minion is not 'hostname'
${StrLoc} $3 $2 "id:" ">" # where is 'id:' in this line
${If} $3 == 0 # is it in the first...
${OrIf} $3 == 1 # or the second position (account for comments)
StrCpy $2 "id: $MinionName_State$\r$\n" # change line
${EndIf} # close if statement
${EndIf} # close if statement
FileWrite $1 $2 ; write changed or unchanged line to temp file
FileWrite $1 $2 # write changed or unchanged line to temp file
Goto loop
done:
FileClose $0 ; close target file
FileClose $1 ; close temp file
Delete "$INSTDIR\conf\minion" ; delete target file
CopyFiles /SILENT $R0 "$INSTDIR\conf\minion" ; copy temp file to target file
Delete $R0 ; delete temp file
FileClose $0 # close target file
FileClose $1 # close temp file
Delete "$INSTDIR\conf\minion" # delete target file
CopyFiles /SILENT $R0 "$INSTDIR\conf\minion" # copy temp file to target file
Delete $R0 # delete temp file
FunctionEnd
Function parseCommandLineSwitches
; Load the parameters
# Load the parameters
${GetParameters} $R0
; Check for start-minion switches
; /start-service is to be deprecated, so we must check for both
# Check for start-minion switches
# /start-service is to be deprecated, so we must check for both
${GetOptions} $R0 "/start-service=" $R1
${GetOptions} $R0 "/start-minion=" $R2
# Service: Start Salt Minion
${IfNot} $R2 == ""
; If start-minion was passed something, then set it
# If start-minion was passed something, then set it
StrCpy $StartMinion $R2
${ElseIfNot} $R1 == ""
; If start-service was passed something, then set StartMinion to that
# If start-service was passed something, then set StartMinion to that
StrCpy $StartMinion $R1
${Else}
; Otherwise default to 1
# Otherwise default to 1
StrCpy $StartMinion 1
${EndIf}
# Service: Minion Startup Type Delayed
ClearErrors
${GetOptions} $R0 "/start-minion-delayed" $R1
IfErrors start_minion_delayed_not_found
StrCpy $StartMinionDelayed 1

View File

@ -293,29 +293,31 @@ class LoadAuth(object):
def authenticate_key(self, load, key):
'''
Authenticate a user by the key passed in load.
Return the effective user id (name) if it's differ from the specified one (for sudo).
If the effective user id is the same as passed one return True on success or False on
Return the effective user id (name) if it's different from the specified one (for sudo).
If the effective user id is the same as the passed one, return True on success or False on
failure.
'''
auth_key = load.pop('key')
if not auth_key:
log.warning('Authentication failure of type "user" occurred.')
error_msg = 'Authentication failure of type "user" occurred.'
auth_key = load.pop('key', None)
if auth_key is None:
log.warning(error_msg)
return False
if 'user' in load:
auth_user = AuthUser(load['user'])
if auth_user.is_sudo():
# If someone sudos check to make sure there is no ACL's around their username
if auth_key != key[self.opts.get('user', 'root')]:
log.warning('Authentication failure of type "user" occurred.')
log.warning(error_msg)
return False
return auth_user.sudo_name()
elif load['user'] == self.opts.get('user', 'root') or load['user'] == 'root':
if auth_key != key[self.opts.get('user', 'root')]:
log.warning('Authentication failure of type "user" occurred.')
log.warning(error_msg)
return False
elif auth_user.is_running_user():
if auth_key != key.get(load['user']):
log.warning('Authentication failure of type "user" occurred.')
log.warning(error_msg)
return False
elif auth_key == key.get('root'):
pass
@ -323,15 +325,15 @@ class LoadAuth(object):
if load['user'] in key:
# User is authorised, check key and check perms
if auth_key != key[load['user']]:
log.warning('Authentication failure of type "user" occurred.')
log.warning(error_msg)
return False
return load['user']
else:
log.warning('Authentication failure of type "user" occurred.')
log.warning(error_msg)
return False
else:
if auth_key != key[salt.utils.get_user()]:
log.warning('Authentication failure of type "other" occurred.')
log.warning(error_msg)
return False
return True
@ -413,6 +415,64 @@ class LoadAuth(object):
return auth_list
def check_authentication(self, load, auth_type, key=None, show_username=False):
'''
.. versionadded:: Oxygen
Go through various checks to see if the token/eauth/user can be authenticated.
Returns a dictionary containing the following keys:
- auth_list
- username
- error
If an error is encountered, return immediately with the relevant error dictionary
as authentication has failed. Otherwise, return the username and valid auth_list.
'''
auth_list = []
username = load.get('username', 'UNKNOWN')
ret = {'auth_list': auth_list,
'username': username,
'error': {}}
# Authenticate
if auth_type == 'token':
token = self.authenticate_token(load)
if not token:
ret['error'] = {'name': 'TokenAuthenticationError',
'message': 'Authentication failure of type "token" occurred.'}
return ret
# Update username for token
username = token['name']
ret['username'] = username
auth_list = self.get_auth_list(load, token=token)
elif auth_type == 'eauth':
if not self.authenticate_eauth(load):
ret['error'] = {'name': 'EauthAuthenticationError',
'message': 'Authentication failure of type "eauth" occurred for '
'user {0}.'.format(username)}
return ret
auth_list = self.get_auth_list(load)
elif auth_type == 'user':
if not self.authenticate_key(load, key):
if show_username:
msg = 'Authentication failure of type "user" occurred for user {0}.'.format(username)
else:
msg = 'Authentication failure of type "user" occurred'
ret['error'] = {'name': 'UserAuthenticationError', 'message': msg}
return ret
else:
ret['error'] = {'name': 'SaltInvocationError',
'message': 'Authentication type not supported.'}
return ret
# Authentication checks passed
ret['auth_list'] = auth_list
return ret
class Authorize(object):
'''
@ -558,6 +618,15 @@ class Authorize(object):
load.get('arg', None),
load.get('tgt', None),
load.get('tgt_type', 'glob'))
# Handle possible return of dict data structure from any_auth call to
# avoid a stacktrace. As mentioned in PR #43181, this entire class is
# dead code and is marked for removal in Salt Neon. But until then, we
# should handle the dict return, which is an error and should return
# False until this class is removed.
if isinstance(good, dict):
return False
if not good:
# Accept find_job so the CLI will function cleanly
if load.get('fun', '') != 'saltutil.find_job':
@ -570,7 +639,7 @@ class Authorize(object):
authorization
Note: this will check that the user has at least one right that will let
him execute "load", this does not deal with conflicting rules
the user execute "load", this does not deal with conflicting rules
'''
adata = self.auth_data

View File

@ -205,10 +205,6 @@ class Beacon(object):
'''
# Fire the complete event back along with the list of beacons
evt = salt.utils.event.get_event('minion', opts=self.opts)
b_conf = self.functions['config.merge']('beacons')
if not isinstance(self.opts['beacons'], dict):
self.opts['beacons'] = {}
self.opts['beacons'].update(b_conf)
evt.fire_event({'complete': True, 'beacons': self.opts['beacons']},
tag='/salt/minion/minion_beacons_list_complete')

View File

@ -44,9 +44,6 @@ from salt.exceptions import (
SaltCloudSystemExit
)
# Import Salt-Cloud Libs
import salt.utils.cloud
# Get logging started
log = logging.getLogger(__name__)
@ -1193,7 +1190,7 @@ def list_nodes_select(call=None):
'''
Return a list of the VMs that are on the provider, with select fields.
'''
return salt.utils.cloud.list_nodes_select(
return __utils__['cloud.list_nodes_select'](
list_nodes_full(), __opts__['query.selection'], call,
)
@ -1503,7 +1500,7 @@ def _query(action=None,
if LASTCALL >= now:
time.sleep(ratelimit_sleep)
result = salt.utils.http.query(
result = __utils__['http.query'](
url,
method,
params=args,

View File

@ -343,7 +343,7 @@ VALID_OPTS = {
# Whether or not scheduled mine updates should be accompanied by a job return for the job cache
'mine_return_job': bool,
# Schedule a mine update every n number of seconds
# The number of minutes between mine updates.
'mine_interval': int,
# The ipc strategy. (i.e., sockets versus tcp, etc)
@ -590,6 +590,23 @@ VALID_OPTS = {
# False in 2016.3.0
'add_proxymodule_to_opts': bool,
# Merge pillar data into configuration opts.
# As multiple proxies can run on the same server, we may need different
# configuration options for each, while there's one single configuration file.
# The solution is merging the pillar data of each proxy minion into the opts.
'proxy_merge_pillar_in_opts': bool,
# Deep merge of pillar data into configuration opts.
# Evaluated only when `proxy_merge_pillar_in_opts` is True.
'proxy_deep_merge_pillar_in_opts': bool,
# The strategy used when merging pillar into opts.
# Considered only when `proxy_merge_pillar_in_opts` is True.
'proxy_merge_pillar_in_opts_strategy': str,
# Allow enabling mine details using pillar data.
'proxy_mines_pillar': bool,
# In some particular cases, always alive proxies are not beneficial.
# This option can be used in those less dynamic environments:
# the user can request the connection
@ -1700,6 +1717,12 @@ DEFAULT_PROXY_MINION_OPTS = {
'append_minionid_config_dirs': ['cachedir', 'pidfile', 'default_include', 'extension_modules'],
'default_include': 'proxy.d/*.conf',
'proxy_merge_pillar_in_opts': False,
'proxy_deep_merge_pillar_in_opts': False,
'proxy_merge_pillar_in_opts_strategy': 'smart',
'proxy_mines_pillar': True,
# By default, proxies will preserve the connection.
# If this option is set to False,
# the connection with the remote dumb device

View File

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
'''
:codeauthor: :email:`Alexandru Bleotu (alexandru.bleotu@morganstanley.com)`
salt.config.schemas.esxcluster
~~~~~~~~~~~~~~~~~~~~~~~
ESX Cluster configuration schemas
'''
# Import Python libs
from __future__ import absolute_import
# Import Salt libs
from salt.utils.schema import (Schema,
ArrayItem,
IntegerItem,
StringItem)
class EsxclusterProxySchema(Schema):
'''
Schema of the esxcluster proxy input
'''
title = 'Esxcluster Proxy Schema'
description = 'Esxcluster proxy schema'
additional_properties = False
proxytype = StringItem(required=True,
enum=['esxcluster'])
vcenter = StringItem(required=True, pattern=r'[^\s]+')
datacenter = StringItem(required=True)
cluster = StringItem(required=True)
mechanism = StringItem(required=True, enum=['userpass', 'sspi'])
username = StringItem()
passwords = ArrayItem(min_items=1,
items=StringItem(),
unique_items=True)
# TODO Should be changed when anyOf is supported for schemas
domain = StringItem()
principal = StringItem()
protocol = StringItem()
port = IntegerItem(minimum=1)

View File

@ -1013,30 +1013,33 @@ class LocalFuncs(object):
'''
Send a master control function back to the runner system
'''
if 'token' in load:
auth_type = 'token'
err_name = 'TokenAuthenticationError'
token = self.loadauth.authenticate_token(load)
if not token:
return dict(error=dict(name=err_name,
message='Authentication failure of type "token" occurred.'))
username = token['name']
auth_list = self.loadauth.get_auth_list(load, token)
else:
auth_type = 'eauth'
err_name = 'EauthAuthenticationError'
username = load.get('username', 'UNKNOWN')
if not self.loadauth.authenticate_eauth(load):
return dict(error=dict(name=err_name,
message=('Authentication failure of type "eauth" occurred '
'for user {0}.').format(username)))
auth_list = self.loadauth.get_auth_list(load)
# All runner opts pass through eauth
auth_type, err_name, key = self._prep_auth_info(load)
if not self.ckminions.runner_check(auth_list, load['fun'], load['kwarg']):
return dict(error=dict(name=err_name,
message=('Authentication failure of type "{0}" occurred '
'for user {1}.').format(auth_type, username)))
# Authenticate
auth_check = self.loadauth.check_authentication(load, auth_type)
error = auth_check.get('error')
if error:
# Authentication error occurred: do not continue.
return {'error': error}
# Authorize
runner_check = self.ckminions.runner_check(
auth_check.get('auth_list', []),
load['fun'],
load['kwarg']
)
username = auth_check.get('username')
if not runner_check:
return {'error': {'name': err_name,
'message': 'Authentication failure of type "{0}" occurred '
'for user {1}.'.format(auth_type, username)}}
elif isinstance(runner_check, dict) and 'error' in runner_check:
# A dictionary with an error name/message was handled by ckminions.runner_check
return runner_check
# Authorized. Do the job!
try:
fun = load.pop('fun')
runner_client = salt.runner.RunnerClient(self.opts)
@ -1045,48 +1048,46 @@ class LocalFuncs(object):
username)
except Exception as exc:
log.error('Exception occurred while '
'introspecting {0}: {1}'.format(fun, exc))
return dict(error=dict(name=exc.__class__.__name__,
args=exc.args,
message=str(exc)))
'introspecting {0}: {1}'.format(fun, exc))
return {'error': {'name': exc.__class__.__name__,
'args': exc.args,
'message': str(exc)}}
def wheel(self, load):
'''
Send a master control function back to the wheel system
'''
# All wheel ops pass through eauth
if 'token' in load:
auth_type = 'token'
err_name = 'TokenAuthenticationError'
token = self.loadauth.authenticate_token(load)
if not token:
return dict(error=dict(name=err_name,
message='Authentication failure of type "token" occurred.'))
username = token['name']
auth_list = self.loadauth.get_auth_list(load, token)
elif 'eauth' in load:
auth_type = 'eauth'
err_name = 'EauthAuthenticationError'
username = load.get('username', 'UNKNOWN')
if not self.loadauth.authenticate_eauth(load):
return dict(error=dict(name=err_name,
message=('Authentication failure of type "eauth" occurred for '
'user {0}.').format(username)))
auth_list = self.loadauth.get_auth_list(load)
else:
auth_type = 'user'
err_name = 'UserAuthenticationError'
username = load.get('username', 'UNKNOWN')
if not self.loadauth.authenticate_key(load, self.key):
return dict(error=dict(name=err_name,
message=('Authentication failure of type "user" occurred for '
'user {0}.').format(username)))
auth_type, err_name, key = self._prep_auth_info(load)
# Authenticate
auth_check = self.loadauth.check_authentication(
load,
auth_type,
key=key,
show_username=True
)
error = auth_check.get('error')
if error:
# Authentication error occurred: do not continue.
return {'error': error}
# Authorize
username = auth_check.get('username')
if auth_type != 'user':
if not self.ckminions.wheel_check(auth_list, load['fun'], load['kwarg']):
return dict(error=dict(name=err_name,
message=('Authentication failure of type "{0}" occurred for '
'user {1}.').format(auth_type, username)))
wheel_check = self.ckminions.wheel_check(
auth_check.get('auth_list', []),
load['fun'],
load['kwarg']
)
if not wheel_check:
return {'error': {'name': err_name,
'message': 'Authentication failure of type "{0}" occurred for '
'user {1}.'.format(auth_type, username)}}
elif isinstance(wheel_check, dict) and 'error' in wheel_check:
# A dictionary with an error name/message was handled by ckminions.wheel_check
return wheel_check
# Authenticated. Do the job.
jid = salt.utils.jid.gen_jid(self.opts)
@ -1106,7 +1107,7 @@ class LocalFuncs(object):
'data': data}
except Exception as exc:
log.error('Exception occurred while '
'introspecting {0}: {1}'.format(fun, exc))
'introspecting {0}: {1}'.format(fun, exc))
data['return'] = 'Exception occurred in wheel {0}: {1}: {2}'.format(
fun,
exc.__class__.__name__,
@ -1371,3 +1372,18 @@ class LocalFuncs(object):
},
'pub': pub_load
}
def _prep_auth_info(self, load):
key = None
if 'token' in load:
auth_type = 'token'
err_name = 'TokenAuthenticationError'
elif 'eauth' in load:
auth_type = 'eauth'
err_name = 'EauthAuthenticationError'
else:
auth_type = 'user'
err_name = 'UserAuthenticationError'
key = self.key
return auth_type, err_name, key

View File

@ -1295,10 +1295,10 @@ class RemoteClient(Client):
hash_type = self.opts.get(u'hash_type', u'md5')
ret[u'hsum'] = salt.utils.get_hash(path, form=hash_type)
ret[u'hash_type'] = hash_type
return ret, list(os.stat(path))
return ret
load = {u'path': path,
u'saltenv': saltenv,
u'cmd': u'_file_hash_and_stat'}
u'cmd': u'_file_hash'}
return self.channel.send(load)
def hash_file(self, path, saltenv=u'base'):
@ -1307,14 +1307,33 @@ class RemoteClient(Client):
master file server prepend the path with salt://<file on server>
otherwise, prepend the file with / for a local file.
'''
return self.__hash_and_stat_file(path, saltenv)[0]
return self.__hash_and_stat_file(path, saltenv)
def hash_and_stat_file(self, path, saltenv=u'base'):
'''
The same as hash_file, but also return the file's mode, or None if no
mode data is present.
'''
return self.__hash_and_stat_file(path, saltenv)
hash_result = self.hash_file(path, saltenv)
try:
path = self._check_proto(path)
except MinionError as err:
if not os.path.isfile(path):
return hash_result, None
else:
try:
return hash_result, list(os.stat(path))
except Exception:
return hash_result, None
load = {'path': path,
'saltenv': saltenv,
'cmd': '_file_find'}
fnd = self.channel.send(load)
try:
stat_result = fnd.get('stat')
except AttributeError:
stat_result = None
return hash_result, stat_result
def list_env(self, saltenv=u'base'):
'''

View File

@ -270,7 +270,7 @@ def raw_mod(opts, name, functions, mod=u'modules'):
testmod['test.ping']()
'''
loader = LazyLoader(
_module_dirs(opts, mod, u'rawmodule'),
_module_dirs(opts, mod, u'module'),
opts,
tag=u'rawmodule',
virtual_enable=False,

View File

@ -1668,44 +1668,36 @@ class ClearFuncs(object):
Send a master control function back to the runner system
'''
# All runner ops pass through eauth
if u'token' in clear_load:
# Authenticate
token = self.loadauth.authenticate_token(clear_load)
auth_type, err_name, key, sensitive_load_keys = self._prep_auth_info(clear_load)
if not token:
return dict(error=dict(name=u'TokenAuthenticationError',
message=u'Authentication failure of type "token" occurred.'))
# Authenticate
auth_check = self.loadauth.check_authentication(clear_load, auth_type, key=key)
error = auth_check.get(u'error')
# Authorize
auth_list = self.loadauth.get_auth_list(clear_load, token)
if error:
# Authentication error occurred: do not continue.
return {u'error': error}
if not self.ckminions.runner_check(auth_list, clear_load[u'fun'], clear_load.get(u'kwarg', {})):
return dict(error=dict(name=u'TokenAuthenticationError',
message=(u'Authentication failure of type "token" occurred for '
u'user {0}.').format(token[u'name'])))
clear_load.pop(u'token')
username = token[u'name']
elif u'eauth' in clear_load:
if not self.loadauth.authenticate_eauth(clear_load):
return dict(error=dict(name=u'EauthAuthenticationError',
message=(u'Authentication failure of type "eauth" occurred for '
u'user {0}.').format(clear_load.get(u'username', u'UNKNOWN'))))
# Authorize
username = auth_check.get(u'username')
if auth_type != u'user':
runner_check = self.ckminions.runner_check(
auth_check.get(u'auth_list', []),
clear_load[u'fun'],
clear_load.get(u'kwarg', {})
)
if not runner_check:
return {u'error': {u'name': err_name,
u'message': u'Authentication failure of type "{0}" occurred for '
u'user {1}.'.format(auth_type, username)}}
elif isinstance(runner_check, dict) and u'error' in runner_check:
# A dictionary with an error name/message was handled by ckminions.runner_check
return runner_check
auth_list = self.loadauth.get_auth_list(clear_load)
if not self.ckminions.runner_check(auth_list, clear_load[u'fun'], clear_load.get(u'kwarg', {})):
return dict(error=dict(name=u'EauthAuthenticationError',
message=(u'Authentication failure of type "eauth" occurred for '
u'user {0}.').format(clear_load.get(u'username', u'UNKNOWN'))))
# No error occurred, consume the password from the clear_load if
# passed
username = clear_load.pop(u'username', u'UNKNOWN')
clear_load.pop(u'password', None)
# No error occurred, consume sensitive settings from the clear_load if passed.
for item in sensitive_load_keys:
clear_load.pop(item, None)
else:
if not self.loadauth.authenticate_key(clear_load, self.key):
return dict(error=dict(name=u'UserAuthenticationError',
message=u'Authentication failure of type "user" occurred'))
if u'user' in clear_load:
username = clear_load[u'user']
if salt.auth.AuthUser(username).is_sudo():
@ -1722,52 +1714,45 @@ class ClearFuncs(object):
username)
except Exception as exc:
log.error(u'Exception occurred while introspecting %s: %s', fun, exc)
return dict(error=dict(name=exc.__class__.__name__,
args=exc.args,
message=str(exc)))
return {u'error': {u'name': exc.__class__.__name__,
u'args': exc.args,
u'message': str(exc)}}
def wheel(self, clear_load):
'''
Send a master control function back to the wheel system
'''
# All wheel ops pass through eauth
username = None
if u'token' in clear_load:
# Authenticate
token = self.loadauth.authenticate_token(clear_load)
if not token:
return dict(error=dict(name=u'TokenAuthenticationError',
message=u'Authentication failure of type "token" occurred.'))
auth_type, err_name, key, sensitive_load_keys = self._prep_auth_info(clear_load)
# Authorize
auth_list = self.loadauth.get_auth_list(clear_load, token)
if not self.ckminions.wheel_check(auth_list, clear_load[u'fun'], clear_load.get(u'kwarg', {})):
return dict(error=dict(name=u'TokenAuthenticationError',
message=(u'Authentication failure of type "token" occurred for '
u'user {0}.').format(token[u'name'])))
clear_load.pop(u'token')
username = token[u'name']
elif u'eauth' in clear_load:
if not self.loadauth.authenticate_eauth(clear_load):
return dict(error=dict(name=u'EauthAuthenticationError',
message=(u'Authentication failure of type "eauth" occurred for '
u'user {0}.').format(clear_load.get(u'username', u'UNKNOWN'))))
# Authenticate
auth_check = self.loadauth.check_authentication(clear_load, auth_type, key=key)
error = auth_check.get(u'error')
auth_list = self.loadauth.get_auth_list(clear_load)
if not self.ckminions.wheel_check(auth_list, clear_load[u'fun'], clear_load.get(u'kwarg', {})):
return dict(error=dict(name=u'EauthAuthenticationError',
message=(u'Authentication failure of type "eauth" occurred for '
u'user {0}.').format(clear_load.get(u'username', u'UNKNOWN'))))
if error:
# Authentication error occurred: do not continue.
return {u'error': error}
# No error occurred, consume the password from the clear_load if
# passed
clear_load.pop(u'password', None)
username = clear_load.pop(u'username', u'UNKNOWN')
# Authorize
username = auth_check.get(u'username')
if auth_type != u'user':
wheel_check = self.ckminions.wheel_check(
auth_check.get(u'auth_list', []),
clear_load[u'fun'],
clear_load.get(u'kwarg', {})
)
if not wheel_check:
return {u'error': {u'name': err_name,
u'message': u'Authentication failure of type "{0}" occurred for '
u'user {1}.'.format(auth_type, username)}}
elif isinstance(wheel_check, dict) and u'error' in wheel_check:
# A dictionary with an error name/message was handled by ckminions.wheel_check
return wheel_check
# No error occurred, consume sensitive settings from the clear_load if passed.
for item in sensitive_load_keys:
clear_load.pop(item, None)
else:
if not self.loadauth.authenticate_key(clear_load, self.key):
return dict(error=dict(name=u'UserAuthenticationError',
message=u'Authentication failure of type "user" occurred'))
if u'user' in clear_load:
username = clear_load[u'user']
if salt.auth.AuthUser(username).is_sudo():
@ -1963,6 +1948,24 @@ class ClearFuncs(object):
}
}
def _prep_auth_info(self, clear_load):
sensitive_load_keys = []
key = None
if u'token' in clear_load:
auth_type = u'token'
err_name = u'TokenAuthenticationError'
sensitive_load_keys = [u'token']
elif u'eauth' in clear_load:
auth_type = u'eauth'
err_name = u'EauthAuthenticationError'
sensitive_load_keys = [u'username', u'password']
else:
auth_type = u'user'
err_name = u'UserAuthenticationError'
key = self.key
return auth_type, err_name, key, sensitive_load_keys
def _prep_jid(self, clear_load, extra):
'''
Return a jid for this publication

View File

@ -103,6 +103,7 @@ import salt.defaults.exitcodes
import salt.cli.daemons
import salt.log.setup
import salt.utils.dictupdate
from salt.config import DEFAULT_MINION_OPTS
from salt.defaults import DEFAULT_TARGET_DELIM
from salt.utils.debug import enable_sigusr1_handler
@ -3213,6 +3214,26 @@ class ProxyMinion(Minion):
if u'proxy' not in self.opts:
self.opts[u'proxy'] = self.opts[u'pillar'][u'proxy']
if self.opts.get(u'proxy_merge_pillar_in_opts'):
# Override proxy opts with pillar data when the user required.
self.opts = salt.utils.dictupdate.merge(self.opts,
self.opts[u'pillar'],
strategy=self.opts.get(u'proxy_merge_pillar_in_opts_strategy'),
merge_lists=self.opts.get(u'proxy_deep_merge_pillar_in_opts', False))
elif self.opts.get(u'proxy_mines_pillar'):
# Even when not required, some details such as mine configuration
# should be merged anyway whenever possible.
if u'mine_interval' in self.opts[u'pillar']:
self.opts[u'mine_interval'] = self.opts[u'pillar'][u'mine_interval']
if u'mine_functions' in self.opts[u'pillar']:
general_proxy_mines = self.opts.get(u'mine_functions', [])
specific_proxy_mines = self.opts[u'pillar'][u'mine_functions']
try:
self.opts[u'mine_functions'] = general_proxy_mines + specific_proxy_mines
except TypeError as terr:
log.error(u'Unable to merge mine functions from the pillar in the opts, for proxy {}'.format(
self.opts[u'id']))
fq_proxyname = self.opts[u'proxy'][u'proxytype']
# Need to load the modules so they get all the dunder variables

View File

@ -12,6 +12,7 @@ import logging
# Import Salt libs
import salt.utils.files
import salt.utils.path
# Import 3rd-party libs
from salt.ext import six
@ -241,4 +242,4 @@ def _read_link(name):
Throws an OSError if the link does not exist
'''
alt_link_path = '/etc/alternatives/{0}'.format(name)
return os.readlink(alt_link_path)
return salt.utils.path.readlink(alt_link_path)

View File

@ -99,7 +99,7 @@ def __virtual__():
'''
Confirm this module is on a Debian based system
'''
if __grains__.get('os_family') in ('Kali', 'Debian', 'neon'):
if __grains__.get('os_family') in ('Kali', 'Debian', 'neon', 'Deepin'):
return __virtualname__
elif __grains__.get('os_family', False) == 'Cumulus':
return __virtualname__

View File

@ -2038,19 +2038,12 @@ def build_network_settings(**settings):
# Write settings
_write_file_network(network, _DEB_NETWORKING_FILE, True)
# Write hostname to /etc/hostname
# Get hostname and domain from opts
sline = opts['hostname'].split('.', 1)
opts['hostname'] = sline[0]
hostname = '{0}\n' . format(opts['hostname'])
current_domainname = current_network_settings['domainname']
current_searchdomain = current_network_settings['searchdomain']
# Only write the hostname if it has changed
if not opts['hostname'] == current_network_settings['hostname']:
if not ('test' in settings and settings['test']):
# TODO replace wiht a call to network.mod_hostname instead
_write_file_network(hostname, _DEB_HOSTNAME_FILE)
new_domain = False
if len(sline) > 1:
new_domainname = sline[1]

View File

@ -75,9 +75,20 @@ def _porttree():
def _p_to_cp(p):
ret = _porttree().dbapi.xmatch("match-all", p)
if ret:
return portage.cpv_getkey(ret[0])
try:
ret = portage.dep_getkey(p)
if ret:
return ret
except portage.exception.InvalidAtom:
pass
try:
ret = _porttree().dbapi.xmatch('bestmatch-visible', p)
if ret:
return portage.dep_getkey(ret)
except portage.exception.InvalidAtom:
pass
return None
@ -91,11 +102,14 @@ def _allnodes():
def _cpv_to_cp(cpv):
ret = portage.cpv_getkey(cpv)
if ret:
return ret
else:
return cpv
try:
ret = portage.dep_getkey(cpv)
if ret:
return ret
except portage.exception.InvalidAtom:
pass
return cpv
def _cpv_to_version(cpv):

View File

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
'''
Module used to access the esxcluster proxy connection methods
'''
from __future__ import absolute_import
# Import python libs
import logging
import salt.utils.platform
log = logging.getLogger(__name__)
__proxyenabled__ = ['esxcluster']
# Define the module's virtual name
__virtualname__ = 'esxcluster'
def __virtual__():
'''
Only work on proxy
'''
if salt.utils.platform.is_proxy():
return __virtualname__
return (False, 'Must be run on a proxy minion')
def get_details():
return __proxy__['esxcluster.get_details']()

View File

@ -17,11 +17,12 @@ except ImportError:
from pipes import quote as _cmd_quote
# Import salt libs
import salt.utils.path
import salt.utils.yast
import salt.utils.preseed
import salt.utils.kickstart
import salt.syspaths
import salt.utils.kickstart
import salt.utils.path
import salt.utils.preseed
import salt.utils.validate.path
import salt.utils.yast
from salt.exceptions import SaltInvocationError
# Import 3rd-party libs
@ -399,10 +400,15 @@ def _bootstrap_deb(
if repo_url is None:
repo_url = 'http://ftp.debian.org/debian/'
if not salt.utils.which('debootstrap'):
if not salt.utils.path.which('debootstrap'):
log.error('Required tool debootstrap is not installed.')
return False
if static_qemu and not salt.utils.validate.path.is_executable(static_qemu):
log.error('Required tool qemu not '
'present/readable at: {0}'.format(static_qemu))
return False
if isinstance(pkgs, (list, tuple)):
pkgs = ','.join(pkgs)
if isinstance(exclude_pkgs, (list, tuple)):
@ -427,11 +433,13 @@ def _bootstrap_deb(
__salt__['cmd.run'](deb_args, python_shell=False)
__salt__['cmd.run'](
'cp {qemu} {root}/usr/bin/'.format(
qemu=_cmd_quote(static_qemu), root=_cmd_quote(root)
if static_qemu:
__salt__['cmd.run'](
'cp {qemu} {root}/usr/bin/'.format(
qemu=_cmd_quote(static_qemu), root=_cmd_quote(root)
)
)
)
env = {'DEBIAN_FRONTEND': 'noninteractive',
'DEBCONF_NONINTERACTIVE_SEEN': 'true',
'LC_ALL': 'C',

View File

@ -31,7 +31,7 @@ def __virtual__():
if __grains__['kernel'] in ('Linux', 'OpenBSD', 'NetBSD'):
return __virtualname__
return (False, 'The groupadd execution module cannot be loaded: '
' only available on Linux, OpenBSD and NetBSD')
' only available on Linux, OpenBSD and NetBSD')
def add(name, gid=None, system=False, root=None):
@ -44,12 +44,12 @@ def add(name, gid=None, system=False, root=None):
salt '*' group.add foo 3456
'''
cmd = 'groupadd '
cmd = ['groupadd']
if gid:
cmd += '-g {0} '.format(gid)
cmd.append('-g {0}'.format(gid))
if system and __grains__['kernel'] != 'OpenBSD':
cmd += '-r '
cmd += name
cmd.append('-r')
cmd.append(name)
if root is not None:
cmd.extend(('-R', root))
@ -69,7 +69,7 @@ def delete(name, root=None):
salt '*' group.delete foo
'''
cmd = ('groupdel', name)
cmd = ['groupdel', name]
if root is not None:
cmd.extend(('-R', root))
@ -140,7 +140,7 @@ def chgid(name, gid, root=None):
pre_gid = __salt__['file.group_to_gid'](name)
if gid == pre_gid:
return True
cmd = ('groupmod', '-g', gid, name)
cmd = ['groupmod', '-g', gid, name]
if root is not None:
cmd.extend(('-R', root))
@ -170,15 +170,15 @@ def adduser(name, username, root=None):
if __grains__['kernel'] == 'Linux':
if on_redhat_5:
cmd = ('gpasswd', '-a', username, name)
cmd = ['gpasswd', '-a', username, name]
elif on_suse_11:
cmd = ('usermod', '-A', name, username)
cmd = ['usermod', '-A', name, username]
else:
cmd = ('gpasswd', '--add', username, name)
cmd = ['gpasswd', '--add', username, name]
if root is not None:
cmd.extend(('-Q', root))
else:
cmd = ('usermod', '-G', name, username)
cmd = ['usermod', '-G', name, username]
if root is not None:
cmd.extend(('-R', root))
@ -208,20 +208,20 @@ def deluser(name, username, root=None):
if username in grp_info['members']:
if __grains__['kernel'] == 'Linux':
if on_redhat_5:
cmd = ('gpasswd', '-d', username, name)
cmd = ['gpasswd', '-d', username, name]
elif on_suse_11:
cmd = ('usermod', '-R', name, username)
cmd = ['usermod', '-R', name, username]
else:
cmd = ('gpasswd', '--del', username, name)
cmd = ['gpasswd', '--del', username, name]
if root is not None:
cmd.extend(('-R', root))
retcode = __salt__['cmd.retcode'](cmd, python_shell=False)
elif __grains__['kernel'] == 'OpenBSD':
out = __salt__['cmd.run_stdout']('id -Gn {0}'.format(username),
python_shell=False)
cmd = 'usermod -S '
cmd += ','.join([g for g in out.split() if g != str(name)])
cmd += ' {0}'.format(username)
cmd = ['usermod', '-S']
cmd.append(','.join([g for g in out.split() if g != str(name)]))
cmd.append('{0}'.format(username))
retcode = __salt__['cmd.retcode'](cmd, python_shell=False)
else:
log.error('group.deluser is not yet supported on this platform')
@ -249,13 +249,13 @@ def members(name, members_list, root=None):
if __grains__['kernel'] == 'Linux':
if on_redhat_5:
cmd = ('gpasswd', '-M', members_list, name)
cmd = ['gpasswd', '-M', members_list, name]
elif on_suse_11:
for old_member in __salt__['group.info'](name).get('members'):
__salt__['cmd.run']('groupmod -R {0} {1}'.format(old_member, name), python_shell=False)
cmd = ('groupmod', '-A', members_list, name)
cmd = ['groupmod', '-A', members_list, name]
else:
cmd = ('gpasswd', '--members', members_list, name)
cmd = ['gpasswd', '--members', members_list, name]
if root is not None:
cmd.extend(('-R', root))
retcode = __salt__['cmd.retcode'](cmd, python_shell=False)
@ -270,7 +270,7 @@ def members(name, members_list, root=None):
for user in members_list.split(","):
if user:
retcode = __salt__['cmd.retcode'](
'usermod -G {0} {1}'.format(name, user),
['usermod', '-G', name, user],
python_shell=False)
if not retcode == 0:
break

View File

@ -318,17 +318,18 @@ class _Section(OrderedDict):
yield '{0}[{1}]{0}'.format(os.linesep, self.name)
sections_dict = OrderedDict()
for name, value in six.iteritems(self):
# Handle Comment Lines
if com_regx.match(name):
yield '{0}{1}'.format(value, os.linesep)
# Handle Sections
elif isinstance(value, _Section):
sections_dict.update({name: value})
# Key / Value pairs
# Adds spaces between the separator
else:
yield '{0}{1}{2}{3}'.format(
name,
(
' {0} '.format(self.sep) if self.sep != ' '
else self.sep
),
' {0} '.format(self.sep) if self.sep != ' ' else self.sep,
value,
os.linesep
)

View File

@ -75,6 +75,8 @@ def _get_config_file(conf, atom):
if parts.cp == '*/*':
# parts.repo will be empty if there is no repo part
relative_path = parts.repo or "gentoo"
elif str(parts.cp).endswith('/*'):
relative_path = str(parts.cp).split("/")[0] + "_"
else:
relative_path = os.path.join(*[x for x in os.path.split(parts.cp) if x != '*'])
else:
@ -92,9 +94,20 @@ def _p_to_cp(p):
Convert a package name or a DEPEND atom to category/package format.
Raises an exception if program name is ambiguous.
'''
ret = _porttree().dbapi.xmatch("match-all", p)
if ret:
return portage.cpv_getkey(ret[0])
try:
ret = portage.dep_getkey(p)
if ret:
return ret
except portage.exception.InvalidAtom:
pass
try:
ret = _porttree().dbapi.xmatch('bestmatch-visible', p)
if ret:
return portage.dep_getkey(ret)
except portage.exception.InvalidAtom:
pass
return None
@ -188,12 +201,7 @@ def _package_conf_file_to_dir(file_name):
else:
os.rename(path, path + '.tmpbak')
os.mkdir(path, 0o755)
with salt.utils.files.fopen(path + '.tmpbak') as fh_:
for line in fh_:
line = line.strip()
if line and not line.startswith('#'):
append_to_package_conf(file_name, string=line)
os.remove(path + '.tmpbak')
os.rename(path + '.tmpbak', os.path.join(path, 'tmp'))
return True
else:
os.mkdir(path, 0o755)
@ -218,7 +226,7 @@ def _package_conf_ordering(conf, clean=True, keep_backup=False):
shutil.copy(file_path, file_path + '.bak')
backup_files.append(file_path + '.bak')
if cp[0] == '/' or cp.split('/') > 2:
if cp[0] == '/' or len(cp.split('/')) > 2:
with salt.utils.files.fopen(file_path) as fp_:
rearrange.extend(fp_.readlines())
os.remove(file_path)

View File

@ -18,6 +18,8 @@ Module to provide redis functionality to Salt
# Import Python libs
from __future__ import absolute_import
from salt.ext.six.moves import zip
from salt.ext import six
from datetime import datetime
# Import third party libs
try:
@ -513,8 +515,14 @@ def lastsave(host=None, port=None, db=None, password=None):
salt '*' redis.lastsave
'''
# Use of %s to get the timestamp is not supported by Python. The reason it
# works is because it's passed to the system strftime which may not support
# it. See: https://stackoverflow.com/a/11743262
server = _connect(host, port, db, password)
return int(server.lastsave().strftime("%s"))
if six.PY2:
return int((server.lastsave() - datetime(1970, 1, 1)).total_seconds())
else:
return int(server.lastsave().timestamp())
def llen(key, host=None, port=None, db=None, password=None):

View File

@ -769,10 +769,13 @@ def set_auth_key(
with salt.utils.files.fopen(fconfig, 'ab+') as _fh:
if new_file is False:
# Let's make sure we have a new line at the end of the file
_fh.seek(1024, 2)
if not _fh.read(1024).rstrip(six.b(' ')).endswith(six.b('\n')):
_fh.seek(0, 2)
_fh.write(six.b('\n'))
_fh.seek(0, 2)
if _fh.tell() > 0:
# File isn't empty, check if last byte is a newline
# If not, add one
_fh.seek(-1, 2)
if _fh.read(1) != six.b('\n'):
_fh.write(six.b('\n'))
if six.PY3:
auth_line = auth_line.encode(__salt_system_encoding__)
_fh.write(auth_line)

View File

@ -200,12 +200,10 @@ def create(path,
for entry in extra_search_dir:
cmd.append('--extra-search-dir={0}'.format(entry))
if never_download is True:
if virtualenv_version_info >= (1, 10):
if virtualenv_version_info >= (1, 10) and virtualenv_version_info < (14, 0, 0):
log.info(
'The virtualenv \'--never-download\' option has been '
'deprecated in virtualenv(>=1.10), as such, the '
'\'never_download\' option to `virtualenv.create()` has '
'also been deprecated and it\'s not necessary anymore.'
'--never-download was deprecated in 1.10.0, but reimplemented in 14.0.0. '
'If this feature is needed, please install a supported virtualenv version.'
)
else:
cmd.append('--never-download')

View File

@ -195,7 +195,7 @@ else:
log = logging.getLogger(__name__)
__virtualname__ = 'vsphere'
__proxyenabled__ = ['esxi', 'esxdatacenter']
__proxyenabled__ = ['esxi', 'esxcluster', 'esxdatacenter']
def __virtual__():
@ -227,6 +227,8 @@ def _get_proxy_connection_details():
proxytype = get_proxy_type()
if proxytype == 'esxi':
details = __salt__['esxi.get_details']()
elif proxytype == 'esxcluster':
details = __salt__['esxcluster.get_details']()
elif proxytype == 'esxdatacenter':
details = __salt__['esxdatacenter.get_details']()
else:
@ -267,7 +269,7 @@ def gets_service_instance_via_proxy(fn):
proxy details and passes the connection (vim.ServiceInstance) to
the decorated function.
Supported proxies: esxi, esxdatacenter.
Supported proxies: esxi, esxcluster, esxdatacenter.
Notes:
1. The decorated function must have a ``service_instance`` parameter
@ -354,7 +356,7 @@ def gets_service_instance_via_proxy(fn):
@depends(HAS_PYVMOMI)
@supports_proxies('esxi', 'esxdatacenter')
@supports_proxies('esxi', 'esxcluster', 'esxdatacenter')
def get_service_instance_via_proxy(service_instance=None):
'''
Returns a service instance to the proxied endpoint (vCenter/ESXi host).
@ -374,7 +376,7 @@ def get_service_instance_via_proxy(service_instance=None):
@depends(HAS_PYVMOMI)
@supports_proxies('esxi', 'esxdatacenter')
@supports_proxies('esxi', 'esxcluster', 'esxdatacenter')
def disconnect(service_instance):
'''
Disconnects from a vCenter or ESXi host
@ -1909,7 +1911,7 @@ def get_vsan_eligible_disks(host, username, password, protocol=None, port=None,
@depends(HAS_PYVMOMI)
@supports_proxies('esxi', 'esxdatacenter')
@supports_proxies('esxi', 'esxcluster', 'esxdatacenter')
@gets_service_instance_via_proxy
def test_vcenter_connection(service_instance=None):
'''
@ -3598,7 +3600,7 @@ def vsan_enable(host, username, password, protocol=None, port=None, host_names=N
@depends(HAS_PYVMOMI)
@supports_proxies('esxdatacenter')
@supports_proxies('esxdatacenter', 'esxcluster')
@gets_service_instance_via_proxy
def list_datacenters_via_proxy(datacenter_names=None, service_instance=None):
'''
@ -4294,3 +4296,14 @@ def _get_esxdatacenter_proxy_details():
return det.get('vcenter'), det.get('username'), det.get('password'), \
det.get('protocol'), det.get('port'), det.get('mechanism'), \
det.get('principal'), det.get('domain'), det.get('datacenter')
def _get_esxcluster_proxy_details():
'''
Returns the running esxcluster's proxy details
'''
det = __salt__['esxcluster.get_details']()
return det.get('vcenter'), det.get('username'), det.get('password'), \
det.get('protocol'), det.get('port'), det.get('mechanism'), \
det.get('principal'), det.get('domain'), det.get('datacenter'), \
det.get('cluster')

View File

@ -987,18 +987,6 @@ def install(name=None, refresh=False, pkgs=None, **kwargs):
# Version 1.2.3 will apply to packages foo and bar
salt '*' pkg.install foo,bar version=1.2.3
cache_file (str):
A single file to copy down for use with the installer. Copied to the
same location as the installer. Use this over ``cache_dir`` if there
are many files in the directory and you only need a specific file
and don't want to cache additional files that may reside in the
installer directory. Only applies to files on ``salt://``
cache_dir (bool):
True will copy the contents of the installer directory. This is
useful for installations that are not a single file. Only applies to
directories on ``salt://``
extra_install_flags (str):
Additional install flags that will be appended to the
``install_flags`` defined in the software definition file. Only
@ -1290,7 +1278,7 @@ def install(name=None, refresh=False, pkgs=None, **kwargs):
if use_msiexec:
cmd = msiexec
arguments = ['/i', cached_pkg]
if pkginfo['version_num'].get('allusers', True):
if pkginfo[version_num].get('allusers', True):
arguments.append('ALLUSERS="1"')
arguments.extend(salt.utils.shlex_split(install_flags))
else:

310
salt/proxy/esxcluster.py Normal file
View File

@ -0,0 +1,310 @@
# -*- coding: utf-8 -*-
'''
Proxy Minion interface module for managing VMWare ESXi clusters.
Dependencies
============
- pyVmomi
- jsonschema
Configuration
=============
To use this integration proxy module, please configure the following:
Pillar
------
Proxy minions get their configuration from Salt's Pillar. This can now happen
from the proxy's configuration file.
Example pillars:
``userpass`` mechanism:
.. code-block:: yaml
proxy:
proxytype: esxcluster
cluster: <cluster name>
datacenter: <datacenter name>
vcenter: <ip or dns name of parent vcenter>
mechanism: userpass
username: <vCenter username>
passwords: (required if userpass is used)
- first_password
- second_password
- third_password
``sspi`` mechanism:
.. code-block:: yaml
proxy:
proxytype: esxcluster
cluster: <cluster name>
datacenter: <datacenter name>
vcenter: <ip or dns name of parent vcenter>
mechanism: sspi
domain: <user domain>
principal: <host kerberos principal>
proxytype
^^^^^^^^^
To use this Proxy Module, set this to ``esxdatacenter``.
cluster
^^^^^^^
Name of the managed cluster. Required.
datacenter
^^^^^^^^^^
Name of the datacenter the managed cluster is in. Required.
vcenter
^^^^^^^
The location of the VMware vCenter server (host of ip) where the datacenter
should be managed. Required.
mechanism
^^^^^^^^
The mechanism used to connect to the vCenter server. Supported values are
``userpass`` and ``sspi``. Required.
Note:
Connections are attempted using all (``username``, ``password``)
combinations on proxy startup.
username
^^^^^^^^
The username used to login to the host, such as ``root``. Required if mechanism
is ``userpass``.
passwords
^^^^^^^^^
A list of passwords to be used to try and login to the vCenter server. At least
one password in this list is required if mechanism is ``userpass``. When the
proxy comes up, it will try the passwords listed in order.
domain
^^^^^^
User domain. Required if mechanism is ``sspi``.
principal
^^^^^^^^
Kerberos principal. Rquired if mechanism is ``sspi``.
protocol
^^^^^^^^
If the ESXi host is not using the default protocol, set this value to an
alternate protocol. Default is ``https``.
port
^^^^
If the ESXi host is not using the default port, set this value to an
alternate port. Default is ``443``.
Salt Proxy
----------
After your pillar is in place, you can test the proxy. The proxy can run on
any machine that has network connectivity to your Salt Master and to the
vCenter server in the pillar. SaltStack recommends that the machine running the
salt-proxy process also run a regular minion, though it is not strictly
necessary.
To start a proxy minion one needs to establish its identity <id>:
.. code-block:: bash
salt-proxy --proxyid <proxy_id>
On the machine that will run the proxy, make sure there is a configuration file
present. By default this is ``/etc/salt/proxy``. If in a different location, the
``<configuration_folder>`` has to be specified when running the proxy:
file with at least the following in it:
.. code-block:: bash
salt-proxy --proxyid <proxy_id> -c <configuration_folder>
Commands
--------
Once the proxy is running it will connect back to the specified master and
individual commands can be runs against it:
.. code-block:: bash
# Master - minion communication
salt <cluster_name> test.ping
# Test vcenter connection
salt <cluster_name> vsphere.test_vcenter_connection
States
------
Associated states are documented in
:mod:`salt.states.esxcluster </ref/states/all/salt.states.esxcluster>`.
Look there to find an example structure for Pillar as well as an example
``.sls`` file for configuring an ESX cluster from scratch.
'''
# Import Python Libs
from __future__ import absolute_import
import logging
import os
# Import Salt Libs
import salt.exceptions
from salt.config.schemas.esxcluster import EsxclusterProxySchema
from salt.utils.dictupdate import merge
# This must be present or the Salt loader won't load this module.
__proxyenabled__ = ['esxcluster']
# External libraries
try:
import jsonschema
HAS_JSONSCHEMA = True
except ImportError:
HAS_JSONSCHEMA = False
# Variables are scoped to this module so we can have persistent data
# across calls to fns in here.
GRAINS_CACHE = {}
DETAILS = {}
# Set up logging
log = logging.getLogger(__name__)
# Define the module's virtual name
__virtualname__ = 'esxcluster'
def __virtual__():
'''
Only load if the vsphere execution module is available.
'''
if HAS_JSONSCHEMA:
return __virtualname__
return False, 'The esxcluster proxy module did not load.'
def init(opts):
'''
This function gets called when the proxy starts up. For
login
the protocol and port are cached.
'''
log.debug('Initting esxcluster proxy module in process '
'{}'.format(os.getpid()))
log.debug('Validating esxcluster proxy input')
schema = EsxclusterProxySchema.serialize()
log.trace('schema = {}'.format(schema))
proxy_conf = merge(opts.get('proxy', {}), __pillar__.get('proxy', {}))
log.trace('proxy_conf = {0}'.format(proxy_conf))
try:
jsonschema.validate(proxy_conf, schema)
except jsonschema.exceptions.ValidationError as exc:
raise salt.exceptions.InvalidConfigError(exc)
# Save mandatory fields in cache
for key in ('vcenter', 'datacenter', 'cluster', 'mechanism'):
DETAILS[key] = proxy_conf[key]
# Additional validation
if DETAILS['mechanism'] == 'userpass':
if 'username' not in proxy_conf:
raise salt.exceptions.InvalidConfigError(
'Mechanism is set to \'userpass\', but no '
'\'username\' key found in proxy config.')
if 'passwords' not in proxy_conf:
raise salt.exceptions.InvalidConfigError(
'Mechanism is set to \'userpass\', but no '
'\'passwords\' key found in proxy config.')
for key in ('username', 'passwords'):
DETAILS[key] = proxy_conf[key]
else:
if 'domain' not in proxy_conf:
raise salt.exceptions.InvalidConfigError(
'Mechanism is set to \'sspi\', but no '
'\'domain\' key found in proxy config.')
if 'principal' not in proxy_conf:
raise salt.exceptions.InvalidConfigError(
'Mechanism is set to \'sspi\', but no '
'\'principal\' key found in proxy config.')
for key in ('domain', 'principal'):
DETAILS[key] = proxy_conf[key]
# Save optional
DETAILS['protocol'] = proxy_conf.get('protocol')
DETAILS['port'] = proxy_conf.get('port')
# Test connection
if DETAILS['mechanism'] == 'userpass':
# Get the correct login details
log.debug('Retrieving credentials and testing vCenter connection for '
'mehchanism \'userpass\'')
try:
username, password = find_credentials()
DETAILS['password'] = password
except salt.exceptions.SaltSystemExit as err:
log.critical('Error: {0}'.format(err))
return False
return True
def ping():
'''
Returns True.
CLI Example:
.. code-block:: bash
salt esx-cluster test.ping
'''
return True
def shutdown():
'''
Shutdown the connection to the proxy device. For this proxy,
shutdown is a no-op.
'''
log.debug('esxcluster proxy shutdown() called...')
def find_credentials():
'''
Cycle through all the possible credentials and return the first one that
works.
'''
# if the username and password were already found don't fo though the
# connection process again
if 'username' in DETAILS and 'password' in DETAILS:
return DETAILS['username'], DETAILS['password']
passwords = DETAILS['passwords']
for password in passwords:
DETAILS['password'] = password
if not __salt__['vsphere.test_vcenter_connection']():
# We are unable to authenticate
continue
# If we have data returned from above, we've successfully authenticated.
return DETAILS['username'], password
# We've reached the end of the list without successfully authenticating.
raise salt.exceptions.VMwareConnectionError('Cannot complete login due to '
'incorrect credentials.')
def get_details():
'''
Function that returns the cached details
'''
return DETAILS

View File

@ -398,7 +398,6 @@ def clean_old_jobs():
Clean out the old jobs from the job cache
'''
if __opts__['keep_jobs'] != 0:
cur = time.time()
jid_root = _job_dir()
if not os.path.exists(jid_root):
@ -428,7 +427,7 @@ def clean_old_jobs():
shutil.rmtree(t_path)
elif os.path.isfile(jid_file):
jid_ctime = os.stat(jid_file).st_ctime
hours_difference = (cur - jid_ctime) / 3600.0
hours_difference = (time.time()- jid_ctime) / 3600.0
if hours_difference > __opts__['keep_jobs'] and os.path.exists(t_path):
# Remove the entire t_path from the original JID dir
shutil.rmtree(t_path)
@ -442,7 +441,7 @@ def clean_old_jobs():
# Checking the time again prevents a possible race condition where
# t_path JID dirs were created, but not yet populated by a jid file.
t_path_ctime = os.stat(t_path).st_ctime
hours_difference = (cur - t_path_ctime) / 3600.0
hours_difference = (time.time() - t_path_ctime) / 3600.0
if hours_difference > __opts__['keep_jobs']:
shutil.rmtree(t_path)

View File

@ -116,9 +116,14 @@ def cert(name,
if res['result'] is None:
ret['changes'] = {}
else:
if not __salt__['acme.has'](name):
new = None
else:
new = __salt__['acme.info'](name)
ret['changes'] = {
'old': old,
'new': __salt__['acme.info'](name)
'new': new
}
return ret

View File

@ -9,6 +9,7 @@ Management of the Salt beacons
ps:
beacon.present:
- save: True
- enable: False
- services:
salt-master: running
@ -37,12 +38,15 @@ log = logging.getLogger(__name__)
def present(name,
save=False,
**kwargs):
'''
Ensure beacon is configured with the included beacon data.
name
The name of the beacon ensure is configured.
save
True/False, if True the beacons.conf file be updated too. Default is False.
'''
@ -76,10 +80,11 @@ def present(name,
ret['changes'] = result['changes']
else:
ret['comment'].append(result['comment'])
else:
if 'test' in __opts__ and __opts__['test']:
kwargs['test'] = True
result = __salt__['beacons.add'](name, beacon_data)
result = __salt__['beacons.add'](name, beacon_data, **kwargs)
ret['comment'].append(result['comment'])
else:
result = __salt__['beacons.add'](name, beacon_data)
@ -90,16 +95,24 @@ def present(name,
else:
ret['comment'].append('Adding {0} to beacons'.format(name))
if save:
result = __salt__['beacons.save']()
ret['comment'].append('Beacon {0} saved'.format(name))
ret['comment'] = '\n'.join(ret['comment'])
return ret
def absent(name, **kwargs):
def absent(name,
save=False,
**kwargs):
'''
Ensure beacon is absent.
name
The name of the beacon ensured absent.
save
True/False, if True the beacons.conf file be updated too. Default is False.
'''
### NOTE: The keyword arguments in **kwargs are ignored in this state, but
@ -128,6 +141,10 @@ def absent(name, **kwargs):
else:
ret['comment'].append('{0} not configured in beacons'.format(name))
if save:
result = __salt__['beacons.save']()
ret['comment'].append('Beacon {0} saved'.format(name))
ret['comment'] = '\n'.join(ret['comment'])
return ret

View File

@ -459,7 +459,7 @@ def bootstrap(vm_, opts=None):
'wait_for_passwd_maxtries', vm_, opts, default=15
),
'preflight_cmds': salt.config.get_cloud_config_value(
'preflight_cmds', vm_, __opts__, default=[]
'preflight_cmds', vm_, opts, default=[]
),
'cloud_grains': {'driver': vm_['driver'],
'provider': vm_['provider'],

View File

@ -101,7 +101,10 @@ class GitConfigParser(RawConfigParser, object): # pylint: disable=undefined-var
optname = None
# no section header in the file?
elif cursect is None:
raise MissingSectionHeaderError(fpname, lineno, line) # pylint: disable=undefined-variable
raise MissingSectionHeaderError( # pylint: disable=undefined-variable
salt.utils.stringutils.to_str(fpname),
lineno,
salt.utils.stringutils.to_str(line))
# an option line?
else:
mo = self._optcre.match(line.lstrip())
@ -142,12 +145,11 @@ class GitConfigParser(RawConfigParser, object): # pylint: disable=undefined-var
if self._optcre is self.OPTCRE or value:
is_list = isinstance(value, list)
if is_list and not allow_list:
raise TypeError(u'option value cannot be a list unless '
u'allow_list is True')
raise TypeError('option value cannot be a list unless allow_list is True') # future lint: disable=non-unicode-string
elif not is_list:
value = [value]
if not all(isinstance(x, six.string_types) for x in value):
raise TypeError(u'option values must be strings')
raise TypeError('option values must be strings') # future lint: disable=non-unicode-string
def get(self, section, option, as_list=False):
'''
@ -183,7 +185,7 @@ class GitConfigParser(RawConfigParser, object): # pylint: disable=undefined-var
sectdict[key] = [sectdict[key]]
sectdict[key].append(value)
else:
raise TypeError(u'Expected str or list for option value, got %s' % type(value).__name__)
raise TypeError('Expected str or list for option value, got %s' % type(value).__name__) # future lint: disable=non-unicode-string
def set_multivar(self, section, option, value=u''):
'''
@ -201,7 +203,8 @@ class GitConfigParser(RawConfigParser, object): # pylint: disable=undefined-var
try:
sectdict = self._sections[section]
except KeyError:
raise NoSectionError(section) # pylint: disable=undefined-variable
raise NoSectionError( # pylint: disable=undefined-variable
salt.utils.stringutils.to_str(section))
key = self.optionxform(option)
self._add_option(sectdict, key, value)
@ -216,7 +219,8 @@ class GitConfigParser(RawConfigParser, object): # pylint: disable=undefined-var
try:
sectdict = self._sections[section]
except KeyError:
raise NoSectionError(section) # pylint: disable=undefined-variable
raise NoSectionError( # pylint: disable=undefined-variable
salt.utils.stringutils.to_str(section))
option = self.optionxform(option)
if option not in sectdict:
return False

View File

@ -985,7 +985,11 @@ class CkMinions(object):
if form != 'cloud':
comps = fun.split('.')
if len(comps) != 2:
return False
# Hint at a syntax error when command is passed improperly,
# rather than returning an authentication error of some kind.
# See Issue #21969 for more information.
return {'error': {'name': 'SaltInvocationError',
'message': 'A command invocation error occurred: Check syntax.'}}
mod_name = comps[0]
fun_name = comps[1]
else:

View File

@ -64,3 +64,14 @@ def is_readable(path):
# The path does not exist
return False
def is_executable(path):
'''
Check if a given path is executable by the current user.
:param path: The path to check
:returns: True or False
'''
return os.access(path, os.X_OK)

View File

@ -19,17 +19,20 @@ from tests.support.mock import (
# Import Salt libs
import salt.config
import salt.utils.platform
import salt.syspaths
MOCK_MASTER_DEFAULT_OPTS = {
'log_file': '/var/log/salt/master',
'pidfile': '/var/run/salt-master.pid',
'root_dir': '/'
'log_file': '{0}/var/log/salt/master'.format(salt.syspaths.ROOT_DIR),
'pidfile': '{0}/var/run/salt-master.pid'.format(salt.syspaths.ROOT_DIR),
'root_dir': format(salt.syspaths.ROOT_DIR)
}
if salt.utils.platform.is_windows():
MOCK_MASTER_DEFAULT_OPTS = {
'log_file': 'c:\\salt\\var\\log\\salt\\master',
'pidfile': 'c:\\salt\\var\\run\\salt-master.pid',
'root_dir': 'c:\\salt'
'log_file': '{0}\\var\\log\\salt\\master'.format(
salt.syspaths.ROOT_DIR),
'pidfile': '{0}\\var\\run\\salt-master.pid'.format(
salt.syspaths.ROOT_DIR),
'root_dir': format(salt.syspaths.ROOT_DIR)
}
@ -54,9 +57,11 @@ class APIConfigTestCase(TestCase):
'''
with patch('salt.config.client_config', MagicMock(return_value=MOCK_MASTER_DEFAULT_OPTS)):
expected = '/var/log/salt/api'
expected = '{0}/var/log/salt/api'.format(
salt.syspaths.ROOT_DIR if salt.syspaths.ROOT_DIR != '/' else '')
if salt.utils.platform.is_windows():
expected = 'c:\\salt\\var\\log\\salt\\api'
expected = '{0}\\var\\log\\salt\\api'.format(
salt.syspaths.ROOT_DIR)
ret = salt.config.api_config('/some/fake/path')
self.assertEqual(ret['log_file'], expected)
@ -69,9 +74,11 @@ class APIConfigTestCase(TestCase):
'''
with patch('salt.config.client_config', MagicMock(return_value=MOCK_MASTER_DEFAULT_OPTS)):
expected = '/var/run/salt-api.pid'
expected = '{0}/var/run/salt-api.pid'.format(
salt.syspaths.ROOT_DIR if salt.syspaths.ROOT_DIR != '/' else '')
if salt.utils.platform.is_windows():
expected = 'c:\\salt\\var\\run\\salt-api.pid'
expected = '{0}\\var\\run\\salt-api.pid'.format(
salt.syspaths.ROOT_DIR)
ret = salt.config.api_config('/some/fake/path')
self.assertEqual(ret['pidfile'], expected)

View File

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View File

@ -0,0 +1,201 @@
# -*- coding: utf-8 -*-
# Import Python libs
from __future__ import absolute_import
# Import Salt libs
import salt.config
import salt.daemons.masterapi as masterapi
# Import Salt Testing Libs
from tests.support.unit import TestCase
from tests.support.mock import (
patch,
MagicMock,
)
class LocalFuncsTestCase(TestCase):
'''
TestCase for salt.daemons.masterapi.LocalFuncs class
'''
def setUp(self):
opts = salt.config.master_config(None)
self.local_funcs = masterapi.LocalFuncs(opts, 'test-key')
def test_runner_token_not_authenticated(self):
'''
Asserts that a TokenAuthenticationError is returned when the token can't authenticate.
'''
mock_ret = {u'error': {u'name': u'TokenAuthenticationError',
u'message': u'Authentication failure of type "token" occurred.'}}
ret = self.local_funcs.runner({u'token': u'asdfasdfasdfasdf'})
self.assertDictEqual(mock_ret, ret)
def test_runner_token_authorization_error(self):
'''
Asserts that a TokenAuthenticationError is returned when the token authenticates, but is
not authorized.
'''
token = u'asdfasdfasdfasdf'
load = {u'token': token, u'fun': u'test.arg', u'kwarg': {}}
mock_token = {u'token': token, u'eauth': u'foo', u'name': u'test'}
mock_ret = {u'error': {u'name': u'TokenAuthenticationError',
u'message': u'Authentication failure of type "token" occurred '
u'for user test.'}}
with patch('salt.auth.LoadAuth.authenticate_token', MagicMock(return_value=mock_token)), \
patch('salt.auth.LoadAuth.get_auth_list', MagicMock(return_value=[])):
ret = self.local_funcs.runner(load)
self.assertDictEqual(mock_ret, ret)
def test_runner_token_salt_invocation_error(self):
'''
Asserts that a SaltInvocationError is returned when the token authenticates, but the
command is malformed.
'''
token = u'asdfasdfasdfasdf'
load = {u'token': token, u'fun': u'badtestarg', u'kwarg': {}}
mock_token = {u'token': token, u'eauth': u'foo', u'name': u'test'}
mock_ret = {u'error': {u'name': u'SaltInvocationError',
u'message': u'A command invocation error occurred: Check syntax.'}}
with patch('salt.auth.LoadAuth.authenticate_token', MagicMock(return_value=mock_token)), \
patch('salt.auth.LoadAuth.get_auth_list', MagicMock(return_value=[])):
ret = self.local_funcs.runner(load)
self.assertDictEqual(mock_ret, ret)
def test_runner_eauth_not_authenticated(self):
'''
Asserts that an EauthAuthenticationError is returned when the user can't authenticate.
'''
mock_ret = {u'error': {u'name': u'EauthAuthenticationError',
u'message': u'Authentication failure of type "eauth" occurred for '
u'user UNKNOWN.'}}
ret = self.local_funcs.runner({u'eauth': u'foo'})
self.assertDictEqual(mock_ret, ret)
def test_runner_eauth_authorization_error(self):
'''
Asserts that an EauthAuthenticationError is returned when the user authenticates, but is
not authorized.
'''
load = {u'eauth': u'foo', u'username': u'test', u'fun': u'test.arg', u'kwarg': {}}
mock_ret = {u'error': {u'name': u'EauthAuthenticationError',
u'message': u'Authentication failure of type "eauth" occurred for '
u'user test.'}}
with patch('salt.auth.LoadAuth.authenticate_eauth', MagicMock(return_value=True)), \
patch('salt.auth.LoadAuth.get_auth_list', MagicMock(return_value=[])):
ret = self.local_funcs.runner(load)
self.assertDictEqual(mock_ret, ret)
def test_runner_eauth_salt_invocation_errpr(self):
'''
Asserts that an EauthAuthenticationError is returned when the user authenticates, but the
command is malformed.
'''
load = {u'eauth': u'foo', u'username': u'test', u'fun': u'bad.test.arg.func', u'kwarg': {}}
mock_ret = {u'error': {u'name': u'SaltInvocationError',
u'message': u'A command invocation error occurred: Check syntax.'}}
with patch('salt.auth.LoadAuth.authenticate_eauth', MagicMock(return_value=True)), \
patch('salt.auth.LoadAuth.get_auth_list', MagicMock(return_value=[])):
ret = self.local_funcs.runner(load)
self.assertDictEqual(mock_ret, ret)
def test_wheel_token_not_authenticated(self):
'''
Asserts that a TokenAuthenticationError is returned when the token can't authenticate.
'''
mock_ret = {u'error': {u'name': u'TokenAuthenticationError',
u'message': u'Authentication failure of type "token" occurred.'}}
ret = self.local_funcs.wheel({u'token': u'asdfasdfasdfasdf'})
self.assertDictEqual(mock_ret, ret)
def test_wheel_token_authorization_error(self):
'''
Asserts that a TokenAuthenticationError is returned when the token authenticates, but is
not authorized.
'''
token = u'asdfasdfasdfasdf'
load = {u'token': token, u'fun': u'test.arg', u'kwarg': {}}
mock_token = {u'token': token, u'eauth': u'foo', u'name': u'test'}
mock_ret = {u'error': {u'name': u'TokenAuthenticationError',
u'message': u'Authentication failure of type "token" occurred '
u'for user test.'}}
with patch('salt.auth.LoadAuth.authenticate_token', MagicMock(return_value=mock_token)), \
patch('salt.auth.LoadAuth.get_auth_list', MagicMock(return_value=[])):
ret = self.local_funcs.wheel(load)
self.assertDictEqual(mock_ret, ret)
def test_wheel_token_salt_invocation_error(self):
'''
Asserts that a SaltInvocationError is returned when the token authenticates, but the
command is malformed.
'''
token = u'asdfasdfasdfasdf'
load = {u'token': token, u'fun': u'badtestarg', u'kwarg': {}}
mock_token = {u'token': token, u'eauth': u'foo', u'name': u'test'}
mock_ret = {u'error': {u'name': u'SaltInvocationError',
u'message': u'A command invocation error occurred: Check syntax.'}}
with patch('salt.auth.LoadAuth.authenticate_token', MagicMock(return_value=mock_token)), \
patch('salt.auth.LoadAuth.get_auth_list', MagicMock(return_value=[])):
ret = self.local_funcs.wheel(load)
self.assertDictEqual(mock_ret, ret)
def test_wheel_eauth_not_authenticated(self):
'''
Asserts that an EauthAuthenticationError is returned when the user can't authenticate.
'''
mock_ret = {u'error': {u'name': u'EauthAuthenticationError',
u'message': u'Authentication failure of type "eauth" occurred for '
u'user UNKNOWN.'}}
ret = self.local_funcs.wheel({u'eauth': u'foo'})
self.assertDictEqual(mock_ret, ret)
def test_wheel_eauth_authorization_error(self):
'''
Asserts that an EauthAuthenticationError is returned when the user authenticates, but is
not authorized.
'''
load = {u'eauth': u'foo', u'username': u'test', u'fun': u'test.arg', u'kwarg': {}}
mock_ret = {u'error': {u'name': u'EauthAuthenticationError',
u'message': u'Authentication failure of type "eauth" occurred for '
u'user test.'}}
with patch('salt.auth.LoadAuth.authenticate_eauth', MagicMock(return_value=True)), \
patch('salt.auth.LoadAuth.get_auth_list', MagicMock(return_value=[])):
ret = self.local_funcs.wheel(load)
self.assertDictEqual(mock_ret, ret)
def test_wheel_eauth_salt_invocation_errpr(self):
'''
Asserts that an EauthAuthenticationError is returned when the user authenticates, but the
command is malformed.
'''
load = {u'eauth': u'foo', u'username': u'test', u'fun': u'bad.test.arg.func', u'kwarg': {}}
mock_ret = {u'error': {u'name': u'SaltInvocationError',
u'message': u'A command invocation error occurred: Check syntax.'}}
with patch('salt.auth.LoadAuth.authenticate_eauth', MagicMock(return_value=True)), \
patch('salt.auth.LoadAuth.get_auth_list', MagicMock(return_value=[])):
ret = self.local_funcs.wheel(load)
self.assertDictEqual(mock_ret, ret)
def test_wheel_user_not_authenticated(self):
'''
Asserts that an UserAuthenticationError is returned when the user can't authenticate.
'''
mock_ret = {u'error': {u'name': u'UserAuthenticationError',
u'message': u'Authentication failure of type "user" occurred for '
u'user UNKNOWN.'}}
ret = self.local_funcs.wheel({})
self.assertDictEqual(mock_ret, ret)

View File

@ -66,30 +66,28 @@ class AlternativesTestCase(TestCase, LoaderModuleMockMixin):
)
def test_show_current(self):
with patch('os.readlink') as os_readlink_mock:
os_readlink_mock.return_value = '/etc/alternatives/salt'
mock = MagicMock(return_value='/etc/alternatives/salt')
with patch('salt.utils.path.readlink', mock):
ret = alternatives.show_current('better-world')
self.assertEqual('/etc/alternatives/salt', ret)
os_readlink_mock.assert_called_once_with(
'/etc/alternatives/better-world'
)
mock.assert_called_once_with('/etc/alternatives/better-world')
with TestsLoggingHandler() as handler:
os_readlink_mock.side_effect = OSError('Hell was not found!!!')
mock.side_effect = OSError('Hell was not found!!!')
self.assertFalse(alternatives.show_current('hell'))
os_readlink_mock.assert_called_with('/etc/alternatives/hell')
mock.assert_called_with('/etc/alternatives/hell')
self.assertIn('ERROR:alternative: hell does not exist',
handler.messages)
def test_check_installed(self):
with patch('os.readlink') as os_readlink_mock:
os_readlink_mock.return_value = '/etc/alternatives/salt'
mock = MagicMock(return_value='/etc/alternatives/salt')
with patch('salt.utils.path.readlink', mock):
self.assertTrue(
alternatives.check_installed(
'better-world', '/etc/alternatives/salt'
)
)
os_readlink_mock.return_value = False
mock.return_value = False
self.assertFalse(
alternatives.check_installed(
'help', '/etc/alternatives/salt'

View File

@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
'''
:codeauthor: :email:`Alexandru Bleotu <alexandru.bleotu@morganstanley.com>`
Tests for functions in salt.modules.esxcluster
'''
# Import Python Libs
from __future__ import absolute_import
# Import Salt Libs
import salt.modules.esxcluster as esxcluster
# Import Salt Testing Libs
from tests.support.mixins import LoaderModuleMockMixin
from tests.support.unit import TestCase, skipIf
from tests.support.mock import (
MagicMock,
patch,
NO_MOCK,
NO_MOCK_REASON
)
@skipIf(NO_MOCK, NO_MOCK_REASON)
class GetDetailsTestCase(TestCase, LoaderModuleMockMixin):
'''Tests for salt.modules.esxcluster.get_details'''
def setup_loader_modules(self):
return {esxcluster: {'__virtual__':
MagicMock(return_value='esxcluster'),
'__proxy__': {}}}
def test_get_details(self):
mock_get_details = MagicMock()
with patch.dict(esxcluster.__proxy__,
{'esxcluster.get_details': mock_get_details}):
esxcluster.get_details()
mock_get_details.assert_called_once_with()

View File

@ -93,10 +93,12 @@ class GenesisTestCase(TestCase, LoaderModuleMockMixin):
'file.directory_exists': MagicMock(),
'cmd.run': MagicMock(),
'disk.blkid': MagicMock(return_value={})}):
with patch('salt.modules.genesis.salt.utils.which', return_value=True):
param_set['params'].update(common_parms)
self.assertEqual(genesis.bootstrap(**param_set['params']), None)
genesis.__salt__['cmd.run'].assert_any_call(param_set['cmd'], python_shell=False)
with patch('salt.modules.genesis.salt.utils.path.which', return_value=True):
with patch('salt.modules.genesis.salt.utils.validate.path.is_executable',
return_value=True):
param_set['params'].update(common_parms)
self.assertEqual(genesis.bootstrap(**param_set['params']), None)
genesis.__salt__['cmd.run'].assert_any_call(param_set['cmd'], python_shell=False)
with patch.object(genesis, '_bootstrap_pacman', return_value='A') as pacman_patch:
with patch.dict(genesis.__salt__, {'mount.umount': MagicMock(),

View File

@ -118,16 +118,16 @@ class GroupAddTestCase(TestCase, LoaderModuleMockMixin):
'''
os_version_list = [
{'grains': {'kernel': 'Linux', 'os_family': 'RedHat', 'osmajorrelease': '5'},
'cmd': ('gpasswd', '-a', 'root', 'test')},
'cmd': ['gpasswd', '-a', 'root', 'test']},
{'grains': {'kernel': 'Linux', 'os_family': 'Suse', 'osmajorrelease': '11'},
'cmd': ('usermod', '-A', 'test', 'root')},
'cmd': ['usermod', '-A', 'test', 'root']},
{'grains': {'kernel': 'Linux'},
'cmd': ('gpasswd', '--add', 'root', 'test')},
'cmd': ['gpasswd', '--add', 'root', 'test']},
{'grains': {'kernel': 'OTHERKERNEL'},
'cmd': ('usermod', '-G', 'test', 'root')},
'cmd': ['usermod', '-G', 'test', 'root']},
]
for os_version in os_version_list:
@ -145,16 +145,16 @@ class GroupAddTestCase(TestCase, LoaderModuleMockMixin):
'''
os_version_list = [
{'grains': {'kernel': 'Linux', 'os_family': 'RedHat', 'osmajorrelease': '5'},
'cmd': ('gpasswd', '-d', 'root', 'test')},
'cmd': ['gpasswd', '-d', 'root', 'test']},
{'grains': {'kernel': 'Linux', 'os_family': 'Suse', 'osmajorrelease': '11'},
'cmd': ('usermod', '-R', 'test', 'root')},
'cmd': ['usermod', '-R', 'test', 'root']},
{'grains': {'kernel': 'Linux'},
'cmd': ('gpasswd', '--del', 'root', 'test')},
'cmd': ['gpasswd', '--del', 'root', 'test']},
{'grains': {'kernel': 'OpenBSD'},
'cmd': 'usermod -S foo root'},
'cmd': ['usermod', '-S', 'foo', 'root']},
]
for os_version in os_version_list:
@ -180,16 +180,16 @@ class GroupAddTestCase(TestCase, LoaderModuleMockMixin):
'''
os_version_list = [
{'grains': {'kernel': 'Linux', 'os_family': 'RedHat', 'osmajorrelease': '5'},
'cmd': ('gpasswd', '-M', 'foo', 'test')},
'cmd': ['gpasswd', '-M', 'foo', 'test']},
{'grains': {'kernel': 'Linux', 'os_family': 'Suse', 'osmajorrelease': '11'},
'cmd': ('groupmod', '-A', 'foo', 'test')},
'cmd': ['groupmod', '-A', 'foo', 'test']},
{'grains': {'kernel': 'Linux'},
'cmd': ('gpasswd', '--members', 'foo', 'test')},
'cmd': ['gpasswd', '--members', 'foo', 'test']},
{'grains': {'kernel': 'OpenBSD'},
'cmd': 'usermod -G test foo'},
'cmd': ['usermod', '-G', 'test', 'foo']},
]
for os_version in os_version_list:

View File

@ -16,6 +16,7 @@ from tests.support.mock import (
)
# Import Salt Libs
import salt.modules.hosts as hosts
import salt.utils
from salt.ext.six.moves import StringIO
@ -92,8 +93,12 @@ class HostsTestCase(TestCase, LoaderModuleMockMixin):
'''
Tests true if the alias is set
'''
hosts_file = '/etc/hosts'
if salt.utils.is_windows():
hosts_file = r'C:\Windows\System32\Drivers\etc\hosts'
with patch('salt.modules.hosts.__get_hosts_filename',
MagicMock(return_value='/etc/hosts')), \
MagicMock(return_value=hosts_file)), \
patch('os.path.isfile', MagicMock(return_value=False)), \
patch.dict(hosts.__salt__,
{'config.option': MagicMock(return_value=None)}):
@ -139,7 +144,16 @@ class HostsTestCase(TestCase, LoaderModuleMockMixin):
self.close()
def close(self):
data[0] = self.getvalue()
# Don't save unless there's something there. In Windows
# the class gets initialized the first time with mode = w
# which sets the initial value to ''. When the class closes
# it clears out data and causes the test to fail.
# I don't know why it get's initialized with a mode of 'w'
# For the purposes of this test data shouldn't be empty
# This is a problem with this class and not with the hosts
# module
if self.getvalue():
data[0] = self.getvalue()
StringIO.close(self)
expected = '\n'.join((
@ -151,6 +165,7 @@ class HostsTestCase(TestCase, LoaderModuleMockMixin):
mock_opt = MagicMock(return_value=None)
with patch.dict(hosts.__salt__, {'config.option': mock_opt}):
self.assertTrue(hosts.set_host('1.1.1.1', ' '))
self.assertEqual(data[0], expected)
# 'rm_host' function tests: 2
@ -182,9 +197,13 @@ class HostsTestCase(TestCase, LoaderModuleMockMixin):
'''
Tests if specified host entry gets added from the hosts file
'''
hosts_file = '/etc/hosts'
if salt.utils.is_windows():
hosts_file = r'C:\Windows\System32\Drivers\etc\hosts'
with patch('salt.utils.files.fopen', mock_open()), \
patch('salt.modules.hosts.__get_hosts_filename',
MagicMock(return_value='/etc/hosts')):
MagicMock(return_value=hosts_file)):
mock_opt = MagicMock(return_value=None)
with patch.dict(hosts.__salt__, {'config.option': mock_opt}):
self.assertTrue(hosts.add_host('10.10.10.10', 'Salt1'))

View File

@ -15,38 +15,38 @@ import salt.modules.ini_manage as ini
class IniManageTestCase(TestCase):
TEST_FILE_CONTENT = '''\
# Comment on the first line
# First main option
option1=main1
# Second main option
option2=main2
[main]
# Another comment
test1=value 1
test2=value 2
[SectionB]
test1=value 1B
# Blank line should be above
test3 = value 3B
[SectionC]
# The following option is empty
empty_option=
'''
TEST_FILE_CONTENT = os.linesep.join([
'# Comment on the first line',
'',
'# First main option',
'option1=main1',
'',
'# Second main option',
'option2=main2',
'',
'',
'[main]',
'# Another comment',
'test1=value 1',
'',
'test2=value 2',
'',
'[SectionB]',
'test1=value 1B',
'',
'# Blank line should be above',
'test3 = value 3B',
'',
'[SectionC]',
'# The following option is empty',
'empty_option='
])
maxDiff = None
def setUp(self):
self.tfile = tempfile.NamedTemporaryFile(delete=False, mode='w+')
self.tfile.write(self.TEST_FILE_CONTENT)
self.tfile = tempfile.NamedTemporaryFile(delete=False, mode='w+b')
self.tfile.write(salt.utils.to_bytes(self.TEST_FILE_CONTENT))
self.tfile.close()
def tearDown(self):
@ -121,40 +121,42 @@ empty_option=
})
with salt.utils.files.fopen(self.tfile.name, 'r') as fp:
file_content = fp.read()
self.assertIn('\nempty_option = \n', file_content,
'empty_option was not preserved')
expected = '{0}{1}{0}'.format(os.linesep, 'empty_option = ')
self.assertIn(expected, file_content, 'empty_option was not preserved')
def test_empty_lines_preserved_after_edit(self):
ini.set_option(self.tfile.name, {
'SectionB': {'test3': 'new value 3B'},
})
expected = os.linesep.join([
'# Comment on the first line',
'',
'# First main option',
'option1 = main1',
'',
'# Second main option',
'option2 = main2',
'',
'[main]',
'# Another comment',
'test1 = value 1',
'',
'test2 = value 2',
'',
'[SectionB]',
'test1 = value 1B',
'',
'# Blank line should be above',
'test3 = new value 3B',
'',
'[SectionC]',
'# The following option is empty',
'empty_option = ',
''
])
with salt.utils.files.fopen(self.tfile.name, 'r') as fp:
file_content = fp.read()
self.assertEqual('''\
# Comment on the first line
# First main option
option1 = main1
# Second main option
option2 = main2
[main]
# Another comment
test1 = value 1
test2 = value 2
[SectionB]
test1 = value 1B
# Blank line should be above
test3 = new value 3B
[SectionC]
# The following option is empty
empty_option =
''', file_content)
self.assertEqual(expected, file_content)
def test_empty_lines_preserved_after_multiple_edits(self):
ini.set_option(self.tfile.name, {

View File

@ -20,6 +20,8 @@ try:
from salt.modules import kubernetes
except ImportError:
kubernetes = False
if not kubernetes.HAS_LIBS:
kubernetes = False
@skipIf(NO_MOCK, NO_MOCK_REASON)

View File

@ -7,11 +7,14 @@
'''
# Import Python libs
from __future__ import absolute_import
import re
# Import Salt Testing libs
from tests.support.mixins import LoaderModuleMockMixin
from tests.support.unit import skipIf, TestCase
from tests.support.mock import NO_MOCK, NO_MOCK_REASON, MagicMock, patch
from tests.support.paths import TMP
import salt.utils.files
# Import salt libs
import salt.modules.portage_config as portage_config
@ -19,27 +22,101 @@ import salt.modules.portage_config as portage_config
@skipIf(NO_MOCK, NO_MOCK_REASON)
class PortageConfigTestCase(TestCase, LoaderModuleMockMixin):
class DummyAtom(object):
def __init__(self, atom):
self.cp, self.repo = atom.split("::") if "::" in atom else (atom, None)
def __init__(self):
self.cp = None
self.repo = None
def __call__(self, atom, *_, **__):
if atom == '#' or isinstance(atom, MagicMock):
self.repo = None
self.cp = None
return self
# extract (and remove) repo
atom, self.repo = atom.split('::') if '::' in atom else (atom, None)
# remove '>, >=, <=, =, ~' etc.
atom = re.sub(r'[<>~+=]', '', atom)
# remove slots
atom = re.sub(r':[0-9][^:]*', '', atom)
# remove version
atom = re.sub(r'-[0-9][\.0-9]*', '', atom)
self.cp = atom
return self
def setup_loader_modules(self):
self.portage = MagicMock()
self.addCleanup(delattr, self, 'portage')
return {portage_config: {'portage': self.portage}}
try:
import portage
return {}
except ImportError:
dummy_atom = self.DummyAtom()
self.portage = MagicMock()
self.portage.dep.Atom = MagicMock(side_effect=dummy_atom)
self.portage.dep_getkey = MagicMock(side_effect=lambda x: dummy_atom(x).cp)
self.portage.exception.InvalidAtom = Exception
self.addCleanup(delattr, self, 'portage')
return {portage_config: {'portage': self.portage}}
def test_get_config_file_wildcards(self):
pairs = [
('*/*::repo', '/etc/portage/package.mask/repo'),
('*/pkg::repo', '/etc/portage/package.mask/pkg'),
('cat/*', '/etc/portage/package.mask/cat'),
('cat/*', '/etc/portage/package.mask/cat_'),
('cat/pkg', '/etc/portage/package.mask/cat/pkg'),
('cat/pkg::repo', '/etc/portage/package.mask/cat/pkg'),
]
for (atom, expected) in pairs:
dummy_atom = self.DummyAtom(atom)
self.portage.dep.Atom = MagicMock(return_value=dummy_atom)
with patch.object(portage_config, '_p_to_cp', MagicMock(return_value=dummy_atom.cp)):
self.assertEqual(portage_config._get_config_file('mask', atom), expected)
self.assertEqual(portage_config._get_config_file('mask', atom), expected)
def test_enforce_nice_config(self):
atoms = [
('*/*::repo', 'repo'),
('*/pkg1::repo', 'pkg1'),
('cat/*', 'cat_'),
('cat/pkg2', 'cat/pkg2'),
('cat/pkg3::repo', 'cat/pkg3'),
('<cat/pkg4-0.0.0.0', 'cat/pkg4'),
('>cat/pkg5-0.0.0.0:0', 'cat/pkg5'),
('>cat/pkg6-0.0.0.0:0::repo', 'cat/pkg6'),
('<=cat/pkg7-0.0.0.0', 'cat/pkg7'),
('=cat/pkg8-0.0.0.0', 'cat/pkg8'),
]
supported = [
('accept_keywords', ['~amd64']),
('env', ['glibc.conf']),
('license', ['LICENCE1', 'LICENCE2']),
('mask', ['']),
('properties', ['* -interactive']),
('unmask', ['']),
('use', ['apple', '-banana', 'ananas', 'orange']),
]
base_path = TMP + '/package.{0}'
def make_line(atom, addition):
return atom + (' ' + addition if addition != '' else '') + '\n'
for typ, additions in supported:
path = base_path.format(typ)
with salt.utils.files.fopen(path, 'a') as fh:
for atom, _ in atoms:
for addition in additions:
line = make_line(atom, addition)
fh.write('# comment for: ' + line)
fh.write(line)
with patch.object(portage_config, 'BASE_PATH', base_path):
with patch.object(portage_config, '_merge_flags', lambda l1, l2, _: list(set(l1 + l2))):
portage_config.enforce_nice_config()
for typ, additions in supported:
for atom, file_name in atoms:
with salt.utils.files.fopen(base_path.format(typ) + "/" + file_name, 'r') as fh:
for line in fh:
self.assertTrue(atom in line, msg="'{}' not in '{}'".format(addition, line))
for addition in additions:
self.assertTrue(addition in line, msg="'{}' not in '{}'".format(addition, line))

View File

@ -3,9 +3,12 @@
# Import Python libs
from __future__ import absolute_import
import os.path
try:
import salt.modules.saltcheck as saltcheck
import salt.config
import salt.syspaths as syspaths
except:
raise
@ -30,6 +33,14 @@ class LinuxSysctlTestCase(TestCase, LoaderModuleMockMixin):
'''
def setup_loader_modules(self):
# Setting the environment to be local
local_opts = salt.config.minion_config(
os.path.join(syspaths.CONFIG_DIR, u'minion'))
local_opts['file_client'] = 'local'
patcher = patch('salt.config.minion_config',
MagicMock(return_value=local_opts))
patcher.start()
self.addCleanup(patcher.stop)
return {saltcheck: {}}
def test_call_salt_command(self):

View File

@ -109,10 +109,9 @@ class VirtualenvTestCase(TestCase, LoaderModuleMockMixin):
# Are we logging the deprecation information?
self.assertIn(
'INFO:The virtualenv \'--never-download\' option has been '
'deprecated in virtualenv(>=1.10), as such, the '
'\'never_download\' option to `virtualenv.create()` has '
'also been deprecated and it\'s not necessary anymore.',
'INFO:--never-download was deprecated in 1.10.0, '
'but reimplemented in 14.0.0. If this feature is needed, '
'please install a supported virtualenv version.',
handler.messages
)

View File

@ -620,6 +620,7 @@ class _GetProxyConnectionDetailsTestCase(TestCase, LoaderModuleMockMixin):
'principal': 'fake_principal',
'domain': 'fake_domain'}
self.esxdatacenter_details = {'vcenter': 'fake_vcenter',
'datacenter': 'fake_dc',
'username': 'fake_username',
'password': 'fake_password',
'protocol': 'fake_protocol',
@ -627,9 +628,20 @@ class _GetProxyConnectionDetailsTestCase(TestCase, LoaderModuleMockMixin):
'mechanism': 'fake_mechanism',
'principal': 'fake_principal',
'domain': 'fake_domain'}
self.esxcluster_details = {'vcenter': 'fake_vcenter',
'datacenter': 'fake_dc',
'cluster': 'fake_cluster',
'username': 'fake_username',
'password': 'fake_password',
'protocol': 'fake_protocol',
'port': 'fake_port',
'mechanism': 'fake_mechanism',
'principal': 'fake_principal',
'domain': 'fake_domain'}
def tearDown(self):
for attrname in ('esxi_host_details', 'esxi_vcenter_details'):
for attrname in ('esxi_host_details', 'esxi_vcenter_details',
'esxdatacenter_details', 'esxcluster_details'):
try:
delattr(self, attrname)
except AttributeError:
@ -651,8 +663,22 @@ class _GetProxyConnectionDetailsTestCase(TestCase, LoaderModuleMockMixin):
MagicMock(return_value='esxdatacenter')):
with patch.dict(vsphere.__salt__,
{'esxdatacenter.get_details': MagicMock(
return_value=self.esxdatacenter_details)}):
return_value=self.esxdatacenter_details)}):
ret = vsphere._get_proxy_connection_details()
self.assertEqual(('fake_vcenter', 'fake_username', 'fake_password',
'fake_protocol', 'fake_port', 'fake_mechanism',
'fake_principal', 'fake_domain'), ret)
def test_esxcluster_proxy_details(self):
with patch('salt.modules.vsphere.get_proxy_type',
MagicMock(return_value='esxcluster')):
with patch.dict(vsphere.__salt__,
{'esxcluster.get_details': MagicMock(
return_value=self.esxcluster_details)}):
ret = vsphere._get_proxy_connection_details()
self.assertEqual(('fake_vcenter', 'fake_username', 'fake_password',
'fake_protocol', 'fake_port', 'fake_mechanism',
'fake_principal', 'fake_domain'), ret)
def test_esxi_proxy_vcenter_details(self):
with patch('salt.modules.vsphere.get_proxy_type',
@ -862,8 +888,8 @@ class GetServiceInstanceViaProxyTestCase(TestCase, LoaderModuleMockMixin):
}
}
def test_supported_proxes(self):
supported_proxies = ['esxi', 'esxdatacenter']
def test_supported_proxies(self):
supported_proxies = ['esxi', 'esxcluster', 'esxdatacenter']
for proxy_type in supported_proxies:
with patch('salt.modules.vsphere.get_proxy_type',
MagicMock(return_value=proxy_type)):
@ -905,8 +931,8 @@ class DisconnectTestCase(TestCase, LoaderModuleMockMixin):
}
}
def test_supported_proxes(self):
supported_proxies = ['esxi', 'esxdatacenter']
def test_supported_proxies(self):
supported_proxies = ['esxi', 'esxcluster', 'esxdatacenter']
for proxy_type in supported_proxies:
with patch('salt.modules.vsphere.get_proxy_type',
MagicMock(return_value=proxy_type)):
@ -946,8 +972,8 @@ class TestVcenterConnectionTestCase(TestCase, LoaderModuleMockMixin):
}
}
def test_supported_proxes(self):
supported_proxies = ['esxi', 'esxdatacenter']
def test_supported_proxies(self):
supported_proxies = ['esxi', 'esxcluster', 'esxdatacenter']
for proxy_type in supported_proxies:
with patch('salt.modules.vsphere.get_proxy_type',
MagicMock(return_value=proxy_type)):
@ -1022,7 +1048,7 @@ class ListDatacentersViaProxyTestCase(TestCase, LoaderModuleMockMixin):
}
def test_supported_proxies(self):
supported_proxies = ['esxdatacenter']
supported_proxies = ['esxcluster', 'esxdatacenter']
for proxy_type in supported_proxies:
with patch('salt.modules.vsphere.get_proxy_type',
MagicMock(return_value=proxy_type)):
@ -1099,7 +1125,7 @@ class CreateDatacenterTestCase(TestCase, LoaderModuleMockMixin):
}
}
def test_supported_proxes(self):
def test_supported_proxies(self):
supported_proxies = ['esxdatacenter']
for proxy_type in supported_proxies:
with patch('salt.modules.vsphere.get_proxy_type',

View File

@ -0,0 +1,185 @@
# -*- coding: utf-8 -*-
'''
:codeauthor: :email:`Alexandru Bleotu <alexandru.bleotu@morganstanley.com>`
Tests for esxcluster proxy
'''
# Import Python Libs
from __future__ import absolute_import
# Import external libs
try:
import jsonschema
HAS_JSONSCHEMA = True
except ImportError:
HAS_JSONSCHEMA = False
# Import Salt Libs
import salt.proxy.esxcluster as esxcluster
import salt.exceptions
from salt.config.schemas.esxcluster import EsxclusterProxySchema
# Import Salt Testing Libs
from tests.support.mixins import LoaderModuleMockMixin
from tests.support.unit import TestCase, skipIf
from tests.support.mock import (
MagicMock,
patch,
NO_MOCK,
NO_MOCK_REASON
)
@skipIf(NO_MOCK, NO_MOCK_REASON)
@skipIf(not HAS_JSONSCHEMA, 'jsonschema is required')
class InitTestCase(TestCase, LoaderModuleMockMixin):
'''Tests for salt.proxy.esxcluster.init'''
def setup_loader_modules(self):
return {esxcluster: {'__virtual__':
MagicMock(return_value='esxcluster'),
'DETAILS': {}, '__pillar__': {}}}
def setUp(self):
self.opts_userpass = {'proxy': {'proxytype': 'esxcluster',
'vcenter': 'fake_vcenter',
'datacenter': 'fake_dc',
'cluster': 'fake_cluster',
'mechanism': 'userpass',
'username': 'fake_username',
'passwords': ['fake_password'],
'protocol': 'fake_protocol',
'port': 100}}
self.opts_sspi = {'proxy': {'proxytype': 'esxcluster',
'vcenter': 'fake_vcenter',
'datacenter': 'fake_dc',
'cluster': 'fake_cluster',
'mechanism': 'sspi',
'domain': 'fake_domain',
'principal': 'fake_principal',
'protocol': 'fake_protocol',
'port': 100}}
patches = (('salt.proxy.esxcluster.merge',
MagicMock(return_value=self.opts_sspi['proxy'])),)
for mod, mock in patches:
patcher = patch(mod, mock)
patcher.start()
self.addCleanup(patcher.stop)
def test_merge(self):
mock_pillar_proxy = MagicMock()
mock_opts_proxy = MagicMock()
mock_merge = MagicMock(return_value=self.opts_sspi['proxy'])
with patch.dict(esxcluster.__pillar__,
{'proxy': mock_pillar_proxy}):
with patch('salt.proxy.esxcluster.merge', mock_merge):
esxcluster.init(opts={'proxy': mock_opts_proxy})
mock_merge.assert_called_once_with(mock_opts_proxy, mock_pillar_proxy)
def test_esxcluster_schema(self):
mock_json_validate = MagicMock()
serialized_schema = EsxclusterProxySchema().serialize()
with patch('salt.proxy.esxcluster.jsonschema.validate',
mock_json_validate):
esxcluster.init(self.opts_sspi)
mock_json_validate.assert_called_once_with(
self.opts_sspi['proxy'], serialized_schema)
def test_invalid_proxy_input_error(self):
with patch('salt.proxy.esxcluster.jsonschema.validate',
MagicMock(side_effect=jsonschema.exceptions.ValidationError(
'Validation Error'))):
with self.assertRaises(salt.exceptions.InvalidConfigError) as \
excinfo:
esxcluster.init(self.opts_userpass)
self.assertEqual(excinfo.exception.strerror.message,
'Validation Error')
def test_no_username(self):
opts = self.opts_userpass.copy()
del opts['proxy']['username']
with patch('salt.proxy.esxcluster.merge',
MagicMock(return_value=opts['proxy'])):
with self.assertRaises(salt.exceptions.InvalidConfigError) as \
excinfo:
esxcluster.init(opts)
self.assertEqual(excinfo.exception.strerror,
'Mechanism is set to \'userpass\', but no '
'\'username\' key found in proxy config.')
def test_no_passwords(self):
opts = self.opts_userpass.copy()
del opts['proxy']['passwords']
with patch('salt.proxy.esxcluster.merge',
MagicMock(return_value=opts['proxy'])):
with self.assertRaises(salt.exceptions.InvalidConfigError) as \
excinfo:
esxcluster.init(opts)
self.assertEqual(excinfo.exception.strerror,
'Mechanism is set to \'userpass\', but no '
'\'passwords\' key found in proxy config.')
def test_no_domain(self):
opts = self.opts_sspi.copy()
del opts['proxy']['domain']
with patch('salt.proxy.esxcluster.merge',
MagicMock(return_value=opts['proxy'])):
with self.assertRaises(salt.exceptions.InvalidConfigError) as \
excinfo:
esxcluster.init(opts)
self.assertEqual(excinfo.exception.strerror,
'Mechanism is set to \'sspi\', but no '
'\'domain\' key found in proxy config.')
def test_no_principal(self):
opts = self.opts_sspi.copy()
del opts['proxy']['principal']
with patch('salt.proxy.esxcluster.merge',
MagicMock(return_value=opts['proxy'])):
with self.assertRaises(salt.exceptions.InvalidConfigError) as \
excinfo:
esxcluster.init(opts)
self.assertEqual(excinfo.exception.strerror,
'Mechanism is set to \'sspi\', but no '
'\'principal\' key found in proxy config.')
def test_find_credentials(self):
mock_find_credentials = MagicMock(return_value=('fake_username',
'fake_password'))
with patch('salt.proxy.esxcluster.merge',
MagicMock(return_value=self.opts_userpass['proxy'])):
with patch('salt.proxy.esxcluster.find_credentials',
mock_find_credentials):
esxcluster.init(self.opts_userpass)
mock_find_credentials.assert_called_once_with()
def test_details_userpass(self):
mock_find_credentials = MagicMock(return_value=('fake_username',
'fake_password'))
with patch('salt.proxy.esxcluster.merge',
MagicMock(return_value=self.opts_userpass['proxy'])):
with patch('salt.proxy.esxcluster.find_credentials',
mock_find_credentials):
esxcluster.init(self.opts_userpass)
self.assertDictEqual(esxcluster.DETAILS,
{'vcenter': 'fake_vcenter',
'datacenter': 'fake_dc',
'cluster': 'fake_cluster',
'mechanism': 'userpass',
'username': 'fake_username',
'password': 'fake_password',
'passwords': ['fake_password'],
'protocol': 'fake_protocol',
'port': 100})
def test_details_sspi(self):
esxcluster.init(self.opts_sspi)
self.assertDictEqual(esxcluster.DETAILS,
{'vcenter': 'fake_vcenter',
'datacenter': 'fake_dc',
'cluster': 'fake_cluster',
'mechanism': 'sspi',
'domain': 'fake_domain',
'principal': 'fake_principal',
'protocol': 'fake_protocol',
'port': 100})

View File

@ -97,7 +97,10 @@ class LocalCacheCleanOldJobsTestCase(TestCase, LoaderModuleMockMixin):
local_cache.clean_old_jobs()
# Get the name of the JID directory that was created to test against
jid_dir_name = jid_dir.rpartition('/')[2]
if salt.utils.is_windows():
jid_dir_name = jid_dir.rpartition('\\')[2]
else:
jid_dir_name = jid_dir.rpartition('/')[2]
# Assert the JID directory is still present to be cleaned after keep_jobs interval
self.assertEqual([jid_dir_name], os.listdir(TMP_JID_DIR))

209
tests/unit/test_master.py Normal file
View File

@ -0,0 +1,209 @@
# -*- coding: utf-8 -*-
# Import Python libs
from __future__ import absolute_import
# Import Salt libs
import salt.config
import salt.master
# Import Salt Testing Libs
from tests.support.unit import TestCase
from tests.support.mock import (
patch,
MagicMock,
)
class ClearFuncsTestCase(TestCase):
'''
TestCase for salt.master.ClearFuncs class
'''
def setUp(self):
opts = salt.config.master_config(None)
self.clear_funcs = salt.master.ClearFuncs(opts, {})
def test_runner_token_not_authenticated(self):
'''
Asserts that a TokenAuthenticationError is returned when the token can't authenticate.
'''
mock_ret = {u'error': {u'name': u'TokenAuthenticationError',
u'message': u'Authentication failure of type "token" occurred.'}}
ret = self.clear_funcs.runner({u'token': u'asdfasdfasdfasdf'})
self.assertDictEqual(mock_ret, ret)
def test_runner_token_authorization_error(self):
'''
Asserts that a TokenAuthenticationError is returned when the token authenticates, but is
not authorized.
'''
token = u'asdfasdfasdfasdf'
clear_load = {u'token': token, u'fun': u'test.arg'}
mock_token = {u'token': token, u'eauth': u'foo', u'name': u'test'}
mock_ret = {u'error': {u'name': u'TokenAuthenticationError',
u'message': u'Authentication failure of type "token" occurred '
u'for user test.'}}
with patch('salt.auth.LoadAuth.authenticate_token', MagicMock(return_value=mock_token)), \
patch('salt.auth.LoadAuth.get_auth_list', MagicMock(return_value=[])):
ret = self.clear_funcs.runner(clear_load)
self.assertDictEqual(mock_ret, ret)
def test_runner_token_salt_invocation_error(self):
'''
Asserts that a SaltInvocationError is returned when the token authenticates, but the
command is malformed.
'''
token = u'asdfasdfasdfasdf'
clear_load = {u'token': token, u'fun': u'badtestarg'}
mock_token = {u'token': token, u'eauth': u'foo', u'name': u'test'}
mock_ret = {u'error': {u'name': u'SaltInvocationError',
u'message': u'A command invocation error occurred: Check syntax.'}}
with patch('salt.auth.LoadAuth.authenticate_token', MagicMock(return_value=mock_token)), \
patch('salt.auth.LoadAuth.get_auth_list', MagicMock(return_value=[])):
ret = self.clear_funcs.runner(clear_load)
self.assertDictEqual(mock_ret, ret)
def test_runner_eauth_not_authenticated(self):
'''
Asserts that an EauthAuthenticationError is returned when the user can't authenticate.
'''
mock_ret = {u'error': {u'name': u'EauthAuthenticationError',
u'message': u'Authentication failure of type "eauth" occurred for '
u'user UNKNOWN.'}}
ret = self.clear_funcs.runner({u'eauth': u'foo'})
self.assertDictEqual(mock_ret, ret)
def test_runner_eauth_authorization_error(self):
'''
Asserts that an EauthAuthenticationError is returned when the user authenticates, but is
not authorized.
'''
clear_load = {u'eauth': u'foo', u'username': u'test', u'fun': u'test.arg'}
mock_ret = {u'error': {u'name': u'EauthAuthenticationError',
u'message': u'Authentication failure of type "eauth" occurred for '
u'user test.'}}
with patch('salt.auth.LoadAuth.authenticate_eauth', MagicMock(return_value=True)), \
patch('salt.auth.LoadAuth.get_auth_list', MagicMock(return_value=[])):
ret = self.clear_funcs.runner(clear_load)
self.assertDictEqual(mock_ret, ret)
def test_runner_eauth_salt_invocation_errpr(self):
'''
Asserts that an EauthAuthenticationError is returned when the user authenticates, but the
command is malformed.
'''
clear_load = {u'eauth': u'foo', u'username': u'test', u'fun': u'bad.test.arg.func'}
mock_ret = {u'error': {u'name': u'SaltInvocationError',
u'message': u'A command invocation error occurred: Check syntax.'}}
with patch('salt.auth.LoadAuth.authenticate_eauth', MagicMock(return_value=True)), \
patch('salt.auth.LoadAuth.get_auth_list', MagicMock(return_value=[])):
ret = self.clear_funcs.runner(clear_load)
self.assertDictEqual(mock_ret, ret)
def test_runner_user_not_authenticated(self):
'''
Asserts that an UserAuthenticationError is returned when the user can't authenticate.
'''
mock_ret = {u'error': {u'name': u'UserAuthenticationError',
u'message': u'Authentication failure of type "user" occurred'}}
ret = self.clear_funcs.runner({})
self.assertDictEqual(mock_ret, ret)
def test_wheel_token_not_authenticated(self):
'''
Asserts that a TokenAuthenticationError is returned when the token can't authenticate.
'''
mock_ret = {u'error': {u'name': u'TokenAuthenticationError',
u'message': u'Authentication failure of type "token" occurred.'}}
ret = self.clear_funcs.wheel({u'token': u'asdfasdfasdfasdf'})
self.assertDictEqual(mock_ret, ret)
def test_wheel_token_authorization_error(self):
'''
Asserts that a TokenAuthenticationError is returned when the token authenticates, but is
not authorized.
'''
token = u'asdfasdfasdfasdf'
clear_load = {u'token': token, u'fun': u'test.arg'}
mock_token = {u'token': token, u'eauth': u'foo', u'name': u'test'}
mock_ret = {u'error': {u'name': u'TokenAuthenticationError',
u'message': u'Authentication failure of type "token" occurred '
u'for user test.'}}
with patch('salt.auth.LoadAuth.authenticate_token', MagicMock(return_value=mock_token)), \
patch('salt.auth.LoadAuth.get_auth_list', MagicMock(return_value=[])):
ret = self.clear_funcs.wheel(clear_load)
self.assertDictEqual(mock_ret, ret)
def test_wheel_token_salt_invocation_error(self):
'''
Asserts that a SaltInvocationError is returned when the token authenticates, but the
command is malformed.
'''
token = u'asdfasdfasdfasdf'
clear_load = {u'token': token, u'fun': u'badtestarg'}
mock_token = {u'token': token, u'eauth': u'foo', u'name': u'test'}
mock_ret = {u'error': {u'name': u'SaltInvocationError',
u'message': u'A command invocation error occurred: Check syntax.'}}
with patch('salt.auth.LoadAuth.authenticate_token', MagicMock(return_value=mock_token)), \
patch('salt.auth.LoadAuth.get_auth_list', MagicMock(return_value=[])):
ret = self.clear_funcs.wheel(clear_load)
self.assertDictEqual(mock_ret, ret)
def test_wheel_eauth_not_authenticated(self):
'''
Asserts that an EauthAuthenticationError is returned when the user can't authenticate.
'''
mock_ret = {u'error': {u'name': u'EauthAuthenticationError',
u'message': u'Authentication failure of type "eauth" occurred for '
u'user UNKNOWN.'}}
ret = self.clear_funcs.wheel({u'eauth': u'foo'})
self.assertDictEqual(mock_ret, ret)
def test_wheel_eauth_authorization_error(self):
'''
Asserts that an EauthAuthenticationError is returned when the user authenticates, but is
not authorized.
'''
clear_load = {u'eauth': u'foo', u'username': u'test', u'fun': u'test.arg'}
mock_ret = {u'error': {u'name': u'EauthAuthenticationError',
u'message': u'Authentication failure of type "eauth" occurred for '
u'user test.'}}
with patch('salt.auth.LoadAuth.authenticate_eauth', MagicMock(return_value=True)), \
patch('salt.auth.LoadAuth.get_auth_list', MagicMock(return_value=[])):
ret = self.clear_funcs.wheel(clear_load)
self.assertDictEqual(mock_ret, ret)
def test_wheel_eauth_salt_invocation_errpr(self):
'''
Asserts that an EauthAuthenticationError is returned when the user authenticates, but the
command is malformed.
'''
clear_load = {u'eauth': u'foo', u'username': u'test', u'fun': u'bad.test.arg.func'}
mock_ret = {u'error': {u'name': u'SaltInvocationError',
u'message': u'A command invocation error occurred: Check syntax.'}}
with patch('salt.auth.LoadAuth.authenticate_eauth', MagicMock(return_value=True)), \
patch('salt.auth.LoadAuth.get_auth_list', MagicMock(return_value=[])):
ret = self.clear_funcs.wheel(clear_load)
self.assertDictEqual(mock_ret, ret)
def test_wheel_user_not_authenticated(self):
'''
Asserts that an UserAuthenticationError is returned when the user can't authenticate.
'''
mock_ret = {u'error': {u'name': u'UserAuthenticationError',
u'message': u'Authentication failure of type "user" occurred'}}
ret = self.clear_funcs.wheel({})
self.assertDictEqual(mock_ret, ret)

View File

@ -122,12 +122,14 @@ class TestGitConfigParser(TestCase):
self.conf.get(u'alias', u'modified'),
u"""! git status --porcelain | awk 'match($1, "M"){print $2}'"""
)
# future lint: disable=non-unicode-string
self.assertEqual(
self.conf.get(u'alias', u'hist'),
salt.utils.stringutils.to_unicode(
r"""log --pretty=format:\"%h %ad | %s%d [%an]\" --graph --date=short"""
)
)
# future lint: enable=non-unicode-string
def test_read_space_indent(self):
'''
@ -180,7 +182,7 @@ class TestGitConfigParser(TestCase):
[orig_refspec, new_refspec]
)
# Write the config object to a file
with salt.utils.files.fopen(self.new_config, 'w') as fp_:
with salt.utils.files.fopen(self.new_config, u'w') as fp_:
self.conf.write(fp_)
# Confirm that the new file was written correctly
expected = self.fix_indent(ORIG_CONFIG)
@ -257,10 +259,10 @@ class TestGitConfigParser(TestCase):
'''
Test writing using non-binary filehandle
'''
self._test_write(mode='w')
self._test_write(mode=u'w')
def test_write_binary(self):
'''
Test writing using binary filehandle
'''
self._test_write(mode='wb')
self._test_write(mode=u'wb')

View File

@ -57,7 +57,9 @@ class CkMinionsTestCase(TestCase):
ret = self.ckminions.spec_check(auth_list, 'test.arg', {}, 'wheel')
self.assertFalse(ret)
ret = self.ckminions.spec_check(auth_list, 'testarg', {}, 'runner')
self.assertFalse(ret)
mock_ret = {'error': {'name': 'SaltInvocationError',
'message': 'A command invocation error occurred: Check syntax.'}}
self.assertDictEqual(mock_ret, ret)
# Test spec in plural form
auth_list = ['@runners']