mirror of
https://github.com/valitydev/salt.git
synced 2024-11-07 17:09:03 +00:00
Merge branch 'develop' into esxdatacenter-gh
This commit is contained in:
commit
f1bbb84efc
@ -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-
|
||||
|
@ -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
|
||||
|
||||
|
@ -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`.
|
||||
|
@ -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
|
||||
------------------
|
||||
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
44
salt/config/schemas/esxcluster.py
Normal file
44
salt/config/schemas/esxcluster.py
Normal 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)
|
@ -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
|
||||
|
@ -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'):
|
||||
'''
|
||||
|
@ -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,
|
||||
|
141
salt/master.py
141
salt/master.py
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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__
|
||||
|
@ -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]
|
||||
|
@ -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):
|
||||
|
29
salt/modules/esxcluster.py
Normal file
29
salt/modules/esxcluster.py
Normal 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']()
|
@ -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',
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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
310
salt/proxy/esxcluster.py
Normal 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
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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'],
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
1
tests/unit/daemons/__init__.py
Normal file
1
tests/unit/daemons/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
201
tests/unit/daemons/test_masterapi.py
Normal file
201
tests/unit/daemons/test_masterapi.py
Normal 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)
|
@ -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'
|
||||
|
38
tests/unit/modules/test_esxcluster.py
Normal file
38
tests/unit/modules/test_esxcluster.py
Normal 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()
|
@ -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(),
|
||||
|
@ -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:
|
||||
|
@ -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'))
|
||||
|
@ -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, {
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
)
|
||||
|
||||
|
@ -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',
|
||||
|
185
tests/unit/proxy/test_esxcluster.py
Normal file
185
tests/unit/proxy/test_esxcluster.py
Normal 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})
|
@ -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
209
tests/unit/test_master.py
Normal 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)
|
@ -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')
|
||||
|
@ -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']
|
||||
|
Loading…
Reference in New Issue
Block a user