First install Ansible
This commit is contained in:
commit
8a3de5338c
247
ansible/bin/Activate.ps1
Normal file
247
ansible/bin/Activate.ps1
Normal file
@ -0,0 +1,247 @@
|
||||
<#
|
||||
.Synopsis
|
||||
Activate a Python virtual environment for the current PowerShell session.
|
||||
|
||||
.Description
|
||||
Pushes the python executable for a virtual environment to the front of the
|
||||
$Env:PATH environment variable and sets the prompt to signify that you are
|
||||
in a Python virtual environment. Makes use of the command line switches as
|
||||
well as the `pyvenv.cfg` file values present in the virtual environment.
|
||||
|
||||
.Parameter VenvDir
|
||||
Path to the directory that contains the virtual environment to activate. The
|
||||
default value for this is the parent of the directory that the Activate.ps1
|
||||
script is located within.
|
||||
|
||||
.Parameter Prompt
|
||||
The prompt prefix to display when this virtual environment is activated. By
|
||||
default, this prompt is the name of the virtual environment folder (VenvDir)
|
||||
surrounded by parentheses and followed by a single space (ie. '(.venv) ').
|
||||
|
||||
.Example
|
||||
Activate.ps1
|
||||
Activates the Python virtual environment that contains the Activate.ps1 script.
|
||||
|
||||
.Example
|
||||
Activate.ps1 -Verbose
|
||||
Activates the Python virtual environment that contains the Activate.ps1 script,
|
||||
and shows extra information about the activation as it executes.
|
||||
|
||||
.Example
|
||||
Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
|
||||
Activates the Python virtual environment located in the specified location.
|
||||
|
||||
.Example
|
||||
Activate.ps1 -Prompt "MyPython"
|
||||
Activates the Python virtual environment that contains the Activate.ps1 script,
|
||||
and prefixes the current prompt with the specified string (surrounded in
|
||||
parentheses) while the virtual environment is active.
|
||||
|
||||
.Notes
|
||||
On Windows, it may be required to enable this Activate.ps1 script by setting the
|
||||
execution policy for the user. You can do this by issuing the following PowerShell
|
||||
command:
|
||||
|
||||
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||
|
||||
For more information on Execution Policies:
|
||||
https://go.microsoft.com/fwlink/?LinkID=135170
|
||||
|
||||
#>
|
||||
Param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[String]
|
||||
$VenvDir,
|
||||
[Parameter(Mandatory = $false)]
|
||||
[String]
|
||||
$Prompt
|
||||
)
|
||||
|
||||
<# Function declarations --------------------------------------------------- #>
|
||||
|
||||
<#
|
||||
.Synopsis
|
||||
Remove all shell session elements added by the Activate script, including the
|
||||
addition of the virtual environment's Python executable from the beginning of
|
||||
the PATH variable.
|
||||
|
||||
.Parameter NonDestructive
|
||||
If present, do not remove this function from the global namespace for the
|
||||
session.
|
||||
|
||||
#>
|
||||
function global:deactivate ([switch]$NonDestructive) {
|
||||
# Revert to original values
|
||||
|
||||
# The prior prompt:
|
||||
if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
|
||||
Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
|
||||
Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
|
||||
}
|
||||
|
||||
# The prior PYTHONHOME:
|
||||
if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
|
||||
Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
|
||||
Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
|
||||
}
|
||||
|
||||
# The prior PATH:
|
||||
if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
|
||||
Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
|
||||
Remove-Item -Path Env:_OLD_VIRTUAL_PATH
|
||||
}
|
||||
|
||||
# Just remove the VIRTUAL_ENV altogether:
|
||||
if (Test-Path -Path Env:VIRTUAL_ENV) {
|
||||
Remove-Item -Path env:VIRTUAL_ENV
|
||||
}
|
||||
|
||||
# Just remove VIRTUAL_ENV_PROMPT altogether.
|
||||
if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
|
||||
Remove-Item -Path env:VIRTUAL_ENV_PROMPT
|
||||
}
|
||||
|
||||
# Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
|
||||
if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
|
||||
Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
|
||||
}
|
||||
|
||||
# Leave deactivate function in the global namespace if requested:
|
||||
if (-not $NonDestructive) {
|
||||
Remove-Item -Path function:deactivate
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
.Description
|
||||
Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
|
||||
given folder, and returns them in a map.
|
||||
|
||||
For each line in the pyvenv.cfg file, if that line can be parsed into exactly
|
||||
two strings separated by `=` (with any amount of whitespace surrounding the =)
|
||||
then it is considered a `key = value` line. The left hand string is the key,
|
||||
the right hand is the value.
|
||||
|
||||
If the value starts with a `'` or a `"` then the first and last character is
|
||||
stripped from the value before being captured.
|
||||
|
||||
.Parameter ConfigDir
|
||||
Path to the directory that contains the `pyvenv.cfg` file.
|
||||
#>
|
||||
function Get-PyVenvConfig(
|
||||
[String]
|
||||
$ConfigDir
|
||||
) {
|
||||
Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
|
||||
|
||||
# Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
|
||||
$pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
|
||||
|
||||
# An empty map will be returned if no config file is found.
|
||||
$pyvenvConfig = @{ }
|
||||
|
||||
if ($pyvenvConfigPath) {
|
||||
|
||||
Write-Verbose "File exists, parse `key = value` lines"
|
||||
$pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
|
||||
|
||||
$pyvenvConfigContent | ForEach-Object {
|
||||
$keyval = $PSItem -split "\s*=\s*", 2
|
||||
if ($keyval[0] -and $keyval[1]) {
|
||||
$val = $keyval[1]
|
||||
|
||||
# Remove extraneous quotations around a string value.
|
||||
if ("'""".Contains($val.Substring(0, 1))) {
|
||||
$val = $val.Substring(1, $val.Length - 2)
|
||||
}
|
||||
|
||||
$pyvenvConfig[$keyval[0]] = $val
|
||||
Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
|
||||
}
|
||||
}
|
||||
}
|
||||
return $pyvenvConfig
|
||||
}
|
||||
|
||||
|
||||
<# Begin Activate script --------------------------------------------------- #>
|
||||
|
||||
# Determine the containing directory of this script
|
||||
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$VenvExecDir = Get-Item -Path $VenvExecPath
|
||||
|
||||
Write-Verbose "Activation script is located in path: '$VenvExecPath'"
|
||||
Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
|
||||
Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
|
||||
|
||||
# Set values required in priority: CmdLine, ConfigFile, Default
|
||||
# First, get the location of the virtual environment, it might not be
|
||||
# VenvExecDir if specified on the command line.
|
||||
if ($VenvDir) {
|
||||
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
|
||||
}
|
||||
else {
|
||||
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
|
||||
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
|
||||
Write-Verbose "VenvDir=$VenvDir"
|
||||
}
|
||||
|
||||
# Next, read the `pyvenv.cfg` file to determine any required value such
|
||||
# as `prompt`.
|
||||
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
|
||||
|
||||
# Next, set the prompt from the command line, or the config file, or
|
||||
# just use the name of the virtual environment folder.
|
||||
if ($Prompt) {
|
||||
Write-Verbose "Prompt specified as argument, using '$Prompt'"
|
||||
}
|
||||
else {
|
||||
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
|
||||
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
|
||||
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
|
||||
$Prompt = $pyvenvCfg['prompt'];
|
||||
}
|
||||
else {
|
||||
Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
|
||||
Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
|
||||
$Prompt = Split-Path -Path $venvDir -Leaf
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose "Prompt = '$Prompt'"
|
||||
Write-Verbose "VenvDir='$VenvDir'"
|
||||
|
||||
# Deactivate any currently active virtual environment, but leave the
|
||||
# deactivate function in place.
|
||||
deactivate -nondestructive
|
||||
|
||||
# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
|
||||
# that there is an activated venv.
|
||||
$env:VIRTUAL_ENV = $VenvDir
|
||||
|
||||
if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
|
||||
|
||||
Write-Verbose "Setting prompt to '$Prompt'"
|
||||
|
||||
# Set the prompt to include the env name
|
||||
# Make sure _OLD_VIRTUAL_PROMPT is global
|
||||
function global:_OLD_VIRTUAL_PROMPT { "" }
|
||||
Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
|
||||
New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
|
||||
|
||||
function global:prompt {
|
||||
Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
|
||||
_OLD_VIRTUAL_PROMPT
|
||||
}
|
||||
$env:VIRTUAL_ENV_PROMPT = $Prompt
|
||||
}
|
||||
|
||||
# Clear PYTHONHOME
|
||||
if (Test-Path -Path Env:PYTHONHOME) {
|
||||
Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
|
||||
Remove-Item -Path Env:PYTHONHOME
|
||||
}
|
||||
|
||||
# Add the venv to the PATH
|
||||
Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
|
||||
$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"
|
||||
69
ansible/bin/activate
Normal file
69
ansible/bin/activate
Normal file
@ -0,0 +1,69 @@
|
||||
# This file must be used with "source bin/activate" *from bash*
|
||||
# you cannot run it directly
|
||||
|
||||
deactivate () {
|
||||
# reset old environment variables
|
||||
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
|
||||
PATH="${_OLD_VIRTUAL_PATH:-}"
|
||||
export PATH
|
||||
unset _OLD_VIRTUAL_PATH
|
||||
fi
|
||||
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
|
||||
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
|
||||
export PYTHONHOME
|
||||
unset _OLD_VIRTUAL_PYTHONHOME
|
||||
fi
|
||||
|
||||
# This should detect bash and zsh, which have a hash command that must
|
||||
# be called to get it to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
||||
hash -r 2> /dev/null
|
||||
fi
|
||||
|
||||
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
|
||||
PS1="${_OLD_VIRTUAL_PS1:-}"
|
||||
export PS1
|
||||
unset _OLD_VIRTUAL_PS1
|
||||
fi
|
||||
|
||||
unset VIRTUAL_ENV
|
||||
unset VIRTUAL_ENV_PROMPT
|
||||
if [ ! "${1:-}" = "nondestructive" ] ; then
|
||||
# Self destruct!
|
||||
unset -f deactivate
|
||||
fi
|
||||
}
|
||||
|
||||
# unset irrelevant variables
|
||||
deactivate nondestructive
|
||||
|
||||
VIRTUAL_ENV=/home/smauro/prod/ansible
|
||||
export VIRTUAL_ENV
|
||||
|
||||
_OLD_VIRTUAL_PATH="$PATH"
|
||||
PATH="$VIRTUAL_ENV/"bin":$PATH"
|
||||
export PATH
|
||||
|
||||
# unset PYTHONHOME if set
|
||||
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
|
||||
# could use `if (set -u; : $PYTHONHOME) ;` in bash
|
||||
if [ -n "${PYTHONHOME:-}" ] ; then
|
||||
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
|
||||
unset PYTHONHOME
|
||||
fi
|
||||
|
||||
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
||||
_OLD_VIRTUAL_PS1="${PS1:-}"
|
||||
PS1='(ansible) '"${PS1:-}"
|
||||
export PS1
|
||||
VIRTUAL_ENV_PROMPT='(ansible) '
|
||||
export VIRTUAL_ENV_PROMPT
|
||||
fi
|
||||
|
||||
# This should detect bash and zsh, which have a hash command that must
|
||||
# be called to get it to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
||||
hash -r 2> /dev/null
|
||||
fi
|
||||
26
ansible/bin/activate.csh
Normal file
26
ansible/bin/activate.csh
Normal file
@ -0,0 +1,26 @@
|
||||
# This file must be used with "source bin/activate.csh" *from csh*.
|
||||
# You cannot run it directly.
|
||||
# Created by Davide Di Blasi <davidedb@gmail.com>.
|
||||
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
|
||||
|
||||
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate'
|
||||
|
||||
# Unset irrelevant variables.
|
||||
deactivate nondestructive
|
||||
|
||||
setenv VIRTUAL_ENV /home/smauro/prod/ansible
|
||||
|
||||
set _OLD_VIRTUAL_PATH="$PATH"
|
||||
setenv PATH "$VIRTUAL_ENV/"bin":$PATH"
|
||||
|
||||
|
||||
set _OLD_VIRTUAL_PROMPT="$prompt"
|
||||
|
||||
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
|
||||
set prompt = '(ansible) '"$prompt"
|
||||
setenv VIRTUAL_ENV_PROMPT '(ansible) '
|
||||
endif
|
||||
|
||||
alias pydoc python -m pydoc
|
||||
|
||||
rehash
|
||||
69
ansible/bin/activate.fish
Normal file
69
ansible/bin/activate.fish
Normal file
@ -0,0 +1,69 @@
|
||||
# This file must be used with "source <venv>/bin/activate.fish" *from fish*
|
||||
# (https://fishshell.com/); you cannot run it directly.
|
||||
|
||||
function deactivate -d "Exit virtual environment and return to normal shell environment"
|
||||
# reset old environment variables
|
||||
if test -n "$_OLD_VIRTUAL_PATH"
|
||||
set -gx PATH $_OLD_VIRTUAL_PATH
|
||||
set -e _OLD_VIRTUAL_PATH
|
||||
end
|
||||
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
|
||||
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
|
||||
set -e _OLD_VIRTUAL_PYTHONHOME
|
||||
end
|
||||
|
||||
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
|
||||
set -e _OLD_FISH_PROMPT_OVERRIDE
|
||||
# prevents error when using nested fish instances (Issue #93858)
|
||||
if functions -q _old_fish_prompt
|
||||
functions -e fish_prompt
|
||||
functions -c _old_fish_prompt fish_prompt
|
||||
functions -e _old_fish_prompt
|
||||
end
|
||||
end
|
||||
|
||||
set -e VIRTUAL_ENV
|
||||
set -e VIRTUAL_ENV_PROMPT
|
||||
if test "$argv[1]" != "nondestructive"
|
||||
# Self-destruct!
|
||||
functions -e deactivate
|
||||
end
|
||||
end
|
||||
|
||||
# Unset irrelevant variables.
|
||||
deactivate nondestructive
|
||||
|
||||
set -gx VIRTUAL_ENV /home/smauro/prod/ansible
|
||||
|
||||
set -gx _OLD_VIRTUAL_PATH $PATH
|
||||
set -gx PATH "$VIRTUAL_ENV/"bin $PATH
|
||||
|
||||
# Unset PYTHONHOME if set.
|
||||
if set -q PYTHONHOME
|
||||
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
|
||||
set -e PYTHONHOME
|
||||
end
|
||||
|
||||
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
||||
# fish uses a function instead of an env var to generate the prompt.
|
||||
|
||||
# Save the current fish_prompt function as the function _old_fish_prompt.
|
||||
functions -c fish_prompt _old_fish_prompt
|
||||
|
||||
# With the original prompt function renamed, we can override with our own.
|
||||
function fish_prompt
|
||||
# Save the return status of the last command.
|
||||
set -l old_status $status
|
||||
|
||||
# Output the venv prompt; color taken from the blue of the Python logo.
|
||||
printf "%s%s%s" (set_color 4B8BBE) '(ansible) ' (set_color normal)
|
||||
|
||||
# Restore the return status of the previous command.
|
||||
echo "exit $old_status" | .
|
||||
# Output the original/"old" prompt.
|
||||
_old_fish_prompt
|
||||
end
|
||||
|
||||
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
|
||||
set -gx VIRTUAL_ENV_PROMPT '(ansible) '
|
||||
end
|
||||
8
ansible/bin/ansible
Executable file
8
ansible/bin/ansible
Executable file
@ -0,0 +1,8 @@
|
||||
#!/home/smauro/prod/ansible/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from ansible.cli.adhoc import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
8
ansible/bin/ansible-community
Executable file
8
ansible/bin/ansible-community
Executable file
@ -0,0 +1,8 @@
|
||||
#!/home/smauro/prod/ansible/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from ansible_collections.ansible_community import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
8
ansible/bin/ansible-config
Executable file
8
ansible/bin/ansible-config
Executable file
@ -0,0 +1,8 @@
|
||||
#!/home/smauro/prod/ansible/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from ansible.cli.config import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
8
ansible/bin/ansible-console
Executable file
8
ansible/bin/ansible-console
Executable file
@ -0,0 +1,8 @@
|
||||
#!/home/smauro/prod/ansible/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from ansible.cli.console import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
8
ansible/bin/ansible-doc
Executable file
8
ansible/bin/ansible-doc
Executable file
@ -0,0 +1,8 @@
|
||||
#!/home/smauro/prod/ansible/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from ansible.cli.doc import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
8
ansible/bin/ansible-galaxy
Executable file
8
ansible/bin/ansible-galaxy
Executable file
@ -0,0 +1,8 @@
|
||||
#!/home/smauro/prod/ansible/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from ansible.cli.galaxy import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
8
ansible/bin/ansible-inventory
Executable file
8
ansible/bin/ansible-inventory
Executable file
@ -0,0 +1,8 @@
|
||||
#!/home/smauro/prod/ansible/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from ansible.cli.inventory import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
8
ansible/bin/ansible-playbook
Executable file
8
ansible/bin/ansible-playbook
Executable file
@ -0,0 +1,8 @@
|
||||
#!/home/smauro/prod/ansible/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from ansible.cli.playbook import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
8
ansible/bin/ansible-pull
Executable file
8
ansible/bin/ansible-pull
Executable file
@ -0,0 +1,8 @@
|
||||
#!/home/smauro/prod/ansible/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from ansible.cli.pull import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
8
ansible/bin/ansible-test
Executable file
8
ansible/bin/ansible-test
Executable file
@ -0,0 +1,8 @@
|
||||
#!/home/smauro/prod/ansible/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from ansible_test._util.target.cli.ansible_test_cli_stub import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
8
ansible/bin/ansible-vault
Executable file
8
ansible/bin/ansible-vault
Executable file
@ -0,0 +1,8 @@
|
||||
#!/home/smauro/prod/ansible/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from ansible.cli.vault import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
8
ansible/bin/pip
Executable file
8
ansible/bin/pip
Executable file
@ -0,0 +1,8 @@
|
||||
#!/home/smauro/prod/ansible/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
8
ansible/bin/pip3
Executable file
8
ansible/bin/pip3
Executable file
@ -0,0 +1,8 @@
|
||||
#!/home/smauro/prod/ansible/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
8
ansible/bin/pip3.11
Executable file
8
ansible/bin/pip3.11
Executable file
@ -0,0 +1,8 @@
|
||||
#!/home/smauro/prod/ansible/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
1
ansible/bin/python
Symbolic link
1
ansible/bin/python
Symbolic link
@ -0,0 +1 @@
|
||||
python3
|
||||
1
ansible/bin/python3
Symbolic link
1
ansible/bin/python3
Symbolic link
@ -0,0 +1 @@
|
||||
/usr/bin/python3
|
||||
1
ansible/bin/python3.11
Symbolic link
1
ansible/bin/python3.11
Symbolic link
@ -0,0 +1 @@
|
||||
python3
|
||||
@ -0,0 +1 @@
|
||||
pip
|
||||
@ -0,0 +1,28 @@
|
||||
Copyright 2010 Pallets
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@ -0,0 +1,92 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: MarkupSafe
|
||||
Version: 3.0.2
|
||||
Summary: Safely add untrusted strings to HTML/XML markup.
|
||||
Maintainer-email: Pallets <contact@palletsprojects.com>
|
||||
License: Copyright 2010 Pallets
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Project-URL: Donate, https://palletsprojects.com/donate
|
||||
Project-URL: Documentation, https://markupsafe.palletsprojects.com/
|
||||
Project-URL: Changes, https://markupsafe.palletsprojects.com/changes/
|
||||
Project-URL: Source, https://github.com/pallets/markupsafe/
|
||||
Project-URL: Chat, https://discord.gg/pallets
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Environment :: Web Environment
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
||||
Classifier: Topic :: Text Processing :: Markup :: HTML
|
||||
Classifier: Typing :: Typed
|
||||
Requires-Python: >=3.9
|
||||
Description-Content-Type: text/markdown
|
||||
License-File: LICENSE.txt
|
||||
|
||||
# MarkupSafe
|
||||
|
||||
MarkupSafe implements a text object that escapes characters so it is
|
||||
safe to use in HTML and XML. Characters that have special meanings are
|
||||
replaced so that they display as the actual characters. This mitigates
|
||||
injection attacks, meaning untrusted user input can safely be displayed
|
||||
on a page.
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
```pycon
|
||||
>>> from markupsafe import Markup, escape
|
||||
|
||||
>>> # escape replaces special characters and wraps in Markup
|
||||
>>> escape("<script>alert(document.cookie);</script>")
|
||||
Markup('<script>alert(document.cookie);</script>')
|
||||
|
||||
>>> # wrap in Markup to mark text "safe" and prevent escaping
|
||||
>>> Markup("<strong>Hello</strong>")
|
||||
Markup('<strong>hello</strong>')
|
||||
|
||||
>>> escape(Markup("<strong>Hello</strong>"))
|
||||
Markup('<strong>hello</strong>')
|
||||
|
||||
>>> # Markup is a str subclass
|
||||
>>> # methods and operators escape their arguments
|
||||
>>> template = Markup("Hello <em>{name}</em>")
|
||||
>>> template.format(name='"World"')
|
||||
Markup('Hello <em>"World"</em>')
|
||||
```
|
||||
|
||||
## Donate
|
||||
|
||||
The Pallets organization develops and supports MarkupSafe and other
|
||||
popular packages. In order to grow the community of contributors and
|
||||
users, and allow the maintainers to devote more time to the projects,
|
||||
[please donate today][].
|
||||
|
||||
[please donate today]: https://palletsprojects.com/donate
|
||||
@ -0,0 +1,14 @@
|
||||
MarkupSafe-3.0.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
MarkupSafe-3.0.2.dist-info/LICENSE.txt,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475
|
||||
MarkupSafe-3.0.2.dist-info/METADATA,sha256=aAwbZhSmXdfFuMM-rEHpeiHRkBOGESyVLJIuwzHP-nw,3975
|
||||
MarkupSafe-3.0.2.dist-info/RECORD,,
|
||||
MarkupSafe-3.0.2.dist-info/WHEEL,sha256=OhaudQk1f3YCu0uQO5v6u-i01XPoX70c0R3T_XY-jOo,151
|
||||
MarkupSafe-3.0.2.dist-info/top_level.txt,sha256=qy0Plje5IJuvsCBjejJyhDCjEAdcDLK_2agVcex8Z6U,11
|
||||
markupsafe/__init__.py,sha256=sr-U6_27DfaSrj5jnHYxWN-pvhM27sjlDplMDPZKm7k,13214
|
||||
markupsafe/__pycache__/__init__.cpython-311.pyc,,
|
||||
markupsafe/__pycache__/_native.cpython-311.pyc,,
|
||||
markupsafe/_native.py,sha256=hSLs8Jmz5aqayuengJJ3kdT5PwNpBWpKrmQSdipndC8,210
|
||||
markupsafe/_speedups.c,sha256=O7XulmTo-epI6n2FtMVOrJXl8EAaIwD2iNYmBI5SEoQ,4149
|
||||
markupsafe/_speedups.cpython-311-x86_64-linux-gnu.so,sha256=6IDH6Z1ajjClhfGerTB8WLb81uXUpLD8e-e1WzCirVY,43456
|
||||
markupsafe/_speedups.pyi,sha256=ENd1bYe7gbBUf2ywyYWOGUpnXOHNJ-cgTNqetlW8h5k,41
|
||||
markupsafe/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
@ -0,0 +1,6 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: setuptools (75.2.0)
|
||||
Root-Is-Purelib: false
|
||||
Tag: cp311-cp311-manylinux_2_17_x86_64
|
||||
Tag: cp311-cp311-manylinux2014_x86_64
|
||||
|
||||
@ -0,0 +1 @@
|
||||
markupsafe
|
||||
@ -0,0 +1 @@
|
||||
pip
|
||||
@ -0,0 +1,20 @@
|
||||
Copyright (c) 2017-2021 Ingy döt Net
|
||||
Copyright (c) 2006-2016 Kirill Simonov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@ -0,0 +1,46 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: PyYAML
|
||||
Version: 6.0.2
|
||||
Summary: YAML parser and emitter for Python
|
||||
Home-page: https://pyyaml.org/
|
||||
Download-URL: https://pypi.org/project/PyYAML/
|
||||
Author: Kirill Simonov
|
||||
Author-email: xi@resolvent.net
|
||||
License: MIT
|
||||
Project-URL: Bug Tracker, https://github.com/yaml/pyyaml/issues
|
||||
Project-URL: CI, https://github.com/yaml/pyyaml/actions
|
||||
Project-URL: Documentation, https://pyyaml.org/wiki/PyYAMLDocumentation
|
||||
Project-URL: Mailing lists, http://lists.sourceforge.net/lists/listinfo/yaml-core
|
||||
Project-URL: Source Code, https://github.com/yaml/pyyaml
|
||||
Platform: Any
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Cython
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Programming Language :: Python :: 3.11
|
||||
Classifier: Programming Language :: Python :: 3.12
|
||||
Classifier: Programming Language :: Python :: 3.13
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
Classifier: Topic :: Text Processing :: Markup
|
||||
Requires-Python: >=3.8
|
||||
License-File: LICENSE
|
||||
|
||||
YAML is a data serialization format designed for human readability
|
||||
and interaction with scripting languages. PyYAML is a YAML parser
|
||||
and emitter for Python.
|
||||
|
||||
PyYAML features a complete YAML 1.1 parser, Unicode support, pickle
|
||||
support, capable extension API, and sensible error messages. PyYAML
|
||||
supports standard YAML tags and provides Python-specific tags that
|
||||
allow to represent an arbitrary Python object.
|
||||
|
||||
PyYAML is applicable for a broad range of tasks from complex
|
||||
configuration files to object serialization and persistence.
|
||||
@ -0,0 +1,43 @@
|
||||
PyYAML-6.0.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
PyYAML-6.0.2.dist-info/LICENSE,sha256=jTko-dxEkP1jVwfLiOsmvXZBAqcoKVQwfT5RZ6V36KQ,1101
|
||||
PyYAML-6.0.2.dist-info/METADATA,sha256=9-odFB5seu4pGPcEv7E8iyxNF51_uKnaNGjLAhz2lto,2060
|
||||
PyYAML-6.0.2.dist-info/RECORD,,
|
||||
PyYAML-6.0.2.dist-info/WHEEL,sha256=YWWHkv6sHhBDPNqgSfLklIm4KZnZJH4x2lIHOwCoU7Q,152
|
||||
PyYAML-6.0.2.dist-info/top_level.txt,sha256=rpj0IVMTisAjh_1vG3Ccf9v5jpCQwAz6cD1IVU5ZdhQ,11
|
||||
_yaml/__init__.py,sha256=04Ae_5osxahpJHa3XBZUAf4wi6XX32gR8D6X6p64GEA,1402
|
||||
_yaml/__pycache__/__init__.cpython-311.pyc,,
|
||||
yaml/__init__.py,sha256=N35S01HMesFTe0aRRMWkPj0Pa8IEbHpE9FK7cr5Bdtw,12311
|
||||
yaml/__pycache__/__init__.cpython-311.pyc,,
|
||||
yaml/__pycache__/composer.cpython-311.pyc,,
|
||||
yaml/__pycache__/constructor.cpython-311.pyc,,
|
||||
yaml/__pycache__/cyaml.cpython-311.pyc,,
|
||||
yaml/__pycache__/dumper.cpython-311.pyc,,
|
||||
yaml/__pycache__/emitter.cpython-311.pyc,,
|
||||
yaml/__pycache__/error.cpython-311.pyc,,
|
||||
yaml/__pycache__/events.cpython-311.pyc,,
|
||||
yaml/__pycache__/loader.cpython-311.pyc,,
|
||||
yaml/__pycache__/nodes.cpython-311.pyc,,
|
||||
yaml/__pycache__/parser.cpython-311.pyc,,
|
||||
yaml/__pycache__/reader.cpython-311.pyc,,
|
||||
yaml/__pycache__/representer.cpython-311.pyc,,
|
||||
yaml/__pycache__/resolver.cpython-311.pyc,,
|
||||
yaml/__pycache__/scanner.cpython-311.pyc,,
|
||||
yaml/__pycache__/serializer.cpython-311.pyc,,
|
||||
yaml/__pycache__/tokens.cpython-311.pyc,,
|
||||
yaml/_yaml.cpython-311-x86_64-linux-gnu.so,sha256=sZBsAqPs6VM8YzOkHpNL0qKIfR0zNM9gttjzjoVPaiI,2466120
|
||||
yaml/composer.py,sha256=_Ko30Wr6eDWUeUpauUGT3Lcg9QPBnOPVlTnIMRGJ9FM,4883
|
||||
yaml/constructor.py,sha256=kNgkfaeLUkwQYY_Q6Ff1Tz2XVw_pG1xVE9Ak7z-viLA,28639
|
||||
yaml/cyaml.py,sha256=6ZrAG9fAYvdVe2FK_w0hmXoG7ZYsoYUwapG8CiC72H0,3851
|
||||
yaml/dumper.py,sha256=PLctZlYwZLp7XmeUdwRuv4nYOZ2UBnDIUy8-lKfLF-o,2837
|
||||
yaml/emitter.py,sha256=jghtaU7eFwg31bG0B7RZea_29Adi9CKmXq_QjgQpCkQ,43006
|
||||
yaml/error.py,sha256=Ah9z-toHJUbE9j-M8YpxgSRM5CgLCcwVzJgLLRF2Fxo,2533
|
||||
yaml/events.py,sha256=50_TksgQiE4up-lKo_V-nBy-tAIxkIPQxY5qDhKCeHw,2445
|
||||
yaml/loader.py,sha256=UVa-zIqmkFSCIYq_PgSGm4NSJttHY2Rf_zQ4_b1fHN0,2061
|
||||
yaml/nodes.py,sha256=gPKNj8pKCdh2d4gr3gIYINnPOaOxGhJAUiYhGRnPE84,1440
|
||||
yaml/parser.py,sha256=ilWp5vvgoHFGzvOZDItFoGjD6D42nhlZrZyjAwa0oJo,25495
|
||||
yaml/reader.py,sha256=0dmzirOiDG4Xo41RnuQS7K9rkY3xjHiVasfDMNTqCNw,6794
|
||||
yaml/representer.py,sha256=IuWP-cAW9sHKEnS0gCqSa894k1Bg4cgTxaDwIcbRQ-Y,14190
|
||||
yaml/resolver.py,sha256=9L-VYfm4mWHxUD1Vg4X7rjDRK_7VZd6b92wzq7Y2IKY,9004
|
||||
yaml/scanner.py,sha256=YEM3iLZSaQwXcQRg2l2R4MdT0zGP2F9eHkKGKnHyWQY,51279
|
||||
yaml/serializer.py,sha256=ChuFgmhU01hj4xgI8GaKv6vfM2Bujwa9i7d2FAHj7cA,4165
|
||||
yaml/tokens.py,sha256=lTQIzSVw8Mg9wv459-TjiOQe6wVziqaRlqX2_89rp54,2573
|
||||
@ -0,0 +1,6 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.44.0)
|
||||
Root-Is-Purelib: false
|
||||
Tag: cp311-cp311-manylinux_2_17_x86_64
|
||||
Tag: cp311-cp311-manylinux2014_x86_64
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
_yaml
|
||||
yaml
|
||||
Binary file not shown.
222
ansible/lib/python3.11/site-packages/_distutils_hack/__init__.py
Normal file
222
ansible/lib/python3.11/site-packages/_distutils_hack/__init__.py
Normal file
@ -0,0 +1,222 @@
|
||||
# don't import any costly modules
|
||||
import sys
|
||||
import os
|
||||
|
||||
|
||||
is_pypy = '__pypy__' in sys.builtin_module_names
|
||||
|
||||
|
||||
def warn_distutils_present():
|
||||
if 'distutils' not in sys.modules:
|
||||
return
|
||||
if is_pypy and sys.version_info < (3, 7):
|
||||
# PyPy for 3.6 unconditionally imports distutils, so bypass the warning
|
||||
# https://foss.heptapod.net/pypy/pypy/-/blob/be829135bc0d758997b3566062999ee8b23872b4/lib-python/3/site.py#L250
|
||||
return
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"Distutils was imported before Setuptools, but importing Setuptools "
|
||||
"also replaces the `distutils` module in `sys.modules`. This may lead "
|
||||
"to undesirable behaviors or errors. To avoid these issues, avoid "
|
||||
"using distutils directly, ensure that setuptools is installed in the "
|
||||
"traditional way (e.g. not an editable install), and/or make sure "
|
||||
"that setuptools is always imported before distutils."
|
||||
)
|
||||
|
||||
|
||||
def clear_distutils():
|
||||
if 'distutils' not in sys.modules:
|
||||
return
|
||||
import warnings
|
||||
|
||||
warnings.warn("Setuptools is replacing distutils.")
|
||||
mods = [
|
||||
name
|
||||
for name in sys.modules
|
||||
if name == "distutils" or name.startswith("distutils.")
|
||||
]
|
||||
for name in mods:
|
||||
del sys.modules[name]
|
||||
|
||||
|
||||
def enabled():
|
||||
"""
|
||||
Allow selection of distutils by environment variable.
|
||||
"""
|
||||
which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'local')
|
||||
return which == 'local'
|
||||
|
||||
|
||||
def ensure_local_distutils():
|
||||
import importlib
|
||||
|
||||
clear_distutils()
|
||||
|
||||
# With the DistutilsMetaFinder in place,
|
||||
# perform an import to cause distutils to be
|
||||
# loaded from setuptools._distutils. Ref #2906.
|
||||
with shim():
|
||||
importlib.import_module('distutils')
|
||||
|
||||
# check that submodules load as expected
|
||||
core = importlib.import_module('distutils.core')
|
||||
assert '_distutils' in core.__file__, core.__file__
|
||||
assert 'setuptools._distutils.log' not in sys.modules
|
||||
|
||||
|
||||
def do_override():
|
||||
"""
|
||||
Ensure that the local copy of distutils is preferred over stdlib.
|
||||
|
||||
See https://github.com/pypa/setuptools/issues/417#issuecomment-392298401
|
||||
for more motivation.
|
||||
"""
|
||||
if enabled():
|
||||
warn_distutils_present()
|
||||
ensure_local_distutils()
|
||||
|
||||
|
||||
class _TrivialRe:
|
||||
def __init__(self, *patterns):
|
||||
self._patterns = patterns
|
||||
|
||||
def match(self, string):
|
||||
return all(pat in string for pat in self._patterns)
|
||||
|
||||
|
||||
class DistutilsMetaFinder:
|
||||
def find_spec(self, fullname, path, target=None):
|
||||
# optimization: only consider top level modules and those
|
||||
# found in the CPython test suite.
|
||||
if path is not None and not fullname.startswith('test.'):
|
||||
return
|
||||
|
||||
method_name = 'spec_for_{fullname}'.format(**locals())
|
||||
method = getattr(self, method_name, lambda: None)
|
||||
return method()
|
||||
|
||||
def spec_for_distutils(self):
|
||||
if self.is_cpython():
|
||||
return
|
||||
|
||||
import importlib
|
||||
import importlib.abc
|
||||
import importlib.util
|
||||
|
||||
try:
|
||||
mod = importlib.import_module('setuptools._distutils')
|
||||
except Exception:
|
||||
# There are a couple of cases where setuptools._distutils
|
||||
# may not be present:
|
||||
# - An older Setuptools without a local distutils is
|
||||
# taking precedence. Ref #2957.
|
||||
# - Path manipulation during sitecustomize removes
|
||||
# setuptools from the path but only after the hook
|
||||
# has been loaded. Ref #2980.
|
||||
# In either case, fall back to stdlib behavior.
|
||||
return
|
||||
|
||||
class DistutilsLoader(importlib.abc.Loader):
|
||||
def create_module(self, spec):
|
||||
mod.__name__ = 'distutils'
|
||||
return mod
|
||||
|
||||
def exec_module(self, module):
|
||||
pass
|
||||
|
||||
return importlib.util.spec_from_loader(
|
||||
'distutils', DistutilsLoader(), origin=mod.__file__
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def is_cpython():
|
||||
"""
|
||||
Suppress supplying distutils for CPython (build and tests).
|
||||
Ref #2965 and #3007.
|
||||
"""
|
||||
return os.path.isfile('pybuilddir.txt')
|
||||
|
||||
def spec_for_pip(self):
|
||||
"""
|
||||
Ensure stdlib distutils when running under pip.
|
||||
See pypa/pip#8761 for rationale.
|
||||
"""
|
||||
if self.pip_imported_during_build():
|
||||
return
|
||||
clear_distutils()
|
||||
self.spec_for_distutils = lambda: None
|
||||
|
||||
@classmethod
|
||||
def pip_imported_during_build(cls):
|
||||
"""
|
||||
Detect if pip is being imported in a build script. Ref #2355.
|
||||
"""
|
||||
import traceback
|
||||
|
||||
return any(
|
||||
cls.frame_file_is_setup(frame) for frame, line in traceback.walk_stack(None)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def frame_file_is_setup(frame):
|
||||
"""
|
||||
Return True if the indicated frame suggests a setup.py file.
|
||||
"""
|
||||
# some frames may not have __file__ (#2940)
|
||||
return frame.f_globals.get('__file__', '').endswith('setup.py')
|
||||
|
||||
def spec_for_sensitive_tests(self):
|
||||
"""
|
||||
Ensure stdlib distutils when running select tests under CPython.
|
||||
|
||||
python/cpython#91169
|
||||
"""
|
||||
clear_distutils()
|
||||
self.spec_for_distutils = lambda: None
|
||||
|
||||
sensitive_tests = (
|
||||
[
|
||||
'test.test_distutils',
|
||||
'test.test_peg_generator',
|
||||
'test.test_importlib',
|
||||
]
|
||||
if sys.version_info < (3, 10)
|
||||
else [
|
||||
'test.test_distutils',
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
for name in DistutilsMetaFinder.sensitive_tests:
|
||||
setattr(
|
||||
DistutilsMetaFinder,
|
||||
f'spec_for_{name}',
|
||||
DistutilsMetaFinder.spec_for_sensitive_tests,
|
||||
)
|
||||
|
||||
|
||||
DISTUTILS_FINDER = DistutilsMetaFinder()
|
||||
|
||||
|
||||
def add_shim():
|
||||
DISTUTILS_FINDER in sys.meta_path or insert_shim()
|
||||
|
||||
|
||||
class shim:
|
||||
def __enter__(self):
|
||||
insert_shim()
|
||||
|
||||
def __exit__(self, exc, value, tb):
|
||||
remove_shim()
|
||||
|
||||
|
||||
def insert_shim():
|
||||
sys.meta_path.insert(0, DISTUTILS_FINDER)
|
||||
|
||||
|
||||
def remove_shim():
|
||||
try:
|
||||
sys.meta_path.remove(DISTUTILS_FINDER)
|
||||
except ValueError:
|
||||
pass
|
||||
Binary file not shown.
Binary file not shown.
@ -0,0 +1 @@
|
||||
__import__('_distutils_hack').do_override()
|
||||
33
ansible/lib/python3.11/site-packages/_yaml/__init__.py
Normal file
33
ansible/lib/python3.11/site-packages/_yaml/__init__.py
Normal file
@ -0,0 +1,33 @@
|
||||
# This is a stub package designed to roughly emulate the _yaml
|
||||
# extension module, which previously existed as a standalone module
|
||||
# and has been moved into the `yaml` package namespace.
|
||||
# It does not perfectly mimic its old counterpart, but should get
|
||||
# close enough for anyone who's relying on it even when they shouldn't.
|
||||
import yaml
|
||||
|
||||
# in some circumstances, the yaml module we imoprted may be from a different version, so we need
|
||||
# to tread carefully when poking at it here (it may not have the attributes we expect)
|
||||
if not getattr(yaml, '__with_libyaml__', False):
|
||||
from sys import version_info
|
||||
|
||||
exc = ModuleNotFoundError if version_info >= (3, 6) else ImportError
|
||||
raise exc("No module named '_yaml'")
|
||||
else:
|
||||
from yaml._yaml import *
|
||||
import warnings
|
||||
warnings.warn(
|
||||
'The _yaml extension module is now located at yaml._yaml'
|
||||
' and its location is subject to change. To use the'
|
||||
' LibYAML-based parser and emitter, import from `yaml`:'
|
||||
' `from yaml import CLoader as Loader, CDumper as Dumper`.',
|
||||
DeprecationWarning
|
||||
)
|
||||
del warnings
|
||||
# Don't `del yaml` here because yaml is actually an existing
|
||||
# namespace member of _yaml.
|
||||
|
||||
__name__ = '_yaml'
|
||||
# If the module is top-level (i.e. not a part of any specific package)
|
||||
# then the attribute should be set to ''.
|
||||
# https://docs.python.org/3.8/library/types.html
|
||||
__package__ = ''
|
||||
Binary file not shown.
@ -0,0 +1,675 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
|
||||
@ -0,0 +1 @@
|
||||
pip
|
||||
@ -0,0 +1,174 @@
|
||||
Metadata-Version: 2.2
|
||||
Name: ansible
|
||||
Version: 11.2.0
|
||||
Summary: Radically simple IT automation
|
||||
Home-page: https://ansible.com/
|
||||
Author: Ansible, Inc.
|
||||
Author-email: info@ansible.com
|
||||
License: GPL-3.0-or-later
|
||||
Project-URL: Build Data, https://github.com/ansible-community/ansible-build-data
|
||||
Project-URL: Code of Conduct, https://docs.ansible.com/ansible/latest/community/code_of_conduct.html
|
||||
Project-URL: Documentation, https://docs.ansible.com/ansible
|
||||
Project-URL: Forum, https://forum.ansible.com
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Environment :: Console
|
||||
Classifier: Framework :: Ansible
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Intended Audience :: Information Technology
|
||||
Classifier: Intended Audience :: System Administrators
|
||||
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
||||
Classifier: Natural Language :: English
|
||||
Classifier: Operating System :: POSIX
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.11
|
||||
Classifier: Programming Language :: Python :: 3.12
|
||||
Classifier: Programming Language :: Python :: 3.13
|
||||
Classifier: Programming Language :: Python :: 3 :: Only
|
||||
Requires-Python: >=3.11
|
||||
Description-Content-Type: text/x-rst
|
||||
License-File: COPYING
|
||||
Requires-Dist: ansible-core~=2.18.2
|
||||
|
||||
..
|
||||
GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
SPDX-FileCopyrightText: Ansible Project, 2020
|
||||
|
||||
|PyPI version| |Docs badge| |Chat badge| |Code Of Conduct| |Forum| |License|
|
||||
|
||||
*******
|
||||
Ansible
|
||||
*******
|
||||
|
||||
Ansible is a radically simple IT automation system. It handles configuration management, application
|
||||
deployment, cloud provisioning, ad-hoc task execution, network automation, and multi-node
|
||||
orchestration. Ansible makes complex changes like zero-downtime rolling updates with load balancers
|
||||
easy. More information on the Ansible `website <https://ansible.com/>`_.
|
||||
|
||||
This is the ``ansible`` community package.
|
||||
The ``ansible`` python package contains a set of
|
||||
independent Ansible collections that are curated by the community,
|
||||
and it pulls in `ansible-core <https://pypi.org/project/ansible-core/>`_.
|
||||
The ``ansible-core`` python package contains the core runtime and CLI tools,
|
||||
such as ``ansible`` and ``ansible-playbook``,
|
||||
while the ``ansible`` package contains extra modules, plugins, and roles.
|
||||
|
||||
``ansible`` follows `semantic versioning <https://semver.org/>`_.
|
||||
Each major version of ``ansible`` depends on a specific major version of
|
||||
``ansible-core`` and contains specific major versions of the collections it
|
||||
includes.
|
||||
|
||||
Design Principles
|
||||
=================
|
||||
|
||||
* Have an extremely simple setup process and a minimal learning curve.
|
||||
* Manage machines quickly and in parallel.
|
||||
* Avoid custom-agents and additional open ports, be agentless by
|
||||
leveraging the existing SSH daemon.
|
||||
* Describe infrastructure in a language that is both machine and human
|
||||
friendly.
|
||||
* Focus on security and easy auditability/review/rewriting of content.
|
||||
* Manage new remote machines instantly, without bootstrapping any
|
||||
software.
|
||||
* Allow module development in any dynamic language, not just Python.
|
||||
* Be usable as non-root.
|
||||
* Be the easiest IT automation system to use, ever.
|
||||
|
||||
Use Ansible
|
||||
===========
|
||||
|
||||
You can install a released version of Ansible with ``pip`` or a package manager. See our
|
||||
`Installation guide <https://docs.ansible.com/ansible/latest/installation_guide/index.html>`_ for details on installing Ansible
|
||||
on a variety of platforms.
|
||||
|
||||
Reporting Issues
|
||||
================
|
||||
Issues with plugins and modules in the Ansible package should be reported
|
||||
on the individual collection's issue tracker.
|
||||
Issues with ``ansible-core`` should be reported on
|
||||
the `ansible-core issue tracker <https://github.com/ansible/ansible/issues/>`_.
|
||||
Issues with the ``ansible`` package build process or serious bugs or
|
||||
vulnerabilities in a collection that are not addressed after opening an issue
|
||||
in the collection's issue tracker should be reported on
|
||||
`ansible-build-data's issue tracker <https://github.com/ansible-community/ansible-build-data#issue-tracker>`_.
|
||||
|
||||
Refer to the `Communication page
|
||||
<https://docs.ansible.com/ansible/latest/community/communication.html>`_ for a
|
||||
list of support channels if you need assistance from the community or are
|
||||
unsure where to report your issue.
|
||||
|
||||
|
||||
Get Involved
|
||||
============
|
||||
|
||||
* Read `Community Information <https://docs.ansible.com/ansible/latest/community>`_ for ways to contribute to
|
||||
and interact with the project, including forum information and how
|
||||
to submit bug reports and code to Ansible or Ansible collections.
|
||||
* Join a `Working Group <https://github.com/ansible/community/wiki>`_, an organized community
|
||||
devoted to a specific technology domain or platform.
|
||||
* Talk to us before making larger changes
|
||||
to avoid duplicate efforts. This not only helps everyone
|
||||
know what is going on, but it also helps save time and effort if we decide
|
||||
some changes are needed.
|
||||
* For a reference to the Forum, a list of Matrix and IRC channels, and Working Groups, see the
|
||||
`Communication page <https://docs.ansible.com/ansible/latest/community/communication.html>`_
|
||||
|
||||
Coding Guidelines
|
||||
=================
|
||||
|
||||
We document our Coding Guidelines in the `Developer Guide <https://docs.ansible.com/ansible/devel/dev_guide/>`_. We also suggest you review:
|
||||
|
||||
* `Developing modules checklist <https://docs.ansible.com/ansible/devel/dev_guide/developing_modules_checklist.html>`_
|
||||
* `Collection contributor guide <https://docs.ansible.com/ansible/devel/community/contributions_collections.html>`_
|
||||
|
||||
Branch Info
|
||||
===========
|
||||
|
||||
* The Ansible package is a 'batteries included' package that brings in ``ansible-core`` and a curated set of collections. Ansible uses `semantic versioning <https://semver.org/>`_ (for example, Ansible 5.6.0).
|
||||
* The Ansible package has only one stable branch, called 'latest' in the documentation.
|
||||
* See `Ansible release and maintenance <https://docs.ansible.com/ansible/latest/reference_appendices/release_and_maintenance.html>`_ for information about active branches and their corresponding ``ansible-core`` versions.
|
||||
* Refer to the
|
||||
`ansible-build-data <https://github.com/ansible-community/ansible-build-data/>`_
|
||||
repository for the exact versions of ``ansible-core`` and collections that
|
||||
are included in each ``ansible`` release.
|
||||
|
||||
Roadmap
|
||||
=======
|
||||
|
||||
Based on team and community feedback, an initial roadmap will be published for a major
|
||||
version (example: 5, 6). The `Ansible Roadmap
|
||||
<https://docs.ansible.com/ansible/devel/roadmap/ansible_roadmap_index.html>`_ details what is planned and how to influence the
|
||||
roadmap.
|
||||
|
||||
Authors
|
||||
=======
|
||||
|
||||
Ansible was created by `Michael DeHaan <https://github.com/mpdehaan>`_
|
||||
and has contributions from over 4700 users (and growing). Thanks everyone!
|
||||
|
||||
`Ansible <https://www.ansible.com>`_ is sponsored by `Red Hat, Inc.
|
||||
<https://www.redhat.com>`_
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
GNU General Public License v3.0 or later
|
||||
|
||||
See `COPYING <https://github.com/ansible-community/antsibull/blob/main/src/antsibull/data/gplv3.txt>`_
|
||||
for the full license text.
|
||||
|
||||
.. |PyPI version| image:: https://img.shields.io/pypi/v/ansible.svg
|
||||
:target: https://pypi.org/project/ansible
|
||||
.. |Docs badge| image:: https://img.shields.io/badge/docs-latest-brightgreen.svg
|
||||
:target: https://docs.ansible.com/ansible/latest/
|
||||
.. |Chat badge| image:: https://img.shields.io/badge/chat-IRC-brightgreen.svg
|
||||
:target: https://docs.ansible.com/ansible/latest/community/communication.html
|
||||
.. |Code Of Conduct| image:: https://img.shields.io/badge/code%20of%20conduct-Ansible-silver.svg
|
||||
:target: https://docs.ansible.com/ansible/latest/community/code_of_conduct.html
|
||||
:alt: Ansible Code of Conduct
|
||||
.. |Forum| image:: https://img.shields.io/badge/forum-Ansible-orange.svg
|
||||
:target: https://forum.ansible.com/
|
||||
:alt: Ansible Forum
|
||||
.. |License| image:: https://img.shields.io/badge/license-GPL%20v3.0-brightgreen.svg
|
||||
:target: COPYING
|
||||
:alt: Repository License
|
||||
39290
ansible/lib/python3.11/site-packages/ansible-11.2.0.dist-info/RECORD
Normal file
39290
ansible/lib/python3.11/site-packages/ansible-11.2.0.dist-info/RECORD
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: setuptools (75.8.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
[console_scripts]
|
||||
ansible-community = ansible_collections.ansible_community:main
|
||||
@ -0,0 +1 @@
|
||||
ansible_collections
|
||||
29
ansible/lib/python3.11/site-packages/ansible/__init__.py
Normal file
29
ansible/lib/python3.11/site-packages/ansible/__init__.py
Normal file
@ -0,0 +1,29 @@
|
||||
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
# make vendored top-level modules accessible EARLY
|
||||
import ansible._vendor
|
||||
|
||||
# Note: Do not add any code to this file. The ansible module may be
|
||||
# a namespace package when using Ansible-2.1+ Anything in this file may not be
|
||||
# available if one of the other packages in the namespace is loaded first.
|
||||
#
|
||||
# This is for backwards compat. Code should be ported to get these from
|
||||
# ansible.release instead of from here.
|
||||
from ansible.release import __version__, __author__
|
||||
27
ansible/lib/python3.11/site-packages/ansible/__main__.py
Normal file
27
ansible/lib/python3.11/site-packages/ansible/__main__.py
Normal file
@ -0,0 +1,27 @@
|
||||
# Copyright: (c) 2021, Matt Martz <matt@sivel.net>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
|
||||
from importlib.metadata import distribution
|
||||
|
||||
|
||||
def _short_name(name):
|
||||
return name.removeprefix('ansible-').replace('ansible', 'adhoc')
|
||||
|
||||
|
||||
def main():
|
||||
dist = distribution('ansible-core')
|
||||
ep_map = {_short_name(ep.name): ep for ep in dist.entry_points if ep.group == 'console_scripts'}
|
||||
|
||||
parser = argparse.ArgumentParser(prog='python -m ansible', add_help=False)
|
||||
parser.add_argument('entry_point', choices=list(ep_map))
|
||||
args, extra = parser.parse_known_args()
|
||||
|
||||
main = ep_map[args.entry_point].load()
|
||||
main([args.entry_point] + extra)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,45 @@
|
||||
# (c) 2020 Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import pkgutil
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
# This package exists to host vendored top-level Python packages for downstream packaging. Any Python packages
|
||||
# installed beneath this one will be masked from the Ansible loader, and available from the front of sys.path.
|
||||
# It is expected that the vendored packages will be loaded very early, so a warning will be fired on import of
|
||||
# the top-level ansible package if any packages beneath this are already loaded at that point.
|
||||
#
|
||||
# Python packages may be installed here during downstream packaging using something like:
|
||||
# pip install --upgrade -t (path to this dir) cryptography pyyaml packaging jinja2
|
||||
|
||||
# mask vendored content below this package from being accessed as an ansible subpackage
|
||||
__path__ = []
|
||||
|
||||
|
||||
def _ensure_vendored_path_entry():
|
||||
"""
|
||||
Ensure that any downstream-bundled content beneath this package is available at the top of sys.path
|
||||
"""
|
||||
# patch our vendored dir onto sys.path
|
||||
vendored_path_entry = os.path.dirname(__file__)
|
||||
vendored_module_names = set(m[1] for m in pkgutil.iter_modules([vendored_path_entry], '')) # m[1] == m.name
|
||||
|
||||
if vendored_module_names:
|
||||
# patch us early to load vendored deps transparently
|
||||
if vendored_path_entry in sys.path:
|
||||
# handle reload case by removing the existing entry, wherever it might be
|
||||
sys.path.remove(vendored_path_entry)
|
||||
sys.path.insert(0, vendored_path_entry)
|
||||
|
||||
already_loaded_vendored_modules = set(sys.modules.keys()).intersection(vendored_module_names)
|
||||
|
||||
if already_loaded_vendored_modules:
|
||||
warnings.warn('One or more Python packages bundled by this ansible-core distribution were already '
|
||||
'loaded ({0}). This may result in undefined behavior.'.format(', '.join(sorted(already_loaded_vendored_modules))))
|
||||
|
||||
|
||||
_ensure_vendored_path_entry()
|
||||
Binary file not shown.
688
ansible/lib/python3.11/site-packages/ansible/cli/__init__.py
Normal file
688
ansible/lib/python3.11/site-packages/ansible/cli/__init__.py
Normal file
@ -0,0 +1,688 @@
|
||||
# Copyright: (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
|
||||
# Copyright: (c) 2016, Toshio Kuratomi <tkuratomi@ansible.com>
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import locale
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Used for determining if the system is running a new enough python version
|
||||
# and should only restrict on our documented minimum versions
|
||||
if sys.version_info < (3, 11):
|
||||
raise SystemExit(
|
||||
'ERROR: Ansible requires Python 3.11 or newer on the controller. '
|
||||
'Current version: %s' % ''.join(sys.version.splitlines())
|
||||
)
|
||||
|
||||
|
||||
def check_blocking_io():
|
||||
"""Check stdin/stdout/stderr to make sure they are using blocking IO."""
|
||||
handles = []
|
||||
|
||||
for handle in (sys.stdin, sys.stdout, sys.stderr):
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
fd = handle.fileno()
|
||||
except Exception:
|
||||
continue # not a real file handle, such as during the import sanity test
|
||||
|
||||
if not os.get_blocking(fd):
|
||||
handles.append(getattr(handle, 'name', None) or '#%s' % fd)
|
||||
|
||||
if handles:
|
||||
raise SystemExit('ERROR: Ansible requires blocking IO on stdin/stdout/stderr. '
|
||||
'Non-blocking file handles detected: %s' % ', '.join(_io for _io in handles))
|
||||
|
||||
|
||||
check_blocking_io()
|
||||
|
||||
|
||||
def initialize_locale():
|
||||
"""Set the locale to the users default setting and ensure
|
||||
the locale and filesystem encoding are UTF-8.
|
||||
"""
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
dummy, encoding = locale.getlocale()
|
||||
except (locale.Error, ValueError) as e:
|
||||
raise SystemExit(
|
||||
'ERROR: Ansible could not initialize the preferred locale: %s' % e
|
||||
)
|
||||
|
||||
if not encoding or encoding.lower() not in ('utf-8', 'utf8'):
|
||||
raise SystemExit('ERROR: Ansible requires the locale encoding to be UTF-8; Detected %s.' % encoding)
|
||||
|
||||
fs_enc = sys.getfilesystemencoding()
|
||||
if fs_enc.lower() != 'utf-8':
|
||||
raise SystemExit('ERROR: Ansible requires the filesystem encoding to be UTF-8; Detected %s.' % fs_enc)
|
||||
|
||||
|
||||
initialize_locale()
|
||||
|
||||
|
||||
from importlib.metadata import version
|
||||
from ansible.module_utils.compat.version import LooseVersion
|
||||
|
||||
# Used for determining if the system is running a new enough Jinja2 version
|
||||
# and should only restrict on our documented minimum versions
|
||||
jinja2_version = version('jinja2')
|
||||
if jinja2_version < LooseVersion('3.0'):
|
||||
raise SystemExit(
|
||||
'ERROR: Ansible requires Jinja2 3.0 or newer on the controller. '
|
||||
'Current version: %s' % jinja2_version
|
||||
)
|
||||
|
||||
import errno
|
||||
import getpass
|
||||
import subprocess
|
||||
import traceback
|
||||
from abc import ABC, abstractmethod
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
from ansible import constants as C
|
||||
from ansible.utils.display import Display
|
||||
display = Display()
|
||||
except Exception as e:
|
||||
print('ERROR: %s' % e, file=sys.stderr)
|
||||
sys.exit(5)
|
||||
|
||||
from ansible import context
|
||||
from ansible.cli.arguments import option_helpers as opt_help
|
||||
from ansible.errors import AnsibleError, AnsibleOptionsError, AnsibleParserError
|
||||
from ansible.inventory.manager import InventoryManager
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_text
|
||||
from ansible.module_utils.common.collections import is_sequence
|
||||
from ansible.module_utils.common.file import is_executable
|
||||
from ansible.parsing.dataloader import DataLoader
|
||||
from ansible.parsing.vault import PromptVaultSecret, get_file_vault_secret
|
||||
from ansible.plugins.loader import add_all_plugin_dirs, init_plugin_loader
|
||||
from ansible.release import __version__
|
||||
from ansible.utils.collection_loader import AnsibleCollectionConfig
|
||||
from ansible.utils.collection_loader._collection_finder import _get_collection_name_from_path
|
||||
from ansible.utils.path import unfrackpath
|
||||
from ansible.utils.unsafe_proxy import to_unsafe_text
|
||||
from ansible.vars.manager import VariableManager
|
||||
|
||||
try:
|
||||
import argcomplete
|
||||
HAS_ARGCOMPLETE = True
|
||||
except ImportError:
|
||||
HAS_ARGCOMPLETE = False
|
||||
|
||||
|
||||
class CLI(ABC):
|
||||
''' code behind bin/ansible* programs '''
|
||||
|
||||
PAGER = C.config.get_config_value('PAGER')
|
||||
|
||||
# -F (quit-if-one-screen) -R (allow raw ansi control chars)
|
||||
# -S (chop long lines) -X (disable termcap init and de-init)
|
||||
LESS_OPTS = 'FRSX'
|
||||
SKIP_INVENTORY_DEFAULTS = False
|
||||
|
||||
def __init__(self, args, callback=None):
|
||||
"""
|
||||
Base init method for all command line programs
|
||||
"""
|
||||
|
||||
if not args:
|
||||
raise ValueError('A non-empty list for args is required')
|
||||
|
||||
self.args = args
|
||||
self.parser = None
|
||||
self.callback = callback
|
||||
|
||||
if C.DEVEL_WARNING and __version__.endswith('dev0'):
|
||||
display.warning(
|
||||
'You are running the development version of Ansible. You should only run Ansible from "devel" if '
|
||||
'you are modifying the Ansible engine, or trying out features under development. This is a rapidly '
|
||||
'changing source of code and can become unstable at any point.'
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def run(self):
|
||||
"""Run the ansible command
|
||||
|
||||
Subclasses must implement this method. It does the actual work of
|
||||
running an Ansible command.
|
||||
"""
|
||||
self.parse()
|
||||
|
||||
# Initialize plugin loader after parse, so that the init code can utilize parsed arguments
|
||||
cli_collections_path = context.CLIARGS.get('collections_path') or []
|
||||
if not is_sequence(cli_collections_path):
|
||||
# In some contexts ``collections_path`` is singular
|
||||
cli_collections_path = [cli_collections_path]
|
||||
init_plugin_loader(cli_collections_path)
|
||||
|
||||
display.vv(to_text(opt_help.version(self.parser.prog)))
|
||||
|
||||
if C.CONFIG_FILE:
|
||||
display.v(u"Using %s as config file" % to_text(C.CONFIG_FILE))
|
||||
else:
|
||||
display.v(u"No config file found; using defaults")
|
||||
|
||||
C.handle_config_noise(display)
|
||||
|
||||
@staticmethod
|
||||
def split_vault_id(vault_id):
|
||||
# return (before_@, after_@)
|
||||
# if no @, return whole string as after_
|
||||
if '@' not in vault_id:
|
||||
return (None, vault_id)
|
||||
|
||||
parts = vault_id.split('@', 1)
|
||||
ret = tuple(parts)
|
||||
return ret
|
||||
|
||||
@staticmethod
|
||||
def build_vault_ids(vault_ids, vault_password_files=None,
|
||||
ask_vault_pass=None, auto_prompt=True):
|
||||
vault_password_files = vault_password_files or []
|
||||
vault_ids = vault_ids or []
|
||||
|
||||
# convert vault_password_files into vault_ids slugs
|
||||
for password_file in vault_password_files:
|
||||
id_slug = u'%s@%s' % (C.DEFAULT_VAULT_IDENTITY, password_file)
|
||||
|
||||
# note this makes --vault-id higher precedence than --vault-password-file
|
||||
# if we want to intertwingle them in order probably need a cli callback to populate vault_ids
|
||||
# used by --vault-id and --vault-password-file
|
||||
vault_ids.append(id_slug)
|
||||
|
||||
# if an action needs an encrypt password (create_new_password=True) and we dont
|
||||
# have other secrets setup, then automatically add a password prompt as well.
|
||||
# prompts cant/shouldnt work without a tty, so dont add prompt secrets
|
||||
if ask_vault_pass or (not vault_ids and auto_prompt):
|
||||
|
||||
id_slug = u'%s@%s' % (C.DEFAULT_VAULT_IDENTITY, u'prompt_ask_vault_pass')
|
||||
vault_ids.append(id_slug)
|
||||
|
||||
return vault_ids
|
||||
|
||||
@staticmethod
|
||||
def setup_vault_secrets(loader, vault_ids, vault_password_files=None,
|
||||
ask_vault_pass=None, create_new_password=False,
|
||||
auto_prompt=True):
|
||||
# list of tuples
|
||||
vault_secrets = []
|
||||
|
||||
# Depending on the vault_id value (including how --ask-vault-pass / --vault-password-file create a vault_id)
|
||||
# we need to show different prompts. This is for compat with older Towers that expect a
|
||||
# certain vault password prompt format, so 'promp_ask_vault_pass' vault_id gets the old format.
|
||||
prompt_formats = {}
|
||||
|
||||
# If there are configured default vault identities, they are considered 'first'
|
||||
# so we prepend them to vault_ids (from cli) here
|
||||
|
||||
vault_password_files = vault_password_files or []
|
||||
if C.DEFAULT_VAULT_PASSWORD_FILE:
|
||||
vault_password_files.append(C.DEFAULT_VAULT_PASSWORD_FILE)
|
||||
|
||||
if create_new_password:
|
||||
prompt_formats['prompt'] = ['New vault password (%(vault_id)s): ',
|
||||
'Confirm new vault password (%(vault_id)s): ']
|
||||
# 2.3 format prompts for --ask-vault-pass
|
||||
prompt_formats['prompt_ask_vault_pass'] = ['New Vault password: ',
|
||||
'Confirm New Vault password: ']
|
||||
else:
|
||||
prompt_formats['prompt'] = ['Vault password (%(vault_id)s): ']
|
||||
# The format when we use just --ask-vault-pass needs to match 'Vault password:\s*?$'
|
||||
prompt_formats['prompt_ask_vault_pass'] = ['Vault password: ']
|
||||
|
||||
vault_ids = CLI.build_vault_ids(vault_ids,
|
||||
vault_password_files,
|
||||
ask_vault_pass,
|
||||
auto_prompt=auto_prompt)
|
||||
|
||||
last_exception = found_vault_secret = None
|
||||
for vault_id_slug in vault_ids:
|
||||
vault_id_name, vault_id_value = CLI.split_vault_id(vault_id_slug)
|
||||
if vault_id_value in ['prompt', 'prompt_ask_vault_pass']:
|
||||
|
||||
# --vault-id some_name@prompt_ask_vault_pass --vault-id other_name@prompt_ask_vault_pass will be a little
|
||||
# confusing since it will use the old format without the vault id in the prompt
|
||||
built_vault_id = vault_id_name or C.DEFAULT_VAULT_IDENTITY
|
||||
|
||||
# choose the prompt based on --vault-id=prompt or --ask-vault-pass. --ask-vault-pass
|
||||
# always gets the old format for Tower compatibility.
|
||||
# ie, we used --ask-vault-pass, so we need to use the old vault password prompt
|
||||
# format since Tower needs to match on that format.
|
||||
prompted_vault_secret = PromptVaultSecret(prompt_formats=prompt_formats[vault_id_value],
|
||||
vault_id=built_vault_id)
|
||||
|
||||
# a empty or invalid password from the prompt will warn and continue to the next
|
||||
# without erroring globally
|
||||
try:
|
||||
prompted_vault_secret.load()
|
||||
except AnsibleError as exc:
|
||||
display.warning('Error in vault password prompt (%s): %s' % (vault_id_name, exc))
|
||||
raise
|
||||
|
||||
found_vault_secret = True
|
||||
vault_secrets.append((built_vault_id, prompted_vault_secret))
|
||||
|
||||
# update loader with new secrets incrementally, so we can load a vault password
|
||||
# that is encrypted with a vault secret provided earlier
|
||||
loader.set_vault_secrets(vault_secrets)
|
||||
continue
|
||||
|
||||
# assuming anything else is a password file
|
||||
display.vvvvv('Reading vault password file: %s' % vault_id_value)
|
||||
# read vault_pass from a file
|
||||
try:
|
||||
file_vault_secret = get_file_vault_secret(filename=vault_id_value,
|
||||
vault_id=vault_id_name,
|
||||
loader=loader)
|
||||
except AnsibleError as exc:
|
||||
display.warning('Error getting vault password file (%s): %s' % (vault_id_name, to_text(exc)))
|
||||
last_exception = exc
|
||||
continue
|
||||
|
||||
try:
|
||||
file_vault_secret.load()
|
||||
except AnsibleError as exc:
|
||||
display.warning('Error in vault password file loading (%s): %s' % (vault_id_name, to_text(exc)))
|
||||
last_exception = exc
|
||||
continue
|
||||
|
||||
found_vault_secret = True
|
||||
if vault_id_name:
|
||||
vault_secrets.append((vault_id_name, file_vault_secret))
|
||||
else:
|
||||
vault_secrets.append((C.DEFAULT_VAULT_IDENTITY, file_vault_secret))
|
||||
|
||||
# update loader with as-yet-known vault secrets
|
||||
loader.set_vault_secrets(vault_secrets)
|
||||
|
||||
# An invalid or missing password file will error globally
|
||||
# if no valid vault secret was found.
|
||||
if last_exception and not found_vault_secret:
|
||||
raise last_exception
|
||||
|
||||
return vault_secrets
|
||||
|
||||
@staticmethod
|
||||
def _get_secret(prompt):
|
||||
|
||||
secret = getpass.getpass(prompt=prompt)
|
||||
if secret:
|
||||
secret = to_unsafe_text(secret)
|
||||
return secret
|
||||
|
||||
@staticmethod
|
||||
def ask_passwords():
|
||||
''' prompt for connection and become passwords if needed '''
|
||||
|
||||
op = context.CLIARGS
|
||||
sshpass = None
|
||||
becomepass = None
|
||||
become_prompt = ''
|
||||
|
||||
become_prompt_method = "BECOME" if C.AGNOSTIC_BECOME_PROMPT else op['become_method'].upper()
|
||||
|
||||
try:
|
||||
become_prompt = "%s password: " % become_prompt_method
|
||||
if op['ask_pass']:
|
||||
sshpass = CLI._get_secret("SSH password: ")
|
||||
become_prompt = "%s password[defaults to SSH password]: " % become_prompt_method
|
||||
elif op['connection_password_file']:
|
||||
sshpass = CLI.get_password_from_file(op['connection_password_file'])
|
||||
|
||||
if op['become_ask_pass']:
|
||||
becomepass = CLI._get_secret(become_prompt)
|
||||
if op['ask_pass'] and becomepass == '':
|
||||
becomepass = sshpass
|
||||
elif op['become_password_file']:
|
||||
becomepass = CLI.get_password_from_file(op['become_password_file'])
|
||||
|
||||
except EOFError:
|
||||
pass
|
||||
|
||||
return (sshpass, becomepass)
|
||||
|
||||
def validate_conflicts(self, op, runas_opts=False, fork_opts=False):
|
||||
''' check for conflicting options '''
|
||||
|
||||
if fork_opts:
|
||||
if op.forks < 1:
|
||||
self.parser.error("The number of processes (--forks) must be >= 1")
|
||||
|
||||
return op
|
||||
|
||||
@abstractmethod
|
||||
def init_parser(self, usage="", desc=None, epilog=None):
|
||||
"""
|
||||
Create an options parser for most ansible scripts
|
||||
|
||||
Subclasses need to implement this method. They will usually call the base class's
|
||||
init_parser to create a basic version and then add their own options on top of that.
|
||||
|
||||
An implementation will look something like this::
|
||||
|
||||
def init_parser(self):
|
||||
super(MyCLI, self).init_parser(usage="My Ansible CLI", inventory_opts=True)
|
||||
ansible.arguments.option_helpers.add_runas_options(self.parser)
|
||||
self.parser.add_option('--my-option', dest='my_option', action='store')
|
||||
"""
|
||||
self.parser = opt_help.create_base_parser(self.name, usage=usage, desc=desc, epilog=epilog)
|
||||
|
||||
@abstractmethod
|
||||
def post_process_args(self, options):
|
||||
"""Process the command line args
|
||||
|
||||
Subclasses need to implement this method. This method validates and transforms the command
|
||||
line arguments. It can be used to check whether conflicting values were given, whether filenames
|
||||
exist, etc.
|
||||
|
||||
An implementation will look something like this::
|
||||
|
||||
def post_process_args(self, options):
|
||||
options = super(MyCLI, self).post_process_args(options)
|
||||
if options.addition and options.subtraction:
|
||||
raise AnsibleOptionsError('Only one of --addition and --subtraction can be specified')
|
||||
if isinstance(options.listofhosts, string_types):
|
||||
options.listofhosts = string_types.split(',')
|
||||
return options
|
||||
"""
|
||||
|
||||
# process tags
|
||||
if hasattr(options, 'tags') and not options.tags:
|
||||
# optparse defaults does not do what's expected
|
||||
# More specifically, we want `--tags` to be additive. So we cannot
|
||||
# simply change C.TAGS_RUN's default to ["all"] because then passing
|
||||
# --tags foo would cause us to have ['all', 'foo']
|
||||
options.tags = ['all']
|
||||
if hasattr(options, 'tags') and options.tags:
|
||||
tags = set()
|
||||
for tag_set in options.tags:
|
||||
for tag in tag_set.split(u','):
|
||||
tags.add(tag.strip())
|
||||
options.tags = list(tags)
|
||||
|
||||
# process skip_tags
|
||||
if hasattr(options, 'skip_tags') and options.skip_tags:
|
||||
skip_tags = set()
|
||||
for tag_set in options.skip_tags:
|
||||
for tag in tag_set.split(u','):
|
||||
skip_tags.add(tag.strip())
|
||||
options.skip_tags = list(skip_tags)
|
||||
|
||||
# Make sure path argument doesn't have a backslash
|
||||
if hasattr(options, 'action') and options.action in ['install', 'download'] and hasattr(options, 'args'):
|
||||
options.args = [path.rstrip("/") for path in options.args]
|
||||
|
||||
# process inventory options except for CLIs that require their own processing
|
||||
if hasattr(options, 'inventory') and not self.SKIP_INVENTORY_DEFAULTS:
|
||||
|
||||
if options.inventory:
|
||||
|
||||
# should always be list
|
||||
if isinstance(options.inventory, string_types):
|
||||
options.inventory = [options.inventory]
|
||||
|
||||
# Ensure full paths when needed
|
||||
options.inventory = [unfrackpath(opt, follow=False) if ',' not in opt else opt for opt in options.inventory]
|
||||
else:
|
||||
options.inventory = C.DEFAULT_HOST_LIST
|
||||
|
||||
return options
|
||||
|
||||
def parse(self):
|
||||
"""Parse the command line args
|
||||
|
||||
This method parses the command line arguments. It uses the parser
|
||||
stored in the self.parser attribute and saves the args and options in
|
||||
context.CLIARGS.
|
||||
|
||||
Subclasses need to implement two helper methods, init_parser() and post_process_args() which
|
||||
are called from this function before and after parsing the arguments.
|
||||
"""
|
||||
self.init_parser()
|
||||
|
||||
if HAS_ARGCOMPLETE:
|
||||
argcomplete.autocomplete(self.parser)
|
||||
|
||||
try:
|
||||
options = self.parser.parse_args(self.args[1:])
|
||||
except SystemExit as ex:
|
||||
if ex.code != 0:
|
||||
self.parser.exit(status=2, message=" \n%s" % self.parser.format_help())
|
||||
raise
|
||||
options = self.post_process_args(options)
|
||||
context._init_global_context(options)
|
||||
|
||||
@staticmethod
|
||||
def version_info(gitinfo=False):
|
||||
''' return full ansible version info '''
|
||||
if gitinfo:
|
||||
# expensive call, user with care
|
||||
ansible_version_string = opt_help.version()
|
||||
else:
|
||||
ansible_version_string = __version__
|
||||
ansible_version = ansible_version_string.split()[0]
|
||||
ansible_versions = ansible_version.split('.')
|
||||
for counter in range(len(ansible_versions)):
|
||||
if ansible_versions[counter] == "":
|
||||
ansible_versions[counter] = 0
|
||||
try:
|
||||
ansible_versions[counter] = int(ansible_versions[counter])
|
||||
except Exception:
|
||||
pass
|
||||
if len(ansible_versions) < 3:
|
||||
for counter in range(len(ansible_versions), 3):
|
||||
ansible_versions.append(0)
|
||||
return {'string': ansible_version_string.strip(),
|
||||
'full': ansible_version,
|
||||
'major': ansible_versions[0],
|
||||
'minor': ansible_versions[1],
|
||||
'revision': ansible_versions[2]}
|
||||
|
||||
@staticmethod
|
||||
def pager(text):
|
||||
''' find reasonable way to display text '''
|
||||
# this is a much simpler form of what is in pydoc.py
|
||||
if not sys.stdout.isatty():
|
||||
display.display(text, screen_only=True)
|
||||
elif CLI.PAGER:
|
||||
if sys.platform == 'win32':
|
||||
display.display(text, screen_only=True)
|
||||
else:
|
||||
CLI.pager_pipe(text)
|
||||
else:
|
||||
p = subprocess.Popen('less --version', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
p.communicate()
|
||||
if p.returncode == 0:
|
||||
CLI.pager_pipe(text, 'less')
|
||||
else:
|
||||
display.display(text, screen_only=True)
|
||||
|
||||
@staticmethod
|
||||
def pager_pipe(text):
|
||||
''' pipe text through a pager '''
|
||||
if 'less' in CLI.PAGER:
|
||||
os.environ['LESS'] = CLI.LESS_OPTS
|
||||
try:
|
||||
cmd = subprocess.Popen(CLI.PAGER, shell=True, stdin=subprocess.PIPE, stdout=sys.stdout)
|
||||
cmd.communicate(input=to_bytes(text))
|
||||
except IOError:
|
||||
pass
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _play_prereqs():
|
||||
# TODO: evaluate moving all of the code that touches ``AnsibleCollectionConfig``
|
||||
# into ``init_plugin_loader`` so that we can specifically remove
|
||||
# ``AnsibleCollectionConfig.playbook_paths`` to make it immutable after instantiation
|
||||
|
||||
options = context.CLIARGS
|
||||
|
||||
# all needs loader
|
||||
loader = DataLoader()
|
||||
|
||||
basedir = options.get('basedir', False)
|
||||
if basedir:
|
||||
loader.set_basedir(basedir)
|
||||
add_all_plugin_dirs(basedir)
|
||||
AnsibleCollectionConfig.playbook_paths = basedir
|
||||
default_collection = _get_collection_name_from_path(basedir)
|
||||
if default_collection:
|
||||
display.warning(u'running with default collection {0}'.format(default_collection))
|
||||
AnsibleCollectionConfig.default_collection = default_collection
|
||||
|
||||
vault_ids = list(options['vault_ids'])
|
||||
default_vault_ids = C.DEFAULT_VAULT_IDENTITY_LIST
|
||||
vault_ids = default_vault_ids + vault_ids
|
||||
|
||||
vault_secrets = CLI.setup_vault_secrets(loader,
|
||||
vault_ids=vault_ids,
|
||||
vault_password_files=list(options['vault_password_files']),
|
||||
ask_vault_pass=options['ask_vault_pass'],
|
||||
auto_prompt=False)
|
||||
loader.set_vault_secrets(vault_secrets)
|
||||
|
||||
# create the inventory, and filter it based on the subset specified (if any)
|
||||
inventory = InventoryManager(loader=loader, sources=options['inventory'], cache=(not options.get('flush_cache')))
|
||||
|
||||
# create the variable manager, which will be shared throughout
|
||||
# the code, ensuring a consistent view of global variables
|
||||
variable_manager = VariableManager(loader=loader, inventory=inventory, version_info=CLI.version_info(gitinfo=False))
|
||||
|
||||
return loader, inventory, variable_manager
|
||||
|
||||
@staticmethod
|
||||
def get_host_list(inventory, subset, pattern='all'):
|
||||
|
||||
no_hosts = False
|
||||
if len(inventory.list_hosts()) == 0:
|
||||
# Empty inventory
|
||||
if C.LOCALHOST_WARNING and pattern not in C.LOCALHOST:
|
||||
display.warning("provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'")
|
||||
no_hosts = True
|
||||
|
||||
inventory.subset(subset)
|
||||
|
||||
hosts = inventory.list_hosts(pattern)
|
||||
if not hosts and no_hosts is False:
|
||||
raise AnsibleError("Specified inventory, host pattern and/or --limit leaves us with no hosts to target.")
|
||||
|
||||
return hosts
|
||||
|
||||
@staticmethod
|
||||
def get_password_from_file(pwd_file):
|
||||
|
||||
b_pwd_file = to_bytes(pwd_file)
|
||||
secret = None
|
||||
if b_pwd_file == b'-':
|
||||
# ensure its read as bytes
|
||||
secret = sys.stdin.buffer.read()
|
||||
|
||||
elif not os.path.exists(b_pwd_file):
|
||||
raise AnsibleError("The password file %s was not found" % pwd_file)
|
||||
|
||||
elif is_executable(b_pwd_file):
|
||||
display.vvvv(u'The password file %s is a script.' % to_text(pwd_file))
|
||||
cmd = [b_pwd_file]
|
||||
|
||||
try:
|
||||
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
except OSError as e:
|
||||
raise AnsibleError("Problem occurred when trying to run the password script %s (%s)."
|
||||
" If this is not a script, remove the executable bit from the file." % (pwd_file, e))
|
||||
|
||||
stdout, stderr = p.communicate()
|
||||
if p.returncode != 0:
|
||||
raise AnsibleError("The password script %s returned an error (rc=%s): %s" % (pwd_file, p.returncode, stderr))
|
||||
secret = stdout
|
||||
|
||||
else:
|
||||
try:
|
||||
f = open(b_pwd_file, "rb")
|
||||
secret = f.read().strip()
|
||||
f.close()
|
||||
except (OSError, IOError) as e:
|
||||
raise AnsibleError("Could not read password file %s: %s" % (pwd_file, e))
|
||||
|
||||
secret = secret.strip(b'\r\n')
|
||||
|
||||
if not secret:
|
||||
raise AnsibleError('Empty password was provided from file (%s)' % pwd_file)
|
||||
|
||||
return to_unsafe_text(secret)
|
||||
|
||||
@classmethod
|
||||
def cli_executor(cls, args=None):
|
||||
if args is None:
|
||||
args = sys.argv
|
||||
|
||||
try:
|
||||
display.debug("starting run")
|
||||
|
||||
ansible_dir = Path(C.ANSIBLE_HOME).expanduser()
|
||||
try:
|
||||
ansible_dir.mkdir(mode=0o700)
|
||||
except OSError as exc:
|
||||
if exc.errno != errno.EEXIST:
|
||||
display.warning(
|
||||
"Failed to create the directory '%s': %s" % (ansible_dir, to_text(exc, errors='surrogate_or_replace'))
|
||||
)
|
||||
else:
|
||||
display.debug("Created the '%s' directory" % ansible_dir)
|
||||
|
||||
try:
|
||||
args = [to_text(a, errors='surrogate_or_strict') for a in args]
|
||||
except UnicodeError:
|
||||
display.error('Command line args are not in utf-8, unable to continue. Ansible currently only understands utf-8')
|
||||
display.display(u"The full traceback was:\n\n%s" % to_text(traceback.format_exc()))
|
||||
exit_code = 6
|
||||
else:
|
||||
cli = cls(args)
|
||||
exit_code = cli.run()
|
||||
|
||||
except AnsibleOptionsError as e:
|
||||
cli.parser.print_help()
|
||||
display.error(to_text(e), wrap_text=False)
|
||||
exit_code = 5
|
||||
except AnsibleParserError as e:
|
||||
display.error(to_text(e), wrap_text=False)
|
||||
exit_code = 4
|
||||
# TQM takes care of these, but leaving comment to reserve the exit codes
|
||||
# except AnsibleHostUnreachable as e:
|
||||
# display.error(str(e))
|
||||
# exit_code = 3
|
||||
# except AnsibleHostFailed as e:
|
||||
# display.error(str(e))
|
||||
# exit_code = 2
|
||||
except AnsibleError as e:
|
||||
display.error(to_text(e), wrap_text=False)
|
||||
exit_code = 1
|
||||
except KeyboardInterrupt:
|
||||
display.error("User interrupted execution")
|
||||
exit_code = 99
|
||||
except Exception as e:
|
||||
if C.DEFAULT_DEBUG:
|
||||
# Show raw stacktraces in debug mode, It also allow pdb to
|
||||
# enter post mortem mode.
|
||||
raise
|
||||
have_cli_options = bool(context.CLIARGS)
|
||||
display.error("Unexpected Exception, this is probably a bug: %s" % to_text(e), wrap_text=False)
|
||||
if not have_cli_options or have_cli_options and context.CLIARGS['verbosity'] > 2:
|
||||
log_only = False
|
||||
if hasattr(e, 'orig_exc'):
|
||||
display.vvv('\nexception type: %s' % to_text(type(e.orig_exc)))
|
||||
why = to_text(e.orig_exc)
|
||||
if to_text(e) != why:
|
||||
display.vvv('\noriginal msg: %s' % why)
|
||||
else:
|
||||
display.display("to see the full traceback, use -vvv")
|
||||
log_only = True
|
||||
display.display(u"the full traceback was:\n\n%s" % to_text(traceback.format_exc()), log_only=log_only)
|
||||
exit_code = 250
|
||||
|
||||
sys.exit(exit_code)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
206
ansible/lib/python3.11/site-packages/ansible/cli/adhoc.py
Normal file
206
ansible/lib/python3.11/site-packages/ansible/cli/adhoc.py
Normal file
@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright: (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# PYTHON_ARGCOMPLETE_OK
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
# ansible.cli needs to be imported first, to ensure the source bin/* scripts run that code first
|
||||
from ansible.cli import CLI
|
||||
from ansible import constants as C
|
||||
from ansible import context
|
||||
from ansible.cli.arguments import option_helpers as opt_help
|
||||
from ansible.errors import AnsibleError, AnsibleOptionsError, AnsibleParserError
|
||||
from ansible.executor.task_queue_manager import TaskQueueManager
|
||||
from ansible.module_utils.common.text.converters import to_text
|
||||
from ansible.parsing.splitter import parse_kv
|
||||
from ansible.parsing.utils.yaml import from_yaml
|
||||
from ansible.playbook import Playbook
|
||||
from ansible.playbook.play import Play
|
||||
from ansible.utils.display import Display
|
||||
|
||||
display = Display()
|
||||
|
||||
|
||||
class AdHocCLI(CLI):
|
||||
''' is an extra-simple tool/framework/API for doing 'remote things'.
|
||||
this command allows you to define and run a single task 'playbook' against a set of hosts
|
||||
'''
|
||||
|
||||
name = 'ansible'
|
||||
|
||||
def init_parser(self):
|
||||
''' create an options parser for bin/ansible '''
|
||||
super(AdHocCLI, self).init_parser(usage='%prog <host-pattern> [options]',
|
||||
desc="Define and run a single task 'playbook' against a set of hosts",
|
||||
epilog="Some actions do not make sense in Ad-Hoc (include, meta, etc)")
|
||||
|
||||
opt_help.add_runas_options(self.parser)
|
||||
opt_help.add_inventory_options(self.parser)
|
||||
opt_help.add_async_options(self.parser)
|
||||
opt_help.add_output_options(self.parser)
|
||||
opt_help.add_connect_options(self.parser)
|
||||
opt_help.add_check_options(self.parser)
|
||||
opt_help.add_runtask_options(self.parser)
|
||||
opt_help.add_vault_options(self.parser)
|
||||
opt_help.add_fork_options(self.parser)
|
||||
opt_help.add_module_options(self.parser)
|
||||
opt_help.add_basedir_options(self.parser)
|
||||
opt_help.add_tasknoplay_options(self.parser)
|
||||
|
||||
# options unique to ansible ad-hoc
|
||||
self.parser.add_argument('-a', '--args', dest='module_args',
|
||||
help="The action's options in space separated k=v format: -a 'opt1=val1 opt2=val2' "
|
||||
"or a json string: -a '{\"opt1\": \"val1\", \"opt2\": \"val2\"}'",
|
||||
default=C.DEFAULT_MODULE_ARGS)
|
||||
self.parser.add_argument('-m', '--module-name', dest='module_name',
|
||||
help="Name of the action to execute (default=%s)" % C.DEFAULT_MODULE_NAME,
|
||||
default=C.DEFAULT_MODULE_NAME)
|
||||
self.parser.add_argument('args', metavar='pattern', help='host pattern')
|
||||
|
||||
def post_process_args(self, options):
|
||||
'''Post process and validate options for bin/ansible '''
|
||||
|
||||
options = super(AdHocCLI, self).post_process_args(options)
|
||||
|
||||
display.verbosity = options.verbosity
|
||||
self.validate_conflicts(options, runas_opts=True, fork_opts=True)
|
||||
|
||||
return options
|
||||
|
||||
def _play_ds(self, pattern, async_val, poll):
|
||||
check_raw = context.CLIARGS['module_name'] in C.MODULE_REQUIRE_ARGS
|
||||
|
||||
module_args_raw = context.CLIARGS['module_args']
|
||||
module_args = None
|
||||
if module_args_raw and module_args_raw.startswith('{') and module_args_raw.endswith('}'):
|
||||
try:
|
||||
module_args = from_yaml(module_args_raw.strip(), json_only=True)
|
||||
except AnsibleParserError:
|
||||
pass
|
||||
|
||||
if not module_args:
|
||||
module_args = parse_kv(module_args_raw, check_raw=check_raw)
|
||||
|
||||
mytask = {'action': {'module': context.CLIARGS['module_name'], 'args': module_args},
|
||||
'timeout': context.CLIARGS['task_timeout']}
|
||||
|
||||
# avoid adding to tasks that don't support it, unless set, then give user an error
|
||||
if context.CLIARGS['module_name'] not in C._ACTION_ALL_INCLUDE_ROLE_TASKS and any(frozenset((async_val, poll))):
|
||||
mytask['async_val'] = async_val
|
||||
mytask['poll'] = poll
|
||||
|
||||
return dict(
|
||||
name="Ansible Ad-Hoc",
|
||||
hosts=pattern,
|
||||
gather_facts='no',
|
||||
tasks=[mytask])
|
||||
|
||||
def run(self):
|
||||
''' create and execute the single task playbook '''
|
||||
|
||||
super(AdHocCLI, self).run()
|
||||
|
||||
# only thing left should be host pattern
|
||||
pattern = to_text(context.CLIARGS['args'], errors='surrogate_or_strict')
|
||||
|
||||
# handle password prompts
|
||||
sshpass = None
|
||||
becomepass = None
|
||||
|
||||
(sshpass, becomepass) = self.ask_passwords()
|
||||
passwords = {'conn_pass': sshpass, 'become_pass': becomepass}
|
||||
|
||||
# get basic objects
|
||||
loader, inventory, variable_manager = self._play_prereqs()
|
||||
|
||||
# get list of hosts to execute against
|
||||
try:
|
||||
hosts = self.get_host_list(inventory, context.CLIARGS['subset'], pattern)
|
||||
except AnsibleError:
|
||||
if context.CLIARGS['subset']:
|
||||
raise
|
||||
else:
|
||||
hosts = []
|
||||
display.warning("No hosts matched, nothing to do")
|
||||
|
||||
# just listing hosts?
|
||||
if context.CLIARGS['listhosts']:
|
||||
display.display(' hosts (%d):' % len(hosts))
|
||||
for host in hosts:
|
||||
display.display(' %s' % host)
|
||||
return 0
|
||||
|
||||
# verify we have arguments if we know we need em
|
||||
if context.CLIARGS['module_name'] in C.MODULE_REQUIRE_ARGS and not context.CLIARGS['module_args']:
|
||||
err = "No argument passed to %s module" % context.CLIARGS['module_name']
|
||||
if pattern.endswith(".yml"):
|
||||
err = err + ' (did you mean to run ansible-playbook?)'
|
||||
raise AnsibleOptionsError(err)
|
||||
|
||||
# Avoid modules that don't work with ad-hoc
|
||||
if context.CLIARGS['module_name'] in C._ACTION_IMPORT_PLAYBOOK:
|
||||
raise AnsibleOptionsError("'%s' is not a valid action for ad-hoc commands"
|
||||
% context.CLIARGS['module_name'])
|
||||
|
||||
# construct playbook objects to wrap task
|
||||
play_ds = self._play_ds(pattern, context.CLIARGS['seconds'], context.CLIARGS['poll_interval'])
|
||||
play = Play().load(play_ds, variable_manager=variable_manager, loader=loader)
|
||||
|
||||
# used in start callback
|
||||
playbook = Playbook(loader)
|
||||
playbook._entries.append(play)
|
||||
playbook._file_name = '__adhoc_playbook__'
|
||||
|
||||
if self.callback:
|
||||
cb = self.callback
|
||||
elif context.CLIARGS['one_line']:
|
||||
cb = 'oneline'
|
||||
# Respect custom 'stdout_callback' only with enabled 'bin_ansible_callbacks'
|
||||
elif C.DEFAULT_LOAD_CALLBACK_PLUGINS and C.DEFAULT_STDOUT_CALLBACK != 'default':
|
||||
cb = C.DEFAULT_STDOUT_CALLBACK
|
||||
else:
|
||||
cb = 'minimal'
|
||||
|
||||
run_tree = False
|
||||
if context.CLIARGS['tree']:
|
||||
C.CALLBACKS_ENABLED.append('tree')
|
||||
C.TREE_DIR = context.CLIARGS['tree']
|
||||
run_tree = True
|
||||
|
||||
# now create a task queue manager to execute the play
|
||||
self._tqm = None
|
||||
try:
|
||||
self._tqm = TaskQueueManager(
|
||||
inventory=inventory,
|
||||
variable_manager=variable_manager,
|
||||
loader=loader,
|
||||
passwords=passwords,
|
||||
stdout_callback=cb,
|
||||
run_additional_callbacks=C.DEFAULT_LOAD_CALLBACK_PLUGINS,
|
||||
run_tree=run_tree,
|
||||
forks=context.CLIARGS['forks'],
|
||||
)
|
||||
|
||||
self._tqm.load_callbacks()
|
||||
self._tqm.send_callback('v2_playbook_on_start', playbook)
|
||||
|
||||
result = self._tqm.run(play)
|
||||
|
||||
self._tqm.send_callback('v2_playbook_on_stats', self._tqm._stats)
|
||||
finally:
|
||||
if self._tqm:
|
||||
self._tqm.cleanup()
|
||||
if loader:
|
||||
loader.cleanup_all_tmp_files()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def main(args=None):
|
||||
AdHocCLI.cli_executor(args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@ -0,0 +1,4 @@
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import annotations
|
||||
Binary file not shown.
Binary file not shown.
@ -0,0 +1,398 @@
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import operator
|
||||
import argparse
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
import time
|
||||
|
||||
from jinja2 import __version__ as j2_version
|
||||
|
||||
import ansible
|
||||
from ansible import constants as C
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
from ansible.module_utils.common.yaml import HAS_LIBYAML, yaml_load
|
||||
from ansible.release import __version__
|
||||
from ansible.utils.path import unfrackpath
|
||||
|
||||
|
||||
#
|
||||
# Special purpose OptionParsers
|
||||
#
|
||||
class SortingHelpFormatter(argparse.HelpFormatter):
|
||||
def add_arguments(self, actions):
|
||||
actions = sorted(actions, key=operator.attrgetter('option_strings'))
|
||||
super(SortingHelpFormatter, self).add_arguments(actions)
|
||||
|
||||
|
||||
class ArgumentParser(argparse.ArgumentParser):
|
||||
def add_argument(self, *args, **kwargs):
|
||||
action = kwargs.get('action')
|
||||
help = kwargs.get('help')
|
||||
if help and action in {'append', 'append_const', 'count', 'extend', PrependListAction}:
|
||||
help = f'{help.rstrip(".")}. This argument may be specified multiple times.'
|
||||
kwargs['help'] = help
|
||||
return super().add_argument(*args, **kwargs)
|
||||
|
||||
|
||||
class AnsibleVersion(argparse.Action):
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
ansible_version = to_native(version(getattr(parser, 'prog')))
|
||||
print(ansible_version)
|
||||
parser.exit()
|
||||
|
||||
|
||||
class UnrecognizedArgument(argparse.Action):
|
||||
def __init__(self, option_strings, dest, const=True, default=None, required=False, help=None, metavar=None, nargs=0):
|
||||
super(UnrecognizedArgument, self).__init__(option_strings=option_strings, dest=dest, nargs=nargs, const=const,
|
||||
default=default, required=required, help=help)
|
||||
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
parser.error('unrecognized arguments: %s' % option_string)
|
||||
|
||||
|
||||
class PrependListAction(argparse.Action):
|
||||
"""A near clone of ``argparse._AppendAction``, but designed to prepend list values
|
||||
instead of appending.
|
||||
"""
|
||||
def __init__(self, option_strings, dest, nargs=None, const=None, default=None, type=None,
|
||||
choices=None, required=False, help=None, metavar=None):
|
||||
if nargs == 0:
|
||||
raise ValueError('nargs for append actions must be > 0; if arg '
|
||||
'strings are not supplying the value to append, '
|
||||
'the append const action may be more appropriate')
|
||||
if const is not None and nargs != argparse.OPTIONAL:
|
||||
raise ValueError('nargs must be %r to supply const' % argparse.OPTIONAL)
|
||||
super(PrependListAction, self).__init__(
|
||||
option_strings=option_strings,
|
||||
dest=dest,
|
||||
nargs=nargs,
|
||||
const=const,
|
||||
default=default,
|
||||
type=type,
|
||||
choices=choices,
|
||||
required=required,
|
||||
help=help,
|
||||
metavar=metavar
|
||||
)
|
||||
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
items = copy.copy(ensure_value(namespace, self.dest, []))
|
||||
items[0:0] = values
|
||||
setattr(namespace, self.dest, items)
|
||||
|
||||
|
||||
def ensure_value(namespace, name, value):
|
||||
if getattr(namespace, name, None) is None:
|
||||
setattr(namespace, name, value)
|
||||
return getattr(namespace, name)
|
||||
|
||||
|
||||
#
|
||||
# Callbacks to validate and normalize Options
|
||||
#
|
||||
def unfrack_path(pathsep=False, follow=True):
|
||||
"""Turn an Option's data into a single path in Ansible locations"""
|
||||
def inner(value):
|
||||
if pathsep:
|
||||
return [unfrackpath(x, follow=follow) for x in value.split(os.pathsep) if x]
|
||||
|
||||
if value == '-':
|
||||
return value
|
||||
|
||||
return unfrackpath(value, follow=follow)
|
||||
return inner
|
||||
|
||||
|
||||
def maybe_unfrack_path(beacon):
|
||||
|
||||
def inner(value):
|
||||
if value.startswith(beacon):
|
||||
return beacon + unfrackpath(value[1:])
|
||||
return value
|
||||
return inner
|
||||
|
||||
|
||||
def _git_repo_info(repo_path):
|
||||
""" returns a string containing git branch, commit id and commit date """
|
||||
result = None
|
||||
if os.path.exists(repo_path):
|
||||
# Check if the .git is a file. If it is a file, it means that we are in a submodule structure.
|
||||
if os.path.isfile(repo_path):
|
||||
try:
|
||||
with open(repo_path) as f:
|
||||
gitdir = yaml_load(f).get('gitdir')
|
||||
# There is a possibility the .git file to have an absolute path.
|
||||
if os.path.isabs(gitdir):
|
||||
repo_path = gitdir
|
||||
else:
|
||||
repo_path = os.path.join(repo_path[:-4], gitdir)
|
||||
except (IOError, AttributeError):
|
||||
return ''
|
||||
with open(os.path.join(repo_path, "HEAD")) as f:
|
||||
line = f.readline().rstrip("\n")
|
||||
if line.startswith("ref:"):
|
||||
branch_path = os.path.join(repo_path, line[5:])
|
||||
else:
|
||||
branch_path = None
|
||||
if branch_path and os.path.exists(branch_path):
|
||||
branch = '/'.join(line.split('/')[2:])
|
||||
with open(branch_path) as f:
|
||||
commit = f.readline()[:10]
|
||||
else:
|
||||
# detached HEAD
|
||||
commit = line[:10]
|
||||
branch = 'detached HEAD'
|
||||
branch_path = os.path.join(repo_path, "HEAD")
|
||||
|
||||
date = time.localtime(os.stat(branch_path).st_mtime)
|
||||
if time.daylight == 0:
|
||||
offset = time.timezone
|
||||
else:
|
||||
offset = time.altzone
|
||||
result = "({0} {1}) last updated {2} (GMT {3:+04d})".format(branch, commit, time.strftime("%Y/%m/%d %H:%M:%S", date), int(offset / -36))
|
||||
else:
|
||||
result = ''
|
||||
return result
|
||||
|
||||
|
||||
def _gitinfo():
|
||||
basedir = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', '..', '..', '..'))
|
||||
repo_path = os.path.join(basedir, '.git')
|
||||
return _git_repo_info(repo_path)
|
||||
|
||||
|
||||
def version(prog=None):
|
||||
""" return ansible version """
|
||||
if prog:
|
||||
result = ["{0} [core {1}]".format(prog, __version__)]
|
||||
else:
|
||||
result = [__version__]
|
||||
|
||||
gitinfo = _gitinfo()
|
||||
if gitinfo:
|
||||
result[0] = "{0} {1}".format(result[0], gitinfo)
|
||||
result.append(" config file = %s" % C.CONFIG_FILE)
|
||||
if C.DEFAULT_MODULE_PATH is None:
|
||||
cpath = "Default w/o overrides"
|
||||
else:
|
||||
cpath = C.DEFAULT_MODULE_PATH
|
||||
result.append(" configured module search path = %s" % cpath)
|
||||
result.append(" ansible python module location = %s" % ':'.join(ansible.__path__))
|
||||
result.append(" ansible collection location = %s" % ':'.join(C.COLLECTIONS_PATHS))
|
||||
result.append(" executable location = %s" % sys.argv[0])
|
||||
result.append(" python version = %s (%s)" % (''.join(sys.version.splitlines()), to_native(sys.executable)))
|
||||
result.append(" jinja version = %s" % j2_version)
|
||||
result.append(" libyaml = %s" % HAS_LIBYAML)
|
||||
return "\n".join(result)
|
||||
|
||||
|
||||
#
|
||||
# Functions to add pre-canned options to an OptionParser
|
||||
#
|
||||
|
||||
def create_base_parser(prog, usage="", desc=None, epilog=None):
|
||||
"""
|
||||
Create an options parser for all ansible scripts
|
||||
"""
|
||||
# base opts
|
||||
parser = ArgumentParser(
|
||||
prog=prog,
|
||||
formatter_class=SortingHelpFormatter,
|
||||
epilog=epilog,
|
||||
description=desc,
|
||||
conflict_handler='resolve',
|
||||
)
|
||||
version_help = "show program's version number, config file location, configured module search path," \
|
||||
" module location, executable location and exit"
|
||||
|
||||
parser.add_argument('--version', action=AnsibleVersion, nargs=0, help=version_help)
|
||||
add_verbosity_options(parser)
|
||||
return parser
|
||||
|
||||
|
||||
def add_verbosity_options(parser):
|
||||
"""Add options for verbosity"""
|
||||
parser.add_argument('-v', '--verbose', dest='verbosity', default=C.DEFAULT_VERBOSITY, action="count",
|
||||
help="Causes Ansible to print more debug messages. Adding multiple -v will increase the verbosity, "
|
||||
"the builtin plugins currently evaluate up to -vvvvvv. A reasonable level to start is -vvv, "
|
||||
"connection debugging might require -vvvv.")
|
||||
|
||||
|
||||
def add_async_options(parser):
|
||||
"""Add options for commands which can launch async tasks"""
|
||||
parser.add_argument('-P', '--poll', default=C.DEFAULT_POLL_INTERVAL, type=int, dest='poll_interval',
|
||||
help="set the poll interval if using -B (default=%s)" % C.DEFAULT_POLL_INTERVAL)
|
||||
parser.add_argument('-B', '--background', dest='seconds', type=int, default=0,
|
||||
help='run asynchronously, failing after X seconds (default=N/A)')
|
||||
|
||||
|
||||
def add_basedir_options(parser):
|
||||
"""Add options for commands which can set a playbook basedir"""
|
||||
parser.add_argument('--playbook-dir', default=C.PLAYBOOK_DIR, dest='basedir', action='store',
|
||||
help="Since this tool does not use playbooks, use this as a substitute playbook directory. "
|
||||
"This sets the relative path for many features including roles/ group_vars/ etc.",
|
||||
type=unfrack_path())
|
||||
|
||||
|
||||
def add_check_options(parser):
|
||||
"""Add options for commands which can run with diagnostic information of tasks"""
|
||||
parser.add_argument("-C", "--check", default=False, dest='check', action='store_true',
|
||||
help="don't make any changes; instead, try to predict some of the changes that may occur")
|
||||
parser.add_argument("-D", "--diff", default=C.DIFF_ALWAYS, dest='diff', action='store_true',
|
||||
help="when changing (small) files and templates, show the differences in those"
|
||||
" files; works great with --check")
|
||||
|
||||
|
||||
def add_connect_options(parser):
|
||||
"""Add options for commands which need to connection to other hosts"""
|
||||
connect_group = parser.add_argument_group("Connection Options", "control as whom and how to connect to hosts")
|
||||
|
||||
connect_group.add_argument('--private-key', '--key-file', default=C.DEFAULT_PRIVATE_KEY_FILE, dest='private_key_file',
|
||||
help='use this file to authenticate the connection', type=unfrack_path())
|
||||
connect_group.add_argument('-u', '--user', default=C.DEFAULT_REMOTE_USER, dest='remote_user',
|
||||
help='connect as this user (default=%s)' % C.DEFAULT_REMOTE_USER)
|
||||
connect_group.add_argument('-c', '--connection', dest='connection', default=C.DEFAULT_TRANSPORT,
|
||||
help="connection type to use (default=%s)" % C.DEFAULT_TRANSPORT)
|
||||
connect_group.add_argument('-T', '--timeout', default=None, type=int, dest='timeout',
|
||||
help="override the connection timeout in seconds (default depends on connection)")
|
||||
|
||||
# ssh only
|
||||
connect_group.add_argument('--ssh-common-args', default=None, dest='ssh_common_args',
|
||||
help="specify common arguments to pass to sftp/scp/ssh (e.g. ProxyCommand)")
|
||||
connect_group.add_argument('--sftp-extra-args', default=None, dest='sftp_extra_args',
|
||||
help="specify extra arguments to pass to sftp only (e.g. -f, -l)")
|
||||
connect_group.add_argument('--scp-extra-args', default=None, dest='scp_extra_args',
|
||||
help="specify extra arguments to pass to scp only (e.g. -l)")
|
||||
connect_group.add_argument('--ssh-extra-args', default=None, dest='ssh_extra_args',
|
||||
help="specify extra arguments to pass to ssh only (e.g. -R)")
|
||||
|
||||
parser.add_argument_group(connect_group)
|
||||
|
||||
connect_password_group = parser.add_mutually_exclusive_group()
|
||||
connect_password_group.add_argument('-k', '--ask-pass', default=C.DEFAULT_ASK_PASS, dest='ask_pass', action='store_true',
|
||||
help='ask for connection password')
|
||||
connect_password_group.add_argument('--connection-password-file', '--conn-pass-file', default=C.CONNECTION_PASSWORD_FILE, dest='connection_password_file',
|
||||
help="Connection password file", type=unfrack_path(), action='store')
|
||||
|
||||
parser.add_argument_group(connect_password_group)
|
||||
|
||||
|
||||
def add_fork_options(parser):
|
||||
"""Add options for commands that can fork worker processes"""
|
||||
parser.add_argument('-f', '--forks', dest='forks', default=C.DEFAULT_FORKS, type=int,
|
||||
help="specify number of parallel processes to use (default=%s)" % C.DEFAULT_FORKS)
|
||||
|
||||
|
||||
def add_inventory_options(parser):
|
||||
"""Add options for commands that utilize inventory"""
|
||||
parser.add_argument('-i', '--inventory', '--inventory-file', dest='inventory', action="append",
|
||||
help="specify inventory host path or comma separated host list. --inventory-file is deprecated")
|
||||
parser.add_argument('--list-hosts', dest='listhosts', action='store_true',
|
||||
help='outputs a list of matching hosts; does not execute anything else')
|
||||
parser.add_argument('-l', '--limit', default=C.DEFAULT_SUBSET, dest='subset',
|
||||
help='further limit selected hosts to an additional pattern')
|
||||
|
||||
|
||||
def add_meta_options(parser):
|
||||
"""Add options for commands which can launch meta tasks from the command line"""
|
||||
parser.add_argument('--force-handlers', default=C.DEFAULT_FORCE_HANDLERS, dest='force_handlers', action='store_true',
|
||||
help="run handlers even if a task fails")
|
||||
parser.add_argument('--flush-cache', dest='flush_cache', action='store_true',
|
||||
help="clear the fact cache for every host in inventory")
|
||||
|
||||
|
||||
def add_module_options(parser):
|
||||
"""Add options for commands that load modules"""
|
||||
module_path = C.config.get_configuration_definition('DEFAULT_MODULE_PATH').get('default', '')
|
||||
parser.add_argument('-M', '--module-path', dest='module_path', default=None,
|
||||
help="prepend colon-separated path(s) to module library (default=%s)" % module_path,
|
||||
type=unfrack_path(pathsep=True), action=PrependListAction)
|
||||
|
||||
|
||||
def add_output_options(parser):
|
||||
"""Add options for commands which can change their output"""
|
||||
parser.add_argument('-o', '--one-line', dest='one_line', action='store_true',
|
||||
help='condense output')
|
||||
parser.add_argument('-t', '--tree', dest='tree', default=None,
|
||||
help='log output to this directory')
|
||||
|
||||
|
||||
def add_runas_options(parser):
|
||||
"""
|
||||
Add options for commands which can run tasks as another user
|
||||
|
||||
Note that this includes the options from add_runas_prompt_options(). Only one of these
|
||||
functions should be used.
|
||||
"""
|
||||
runas_group = parser.add_argument_group("Privilege Escalation Options", "control how and which user you become as on target hosts")
|
||||
|
||||
# consolidated privilege escalation (become)
|
||||
runas_group.add_argument("-b", "--become", default=C.DEFAULT_BECOME, action="store_true", dest='become',
|
||||
help="run operations with become (does not imply password prompting)")
|
||||
runas_group.add_argument('--become-method', dest='become_method', default=C.DEFAULT_BECOME_METHOD,
|
||||
help='privilege escalation method to use (default=%s)' % C.DEFAULT_BECOME_METHOD +
|
||||
', use `ansible-doc -t become -l` to list valid choices.')
|
||||
runas_group.add_argument('--become-user', default=None, dest='become_user', type=str,
|
||||
help='run operations as this user (default=%s)' % C.DEFAULT_BECOME_USER)
|
||||
|
||||
parser.add_argument_group(runas_group)
|
||||
|
||||
add_runas_prompt_options(parser)
|
||||
|
||||
|
||||
def add_runas_prompt_options(parser, runas_group=None):
|
||||
"""
|
||||
Add options for commands which need to prompt for privilege escalation credentials
|
||||
|
||||
Note that add_runas_options() includes these options already. Only one of the two functions
|
||||
should be used.
|
||||
"""
|
||||
if runas_group is not None:
|
||||
parser.add_argument_group(runas_group)
|
||||
|
||||
runas_pass_group = parser.add_mutually_exclusive_group()
|
||||
|
||||
runas_pass_group.add_argument('-K', '--ask-become-pass', dest='become_ask_pass', action='store_true',
|
||||
default=C.DEFAULT_BECOME_ASK_PASS,
|
||||
help='ask for privilege escalation password')
|
||||
runas_pass_group.add_argument('--become-password-file', '--become-pass-file', default=C.BECOME_PASSWORD_FILE, dest='become_password_file',
|
||||
help="Become password file", type=unfrack_path(), action='store')
|
||||
|
||||
parser.add_argument_group(runas_pass_group)
|
||||
|
||||
|
||||
def add_runtask_options(parser):
|
||||
"""Add options for commands that run a task"""
|
||||
parser.add_argument('-e', '--extra-vars', dest="extra_vars", action="append", type=maybe_unfrack_path('@'),
|
||||
help="set additional variables as key=value or YAML/JSON, if filename prepend with @", default=[])
|
||||
|
||||
|
||||
def add_tasknoplay_options(parser):
|
||||
"""Add options for commands that run a task w/o a defined play"""
|
||||
parser.add_argument('--task-timeout', type=int, dest="task_timeout", action="store", default=C.TASK_TIMEOUT,
|
||||
help="set task timeout limit in seconds, must be positive integer.")
|
||||
|
||||
|
||||
def add_subset_options(parser):
|
||||
"""Add options for commands which can run a subset of tasks"""
|
||||
parser.add_argument('-t', '--tags', dest='tags', default=C.TAGS_RUN, action='append',
|
||||
help="only run plays and tasks tagged with these values")
|
||||
parser.add_argument('--skip-tags', dest='skip_tags', default=C.TAGS_SKIP, action='append',
|
||||
help="only run plays and tasks whose tags do not match these values")
|
||||
|
||||
|
||||
def add_vault_options(parser):
|
||||
"""Add options for loading vault files"""
|
||||
parser.add_argument('--vault-id', default=[], dest='vault_ids', action='append', type=str,
|
||||
help='the vault identity to use')
|
||||
base_group = parser.add_mutually_exclusive_group()
|
||||
base_group.add_argument('-J', '--ask-vault-password', '--ask-vault-pass', default=C.DEFAULT_ASK_VAULT_PASS, dest='ask_vault_pass', action='store_true',
|
||||
help='ask for vault password')
|
||||
base_group.add_argument('--vault-password-file', '--vault-pass-file', default=[], dest='vault_password_files',
|
||||
help="vault password file", type=unfrack_path(follow=False), action='append')
|
||||
724
ansible/lib/python3.11/site-packages/ansible/cli/config.py
Normal file
724
ansible/lib/python3.11/site-packages/ansible/cli/config.py
Normal file
@ -0,0 +1,724 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright: (c) 2017, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# PYTHON_ARGCOMPLETE_OK
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
# ansible.cli needs to be imported first, to ensure the source bin/* scripts run that code first
|
||||
from ansible.cli import CLI
|
||||
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
import yaml
|
||||
|
||||
from collections.abc import Mapping
|
||||
|
||||
from ansible import context
|
||||
import ansible.plugins.loader as plugin_loader
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible.cli.arguments import option_helpers as opt_help
|
||||
from ansible.config.manager import ConfigManager, Setting
|
||||
from ansible.errors import AnsibleError, AnsibleOptionsError, AnsibleRequiredOptionError
|
||||
from ansible.module_utils.common.text.converters import to_native, to_text, to_bytes
|
||||
from ansible.module_utils.common.json import json_dump
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.parsing.quoting import is_quoted
|
||||
from ansible.parsing.yaml.dumper import AnsibleDumper
|
||||
from ansible.utils.color import stringc
|
||||
from ansible.utils.display import Display
|
||||
from ansible.utils.path import unfrackpath
|
||||
|
||||
display = Display()
|
||||
|
||||
|
||||
_IGNORE_CHANGED = frozenset({'_terms', '_input'})
|
||||
|
||||
|
||||
def yaml_dump(data, default_flow_style=False, default_style=None):
|
||||
return yaml.dump(data, Dumper=AnsibleDumper, default_flow_style=default_flow_style, default_style=default_style)
|
||||
|
||||
|
||||
def yaml_short(data):
|
||||
return yaml_dump(data, default_flow_style=True, default_style="''")
|
||||
|
||||
|
||||
def get_constants():
|
||||
''' helper method to ensure we can template based on existing constants '''
|
||||
if not hasattr(get_constants, 'cvars'):
|
||||
get_constants.cvars = {k: getattr(C, k) for k in dir(C) if not k.startswith('__')}
|
||||
return get_constants.cvars
|
||||
|
||||
|
||||
def _ansible_env_vars(varname):
|
||||
''' return true or false depending if variable name is possibly a 'configurable' ansible env variable '''
|
||||
return all(
|
||||
[
|
||||
varname.startswith("ANSIBLE_"),
|
||||
not varname.startswith(("ANSIBLE_TEST_", "ANSIBLE_LINT_")),
|
||||
varname not in ("ANSIBLE_CONFIG", "ANSIBLE_DEV_HOME"),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def _get_evar_list(settings):
|
||||
data = []
|
||||
for setting in settings:
|
||||
if 'env' in settings[setting] and settings[setting]['env']:
|
||||
for varname in settings[setting]['env']:
|
||||
data.append(varname.get('name'))
|
||||
return data
|
||||
|
||||
|
||||
def _get_ini_entries(settings):
|
||||
data = {}
|
||||
for setting in settings:
|
||||
if 'ini' in settings[setting] and settings[setting]['ini']:
|
||||
for kv in settings[setting]['ini']:
|
||||
if not kv['section'] in data:
|
||||
data[kv['section']] = set()
|
||||
data[kv['section']].add(kv['key'])
|
||||
return data
|
||||
|
||||
|
||||
class ConfigCLI(CLI):
|
||||
""" Config command line class """
|
||||
|
||||
name = 'ansible-config'
|
||||
|
||||
def __init__(self, args, callback=None):
|
||||
|
||||
self.config_file = None
|
||||
self.config = None
|
||||
super(ConfigCLI, self).__init__(args, callback)
|
||||
|
||||
def init_parser(self):
|
||||
|
||||
super(ConfigCLI, self).init_parser(
|
||||
desc="View ansible configuration.",
|
||||
)
|
||||
|
||||
common = opt_help.ArgumentParser(add_help=False)
|
||||
opt_help.add_verbosity_options(common)
|
||||
common.add_argument('-c', '--config', dest='config_file',
|
||||
help="path to configuration file, defaults to first file found in precedence.")
|
||||
common.add_argument("-t", "--type", action="store", default='base', dest='type', choices=['all', 'base'] + list(C.CONFIGURABLE_PLUGINS),
|
||||
help="Filter down to a specific plugin type.")
|
||||
common.add_argument('args', help='Specific plugin to target, requires type of plugin to be set', nargs='*')
|
||||
|
||||
subparsers = self.parser.add_subparsers(dest='action')
|
||||
subparsers.required = True
|
||||
|
||||
list_parser = subparsers.add_parser('list', help='Print all config options', parents=[common])
|
||||
list_parser.set_defaults(func=self.execute_list)
|
||||
list_parser.add_argument('--format', '-f', dest='format', action='store', choices=['json', 'yaml'], default='yaml',
|
||||
help='Output format for list')
|
||||
|
||||
dump_parser = subparsers.add_parser('dump', help='Dump configuration', parents=[common])
|
||||
dump_parser.set_defaults(func=self.execute_dump)
|
||||
dump_parser.add_argument('--only-changed', '--changed-only', dest='only_changed', action='store_true',
|
||||
help="Only show configurations that have changed from the default")
|
||||
dump_parser.add_argument('--format', '-f', dest='format', action='store', choices=['json', 'yaml', 'display'], default='display',
|
||||
help='Output format for dump')
|
||||
|
||||
view_parser = subparsers.add_parser('view', help='View configuration file', parents=[common])
|
||||
view_parser.set_defaults(func=self.execute_view)
|
||||
|
||||
init_parser = subparsers.add_parser('init', help='Create initial configuration', parents=[common])
|
||||
init_parser.set_defaults(func=self.execute_init)
|
||||
init_parser.add_argument('--format', '-f', dest='format', action='store', choices=['ini', 'env', 'vars'], default='ini',
|
||||
help='Output format for init')
|
||||
init_parser.add_argument('--disabled', dest='commented', action='store_true', default=False,
|
||||
help='Prefixes all entries with a comment character to disable them')
|
||||
|
||||
validate_parser = subparsers.add_parser('validate',
|
||||
help='Validate the configuration file and environment variables. '
|
||||
'By default it only checks the base settings without accounting for plugins (see -t).',
|
||||
parents=[common])
|
||||
validate_parser.set_defaults(func=self.execute_validate)
|
||||
validate_parser.add_argument('--format', '-f', dest='format', action='store', choices=['ini', 'env'] , default='ini',
|
||||
help='Output format for init')
|
||||
|
||||
def post_process_args(self, options):
|
||||
options = super(ConfigCLI, self).post_process_args(options)
|
||||
display.verbosity = options.verbosity
|
||||
|
||||
return options
|
||||
|
||||
def run(self):
|
||||
|
||||
super(ConfigCLI, self).run()
|
||||
|
||||
# initialize each galaxy server's options from known listed servers
|
||||
self._galaxy_servers = [s for s in C.GALAXY_SERVER_LIST or [] if s] # clean list, reused later here
|
||||
C.config.load_galaxy_server_defs(self._galaxy_servers)
|
||||
|
||||
if context.CLIARGS['config_file']:
|
||||
self.config_file = unfrackpath(context.CLIARGS['config_file'], follow=False)
|
||||
b_config = to_bytes(self.config_file)
|
||||
if os.path.exists(b_config) and os.access(b_config, os.R_OK):
|
||||
self.config = ConfigManager(self.config_file)
|
||||
else:
|
||||
raise AnsibleOptionsError('The provided configuration file is missing or not accessible: %s' % to_native(self.config_file))
|
||||
else:
|
||||
self.config = C.config
|
||||
self.config_file = self.config._config_file
|
||||
|
||||
if self.config_file:
|
||||
try:
|
||||
if not os.path.exists(self.config_file):
|
||||
raise AnsibleOptionsError("%s does not exist or is not accessible" % (self.config_file))
|
||||
elif not os.path.isfile(self.config_file):
|
||||
raise AnsibleOptionsError("%s is not a valid file" % (self.config_file))
|
||||
|
||||
os.environ['ANSIBLE_CONFIG'] = to_native(self.config_file)
|
||||
except Exception:
|
||||
if context.CLIARGS['action'] in ['view']:
|
||||
raise
|
||||
elif context.CLIARGS['action'] in ['edit', 'update']:
|
||||
display.warning("File does not exist, used empty file: %s" % self.config_file)
|
||||
|
||||
elif context.CLIARGS['action'] == 'view':
|
||||
raise AnsibleError('Invalid or no config file was supplied')
|
||||
|
||||
# run the requested action
|
||||
context.CLIARGS['func']()
|
||||
|
||||
def execute_update(self):
|
||||
'''
|
||||
Updates a single setting in the specified ansible.cfg
|
||||
'''
|
||||
raise AnsibleError("Option not implemented yet")
|
||||
|
||||
# pylint: disable=unreachable
|
||||
if context.CLIARGS['setting'] is None:
|
||||
raise AnsibleOptionsError("update option requires a setting to update")
|
||||
|
||||
(entry, value) = context.CLIARGS['setting'].split('=')
|
||||
if '.' in entry:
|
||||
(section, option) = entry.split('.')
|
||||
else:
|
||||
section = 'defaults'
|
||||
option = entry
|
||||
subprocess.call([
|
||||
'ansible',
|
||||
'-m', 'ini_file',
|
||||
'localhost',
|
||||
'-c', 'local',
|
||||
'-a', '"dest=%s section=%s option=%s value=%s backup=yes"' % (self.config_file, section, option, value)
|
||||
])
|
||||
|
||||
def execute_view(self):
|
||||
'''
|
||||
Displays the current config file
|
||||
'''
|
||||
try:
|
||||
with open(self.config_file, 'rb') as f:
|
||||
self.pager(to_text(f.read(), errors='surrogate_or_strict'))
|
||||
except Exception as e:
|
||||
raise AnsibleError("Failed to open config file: %s" % to_native(e))
|
||||
|
||||
def execute_edit(self):
|
||||
'''
|
||||
Opens ansible.cfg in the default EDITOR
|
||||
'''
|
||||
raise AnsibleError("Option not implemented yet")
|
||||
|
||||
# pylint: disable=unreachable
|
||||
try:
|
||||
editor = shlex.split(C.config.get_config_value('EDITOR'))
|
||||
editor.append(self.config_file)
|
||||
subprocess.call(editor)
|
||||
except Exception as e:
|
||||
raise AnsibleError("Failed to open editor: %s" % to_native(e))
|
||||
|
||||
def _list_plugin_settings(self, ptype, plugins=None):
|
||||
entries = {}
|
||||
loader = getattr(plugin_loader, '%s_loader' % ptype)
|
||||
|
||||
# build list
|
||||
if plugins:
|
||||
plugin_cs = []
|
||||
for plugin in plugins:
|
||||
p = loader.get(plugin, class_only=True)
|
||||
if p is None:
|
||||
display.warning("Skipping %s as we could not find matching plugin" % plugin)
|
||||
else:
|
||||
plugin_cs.append(p)
|
||||
else:
|
||||
plugin_cs = loader.all(class_only=True)
|
||||
|
||||
# iterate over class instances
|
||||
for plugin in plugin_cs:
|
||||
finalname = name = plugin._load_name
|
||||
if name.startswith('_'):
|
||||
# alias or deprecated
|
||||
if os.path.islink(plugin._original_path):
|
||||
continue
|
||||
else:
|
||||
finalname = name.replace('_', '', 1) + ' (DEPRECATED)'
|
||||
|
||||
entries[finalname] = self.config.get_configuration_definitions(ptype, name)
|
||||
|
||||
return entries
|
||||
|
||||
def _list_entries_from_args(self):
|
||||
'''
|
||||
build a dict with the list requested configs
|
||||
'''
|
||||
|
||||
config_entries = {}
|
||||
if context.CLIARGS['type'] in ('base', 'all'):
|
||||
# this dumps main/common configs
|
||||
config_entries = self.config.get_configuration_definitions(ignore_private=True)
|
||||
|
||||
# for base and all, we include galaxy servers
|
||||
config_entries['GALAXY_SERVERS'] = {}
|
||||
for server in self._galaxy_servers:
|
||||
config_entries['GALAXY_SERVERS'][server] = self.config.get_configuration_definitions('galaxy_server', server)
|
||||
|
||||
if context.CLIARGS['type'] != 'base':
|
||||
config_entries['PLUGINS'] = {}
|
||||
|
||||
if context.CLIARGS['type'] == 'all':
|
||||
# now each plugin type
|
||||
for ptype in C.CONFIGURABLE_PLUGINS:
|
||||
config_entries['PLUGINS'][ptype.upper()] = self._list_plugin_settings(ptype)
|
||||
elif context.CLIARGS['type'] != 'base':
|
||||
# only for requested types
|
||||
config_entries['PLUGINS'][context.CLIARGS['type']] = self._list_plugin_settings(context.CLIARGS['type'], context.CLIARGS['args'])
|
||||
|
||||
return config_entries
|
||||
|
||||
def execute_list(self):
|
||||
'''
|
||||
list and output available configs
|
||||
'''
|
||||
|
||||
config_entries = self._list_entries_from_args()
|
||||
if context.CLIARGS['format'] == 'yaml':
|
||||
output = yaml_dump(config_entries)
|
||||
elif context.CLIARGS['format'] == 'json':
|
||||
output = json_dump(config_entries)
|
||||
|
||||
self.pager(to_text(output, errors='surrogate_or_strict'))
|
||||
|
||||
def _get_settings_vars(self, settings, subkey):
|
||||
|
||||
data = []
|
||||
if context.CLIARGS['commented']:
|
||||
prefix = '#'
|
||||
else:
|
||||
prefix = ''
|
||||
|
||||
for setting in settings:
|
||||
|
||||
if not settings[setting].get('description'):
|
||||
continue
|
||||
|
||||
default = self.config.template_default(settings[setting].get('default', ''), get_constants())
|
||||
if subkey == 'env':
|
||||
stype = settings[setting].get('type', '')
|
||||
if stype == 'boolean':
|
||||
if default:
|
||||
default = '1'
|
||||
else:
|
||||
default = '0'
|
||||
elif default:
|
||||
if stype == 'list':
|
||||
if not isinstance(default, string_types):
|
||||
# python lists are not valid env ones
|
||||
try:
|
||||
default = ', '.join(default)
|
||||
except Exception as e:
|
||||
# list of other stuff
|
||||
default = '%s' % to_native(default)
|
||||
if isinstance(default, string_types) and not is_quoted(default):
|
||||
default = shlex.quote(default)
|
||||
elif default is None:
|
||||
default = ''
|
||||
|
||||
if subkey in settings[setting] and settings[setting][subkey]:
|
||||
entry = settings[setting][subkey][-1]['name']
|
||||
if isinstance(settings[setting]['description'], string_types):
|
||||
desc = settings[setting]['description']
|
||||
else:
|
||||
desc = '\n#'.join(settings[setting]['description'])
|
||||
name = settings[setting].get('name', setting)
|
||||
data.append('# %s(%s): %s' % (name, settings[setting].get('type', 'string'), desc))
|
||||
|
||||
# TODO: might need quoting and value coercion depending on type
|
||||
if subkey == 'env':
|
||||
if entry.startswith('_ANSIBLE_'):
|
||||
continue
|
||||
data.append('%s%s=%s' % (prefix, entry, default))
|
||||
elif subkey == 'vars':
|
||||
if entry.startswith('_ansible_'):
|
||||
continue
|
||||
data.append(prefix + '%s: %s' % (entry, to_text(yaml_short(default), errors='surrogate_or_strict')))
|
||||
data.append('')
|
||||
|
||||
return data
|
||||
|
||||
def _get_settings_ini(self, settings, seen):
|
||||
|
||||
sections = {}
|
||||
for o in sorted(settings.keys()):
|
||||
|
||||
opt = settings[o]
|
||||
|
||||
if not isinstance(opt, Mapping):
|
||||
# recursed into one of the few settings that is a mapping, now hitting it's strings
|
||||
continue
|
||||
|
||||
if not opt.get('description'):
|
||||
# its a plugin
|
||||
new_sections = self._get_settings_ini(opt, seen)
|
||||
for s in new_sections:
|
||||
if s in sections:
|
||||
sections[s].extend(new_sections[s])
|
||||
else:
|
||||
sections[s] = new_sections[s]
|
||||
continue
|
||||
|
||||
if isinstance(opt['description'], string_types):
|
||||
desc = '# (%s) %s' % (opt.get('type', 'string'), opt['description'])
|
||||
else:
|
||||
desc = "# (%s) " % opt.get('type', 'string')
|
||||
desc += "\n# ".join(opt['description'])
|
||||
|
||||
if 'ini' in opt and opt['ini']:
|
||||
entry = opt['ini'][-1]
|
||||
if entry['section'] not in seen:
|
||||
seen[entry['section']] = []
|
||||
if entry['section'] not in sections:
|
||||
sections[entry['section']] = []
|
||||
|
||||
# avoid dupes
|
||||
if entry['key'] not in seen[entry['section']]:
|
||||
seen[entry['section']].append(entry['key'])
|
||||
|
||||
default = self.config.template_default(opt.get('default', ''), get_constants())
|
||||
if opt.get('type', '') == 'list' and not isinstance(default, string_types):
|
||||
# python lists are not valid ini ones
|
||||
default = ', '.join(default)
|
||||
elif default is None:
|
||||
default = ''
|
||||
|
||||
if context.CLIARGS.get('commented', False):
|
||||
entry['key'] = ';%s' % entry['key']
|
||||
|
||||
key = desc + '\n%s=%s' % (entry['key'], default)
|
||||
|
||||
sections[entry['section']].append(key)
|
||||
|
||||
return sections
|
||||
|
||||
def execute_init(self):
|
||||
"""Create initial configuration"""
|
||||
|
||||
seen = {}
|
||||
data = []
|
||||
config_entries = self._list_entries_from_args()
|
||||
plugin_types = config_entries.pop('PLUGINS', None)
|
||||
|
||||
if context.CLIARGS['format'] == 'ini':
|
||||
sections = self._get_settings_ini(config_entries, seen)
|
||||
|
||||
if plugin_types:
|
||||
for ptype in plugin_types:
|
||||
plugin_sections = self._get_settings_ini(plugin_types[ptype], seen)
|
||||
for s in plugin_sections:
|
||||
if s in sections:
|
||||
sections[s].extend(plugin_sections[s])
|
||||
else:
|
||||
sections[s] = plugin_sections[s]
|
||||
|
||||
if sections:
|
||||
for section in sections.keys():
|
||||
data.append('[%s]' % section)
|
||||
for key in sections[section]:
|
||||
data.append(key)
|
||||
data.append('')
|
||||
data.append('')
|
||||
|
||||
elif context.CLIARGS['format'] in ('env', 'vars'): # TODO: add yaml once that config option is added
|
||||
data = self._get_settings_vars(config_entries, context.CLIARGS['format'])
|
||||
if plugin_types:
|
||||
for ptype in plugin_types:
|
||||
for plugin in plugin_types[ptype].keys():
|
||||
data.extend(self._get_settings_vars(plugin_types[ptype][plugin], context.CLIARGS['format']))
|
||||
|
||||
self.pager(to_text('\n'.join(data), errors='surrogate_or_strict'))
|
||||
|
||||
def _render_settings(self, config):
|
||||
|
||||
entries = []
|
||||
for setting in sorted(config):
|
||||
changed = (config[setting].origin not in ('default', 'REQUIRED') and setting not in _IGNORE_CHANGED)
|
||||
|
||||
if context.CLIARGS['format'] == 'display':
|
||||
if isinstance(config[setting], Setting):
|
||||
# proceed normally
|
||||
value = config[setting].value
|
||||
if config[setting].origin == 'default' or setting in _IGNORE_CHANGED:
|
||||
color = 'green'
|
||||
value = self.config.template_default(value, get_constants())
|
||||
elif config[setting].origin == 'REQUIRED':
|
||||
# should include '_terms', '_input', etc
|
||||
color = 'red'
|
||||
else:
|
||||
color = 'yellow'
|
||||
msg = "%s(%s) = %s" % (setting, config[setting].origin, value)
|
||||
else:
|
||||
color = 'green'
|
||||
msg = "%s(%s) = %s" % (setting, 'default', config[setting].get('default'))
|
||||
|
||||
entry = stringc(msg, color)
|
||||
else:
|
||||
entry = {}
|
||||
for key in config[setting]._fields:
|
||||
if key == 'type':
|
||||
continue
|
||||
entry[key] = getattr(config[setting], key)
|
||||
|
||||
if not context.CLIARGS['only_changed'] or changed:
|
||||
entries.append(entry)
|
||||
|
||||
return entries
|
||||
|
||||
def _get_global_configs(self):
|
||||
|
||||
# Add base
|
||||
config = self.config.get_configuration_definitions(ignore_private=True)
|
||||
# convert to settings
|
||||
for setting in config.keys():
|
||||
v, o = C.config.get_config_value_and_origin(setting, cfile=self.config_file, variables=get_constants())
|
||||
config[setting] = Setting(setting, v, o, None)
|
||||
|
||||
return self._render_settings(config)
|
||||
|
||||
def _get_plugin_configs(self, ptype, plugins):
|
||||
|
||||
# prep loading
|
||||
loader = getattr(plugin_loader, '%s_loader' % ptype)
|
||||
|
||||
# accumulators
|
||||
output = []
|
||||
config_entries = {}
|
||||
|
||||
# build list
|
||||
if plugins:
|
||||
plugin_cs = []
|
||||
for plugin in plugins:
|
||||
p = loader.get(plugin, class_only=True)
|
||||
if p is None:
|
||||
display.warning("Skipping %s as we could not find matching plugin" % plugin)
|
||||
else:
|
||||
plugin_cs.append(loader.get(plugin, class_only=True))
|
||||
else:
|
||||
plugin_cs = loader.all(class_only=True)
|
||||
|
||||
for plugin in plugin_cs:
|
||||
# in case of deprecation they diverge
|
||||
finalname = name = plugin._load_name
|
||||
if name.startswith('_'):
|
||||
if os.path.islink(plugin._original_path):
|
||||
# skip alias
|
||||
continue
|
||||
# deprecated, but use 'nice name'
|
||||
finalname = name.replace('_', '', 1) + ' (DEPRECATED)'
|
||||
|
||||
# default entries per plugin
|
||||
config_entries[finalname] = self.config.get_configuration_definitions(ptype, name)
|
||||
|
||||
try:
|
||||
# populate config entries by loading plugin
|
||||
dump = loader.get(name, class_only=True)
|
||||
except Exception as e:
|
||||
display.warning('Skipping "%s" %s plugin, as we cannot load plugin to check config due to : %s' % (name, ptype, to_native(e)))
|
||||
continue
|
||||
|
||||
# actually get the values
|
||||
for setting in config_entries[finalname].keys():
|
||||
try:
|
||||
v, o = C.config.get_config_value_and_origin(setting, cfile=self.config_file, plugin_type=ptype, plugin_name=name, variables=get_constants())
|
||||
except AnsibleRequiredOptionError:
|
||||
v = None
|
||||
o = 'REQUIRED'
|
||||
|
||||
if v is None and o is None:
|
||||
# not all cases will be error
|
||||
o = 'REQUIRED'
|
||||
|
||||
config_entries[finalname][setting] = Setting(setting, v, o, None)
|
||||
|
||||
# pretty please!
|
||||
results = self._render_settings(config_entries[finalname])
|
||||
if results:
|
||||
if context.CLIARGS['format'] == 'display':
|
||||
# avoid header for empty lists (only changed!)
|
||||
output.append('\n%s:\n%s' % (finalname, '_' * len(finalname)))
|
||||
output.extend(results)
|
||||
else:
|
||||
output.append({finalname: results})
|
||||
|
||||
return output
|
||||
|
||||
def _get_galaxy_server_configs(self):
|
||||
|
||||
output = []
|
||||
# add galaxy servers
|
||||
for server in self._galaxy_servers:
|
||||
server_config = {}
|
||||
s_config = self.config.get_configuration_definitions('galaxy_server', server)
|
||||
for setting in s_config.keys():
|
||||
try:
|
||||
v, o = C.config.get_config_value_and_origin(setting, plugin_type='galaxy_server', plugin_name=server, cfile=self.config_file)
|
||||
except AnsibleError as e:
|
||||
if s_config[setting].get('required', False):
|
||||
v = None
|
||||
o = 'REQUIRED'
|
||||
else:
|
||||
raise e
|
||||
if v is None and o is None:
|
||||
# not all cases will be error
|
||||
o = 'REQUIRED'
|
||||
server_config[setting] = Setting(setting, v, o, None)
|
||||
if context.CLIARGS['format'] == 'display':
|
||||
if not context.CLIARGS['only_changed'] or server_config:
|
||||
equals = '=' * len(server)
|
||||
output.append(f'\n{server}\n{equals}')
|
||||
output.extend(self._render_settings(server_config))
|
||||
else:
|
||||
output.append({server: server_config})
|
||||
|
||||
return output
|
||||
|
||||
def execute_dump(self):
|
||||
'''
|
||||
Shows the current settings, merges ansible.cfg if specified
|
||||
'''
|
||||
output = []
|
||||
if context.CLIARGS['type'] in ('base', 'all'):
|
||||
# deal with base
|
||||
output = self._get_global_configs()
|
||||
|
||||
# add galaxy servers
|
||||
server_config_list = self._get_galaxy_server_configs()
|
||||
if context.CLIARGS['format'] == 'display':
|
||||
output.append('\nGALAXY_SERVERS:\n')
|
||||
output.extend(server_config_list)
|
||||
else:
|
||||
configs = {}
|
||||
for server_config in server_config_list:
|
||||
server = list(server_config.keys())[0]
|
||||
server_reduced_config = server_config.pop(server)
|
||||
configs[server] = server_reduced_config
|
||||
output.append({'GALAXY_SERVERS': configs})
|
||||
|
||||
if context.CLIARGS['type'] == 'all':
|
||||
# add all plugins
|
||||
for ptype in C.CONFIGURABLE_PLUGINS:
|
||||
plugin_list = self._get_plugin_configs(ptype, context.CLIARGS['args'])
|
||||
if context.CLIARGS['format'] == 'display':
|
||||
if not context.CLIARGS['only_changed'] or plugin_list:
|
||||
output.append('\n%s:\n%s' % (ptype.upper(), '=' * len(ptype)))
|
||||
output.extend(plugin_list)
|
||||
else:
|
||||
if ptype in ('modules', 'doc_fragments'):
|
||||
pname = ptype.upper()
|
||||
else:
|
||||
pname = '%s_PLUGINS' % ptype.upper()
|
||||
output.append({pname: plugin_list})
|
||||
|
||||
elif context.CLIARGS['type'] != 'base':
|
||||
# deal with specific plugin
|
||||
output = self._get_plugin_configs(context.CLIARGS['type'], context.CLIARGS['args'])
|
||||
|
||||
if context.CLIARGS['format'] == 'display':
|
||||
text = '\n'.join(output)
|
||||
if context.CLIARGS['format'] == 'yaml':
|
||||
text = yaml_dump(output)
|
||||
elif context.CLIARGS['format'] == 'json':
|
||||
text = json_dump(output)
|
||||
|
||||
self.pager(to_text(text, errors='surrogate_or_strict'))
|
||||
|
||||
def execute_validate(self):
|
||||
|
||||
found = False
|
||||
config_entries = self._list_entries_from_args()
|
||||
plugin_types = config_entries.pop('PLUGINS', None)
|
||||
galaxy_servers = config_entries.pop('GALAXY_SERVERS', None)
|
||||
|
||||
if context.CLIARGS['format'] == 'ini':
|
||||
if C.CONFIG_FILE is not None:
|
||||
# validate ini config since it is found
|
||||
|
||||
sections = _get_ini_entries(config_entries)
|
||||
# Also from plugins
|
||||
if plugin_types:
|
||||
for ptype in plugin_types:
|
||||
for plugin in plugin_types[ptype].keys():
|
||||
plugin_sections = _get_ini_entries(plugin_types[ptype][plugin])
|
||||
for s in plugin_sections:
|
||||
if s in sections:
|
||||
sections[s].update(plugin_sections[s])
|
||||
else:
|
||||
sections[s] = plugin_sections[s]
|
||||
if galaxy_servers:
|
||||
for server in galaxy_servers:
|
||||
server_sections = _get_ini_entries(galaxy_servers[server])
|
||||
for s in server_sections:
|
||||
if s in sections:
|
||||
sections[s].update(server_sections[s])
|
||||
else:
|
||||
sections[s] = server_sections[s]
|
||||
if sections:
|
||||
p = C.config._parsers[C.CONFIG_FILE]
|
||||
for s in p.sections():
|
||||
# check for valid sections
|
||||
if s not in sections:
|
||||
display.error(f"Found unknown section '{s}' in '{C.CONFIG_FILE}.")
|
||||
found = True
|
||||
continue
|
||||
|
||||
# check keys in valid sections
|
||||
for k in p.options(s):
|
||||
if k not in sections[s]:
|
||||
display.error(f"Found unknown key '{k}' in section '{s}' in '{C.CONFIG_FILE}.")
|
||||
found = True
|
||||
|
||||
elif context.CLIARGS['format'] == 'env':
|
||||
# validate any 'ANSIBLE_' env vars found
|
||||
evars = [varname for varname in os.environ.keys() if _ansible_env_vars(varname)]
|
||||
if evars:
|
||||
data = _get_evar_list(config_entries)
|
||||
if plugin_types:
|
||||
for ptype in plugin_types:
|
||||
for plugin in plugin_types[ptype].keys():
|
||||
data.extend(_get_evar_list(plugin_types[ptype][plugin]))
|
||||
|
||||
for evar in evars:
|
||||
if evar not in data:
|
||||
display.error(f"Found unknown environment variable '{evar}'.")
|
||||
found = True
|
||||
|
||||
# we found discrepancies!
|
||||
if found:
|
||||
sys.exit(1)
|
||||
|
||||
# allsgood
|
||||
display.display("All configurations seem valid!")
|
||||
|
||||
|
||||
def main(args=None):
|
||||
ConfigCLI.cli_executor(args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
607
ansible/lib/python3.11/site-packages/ansible/cli/console.py
Normal file
607
ansible/lib/python3.11/site-packages/ansible/cli/console.py
Normal file
@ -0,0 +1,607 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright: (c) 2014, Nandor Sivok <dominis@haxor.hu>
|
||||
# Copyright: (c) 2016, Redhat Inc
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# PYTHON_ARGCOMPLETE_OK
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
# ansible.cli needs to be imported first, to ensure the source bin/* scripts run that code first
|
||||
from ansible.cli import CLI
|
||||
|
||||
import atexit
|
||||
import cmd
|
||||
import getpass
|
||||
import readline
|
||||
import os
|
||||
import sys
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible import context
|
||||
from ansible.cli.arguments import option_helpers as opt_help
|
||||
from ansible.executor.task_queue_manager import TaskQueueManager
|
||||
from ansible.module_utils.common.text.converters import to_native, to_text
|
||||
from ansible.module_utils.parsing.convert_bool import boolean
|
||||
from ansible.parsing.splitter import parse_kv
|
||||
from ansible.playbook.play import Play
|
||||
from ansible.plugins.list import list_plugins
|
||||
from ansible.plugins.loader import module_loader, fragment_loader
|
||||
from ansible.utils import plugin_docs
|
||||
from ansible.utils.color import stringc
|
||||
from ansible.utils.display import Display
|
||||
|
||||
display = Display()
|
||||
|
||||
|
||||
class ConsoleCLI(CLI, cmd.Cmd):
|
||||
'''
|
||||
A REPL that allows for running ad-hoc tasks against a chosen inventory
|
||||
from a nice shell with built-in tab completion (based on dominis'
|
||||
``ansible-shell``).
|
||||
|
||||
It supports several commands, and you can modify its configuration at
|
||||
runtime:
|
||||
|
||||
- ``cd [pattern]``: change host/group
|
||||
(you can use host patterns eg.: ``app*.dc*:!app01*``)
|
||||
- ``list``: list available hosts in the current path
|
||||
- ``list groups``: list groups included in the current path
|
||||
- ``become``: toggle the become flag
|
||||
- ``!``: forces shell module instead of the ansible module
|
||||
(``!yum update -y``)
|
||||
- ``verbosity [num]``: set the verbosity level
|
||||
- ``forks [num]``: set the number of forks
|
||||
- ``become_user [user]``: set the become_user
|
||||
- ``remote_user [user]``: set the remote_user
|
||||
- ``become_method [method]``: set the privilege escalation method
|
||||
- ``check [bool]``: toggle check mode
|
||||
- ``diff [bool]``: toggle diff mode
|
||||
- ``timeout [integer]``: set the timeout of tasks in seconds
|
||||
(0 to disable)
|
||||
- ``help [command/module]``: display documentation for
|
||||
the command or module
|
||||
- ``exit``: exit ``ansible-console``
|
||||
'''
|
||||
|
||||
name = 'ansible-console'
|
||||
modules = [] # type: list[str] | None
|
||||
ARGUMENTS = {'host-pattern': 'A name of a group in the inventory, a shell-like glob '
|
||||
'selecting hosts in inventory or any combination of the two separated by commas.'}
|
||||
|
||||
# use specific to console, but fallback to highlight for backwards compatibility
|
||||
NORMAL_PROMPT = C.COLOR_CONSOLE_PROMPT or C.COLOR_HIGHLIGHT
|
||||
|
||||
def __init__(self, args):
|
||||
|
||||
super(ConsoleCLI, self).__init__(args)
|
||||
|
||||
self.intro = 'Welcome to the ansible console. Type help or ? to list commands.\n'
|
||||
|
||||
self.groups = []
|
||||
self.hosts = []
|
||||
self.pattern = None
|
||||
self.variable_manager = None
|
||||
self.loader = None
|
||||
self.passwords = dict()
|
||||
|
||||
self.cwd = '*'
|
||||
|
||||
# Defaults for these are set from the CLI in run()
|
||||
self.remote_user = None
|
||||
self.become = None
|
||||
self.become_user = None
|
||||
self.become_method = None
|
||||
self.check_mode = None
|
||||
self.diff = None
|
||||
self.forks = None
|
||||
self.task_timeout = None
|
||||
self.collections = None
|
||||
|
||||
cmd.Cmd.__init__(self)
|
||||
|
||||
def init_parser(self):
|
||||
super(ConsoleCLI, self).init_parser(
|
||||
desc="REPL console for executing Ansible tasks.",
|
||||
epilog="This is not a live session/connection: each task is executed in the background and returns its results."
|
||||
)
|
||||
opt_help.add_runas_options(self.parser)
|
||||
opt_help.add_inventory_options(self.parser)
|
||||
opt_help.add_connect_options(self.parser)
|
||||
opt_help.add_check_options(self.parser)
|
||||
opt_help.add_vault_options(self.parser)
|
||||
opt_help.add_fork_options(self.parser)
|
||||
opt_help.add_module_options(self.parser)
|
||||
opt_help.add_basedir_options(self.parser)
|
||||
opt_help.add_runtask_options(self.parser)
|
||||
opt_help.add_tasknoplay_options(self.parser)
|
||||
|
||||
# options unique to shell
|
||||
self.parser.add_argument('pattern', help='host pattern', metavar='pattern', default='all', nargs='?')
|
||||
self.parser.add_argument('--step', dest='step', action='store_true',
|
||||
help="one-step-at-a-time: confirm each task before running")
|
||||
|
||||
def post_process_args(self, options):
|
||||
options = super(ConsoleCLI, self).post_process_args(options)
|
||||
display.verbosity = options.verbosity
|
||||
self.validate_conflicts(options, runas_opts=True, fork_opts=True)
|
||||
return options
|
||||
|
||||
def get_names(self):
|
||||
return dir(self)
|
||||
|
||||
def cmdloop(self):
|
||||
try:
|
||||
cmd.Cmd.cmdloop(self)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
self.cmdloop()
|
||||
|
||||
except EOFError:
|
||||
self.display("[Ansible-console was exited]")
|
||||
self.do_exit(self)
|
||||
|
||||
def set_prompt(self):
|
||||
login_user = self.remote_user or getpass.getuser()
|
||||
self.selected = self.inventory.list_hosts(self.cwd)
|
||||
prompt = "%s@%s (%d)[f:%s]" % (login_user, self.cwd, len(self.selected), self.forks)
|
||||
if self.become and self.become_user in [None, 'root']:
|
||||
prompt += "# "
|
||||
color = C.COLOR_ERROR
|
||||
else:
|
||||
prompt += "$ "
|
||||
color = self.NORMAL_PROMPT
|
||||
self.prompt = stringc(prompt, color, wrap_nonvisible_chars=True)
|
||||
|
||||
def list_modules(self):
|
||||
return list_plugins('module', self.collections)
|
||||
|
||||
def default(self, line, forceshell=False):
|
||||
""" actually runs modules """
|
||||
if line.startswith("#"):
|
||||
return False
|
||||
|
||||
if not self.cwd:
|
||||
display.error("No host found")
|
||||
return False
|
||||
|
||||
# defaults
|
||||
module = 'shell'
|
||||
module_args = line
|
||||
|
||||
if forceshell is not True:
|
||||
possible_module, *possible_args = line.split()
|
||||
if module_loader.find_plugin(possible_module):
|
||||
# we found module!
|
||||
module = possible_module
|
||||
if possible_args:
|
||||
module_args = ' '.join(possible_args)
|
||||
else:
|
||||
module_args = ''
|
||||
|
||||
if self.callback:
|
||||
cb = self.callback
|
||||
elif C.DEFAULT_LOAD_CALLBACK_PLUGINS and C.DEFAULT_STDOUT_CALLBACK != 'default':
|
||||
cb = C.DEFAULT_STDOUT_CALLBACK
|
||||
else:
|
||||
cb = 'minimal'
|
||||
|
||||
result = None
|
||||
try:
|
||||
check_raw = module in C._ACTION_ALLOWS_RAW_ARGS
|
||||
task = dict(action=dict(module=module, args=parse_kv(module_args, check_raw=check_raw)), timeout=self.task_timeout)
|
||||
play_ds = dict(
|
||||
name="Ansible Shell",
|
||||
hosts=self.cwd,
|
||||
gather_facts='no',
|
||||
tasks=[task],
|
||||
remote_user=self.remote_user,
|
||||
become=self.become,
|
||||
become_user=self.become_user,
|
||||
become_method=self.become_method,
|
||||
check_mode=self.check_mode,
|
||||
diff=self.diff,
|
||||
collections=self.collections,
|
||||
)
|
||||
play = Play().load(play_ds, variable_manager=self.variable_manager, loader=self.loader)
|
||||
except Exception as e:
|
||||
display.error(u"Unable to build command: %s" % to_text(e))
|
||||
return False
|
||||
|
||||
try:
|
||||
# now create a task queue manager to execute the play
|
||||
self._tqm = None
|
||||
try:
|
||||
self._tqm = TaskQueueManager(
|
||||
inventory=self.inventory,
|
||||
variable_manager=self.variable_manager,
|
||||
loader=self.loader,
|
||||
passwords=self.passwords,
|
||||
stdout_callback=cb,
|
||||
run_additional_callbacks=C.DEFAULT_LOAD_CALLBACK_PLUGINS,
|
||||
run_tree=False,
|
||||
forks=self.forks,
|
||||
)
|
||||
|
||||
result = self._tqm.run(play)
|
||||
display.debug(result)
|
||||
finally:
|
||||
if self._tqm:
|
||||
self._tqm.cleanup()
|
||||
if self.loader:
|
||||
self.loader.cleanup_all_tmp_files()
|
||||
|
||||
if result is None:
|
||||
display.error("No hosts found")
|
||||
return False
|
||||
except KeyboardInterrupt:
|
||||
display.error('User interrupted execution')
|
||||
return False
|
||||
except Exception as e:
|
||||
if self.verbosity >= 3:
|
||||
import traceback
|
||||
display.v(traceback.format_exc())
|
||||
display.error(to_text(e))
|
||||
return False
|
||||
|
||||
def emptyline(self):
|
||||
return
|
||||
|
||||
def do_shell(self, arg):
|
||||
"""
|
||||
You can run shell commands through the shell module.
|
||||
|
||||
eg.:
|
||||
shell ps uax | grep java | wc -l
|
||||
shell killall python
|
||||
shell halt -n
|
||||
|
||||
You can use the ! to force the shell module. eg.:
|
||||
!ps aux | grep java | wc -l
|
||||
"""
|
||||
self.default(arg, True)
|
||||
|
||||
def help_shell(self):
|
||||
display.display("You can run shell commands through the shell module.")
|
||||
|
||||
def do_forks(self, arg):
|
||||
"""Set the number of forks"""
|
||||
if arg:
|
||||
try:
|
||||
forks = int(arg)
|
||||
except TypeError:
|
||||
display.error('Invalid argument for "forks"')
|
||||
self.usage_forks()
|
||||
|
||||
if forks > 0:
|
||||
self.forks = forks
|
||||
self.set_prompt()
|
||||
|
||||
else:
|
||||
display.display('forks must be greater than or equal to 1')
|
||||
else:
|
||||
self.usage_forks()
|
||||
|
||||
def help_forks(self):
|
||||
display.display("Set the number of forks to use per task")
|
||||
self.usage_forks()
|
||||
|
||||
def usage_forks(self):
|
||||
display.display('Usage: forks <number>')
|
||||
|
||||
do_serial = do_forks
|
||||
help_serial = help_forks
|
||||
|
||||
def do_collections(self, arg):
|
||||
"""Set list of collections for 'short name' usage"""
|
||||
if arg in ('', 'none'):
|
||||
self.collections = None
|
||||
elif not arg:
|
||||
self.usage_collections()
|
||||
else:
|
||||
collections = arg.split(',')
|
||||
for collection in collections:
|
||||
if self.collections is None:
|
||||
self.collections = []
|
||||
self.collections.append(collection.strip())
|
||||
|
||||
if self.collections:
|
||||
display.v('Collections name search is set to: %s' % ', '.join(self.collections))
|
||||
else:
|
||||
display.v('Collections name search is using defaults')
|
||||
|
||||
def help_collections(self):
|
||||
display.display("Set the collection name search path when using short names for plugins")
|
||||
self.usage_collections()
|
||||
|
||||
def usage_collections(self):
|
||||
display.display('Usage: collections <collection1>[, <collection2> ...]\n Use empty quotes or "none" to reset to default.\n')
|
||||
|
||||
def do_verbosity(self, arg):
|
||||
"""Set verbosity level"""
|
||||
if not arg:
|
||||
display.display('Usage: verbosity <number>')
|
||||
else:
|
||||
try:
|
||||
display.verbosity = int(arg)
|
||||
display.v('verbosity level set to %s' % arg)
|
||||
except (TypeError, ValueError) as e:
|
||||
display.error('The verbosity must be a valid integer: %s' % to_text(e))
|
||||
|
||||
def help_verbosity(self):
|
||||
display.display("Set the verbosity level, equivalent to -v for 1 and -vvvv for 4.")
|
||||
|
||||
def do_cd(self, arg):
|
||||
"""
|
||||
Change active host/group. You can use hosts patterns as well eg.:
|
||||
cd webservers
|
||||
cd webservers:dbservers
|
||||
cd webservers:!phoenix
|
||||
cd webservers:&staging
|
||||
cd webservers:dbservers:&staging:!phoenix
|
||||
"""
|
||||
if not arg:
|
||||
self.cwd = '*'
|
||||
elif arg in '/*':
|
||||
self.cwd = 'all'
|
||||
elif self.inventory.get_hosts(arg):
|
||||
self.cwd = arg
|
||||
else:
|
||||
display.display("no host matched")
|
||||
|
||||
self.set_prompt()
|
||||
|
||||
def help_cd(self):
|
||||
display.display("Change active host/group. ")
|
||||
self.usage_cd()
|
||||
|
||||
def usage_cd(self):
|
||||
display.display("Usage: cd <group>|<host>|<host pattern>")
|
||||
|
||||
def do_list(self, arg):
|
||||
"""List the hosts in the current group"""
|
||||
if not arg:
|
||||
for host in self.selected:
|
||||
display.display(host.name)
|
||||
elif arg == 'groups':
|
||||
for group in self.groups:
|
||||
display.display(group)
|
||||
else:
|
||||
display.error('Invalid option passed to "list"')
|
||||
self.help_list()
|
||||
|
||||
def help_list(self):
|
||||
display.display("List the hosts in the current group or a list of groups if you add 'groups'.")
|
||||
|
||||
def do_become(self, arg):
|
||||
"""Toggle whether plays run with become"""
|
||||
if arg:
|
||||
self.become = boolean(arg, strict=False)
|
||||
display.v("become changed to %s" % self.become)
|
||||
self.set_prompt()
|
||||
else:
|
||||
display.display("Please specify become value, e.g. `become yes`")
|
||||
|
||||
def help_become(self):
|
||||
display.display("Toggle whether the tasks are run with become")
|
||||
|
||||
def do_remote_user(self, arg):
|
||||
"""Given a username, set the remote user plays are run by"""
|
||||
if arg:
|
||||
self.remote_user = arg
|
||||
self.set_prompt()
|
||||
else:
|
||||
display.display("Please specify a remote user, e.g. `remote_user root`")
|
||||
|
||||
def help_remote_user(self):
|
||||
display.display("Set the user for use as login to the remote target")
|
||||
|
||||
def do_become_user(self, arg):
|
||||
"""Given a username, set the user that plays are run by when using become"""
|
||||
if arg:
|
||||
self.become_user = arg
|
||||
else:
|
||||
display.display("Please specify a user, e.g. `become_user jenkins`")
|
||||
display.v("Current user is %s" % self.become_user)
|
||||
self.set_prompt()
|
||||
|
||||
def help_become_user(self):
|
||||
display.display("Set the user for use with privilege escalation (which remote user attempts to 'become' when become is enabled)")
|
||||
|
||||
def do_become_method(self, arg):
|
||||
"""Given a become_method, set the privilege escalation method when using become"""
|
||||
if arg:
|
||||
self.become_method = arg
|
||||
display.v("become_method changed to %s" % self.become_method)
|
||||
else:
|
||||
display.display("Please specify a become_method, e.g. `become_method su`")
|
||||
display.v("Current become_method is %s" % self.become_method)
|
||||
|
||||
def help_become_method(self):
|
||||
display.display("Set the privilege escalation plugin to use when become is enabled")
|
||||
|
||||
def do_check(self, arg):
|
||||
"""Toggle whether plays run with check mode"""
|
||||
if arg:
|
||||
self.check_mode = boolean(arg, strict=False)
|
||||
display.display("check mode changed to %s" % self.check_mode)
|
||||
else:
|
||||
display.display("Please specify check mode value, e.g. `check yes`")
|
||||
display.v("check mode is currently %s." % self.check_mode)
|
||||
|
||||
def help_check(self):
|
||||
display.display("Toggle check_mode for the tasks")
|
||||
|
||||
def do_diff(self, arg):
|
||||
"""Toggle whether plays run with diff"""
|
||||
if arg:
|
||||
self.diff = boolean(arg, strict=False)
|
||||
display.display("diff mode changed to %s" % self.diff)
|
||||
else:
|
||||
display.display("Please specify a diff value , e.g. `diff yes`")
|
||||
display.v("diff mode is currently %s" % self.diff)
|
||||
|
||||
def help_diff(self):
|
||||
display.display("Toggle diff output for the tasks")
|
||||
|
||||
def do_timeout(self, arg):
|
||||
"""Set the timeout"""
|
||||
if arg:
|
||||
try:
|
||||
timeout = int(arg)
|
||||
if timeout < 0:
|
||||
display.error('The timeout must be greater than or equal to 1, use 0 to disable')
|
||||
else:
|
||||
self.task_timeout = timeout
|
||||
except (TypeError, ValueError) as e:
|
||||
display.error('The timeout must be a valid positive integer, or 0 to disable: %s' % to_text(e))
|
||||
else:
|
||||
self.usage_timeout()
|
||||
|
||||
def help_timeout(self):
|
||||
display.display("Set task timeout in seconds")
|
||||
self.usage_timeout()
|
||||
|
||||
def usage_timeout(self):
|
||||
display.display('Usage: timeout <seconds>')
|
||||
|
||||
def do_exit(self, args):
|
||||
"""Exits from the console"""
|
||||
sys.stdout.write('\nAnsible-console was exited.\n')
|
||||
return -1
|
||||
|
||||
def help_exit(self):
|
||||
display.display("LEAVE!")
|
||||
|
||||
do_EOF = do_exit
|
||||
help_EOF = help_exit
|
||||
|
||||
def helpdefault(self, module_name):
|
||||
if module_name:
|
||||
in_path = module_loader.find_plugin(module_name)
|
||||
if in_path:
|
||||
oc, a, _dummy1, _dummy2 = plugin_docs.get_docstring(in_path, fragment_loader)
|
||||
if oc:
|
||||
display.display(oc['short_description'])
|
||||
display.display('Parameters:')
|
||||
for opt in oc['options'].keys():
|
||||
display.display(' ' + stringc(opt, self.NORMAL_PROMPT) + ' ' + oc['options'][opt]['description'][0])
|
||||
else:
|
||||
display.error('No documentation found for %s.' % module_name)
|
||||
else:
|
||||
display.error('%s is not a valid command, use ? to list all valid commands.' % module_name)
|
||||
|
||||
def help_help(self):
|
||||
display.warning("Don't be redundant!")
|
||||
|
||||
def complete_cd(self, text, line, begidx, endidx):
|
||||
mline = line.partition(' ')[2]
|
||||
offs = len(mline) - len(text)
|
||||
|
||||
if self.cwd in ('all', '*', '\\'):
|
||||
completions = self.hosts + self.groups
|
||||
else:
|
||||
completions = [x.name for x in self.inventory.list_hosts(self.cwd)]
|
||||
|
||||
return [to_native(s)[offs:] for s in completions if to_native(s).startswith(to_native(mline))]
|
||||
|
||||
def completedefault(self, text, line, begidx, endidx):
|
||||
if line.split()[0] in self.list_modules():
|
||||
mline = line.split(' ')[-1]
|
||||
offs = len(mline) - len(text)
|
||||
completions = self.module_args(line.split()[0])
|
||||
|
||||
return [s[offs:] + '=' for s in completions if s.startswith(mline)]
|
||||
|
||||
def module_args(self, module_name):
|
||||
in_path = module_loader.find_plugin(module_name)
|
||||
oc, a, _dummy1, _dummy2 = plugin_docs.get_docstring(in_path, fragment_loader, is_module=True)
|
||||
return list(oc['options'].keys())
|
||||
|
||||
def run(self):
|
||||
|
||||
super(ConsoleCLI, self).run()
|
||||
|
||||
sshpass = None
|
||||
becomepass = None
|
||||
|
||||
# hosts
|
||||
self.pattern = context.CLIARGS['pattern']
|
||||
self.cwd = self.pattern
|
||||
|
||||
# Defaults from the command line
|
||||
self.remote_user = context.CLIARGS['remote_user']
|
||||
self.become = context.CLIARGS['become']
|
||||
self.become_user = context.CLIARGS['become_user']
|
||||
self.become_method = context.CLIARGS['become_method']
|
||||
self.check_mode = context.CLIARGS['check']
|
||||
self.diff = context.CLIARGS['diff']
|
||||
self.forks = context.CLIARGS['forks']
|
||||
self.task_timeout = context.CLIARGS['task_timeout']
|
||||
|
||||
# set module path if needed
|
||||
if context.CLIARGS['module_path']:
|
||||
for path in context.CLIARGS['module_path']:
|
||||
if path:
|
||||
module_loader.add_directory(path)
|
||||
|
||||
# dynamically add 'canonical' modules as commands, aliases could be used and dynamically loaded
|
||||
self.modules = self.list_modules()
|
||||
for module in self.modules:
|
||||
setattr(self, 'do_' + module, lambda arg, module=module: self.default(module + ' ' + arg))
|
||||
setattr(self, 'help_' + module, lambda module=module: self.helpdefault(module))
|
||||
|
||||
(sshpass, becomepass) = self.ask_passwords()
|
||||
self.passwords = {'conn_pass': sshpass, 'become_pass': becomepass}
|
||||
|
||||
self.loader, self.inventory, self.variable_manager = self._play_prereqs()
|
||||
|
||||
hosts = self.get_host_list(self.inventory, context.CLIARGS['subset'], self.pattern)
|
||||
|
||||
self.groups = self.inventory.list_groups()
|
||||
self.hosts = [x.name for x in hosts]
|
||||
|
||||
# This hack is to work around readline issues on a mac:
|
||||
# http://stackoverflow.com/a/7116997/541202
|
||||
if 'libedit' in readline.__doc__:
|
||||
readline.parse_and_bind("bind ^I rl_complete")
|
||||
else:
|
||||
readline.parse_and_bind("tab: complete")
|
||||
|
||||
histfile = os.path.join(os.path.expanduser("~"), ".ansible-console_history")
|
||||
try:
|
||||
readline.read_history_file(histfile)
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
atexit.register(readline.write_history_file, histfile)
|
||||
self.set_prompt()
|
||||
self.cmdloop()
|
||||
|
||||
def __getattr__(self, name):
|
||||
''' handle not found to populate dynamically a module function if module matching name exists '''
|
||||
attr = None
|
||||
|
||||
if name.startswith('do_'):
|
||||
module = name.replace('do_', '')
|
||||
if module_loader.find_plugin(module):
|
||||
setattr(self, name, lambda arg, module=module: self.default(module + ' ' + arg))
|
||||
attr = object.__getattr__(self, name)
|
||||
elif name.startswith('help_'):
|
||||
module = name.replace('help_', '')
|
||||
if module_loader.find_plugin(module):
|
||||
setattr(self, name, lambda module=module: self.helpdefault(module))
|
||||
attr = object.__getattr__(self, name)
|
||||
|
||||
if attr is None:
|
||||
raise AttributeError(f"{self.__class__} does not have a {name} attribute")
|
||||
|
||||
return attr
|
||||
|
||||
|
||||
def main(args=None):
|
||||
ConsoleCLI.cli_executor(args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
1610
ansible/lib/python3.11/site-packages/ansible/cli/doc.py
Normal file
1610
ansible/lib/python3.11/site-packages/ansible/cli/doc.py
Normal file
File diff suppressed because it is too large
Load Diff
1878
ansible/lib/python3.11/site-packages/ansible/cli/galaxy.py
Normal file
1878
ansible/lib/python3.11/site-packages/ansible/cli/galaxy.py
Normal file
File diff suppressed because it is too large
Load Diff
417
ansible/lib/python3.11/site-packages/ansible/cli/inventory.py
Normal file
417
ansible/lib/python3.11/site-packages/ansible/cli/inventory.py
Normal file
@ -0,0 +1,417 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright: (c) 2017, Brian Coca <bcoca@ansible.com>
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# PYTHON_ARGCOMPLETE_OK
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
# ansible.cli needs to be imported first, to ensure the source bin/* scripts run that code first
|
||||
from ansible.cli import CLI
|
||||
|
||||
import sys
|
||||
|
||||
import argparse
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible import context
|
||||
from ansible.cli.arguments import option_helpers as opt_help
|
||||
from ansible.errors import AnsibleError, AnsibleOptionsError
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
|
||||
from ansible.utils.vars import combine_vars
|
||||
from ansible.utils.display import Display
|
||||
from ansible.vars.plugins import get_vars_from_inventory_sources, get_vars_from_path
|
||||
|
||||
display = Display()
|
||||
|
||||
|
||||
class InventoryCLI(CLI):
|
||||
''' used to display or dump the configured inventory as Ansible sees it '''
|
||||
|
||||
name = 'ansible-inventory'
|
||||
|
||||
ARGUMENTS = {'group': 'The name of a group in the inventory, relevant when using --graph', }
|
||||
|
||||
def __init__(self, args):
|
||||
|
||||
super(InventoryCLI, self).__init__(args)
|
||||
self.vm = None
|
||||
self.loader = None
|
||||
self.inventory = None
|
||||
|
||||
def init_parser(self):
|
||||
super(InventoryCLI, self).init_parser(
|
||||
usage='usage: %prog [options] [group]',
|
||||
desc='Show Ansible inventory information, by default it uses the inventory script JSON format')
|
||||
|
||||
opt_help.add_inventory_options(self.parser)
|
||||
opt_help.add_vault_options(self.parser)
|
||||
opt_help.add_basedir_options(self.parser)
|
||||
opt_help.add_runtask_options(self.parser)
|
||||
|
||||
# remove unused default options
|
||||
self.parser.add_argument('--list-hosts', help=argparse.SUPPRESS, action=opt_help.UnrecognizedArgument)
|
||||
|
||||
self.parser.add_argument('args', metavar='group', nargs='?', help='The name of a group in the inventory, relevant when using --graph')
|
||||
|
||||
# Actions
|
||||
action_group = self.parser.add_argument_group("Actions", "One of following must be used on invocation, ONLY ONE!")
|
||||
action_group.add_argument("--list", action="store_true", default=False, dest='list', help='Output all hosts info, works as inventory script')
|
||||
action_group.add_argument("--host", action="store", default=None, dest='host',
|
||||
help='Output specific host info, works as inventory script. It will ignore limit')
|
||||
action_group.add_argument("--graph", action="store_true", default=False, dest='graph',
|
||||
help='create inventory graph, if supplying pattern it must be a valid group name. It will ignore limit')
|
||||
self.parser.add_argument_group(action_group)
|
||||
|
||||
# graph
|
||||
self.parser.add_argument("-y", "--yaml", action="store_true", default=False, dest='yaml',
|
||||
help='Use YAML format instead of default JSON, ignored for --graph')
|
||||
self.parser.add_argument('--toml', action='store_true', default=False, dest='toml',
|
||||
help='Use TOML format instead of default JSON, ignored for --graph')
|
||||
self.parser.add_argument("--vars", action="store_true", default=False, dest='show_vars',
|
||||
help='Add vars to graph display, ignored unless used with --graph')
|
||||
|
||||
# list
|
||||
self.parser.add_argument("--export", action="store_true", default=C.INVENTORY_EXPORT, dest='export',
|
||||
help="When doing --list, represent in a way that is optimized for export,"
|
||||
"not as an accurate representation of how Ansible has processed it")
|
||||
self.parser.add_argument('--output', default=None, dest='output_file',
|
||||
help="When doing --list, send the inventory to a file instead of to the screen")
|
||||
# self.parser.add_argument("--ignore-vars-plugins", action="store_true", default=False, dest='ignore_vars_plugins',
|
||||
# help="When doing --list, skip vars data from vars plugins, by default, this would include group_vars/ and host_vars/")
|
||||
|
||||
def post_process_args(self, options):
|
||||
options = super(InventoryCLI, self).post_process_args(options)
|
||||
|
||||
display.verbosity = options.verbosity
|
||||
self.validate_conflicts(options)
|
||||
|
||||
# there can be only one! and, at least, one!
|
||||
used = 0
|
||||
for opt in (options.list, options.host, options.graph):
|
||||
if opt:
|
||||
used += 1
|
||||
if used == 0:
|
||||
raise AnsibleOptionsError("No action selected, at least one of --host, --graph or --list needs to be specified.")
|
||||
elif used > 1:
|
||||
raise AnsibleOptionsError("Conflicting options used, only one of --host, --graph or --list can be used at the same time.")
|
||||
|
||||
# set host pattern to default if not supplied
|
||||
if options.args:
|
||||
options.pattern = options.args
|
||||
else:
|
||||
options.pattern = 'all'
|
||||
|
||||
return options
|
||||
|
||||
def run(self):
|
||||
|
||||
super(InventoryCLI, self).run()
|
||||
|
||||
# Initialize needed objects
|
||||
self.loader, self.inventory, self.vm = self._play_prereqs()
|
||||
|
||||
results = None
|
||||
if context.CLIARGS['host']:
|
||||
hosts = self.inventory.get_hosts(context.CLIARGS['host'])
|
||||
if len(hosts) != 1:
|
||||
raise AnsibleOptionsError("You must pass a single valid host to --host parameter")
|
||||
|
||||
myvars = self._get_host_variables(host=hosts[0])
|
||||
|
||||
# FIXME: should we template first?
|
||||
results = self.dump(myvars)
|
||||
|
||||
else:
|
||||
if context.CLIARGS['subset']:
|
||||
# not doing single host, set limit in general if given
|
||||
self.inventory.subset(context.CLIARGS['subset'])
|
||||
|
||||
if context.CLIARGS['graph']:
|
||||
results = self.inventory_graph()
|
||||
elif context.CLIARGS['list']:
|
||||
top = self._get_group('all')
|
||||
if context.CLIARGS['yaml']:
|
||||
results = self.yaml_inventory(top)
|
||||
elif context.CLIARGS['toml']:
|
||||
results = self.toml_inventory(top)
|
||||
else:
|
||||
results = self.json_inventory(top)
|
||||
results = self.dump(results)
|
||||
|
||||
if results:
|
||||
outfile = context.CLIARGS['output_file']
|
||||
if outfile is None:
|
||||
# FIXME: pager?
|
||||
display.display(results)
|
||||
else:
|
||||
try:
|
||||
with open(to_bytes(outfile), 'wb') as f:
|
||||
f.write(to_bytes(results))
|
||||
except (OSError, IOError) as e:
|
||||
raise AnsibleError('Unable to write to destination file (%s): %s' % (to_native(outfile), to_native(e)))
|
||||
sys.exit(0)
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
@staticmethod
|
||||
def dump(stuff):
|
||||
|
||||
if context.CLIARGS['yaml']:
|
||||
import yaml
|
||||
from ansible.parsing.yaml.dumper import AnsibleDumper
|
||||
results = to_text(yaml.dump(stuff, Dumper=AnsibleDumper, default_flow_style=False, allow_unicode=True))
|
||||
elif context.CLIARGS['toml']:
|
||||
from ansible.plugins.inventory.toml import toml_dumps
|
||||
try:
|
||||
results = toml_dumps(stuff)
|
||||
except TypeError as e:
|
||||
raise AnsibleError(
|
||||
'The source inventory contains a value that cannot be represented in TOML: %s' % e
|
||||
)
|
||||
except KeyError as e:
|
||||
raise AnsibleError(
|
||||
'The source inventory contains a non-string key (%s) which cannot be represented in TOML. '
|
||||
'The specified key will need to be converted to a string. Be aware that if your playbooks '
|
||||
'expect this key to be non-string, your playbooks will need to be modified to support this '
|
||||
'change.' % e.args[0]
|
||||
)
|
||||
else:
|
||||
import json
|
||||
from ansible.parsing.ajson import AnsibleJSONEncoder
|
||||
try:
|
||||
results = json.dumps(stuff, cls=AnsibleJSONEncoder, sort_keys=True, indent=4, preprocess_unsafe=True, ensure_ascii=False)
|
||||
except TypeError as e:
|
||||
results = json.dumps(stuff, cls=AnsibleJSONEncoder, sort_keys=False, indent=4, preprocess_unsafe=True, ensure_ascii=False)
|
||||
display.warning("Could not sort JSON output due to issues while sorting keys: %s" % to_native(e))
|
||||
|
||||
return results
|
||||
|
||||
def _get_group_variables(self, group):
|
||||
|
||||
# get info from inventory source
|
||||
res = group.get_vars()
|
||||
|
||||
# Always load vars plugins
|
||||
res = combine_vars(res, get_vars_from_inventory_sources(self.loader, self.inventory._sources, [group], 'all'))
|
||||
if context.CLIARGS['basedir']:
|
||||
res = combine_vars(res, get_vars_from_path(self.loader, context.CLIARGS['basedir'], [group], 'all'))
|
||||
|
||||
if group.priority != 1:
|
||||
res['ansible_group_priority'] = group.priority
|
||||
|
||||
return self._remove_internal(res)
|
||||
|
||||
def _get_host_variables(self, host):
|
||||
|
||||
if context.CLIARGS['export']:
|
||||
# only get vars defined directly host
|
||||
hostvars = host.get_vars()
|
||||
|
||||
# Always load vars plugins
|
||||
hostvars = combine_vars(hostvars, get_vars_from_inventory_sources(self.loader, self.inventory._sources, [host], 'all'))
|
||||
if context.CLIARGS['basedir']:
|
||||
hostvars = combine_vars(hostvars, get_vars_from_path(self.loader, context.CLIARGS['basedir'], [host], 'all'))
|
||||
else:
|
||||
# get all vars flattened by host, but skip magic hostvars
|
||||
hostvars = self.vm.get_vars(host=host, include_hostvars=False, stage='all')
|
||||
|
||||
return self._remove_internal(hostvars)
|
||||
|
||||
def _get_group(self, gname):
|
||||
group = self.inventory.groups.get(gname)
|
||||
return group
|
||||
|
||||
@staticmethod
|
||||
def _remove_internal(dump):
|
||||
|
||||
for internal in C.INTERNAL_STATIC_VARS:
|
||||
if internal in dump:
|
||||
del dump[internal]
|
||||
|
||||
return dump
|
||||
|
||||
@staticmethod
|
||||
def _remove_empty_keys(dump):
|
||||
# remove empty keys
|
||||
for x in ('hosts', 'vars', 'children'):
|
||||
if x in dump and not dump[x]:
|
||||
del dump[x]
|
||||
|
||||
@staticmethod
|
||||
def _show_vars(dump, depth):
|
||||
result = []
|
||||
for (name, val) in sorted(dump.items()):
|
||||
result.append(InventoryCLI._graph_name('{%s = %s}' % (name, val), depth))
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _graph_name(name, depth=0):
|
||||
if depth:
|
||||
name = " |" * (depth) + "--%s" % name
|
||||
return name
|
||||
|
||||
def _graph_group(self, group, depth=0):
|
||||
|
||||
result = [self._graph_name('@%s:' % group.name, depth)]
|
||||
depth = depth + 1
|
||||
for kid in group.child_groups:
|
||||
result.extend(self._graph_group(kid, depth))
|
||||
|
||||
if group.name != 'all':
|
||||
for host in group.hosts:
|
||||
result.append(self._graph_name(host.name, depth))
|
||||
if context.CLIARGS['show_vars']:
|
||||
result.extend(self._show_vars(self._get_host_variables(host), depth + 1))
|
||||
|
||||
if context.CLIARGS['show_vars']:
|
||||
result.extend(self._show_vars(self._get_group_variables(group), depth))
|
||||
|
||||
return result
|
||||
|
||||
def inventory_graph(self):
|
||||
|
||||
start_at = self._get_group(context.CLIARGS['pattern'])
|
||||
if start_at:
|
||||
return '\n'.join(self._graph_group(start_at))
|
||||
else:
|
||||
raise AnsibleOptionsError("Pattern must be valid group name when using --graph")
|
||||
|
||||
def json_inventory(self, top):
|
||||
|
||||
seen_groups = set()
|
||||
|
||||
def format_group(group, available_hosts):
|
||||
results = {}
|
||||
results[group.name] = {}
|
||||
if group.name != 'all':
|
||||
results[group.name]['hosts'] = [h.name for h in group.hosts if h.name in available_hosts]
|
||||
results[group.name]['children'] = []
|
||||
for subgroup in group.child_groups:
|
||||
results[group.name]['children'].append(subgroup.name)
|
||||
if subgroup.name not in seen_groups:
|
||||
results.update(format_group(subgroup, available_hosts))
|
||||
seen_groups.add(subgroup.name)
|
||||
if context.CLIARGS['export']:
|
||||
results[group.name]['vars'] = self._get_group_variables(group)
|
||||
|
||||
self._remove_empty_keys(results[group.name])
|
||||
# remove empty groups
|
||||
if not results[group.name]:
|
||||
del results[group.name]
|
||||
|
||||
return results
|
||||
|
||||
hosts = self.inventory.get_hosts(top.name)
|
||||
results = format_group(top, frozenset(h.name for h in hosts))
|
||||
|
||||
# populate meta
|
||||
results['_meta'] = {'hostvars': {}}
|
||||
for host in hosts:
|
||||
hvars = self._get_host_variables(host)
|
||||
if hvars:
|
||||
results['_meta']['hostvars'][host.name] = hvars
|
||||
|
||||
return results
|
||||
|
||||
def yaml_inventory(self, top):
|
||||
|
||||
seen_hosts = set()
|
||||
seen_groups = set()
|
||||
|
||||
def format_group(group, available_hosts):
|
||||
results = {}
|
||||
|
||||
# initialize group + vars
|
||||
results[group.name] = {}
|
||||
|
||||
# subgroups
|
||||
results[group.name]['children'] = {}
|
||||
for subgroup in group.child_groups:
|
||||
if subgroup.name != 'all':
|
||||
if subgroup.name in seen_groups:
|
||||
results[group.name]['children'].update({subgroup.name: {}})
|
||||
else:
|
||||
results[group.name]['children'].update(format_group(subgroup, available_hosts))
|
||||
seen_groups.add(subgroup.name)
|
||||
|
||||
# hosts for group
|
||||
results[group.name]['hosts'] = {}
|
||||
if group.name != 'all':
|
||||
for h in group.hosts:
|
||||
if h.name not in available_hosts:
|
||||
continue # observe limit
|
||||
myvars = {}
|
||||
if h.name not in seen_hosts: # avoid defining host vars more than once
|
||||
seen_hosts.add(h.name)
|
||||
myvars = self._get_host_variables(host=h)
|
||||
results[group.name]['hosts'][h.name] = myvars
|
||||
|
||||
if context.CLIARGS['export']:
|
||||
gvars = self._get_group_variables(group)
|
||||
if gvars:
|
||||
results[group.name]['vars'] = gvars
|
||||
|
||||
self._remove_empty_keys(results[group.name])
|
||||
# remove empty groups
|
||||
if not results[group.name]:
|
||||
del results[group.name]
|
||||
|
||||
return results
|
||||
|
||||
available_hosts = frozenset(h.name for h in self.inventory.get_hosts(top.name))
|
||||
return format_group(top, available_hosts)
|
||||
|
||||
def toml_inventory(self, top):
|
||||
seen_hosts = set()
|
||||
seen_hosts = set()
|
||||
has_ungrouped = bool(next(g.hosts for g in top.child_groups if g.name == 'ungrouped'))
|
||||
|
||||
def format_group(group, available_hosts):
|
||||
results = {}
|
||||
results[group.name] = {}
|
||||
|
||||
results[group.name]['children'] = []
|
||||
for subgroup in group.child_groups:
|
||||
if subgroup.name == 'ungrouped' and not has_ungrouped:
|
||||
continue
|
||||
if group.name != 'all':
|
||||
results[group.name]['children'].append(subgroup.name)
|
||||
results.update(format_group(subgroup, available_hosts))
|
||||
|
||||
if group.name != 'all':
|
||||
for host in group.hosts:
|
||||
if host.name not in available_hosts:
|
||||
continue
|
||||
if host.name not in seen_hosts:
|
||||
seen_hosts.add(host.name)
|
||||
host_vars = self._get_host_variables(host=host)
|
||||
else:
|
||||
host_vars = {}
|
||||
try:
|
||||
results[group.name]['hosts'][host.name] = host_vars
|
||||
except KeyError:
|
||||
results[group.name]['hosts'] = {host.name: host_vars}
|
||||
|
||||
if context.CLIARGS['export']:
|
||||
results[group.name]['vars'] = self._get_group_variables(group)
|
||||
|
||||
self._remove_empty_keys(results[group.name])
|
||||
# remove empty groups
|
||||
if not results[group.name]:
|
||||
del results[group.name]
|
||||
|
||||
return results
|
||||
|
||||
available_hosts = frozenset(h.name for h in self.inventory.get_hosts(top.name))
|
||||
results = format_group(top, available_hosts)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def main(args=None):
|
||||
InventoryCLI.cli_executor(args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
243
ansible/lib/python3.11/site-packages/ansible/cli/playbook.py
Normal file
243
ansible/lib/python3.11/site-packages/ansible/cli/playbook.py
Normal file
@ -0,0 +1,243 @@
|
||||
#!/usr/bin/env python
|
||||
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# PYTHON_ARGCOMPLETE_OK
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
# ansible.cli needs to be imported first, to ensure the source bin/* scripts run that code first
|
||||
from ansible.cli import CLI
|
||||
|
||||
import os
|
||||
import stat
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible import context
|
||||
from ansible.cli.arguments import option_helpers as opt_help
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.executor.playbook_executor import PlaybookExecutor
|
||||
from ansible.module_utils.common.text.converters import to_bytes
|
||||
from ansible.playbook.block import Block
|
||||
from ansible.plugins.loader import add_all_plugin_dirs
|
||||
from ansible.utils.collection_loader import AnsibleCollectionConfig
|
||||
from ansible.utils.collection_loader._collection_finder import _get_collection_name_from_path, _get_collection_playbook_path
|
||||
from ansible.utils.display import Display
|
||||
|
||||
|
||||
display = Display()
|
||||
|
||||
|
||||
class PlaybookCLI(CLI):
|
||||
''' the tool to run *Ansible playbooks*, which are a configuration and multinode deployment system.
|
||||
See the project home page (https://docs.ansible.com) for more information. '''
|
||||
|
||||
name = 'ansible-playbook'
|
||||
|
||||
def init_parser(self):
|
||||
|
||||
# create parser for CLI options
|
||||
super(PlaybookCLI, self).init_parser(
|
||||
usage="%prog [options] playbook.yml [playbook2 ...]",
|
||||
desc="Runs Ansible playbooks, executing the defined tasks on the targeted hosts.")
|
||||
|
||||
opt_help.add_connect_options(self.parser)
|
||||
opt_help.add_meta_options(self.parser)
|
||||
opt_help.add_runas_options(self.parser)
|
||||
opt_help.add_subset_options(self.parser)
|
||||
opt_help.add_check_options(self.parser)
|
||||
opt_help.add_inventory_options(self.parser)
|
||||
opt_help.add_runtask_options(self.parser)
|
||||
opt_help.add_vault_options(self.parser)
|
||||
opt_help.add_fork_options(self.parser)
|
||||
opt_help.add_module_options(self.parser)
|
||||
|
||||
# ansible playbook specific opts
|
||||
self.parser.add_argument('--syntax-check', dest='syntax', action='store_true',
|
||||
help="perform a syntax check on the playbook, but do not execute it")
|
||||
self.parser.add_argument('--list-tasks', dest='listtasks', action='store_true',
|
||||
help="list all tasks that would be executed")
|
||||
self.parser.add_argument('--list-tags', dest='listtags', action='store_true',
|
||||
help="list all available tags")
|
||||
self.parser.add_argument('--step', dest='step', action='store_true',
|
||||
help="one-step-at-a-time: confirm each task before running")
|
||||
self.parser.add_argument('--start-at-task', dest='start_at_task',
|
||||
help="start the playbook at the task matching this name")
|
||||
self.parser.add_argument('args', help='Playbook(s)', metavar='playbook', nargs='+')
|
||||
|
||||
def post_process_args(self, options):
|
||||
|
||||
# for listing, we need to know if user had tag input
|
||||
# capture here as parent function sets defaults for tags
|
||||
havetags = bool(options.tags or options.skip_tags)
|
||||
|
||||
options = super(PlaybookCLI, self).post_process_args(options)
|
||||
|
||||
if options.listtags:
|
||||
# default to all tags (including never), when listing tags
|
||||
# unless user specified tags
|
||||
if not havetags:
|
||||
options.tags = ['never', 'all']
|
||||
|
||||
display.verbosity = options.verbosity
|
||||
self.validate_conflicts(options, runas_opts=True, fork_opts=True)
|
||||
|
||||
return options
|
||||
|
||||
def run(self):
|
||||
|
||||
super(PlaybookCLI, self).run()
|
||||
|
||||
# Note: slightly wrong, this is written so that implicit localhost
|
||||
# manages passwords
|
||||
sshpass = None
|
||||
becomepass = None
|
||||
passwords = {}
|
||||
|
||||
# initial error check, to make sure all specified playbooks are accessible
|
||||
# before we start running anything through the playbook executor
|
||||
# also prep plugin paths
|
||||
b_playbook_dirs = []
|
||||
for playbook in context.CLIARGS['args']:
|
||||
|
||||
# resolve if it is collection playbook with FQCN notation, if not, leaves unchanged
|
||||
resource = _get_collection_playbook_path(playbook)
|
||||
if resource is not None:
|
||||
playbook_collection = resource[2]
|
||||
else:
|
||||
# not an FQCN so must be a file
|
||||
if not os.path.exists(playbook):
|
||||
raise AnsibleError("the playbook: %s could not be found" % playbook)
|
||||
if not (os.path.isfile(playbook) or stat.S_ISFIFO(os.stat(playbook).st_mode)):
|
||||
raise AnsibleError("the playbook: %s does not appear to be a file" % playbook)
|
||||
|
||||
# check if playbook is from collection (path can be passed directly)
|
||||
playbook_collection = _get_collection_name_from_path(playbook)
|
||||
|
||||
# don't add collection playbooks to adjacency search path
|
||||
if not playbook_collection:
|
||||
# setup dirs to enable loading plugins from all playbooks in case they add callbacks/inventory/etc
|
||||
b_playbook_dir = os.path.dirname(os.path.abspath(to_bytes(playbook, errors='surrogate_or_strict')))
|
||||
add_all_plugin_dirs(b_playbook_dir)
|
||||
b_playbook_dirs.append(b_playbook_dir)
|
||||
|
||||
if b_playbook_dirs:
|
||||
# allow collections adjacent to these playbooks
|
||||
# we use list copy to avoid opening up 'adjacency' in the previous loop
|
||||
AnsibleCollectionConfig.playbook_paths = b_playbook_dirs
|
||||
|
||||
# don't deal with privilege escalation or passwords when we don't need to
|
||||
if not (context.CLIARGS['listhosts'] or context.CLIARGS['listtasks'] or
|
||||
context.CLIARGS['listtags'] or context.CLIARGS['syntax']):
|
||||
(sshpass, becomepass) = self.ask_passwords()
|
||||
passwords = {'conn_pass': sshpass, 'become_pass': becomepass}
|
||||
|
||||
# create base objects
|
||||
loader, inventory, variable_manager = self._play_prereqs()
|
||||
|
||||
# (which is not returned in list_hosts()) is taken into account for
|
||||
# warning if inventory is empty. But it can't be taken into account for
|
||||
# checking if limit doesn't match any hosts. Instead we don't worry about
|
||||
# limit if only implicit localhost was in inventory to start with.
|
||||
#
|
||||
# Fix this when we rewrite inventory by making localhost a real host (and thus show up in list_hosts())
|
||||
CLI.get_host_list(inventory, context.CLIARGS['subset'])
|
||||
|
||||
# flush fact cache if requested
|
||||
if context.CLIARGS['flush_cache']:
|
||||
self._flush_cache(inventory, variable_manager)
|
||||
|
||||
# create the playbook executor, which manages running the plays via a task queue manager
|
||||
pbex = PlaybookExecutor(playbooks=context.CLIARGS['args'], inventory=inventory,
|
||||
variable_manager=variable_manager, loader=loader,
|
||||
passwords=passwords)
|
||||
|
||||
results = pbex.run()
|
||||
|
||||
if isinstance(results, list):
|
||||
for p in results:
|
||||
|
||||
display.display('\nplaybook: %s' % p['playbook'])
|
||||
for idx, play in enumerate(p['plays']):
|
||||
if play._included_path is not None:
|
||||
loader.set_basedir(play._included_path)
|
||||
else:
|
||||
pb_dir = os.path.realpath(os.path.dirname(p['playbook']))
|
||||
loader.set_basedir(pb_dir)
|
||||
|
||||
# show host list if we were able to template into a list
|
||||
try:
|
||||
host_list = ','.join(play.hosts)
|
||||
except TypeError:
|
||||
host_list = ''
|
||||
|
||||
msg = "\n play #%d (%s): %s" % (idx + 1, host_list, play.name)
|
||||
mytags = set(play.tags)
|
||||
msg += '\tTAGS: [%s]' % (','.join(mytags))
|
||||
|
||||
if context.CLIARGS['listhosts']:
|
||||
playhosts = set(inventory.get_hosts(play.hosts))
|
||||
msg += "\n pattern: %s\n hosts (%d):" % (play.hosts, len(playhosts))
|
||||
for host in playhosts:
|
||||
msg += "\n %s" % host
|
||||
|
||||
display.display(msg)
|
||||
|
||||
all_tags = set()
|
||||
if context.CLIARGS['listtags'] or context.CLIARGS['listtasks']:
|
||||
taskmsg = ''
|
||||
if context.CLIARGS['listtasks']:
|
||||
taskmsg = ' tasks:\n'
|
||||
|
||||
def _process_block(b):
|
||||
taskmsg = ''
|
||||
for task in b.block:
|
||||
if isinstance(task, Block):
|
||||
taskmsg += _process_block(task)
|
||||
else:
|
||||
if task.action in C._ACTION_META and task.implicit:
|
||||
continue
|
||||
|
||||
all_tags.update(task.tags)
|
||||
if context.CLIARGS['listtasks']:
|
||||
cur_tags = list(mytags.union(set(task.tags)))
|
||||
cur_tags.sort()
|
||||
if task.name:
|
||||
taskmsg += " %s" % task.get_name()
|
||||
else:
|
||||
taskmsg += " %s" % task.action
|
||||
taskmsg += "\tTAGS: [%s]\n" % ', '.join(cur_tags)
|
||||
|
||||
return taskmsg
|
||||
|
||||
all_vars = variable_manager.get_vars(play=play)
|
||||
for block in play.compile():
|
||||
block = block.filter_tagged_tasks(all_vars)
|
||||
if not block.has_tasks():
|
||||
continue
|
||||
taskmsg += _process_block(block)
|
||||
|
||||
if context.CLIARGS['listtags']:
|
||||
cur_tags = list(mytags.union(all_tags))
|
||||
cur_tags.sort()
|
||||
taskmsg += " TASK TAGS: [%s]\n" % ', '.join(cur_tags)
|
||||
|
||||
display.display(taskmsg)
|
||||
|
||||
return 0
|
||||
else:
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def _flush_cache(inventory, variable_manager):
|
||||
for host in inventory.list_hosts():
|
||||
hostname = host.get_name()
|
||||
variable_manager.clear_facts(hostname)
|
||||
|
||||
|
||||
def main(args=None):
|
||||
PlaybookCLI.cli_executor(args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
371
ansible/lib/python3.11/site-packages/ansible/cli/pull.py
Normal file
371
ansible/lib/python3.11/site-packages/ansible/cli/pull.py
Normal file
@ -0,0 +1,371 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright: (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# PYTHON_ARGCOMPLETE_OK
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
# ansible.cli needs to be imported first, to ensure the source bin/* scripts run that code first
|
||||
from ansible.cli import CLI
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import platform
|
||||
import secrets
|
||||
import shlex
|
||||
import shutil
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible import context
|
||||
from ansible.cli.arguments import option_helpers as opt_help
|
||||
from ansible.errors import AnsibleOptionsError
|
||||
from ansible.module_utils.common.text.converters import to_native, to_text
|
||||
from ansible.plugins.loader import module_loader
|
||||
from ansible.utils.cmd_functions import run_cmd
|
||||
from ansible.utils.display import Display
|
||||
|
||||
|
||||
display = Display()
|
||||
|
||||
|
||||
class PullCLI(CLI):
|
||||
''' Used to pull a remote copy of ansible on each managed node,
|
||||
each set to run via cron and update playbook source via a source repository.
|
||||
This inverts the default *push* architecture of ansible into a *pull* architecture,
|
||||
which has near-limitless scaling potential.
|
||||
|
||||
None of the CLI tools are designed to run concurrently with themselves,
|
||||
you should use an external scheduler and/or locking to ensure there are no clashing operations.
|
||||
|
||||
The setup playbook can be tuned to change the cron frequency, logging locations, and parameters to ansible-pull.
|
||||
This is useful both for extreme scale-out as well as periodic remediation.
|
||||
Usage of the 'fetch' module to retrieve logs from ansible-pull runs would be an
|
||||
excellent way to gather and analyze remote logs from ansible-pull.
|
||||
'''
|
||||
|
||||
name = 'ansible-pull'
|
||||
|
||||
DEFAULT_REPO_TYPE = 'git'
|
||||
DEFAULT_PLAYBOOK = 'local.yml'
|
||||
REPO_CHOICES = ('git', 'subversion', 'hg', 'bzr')
|
||||
PLAYBOOK_ERRORS = {
|
||||
1: 'File does not exist',
|
||||
2: 'File is not readable',
|
||||
}
|
||||
ARGUMENTS = {'playbook.yml': 'The name of one the YAML format files to run as an Ansible playbook. '
|
||||
'This can be a relative path within the checkout. By default, Ansible will '
|
||||
"look for a playbook based on the host's fully-qualified domain name, "
|
||||
'on the host hostname and finally a playbook named *local.yml*.', }
|
||||
|
||||
SKIP_INVENTORY_DEFAULTS = True
|
||||
|
||||
@staticmethod
|
||||
def _get_inv_cli():
|
||||
inv_opts = ''
|
||||
if context.CLIARGS.get('inventory', False):
|
||||
for inv in context.CLIARGS['inventory']:
|
||||
if isinstance(inv, list):
|
||||
inv_opts += " -i '%s' " % ','.join(inv)
|
||||
elif ',' in inv or os.path.exists(inv):
|
||||
inv_opts += ' -i %s ' % inv
|
||||
|
||||
return inv_opts
|
||||
|
||||
def init_parser(self):
|
||||
''' create an options parser for bin/ansible '''
|
||||
|
||||
super(PullCLI, self).init_parser(
|
||||
usage='%prog -U <repository> [options] [<playbook.yml>]',
|
||||
desc="pulls playbooks from a VCS repo and executes them on target host")
|
||||
|
||||
# Do not add check_options as there's a conflict with --checkout/-C
|
||||
opt_help.add_connect_options(self.parser)
|
||||
opt_help.add_vault_options(self.parser)
|
||||
opt_help.add_runtask_options(self.parser)
|
||||
opt_help.add_subset_options(self.parser)
|
||||
opt_help.add_inventory_options(self.parser)
|
||||
opt_help.add_module_options(self.parser)
|
||||
opt_help.add_runas_prompt_options(self.parser)
|
||||
|
||||
self.parser.add_argument('args', help='Playbook(s)', metavar='playbook.yml', nargs='*')
|
||||
|
||||
# options unique to pull
|
||||
self.parser.add_argument('--purge', default=False, action='store_true', help='purge checkout after playbook run')
|
||||
self.parser.add_argument('-o', '--only-if-changed', dest='ifchanged', default=False, action='store_true',
|
||||
help='only run the playbook if the repository has been updated')
|
||||
self.parser.add_argument('-s', '--sleep', dest='sleep', default=None,
|
||||
help='sleep for random interval (between 0 and n number of seconds) before starting. '
|
||||
'This is a useful way to disperse git requests')
|
||||
self.parser.add_argument('-f', '--force', dest='force', default=False, action='store_true',
|
||||
help='run the playbook even if the repository could not be updated')
|
||||
self.parser.add_argument('-d', '--directory', dest='dest', default=None, type=opt_help.unfrack_path(),
|
||||
help='path to the directory to which Ansible will checkout the repository.')
|
||||
self.parser.add_argument('-U', '--url', dest='url', default=None, help='URL of the playbook repository')
|
||||
self.parser.add_argument('--full', dest='fullclone', action='store_true', help='Do a full clone, instead of a shallow one.')
|
||||
self.parser.add_argument('-C', '--checkout', dest='checkout',
|
||||
help='branch/tag/commit to checkout. Defaults to behavior of repository module.')
|
||||
self.parser.add_argument('--accept-host-key', default=False, dest='accept_host_key', action='store_true',
|
||||
help='adds the hostkey for the repo url if not already added')
|
||||
self.parser.add_argument('-m', '--module-name', dest='module_name', default=self.DEFAULT_REPO_TYPE,
|
||||
help='Repository module name, which ansible will use to check out the repo. Choices are %s. Default is %s.'
|
||||
% (self.REPO_CHOICES, self.DEFAULT_REPO_TYPE))
|
||||
self.parser.add_argument('--verify-commit', dest='verify', default=False, action='store_true',
|
||||
help='verify GPG signature of checked out commit, if it fails abort running the playbook. '
|
||||
'This needs the corresponding VCS module to support such an operation')
|
||||
self.parser.add_argument('--clean', dest='clean', default=False, action='store_true',
|
||||
help='modified files in the working repository will be discarded')
|
||||
self.parser.add_argument('--track-subs', dest='tracksubs', default=False, action='store_true',
|
||||
help='submodules will track the latest changes. This is equivalent to specifying the --remote flag to git submodule update')
|
||||
# add a subset of the check_opts flag group manually, as the full set's
|
||||
# shortcodes conflict with above --checkout/-C
|
||||
self.parser.add_argument("--check", default=False, dest='check', action='store_true',
|
||||
help="don't make any changes; instead, try to predict some of the changes that may occur")
|
||||
self.parser.add_argument("--diff", default=C.DIFF_ALWAYS, dest='diff', action='store_true',
|
||||
help="when changing (small) files and templates, show the differences in those files; works great with --check")
|
||||
|
||||
def post_process_args(self, options):
|
||||
options = super(PullCLI, self).post_process_args(options)
|
||||
|
||||
if not options.dest:
|
||||
hostname = socket.getfqdn()
|
||||
# use a hostname dependent directory, in case of $HOME on nfs
|
||||
options.dest = os.path.join(C.ANSIBLE_HOME, 'pull', hostname)
|
||||
|
||||
if os.path.exists(options.dest) and not os.path.isdir(options.dest):
|
||||
raise AnsibleOptionsError("%s is not a valid or accessible directory." % options.dest)
|
||||
|
||||
if options.sleep:
|
||||
try:
|
||||
secs = secrets.randbelow(int(options.sleep))
|
||||
options.sleep = secs
|
||||
except ValueError:
|
||||
raise AnsibleOptionsError("%s is not a number." % options.sleep)
|
||||
|
||||
if not options.url:
|
||||
raise AnsibleOptionsError("URL for repository not specified, use -h for help")
|
||||
|
||||
if options.module_name not in self.REPO_CHOICES:
|
||||
raise AnsibleOptionsError("Unsupported repo module %s, choices are %s" % (options.module_name, ','.join(self.REPO_CHOICES)))
|
||||
|
||||
display.verbosity = options.verbosity
|
||||
self.validate_conflicts(options)
|
||||
|
||||
return options
|
||||
|
||||
def run(self):
|
||||
''' use Runner lib to do SSH things '''
|
||||
|
||||
super(PullCLI, self).run()
|
||||
|
||||
# log command line
|
||||
now = datetime.datetime.now()
|
||||
display.display(now.strftime("Starting Ansible Pull at %F %T"))
|
||||
display.display(' '.join(sys.argv))
|
||||
|
||||
# Build Checkout command
|
||||
# Now construct the ansible command
|
||||
node = platform.node()
|
||||
host = socket.getfqdn()
|
||||
hostnames = ','.join(set([host, node, host.split('.')[0], node.split('.')[0]]))
|
||||
if hostnames:
|
||||
limit_opts = 'localhost,%s,127.0.0.1' % hostnames
|
||||
else:
|
||||
limit_opts = 'localhost,127.0.0.1'
|
||||
base_opts = '-c local '
|
||||
if context.CLIARGS['verbosity'] > 0:
|
||||
base_opts += ' -%s' % ''.join(["v" for x in range(0, context.CLIARGS['verbosity'])])
|
||||
|
||||
# Attempt to use the inventory passed in as an argument
|
||||
# It might not yet have been downloaded so use localhost as default
|
||||
inv_opts = self._get_inv_cli()
|
||||
if not inv_opts:
|
||||
inv_opts = " -i localhost, "
|
||||
# avoid interpreter discovery since we already know which interpreter to use on localhost
|
||||
inv_opts += '-e %s ' % shlex.quote('ansible_python_interpreter=%s' % sys.executable)
|
||||
|
||||
# SCM specific options
|
||||
if context.CLIARGS['module_name'] == 'git':
|
||||
repo_opts = "name=%s dest=%s" % (context.CLIARGS['url'], context.CLIARGS['dest'])
|
||||
if context.CLIARGS['checkout']:
|
||||
repo_opts += ' version=%s' % context.CLIARGS['checkout']
|
||||
|
||||
if context.CLIARGS['accept_host_key']:
|
||||
repo_opts += ' accept_hostkey=yes'
|
||||
|
||||
if context.CLIARGS['private_key_file']:
|
||||
repo_opts += ' key_file=%s' % context.CLIARGS['private_key_file']
|
||||
|
||||
if context.CLIARGS['verify']:
|
||||
repo_opts += ' verify_commit=yes'
|
||||
|
||||
if context.CLIARGS['tracksubs']:
|
||||
repo_opts += ' track_submodules=yes'
|
||||
|
||||
if not context.CLIARGS['fullclone']:
|
||||
repo_opts += ' depth=1'
|
||||
elif context.CLIARGS['module_name'] == 'subversion':
|
||||
repo_opts = "repo=%s dest=%s" % (context.CLIARGS['url'], context.CLIARGS['dest'])
|
||||
if context.CLIARGS['checkout']:
|
||||
repo_opts += ' revision=%s' % context.CLIARGS['checkout']
|
||||
if not context.CLIARGS['fullclone']:
|
||||
repo_opts += ' export=yes'
|
||||
elif context.CLIARGS['module_name'] == 'hg':
|
||||
repo_opts = "repo=%s dest=%s" % (context.CLIARGS['url'], context.CLIARGS['dest'])
|
||||
if context.CLIARGS['checkout']:
|
||||
repo_opts += ' revision=%s' % context.CLIARGS['checkout']
|
||||
elif context.CLIARGS['module_name'] == 'bzr':
|
||||
repo_opts = "name=%s dest=%s" % (context.CLIARGS['url'], context.CLIARGS['dest'])
|
||||
if context.CLIARGS['checkout']:
|
||||
repo_opts += ' version=%s' % context.CLIARGS['checkout']
|
||||
else:
|
||||
raise AnsibleOptionsError('Unsupported (%s) SCM module for pull, choices are: %s'
|
||||
% (context.CLIARGS['module_name'],
|
||||
','.join(self.REPO_CHOICES)))
|
||||
|
||||
# options common to all supported SCMS
|
||||
if context.CLIARGS['clean']:
|
||||
repo_opts += ' force=yes'
|
||||
|
||||
path = module_loader.find_plugin(context.CLIARGS['module_name'])
|
||||
if path is None:
|
||||
raise AnsibleOptionsError(("module '%s' not found.\n" % context.CLIARGS['module_name']))
|
||||
|
||||
bin_path = os.path.dirname(os.path.abspath(sys.argv[0]))
|
||||
# hardcode local and inventory/host as this is just meant to fetch the repo
|
||||
cmd = '%s/ansible %s %s -m %s -a "%s" all -l "%s"' % (bin_path, inv_opts, base_opts,
|
||||
context.CLIARGS['module_name'],
|
||||
repo_opts, limit_opts)
|
||||
for ev in context.CLIARGS['extra_vars']:
|
||||
cmd += ' -e %s' % shlex.quote(ev)
|
||||
|
||||
# Nap?
|
||||
if context.CLIARGS['sleep']:
|
||||
display.display("Sleeping for %d seconds..." % context.CLIARGS['sleep'])
|
||||
time.sleep(context.CLIARGS['sleep'])
|
||||
|
||||
# RUN the Checkout command
|
||||
display.debug("running ansible with VCS module to checkout repo")
|
||||
display.vvvv('EXEC: %s' % cmd)
|
||||
rc, b_out, b_err = run_cmd(cmd, live=True)
|
||||
|
||||
if rc != 0:
|
||||
if context.CLIARGS['force']:
|
||||
display.warning("Unable to update repository. Continuing with (forced) run of playbook.")
|
||||
else:
|
||||
return rc
|
||||
elif context.CLIARGS['ifchanged'] and b'"changed": true' not in b_out:
|
||||
display.display("Repository has not changed, quitting.")
|
||||
return 0
|
||||
|
||||
playbook = self.select_playbook(context.CLIARGS['dest'])
|
||||
if playbook is None:
|
||||
raise AnsibleOptionsError("Could not find a playbook to run.")
|
||||
|
||||
# Build playbook command
|
||||
cmd = '%s/ansible-playbook %s %s' % (bin_path, base_opts, playbook)
|
||||
if context.CLIARGS['vault_password_files']:
|
||||
for vault_password_file in context.CLIARGS['vault_password_files']:
|
||||
cmd += " --vault-password-file=%s" % vault_password_file
|
||||
if context.CLIARGS['vault_ids']:
|
||||
for vault_id in context.CLIARGS['vault_ids']:
|
||||
cmd += " --vault-id=%s" % vault_id
|
||||
|
||||
if context.CLIARGS['become_password_file']:
|
||||
cmd += " --become-password-file=%s" % context.CLIARGS['become_password_file']
|
||||
|
||||
if context.CLIARGS['connection_password_file']:
|
||||
cmd += " --connection-password-file=%s" % context.CLIARGS['connection_password_file']
|
||||
|
||||
for ev in context.CLIARGS['extra_vars']:
|
||||
cmd += ' -e %s' % shlex.quote(ev)
|
||||
|
||||
if context.CLIARGS['become_ask_pass']:
|
||||
cmd += ' --ask-become-pass'
|
||||
if context.CLIARGS['skip_tags']:
|
||||
cmd += ' --skip-tags "%s"' % to_native(u','.join(context.CLIARGS['skip_tags']))
|
||||
if context.CLIARGS['tags']:
|
||||
cmd += ' -t "%s"' % to_native(u','.join(context.CLIARGS['tags']))
|
||||
if context.CLIARGS['subset']:
|
||||
cmd += ' -l "%s"' % context.CLIARGS['subset']
|
||||
else:
|
||||
cmd += ' -l "%s"' % limit_opts
|
||||
if context.CLIARGS['check']:
|
||||
cmd += ' -C'
|
||||
if context.CLIARGS['diff']:
|
||||
cmd += ' -D'
|
||||
|
||||
os.chdir(context.CLIARGS['dest'])
|
||||
|
||||
# redo inventory options as new files might exist now
|
||||
inv_opts = self._get_inv_cli()
|
||||
if inv_opts:
|
||||
cmd += inv_opts
|
||||
|
||||
# RUN THE PLAYBOOK COMMAND
|
||||
display.debug("running ansible-playbook to do actual work")
|
||||
display.debug('EXEC: %s' % cmd)
|
||||
rc, b_out, b_err = run_cmd(cmd, live=True)
|
||||
|
||||
if context.CLIARGS['purge']:
|
||||
os.chdir('/')
|
||||
try:
|
||||
display.debug("removing: %s" % context.CLIARGS['dest'])
|
||||
shutil.rmtree(context.CLIARGS['dest'])
|
||||
except Exception as e:
|
||||
display.error(u"Failed to remove %s: %s" % (context.CLIARGS['dest'], to_text(e)))
|
||||
|
||||
return rc
|
||||
|
||||
@staticmethod
|
||||
def try_playbook(path):
|
||||
if not os.path.exists(path):
|
||||
return 1
|
||||
if not os.access(path, os.R_OK):
|
||||
return 2
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def select_playbook(path):
|
||||
playbook = None
|
||||
errors = []
|
||||
if context.CLIARGS['args'] and context.CLIARGS['args'][0] is not None:
|
||||
playbooks = []
|
||||
for book in context.CLIARGS['args']:
|
||||
book_path = os.path.join(path, book)
|
||||
rc = PullCLI.try_playbook(book_path)
|
||||
if rc != 0:
|
||||
errors.append("%s: %s" % (book_path, PullCLI.PLAYBOOK_ERRORS[rc]))
|
||||
continue
|
||||
playbooks.append(book_path)
|
||||
if 0 < len(errors):
|
||||
display.warning("\n".join(errors))
|
||||
elif len(playbooks) == len(context.CLIARGS['args']):
|
||||
playbook = " ".join(playbooks)
|
||||
return playbook
|
||||
else:
|
||||
fqdn = socket.getfqdn()
|
||||
hostpb = os.path.join(path, fqdn + '.yml')
|
||||
shorthostpb = os.path.join(path, fqdn.split('.')[0] + '.yml')
|
||||
localpb = os.path.join(path, PullCLI.DEFAULT_PLAYBOOK)
|
||||
for pb in [hostpb, shorthostpb, localpb]:
|
||||
rc = PullCLI.try_playbook(pb)
|
||||
if rc == 0:
|
||||
playbook = pb
|
||||
break
|
||||
else:
|
||||
errors.append("%s: %s" % (pb, PullCLI.PLAYBOOK_ERRORS[rc]))
|
||||
if playbook is None:
|
||||
display.warning("\n".join(errors))
|
||||
return playbook
|
||||
|
||||
|
||||
def main(args=None):
|
||||
PullCLI.cli_executor(args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Binary file not shown.
Binary file not shown.
@ -0,0 +1,342 @@
|
||||
# Copyright: (c) 2017, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import annotations
|
||||
|
||||
import fcntl
|
||||
import io
|
||||
import os
|
||||
import pickle
|
||||
import signal
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
import errno
|
||||
import json
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible.cli.arguments import option_helpers as opt_help
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_text
|
||||
from ansible.module_utils.connection import Connection, ConnectionError, send_data, recv_data
|
||||
from ansible.module_utils.service import fork_process
|
||||
from ansible.parsing.ajson import AnsibleJSONEncoder, AnsibleJSONDecoder
|
||||
from ansible.playbook.play_context import PlayContext
|
||||
from ansible.plugins.loader import connection_loader, init_plugin_loader
|
||||
from ansible.utils.path import unfrackpath, makedirs_safe
|
||||
from ansible.utils.display import Display
|
||||
from ansible.utils.jsonrpc import JsonRpcServer
|
||||
|
||||
display = Display()
|
||||
|
||||
|
||||
def read_stream(byte_stream):
|
||||
size = int(byte_stream.readline().strip())
|
||||
|
||||
data = byte_stream.read(size)
|
||||
if len(data) < size:
|
||||
raise Exception("EOF found before data was complete")
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@contextmanager
|
||||
def file_lock(lock_path):
|
||||
"""
|
||||
Uses contextmanager to create and release a file lock based on the
|
||||
given path. This allows us to create locks using `with file_lock()`
|
||||
to prevent deadlocks related to failure to unlock properly.
|
||||
"""
|
||||
|
||||
lock_fd = os.open(lock_path, os.O_RDWR | os.O_CREAT, 0o600)
|
||||
fcntl.lockf(lock_fd, fcntl.LOCK_EX)
|
||||
yield
|
||||
fcntl.lockf(lock_fd, fcntl.LOCK_UN)
|
||||
os.close(lock_fd)
|
||||
|
||||
|
||||
class ConnectionProcess(object):
|
||||
'''
|
||||
The connection process wraps around a Connection object that manages
|
||||
the connection to a remote device that persists over the playbook
|
||||
'''
|
||||
def __init__(self, fd, play_context, socket_path, original_path, task_uuid=None, ansible_playbook_pid=None):
|
||||
self.play_context = play_context
|
||||
self.socket_path = socket_path
|
||||
self.original_path = original_path
|
||||
self._task_uuid = task_uuid
|
||||
|
||||
self.fd = fd
|
||||
self.exception = None
|
||||
|
||||
self.srv = JsonRpcServer()
|
||||
self.sock = None
|
||||
|
||||
self.connection = None
|
||||
self._ansible_playbook_pid = ansible_playbook_pid
|
||||
|
||||
def start(self, options):
|
||||
messages = list()
|
||||
result = {}
|
||||
|
||||
try:
|
||||
messages.append(('vvvv', 'control socket path is %s' % self.socket_path))
|
||||
|
||||
# If this is a relative path (~ gets expanded later) then plug the
|
||||
# key's path on to the directory we originally came from, so we can
|
||||
# find it now that our cwd is /
|
||||
if self.play_context.private_key_file and self.play_context.private_key_file[0] not in '~/':
|
||||
self.play_context.private_key_file = os.path.join(self.original_path, self.play_context.private_key_file)
|
||||
self.connection = connection_loader.get(self.play_context.connection, self.play_context, '/dev/null',
|
||||
task_uuid=self._task_uuid, ansible_playbook_pid=self._ansible_playbook_pid)
|
||||
try:
|
||||
self.connection.set_options(direct=options)
|
||||
except ConnectionError as exc:
|
||||
messages.append(('debug', to_text(exc)))
|
||||
raise ConnectionError('Unable to decode JSON from response set_options. See the debug log for more information.')
|
||||
|
||||
self.connection._socket_path = self.socket_path
|
||||
self.srv.register(self.connection)
|
||||
messages.extend([('vvvv', msg) for msg in sys.stdout.getvalue().splitlines()])
|
||||
|
||||
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
self.sock.bind(self.socket_path)
|
||||
self.sock.listen(1)
|
||||
messages.append(('vvvv', 'local domain socket listeners started successfully'))
|
||||
except Exception as exc:
|
||||
messages.extend(self.connection.pop_messages())
|
||||
result['error'] = to_text(exc)
|
||||
result['exception'] = traceback.format_exc()
|
||||
finally:
|
||||
result['messages'] = messages
|
||||
self.fd.write(json.dumps(result, cls=AnsibleJSONEncoder))
|
||||
self.fd.close()
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
log_messages = self.connection.get_option('persistent_log_messages')
|
||||
while not self.connection._conn_closed:
|
||||
signal.signal(signal.SIGALRM, self.connect_timeout)
|
||||
signal.signal(signal.SIGTERM, self.handler)
|
||||
signal.alarm(self.connection.get_option('persistent_connect_timeout'))
|
||||
|
||||
self.exception = None
|
||||
(s, addr) = self.sock.accept()
|
||||
signal.alarm(0)
|
||||
signal.signal(signal.SIGALRM, self.command_timeout)
|
||||
while True:
|
||||
data = recv_data(s)
|
||||
if not data:
|
||||
break
|
||||
|
||||
if log_messages:
|
||||
display.display("jsonrpc request: %s" % data, log_only=True)
|
||||
|
||||
request = json.loads(to_text(data, errors='surrogate_or_strict'))
|
||||
if request.get('method') == "exec_command" and not self.connection.connected:
|
||||
self.connection._connect()
|
||||
|
||||
signal.alarm(self.connection.get_option('persistent_command_timeout'))
|
||||
|
||||
resp = self.srv.handle_request(data)
|
||||
signal.alarm(0)
|
||||
|
||||
if log_messages:
|
||||
display.display("jsonrpc response: %s" % resp, log_only=True)
|
||||
|
||||
send_data(s, to_bytes(resp))
|
||||
|
||||
s.close()
|
||||
|
||||
except Exception as e:
|
||||
# socket.accept() will raise EINTR if the socket.close() is called
|
||||
if hasattr(e, 'errno'):
|
||||
if e.errno != errno.EINTR:
|
||||
self.exception = traceback.format_exc()
|
||||
else:
|
||||
self.exception = traceback.format_exc()
|
||||
|
||||
finally:
|
||||
# allow time for any exception msg send over socket to receive at other end before shutting down
|
||||
time.sleep(0.1)
|
||||
|
||||
# when done, close the connection properly and cleanup the socket file so it can be recreated
|
||||
self.shutdown()
|
||||
|
||||
def connect_timeout(self, signum, frame):
|
||||
msg = 'persistent connection idle timeout triggered, timeout value is %s secs.\nSee the timeout setting options in the Network Debug and ' \
|
||||
'Troubleshooting Guide.' % self.connection.get_option('persistent_connect_timeout')
|
||||
display.display(msg, log_only=True)
|
||||
raise Exception(msg)
|
||||
|
||||
def command_timeout(self, signum, frame):
|
||||
msg = 'command timeout triggered, timeout value is %s secs.\nSee the timeout setting options in the Network Debug and Troubleshooting Guide.'\
|
||||
% self.connection.get_option('persistent_command_timeout')
|
||||
display.display(msg, log_only=True)
|
||||
raise Exception(msg)
|
||||
|
||||
def handler(self, signum, frame):
|
||||
msg = 'signal handler called with signal %s.' % signum
|
||||
display.display(msg, log_only=True)
|
||||
raise Exception(msg)
|
||||
|
||||
def shutdown(self):
|
||||
""" Shuts down the local domain socket
|
||||
"""
|
||||
lock_path = unfrackpath("%s/.ansible_pc_lock_%s" % os.path.split(self.socket_path))
|
||||
if os.path.exists(self.socket_path):
|
||||
try:
|
||||
if self.sock:
|
||||
self.sock.close()
|
||||
if self.connection:
|
||||
self.connection.close()
|
||||
if self.connection.get_option("persistent_log_messages"):
|
||||
for _level, message in self.connection.pop_messages():
|
||||
display.display(message, log_only=True)
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
if os.path.exists(self.socket_path):
|
||||
os.remove(self.socket_path)
|
||||
setattr(self.connection, '_socket_path', None)
|
||||
setattr(self.connection, '_connected', False)
|
||||
|
||||
if os.path.exists(lock_path):
|
||||
os.remove(lock_path)
|
||||
|
||||
display.display('shutdown complete', log_only=True)
|
||||
|
||||
|
||||
def main(args=None):
|
||||
""" Called to initiate the connect to the remote device
|
||||
"""
|
||||
|
||||
parser = opt_help.create_base_parser(prog=None)
|
||||
opt_help.add_verbosity_options(parser)
|
||||
parser.add_argument('playbook_pid')
|
||||
parser.add_argument('task_uuid')
|
||||
args = parser.parse_args(args[1:] if args is not None else args)
|
||||
init_plugin_loader()
|
||||
|
||||
# initialize verbosity
|
||||
display.verbosity = args.verbosity
|
||||
|
||||
rc = 0
|
||||
result = {}
|
||||
messages = list()
|
||||
socket_path = None
|
||||
|
||||
# Need stdin as a byte stream
|
||||
stdin = sys.stdin.buffer
|
||||
|
||||
# Note: update the below log capture code after Display.display() is refactored.
|
||||
saved_stdout = sys.stdout
|
||||
sys.stdout = io.StringIO()
|
||||
|
||||
try:
|
||||
# read the play context data via stdin, which means depickling it
|
||||
opts_data = read_stream(stdin)
|
||||
init_data = read_stream(stdin)
|
||||
|
||||
pc_data = pickle.loads(init_data, encoding='bytes')
|
||||
options = pickle.loads(opts_data, encoding='bytes')
|
||||
|
||||
play_context = PlayContext()
|
||||
play_context.deserialize(pc_data)
|
||||
|
||||
except Exception as e:
|
||||
rc = 1
|
||||
result.update({
|
||||
'error': to_text(e),
|
||||
'exception': traceback.format_exc()
|
||||
})
|
||||
|
||||
if rc == 0:
|
||||
ssh = connection_loader.get('ssh', class_only=True)
|
||||
ansible_playbook_pid = args.playbook_pid
|
||||
task_uuid = args.task_uuid
|
||||
cp = ssh._create_control_path(play_context.remote_addr, play_context.port, play_context.remote_user, play_context.connection, ansible_playbook_pid)
|
||||
# create the persistent connection dir if need be and create the paths
|
||||
# which we will be using later
|
||||
tmp_path = unfrackpath(C.PERSISTENT_CONTROL_PATH_DIR)
|
||||
makedirs_safe(tmp_path)
|
||||
|
||||
socket_path = unfrackpath(cp % dict(directory=tmp_path))
|
||||
lock_path = unfrackpath("%s/.ansible_pc_lock_%s" % os.path.split(socket_path))
|
||||
|
||||
with file_lock(lock_path):
|
||||
if not os.path.exists(socket_path):
|
||||
messages.append(('vvvv', 'local domain socket does not exist, starting it'))
|
||||
original_path = os.getcwd()
|
||||
r, w = os.pipe()
|
||||
pid = fork_process()
|
||||
|
||||
if pid == 0:
|
||||
try:
|
||||
os.close(r)
|
||||
wfd = os.fdopen(w, 'w')
|
||||
process = ConnectionProcess(wfd, play_context, socket_path, original_path, task_uuid, ansible_playbook_pid)
|
||||
process.start(options)
|
||||
except Exception:
|
||||
messages.append(('error', traceback.format_exc()))
|
||||
rc = 1
|
||||
|
||||
if rc == 0:
|
||||
process.run()
|
||||
else:
|
||||
process.shutdown()
|
||||
|
||||
sys.exit(rc)
|
||||
|
||||
else:
|
||||
os.close(w)
|
||||
rfd = os.fdopen(r, 'r')
|
||||
data = json.loads(rfd.read(), cls=AnsibleJSONDecoder)
|
||||
messages.extend(data.pop('messages'))
|
||||
result.update(data)
|
||||
|
||||
else:
|
||||
messages.append(('vvvv', 'found existing local domain socket, using it!'))
|
||||
conn = Connection(socket_path)
|
||||
try:
|
||||
conn.set_options(direct=options)
|
||||
except ConnectionError as exc:
|
||||
messages.append(('debug', to_text(exc)))
|
||||
raise ConnectionError('Unable to decode JSON from response set_options. See the debug log for more information.')
|
||||
pc_data = to_text(init_data)
|
||||
try:
|
||||
conn.update_play_context(pc_data)
|
||||
conn.set_check_prompt(task_uuid)
|
||||
except Exception as exc:
|
||||
# Only network_cli has update_play context and set_check_prompt, so missing this is
|
||||
# not fatal e.g. netconf
|
||||
if isinstance(exc, ConnectionError) and getattr(exc, 'code', None) == -32601:
|
||||
pass
|
||||
else:
|
||||
result.update({
|
||||
'error': to_text(exc),
|
||||
'exception': traceback.format_exc()
|
||||
})
|
||||
|
||||
if os.path.exists(socket_path):
|
||||
messages.extend(Connection(socket_path).pop_messages())
|
||||
messages.append(('vvvv', sys.stdout.getvalue()))
|
||||
result.update({
|
||||
'messages': messages,
|
||||
'socket_path': socket_path
|
||||
})
|
||||
|
||||
sys.stdout = saved_stdout
|
||||
if 'exception' in result:
|
||||
rc = 1
|
||||
sys.stderr.write(json.dumps(result, cls=AnsibleJSONEncoder))
|
||||
else:
|
||||
rc = 0
|
||||
sys.stdout.write(json.dumps(result, cls=AnsibleJSONEncoder))
|
||||
|
||||
sys.exit(rc)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
490
ansible/lib/python3.11/site-packages/ansible/cli/vault.py
Normal file
490
ansible/lib/python3.11/site-packages/ansible/cli/vault.py
Normal file
@ -0,0 +1,490 @@
|
||||
#!/usr/bin/env python
|
||||
# (c) 2014, James Tanner <tanner.jc@gmail.com>
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# PYTHON_ARGCOMPLETE_OK
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
# ansible.cli needs to be imported first, to ensure the source bin/* scripts run that code first
|
||||
from ansible.cli import CLI
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible import context
|
||||
from ansible.cli.arguments import option_helpers as opt_help
|
||||
from ansible.errors import AnsibleOptionsError
|
||||
from ansible.module_utils.common.text.converters import to_text, to_bytes
|
||||
from ansible.parsing.dataloader import DataLoader
|
||||
from ansible.parsing.vault import VaultEditor, VaultLib, match_encrypt_secret
|
||||
from ansible.utils.display import Display
|
||||
|
||||
display = Display()
|
||||
|
||||
|
||||
class VaultCLI(CLI):
|
||||
''' can encrypt any structured data file used by Ansible.
|
||||
This can include *group_vars/* or *host_vars/* inventory variables,
|
||||
variables loaded by *include_vars* or *vars_files*, or variable files
|
||||
passed on the ansible-playbook command line with *-e @file.yml* or *-e @file.json*.
|
||||
Role variables and defaults are also included!
|
||||
|
||||
Because Ansible tasks, handlers, and other objects are data, these can also be encrypted with vault.
|
||||
If you'd like to not expose what variables you are using, you can keep an individual task file entirely encrypted.
|
||||
'''
|
||||
|
||||
name = 'ansible-vault'
|
||||
|
||||
FROM_STDIN = "stdin"
|
||||
FROM_ARGS = "the command line args"
|
||||
FROM_PROMPT = "the interactive prompt"
|
||||
|
||||
def __init__(self, args):
|
||||
|
||||
self.b_vault_pass = None
|
||||
self.b_new_vault_pass = None
|
||||
self.encrypt_string_read_stdin = False
|
||||
|
||||
self.encrypt_secret = None
|
||||
self.encrypt_vault_id = None
|
||||
self.new_encrypt_secret = None
|
||||
self.new_encrypt_vault_id = None
|
||||
|
||||
super(VaultCLI, self).__init__(args)
|
||||
|
||||
def init_parser(self):
|
||||
super(VaultCLI, self).init_parser(
|
||||
desc="encryption/decryption utility for Ansible data files",
|
||||
epilog="\nSee '%s <command> --help' for more information on a specific command.\n\n" % os.path.basename(sys.argv[0])
|
||||
)
|
||||
|
||||
common = opt_help.ArgumentParser(add_help=False)
|
||||
opt_help.add_vault_options(common)
|
||||
opt_help.add_verbosity_options(common)
|
||||
|
||||
subparsers = self.parser.add_subparsers(dest='action')
|
||||
subparsers.required = True
|
||||
|
||||
output = opt_help.ArgumentParser(add_help=False)
|
||||
output.add_argument('--output', default=None, dest='output_file',
|
||||
help='output file name for encrypt or decrypt; use - for stdout',
|
||||
type=opt_help.unfrack_path())
|
||||
|
||||
# For encrypting actions, we can also specify which of multiple vault ids should be used for encrypting
|
||||
vault_id = opt_help.ArgumentParser(add_help=False)
|
||||
vault_id.add_argument('--encrypt-vault-id', default=[], dest='encrypt_vault_id',
|
||||
action='store', type=str,
|
||||
help='the vault id used to encrypt (required if more than one vault-id is provided)')
|
||||
|
||||
create_parser = subparsers.add_parser('create', help='Create new vault encrypted file', parents=[vault_id, common])
|
||||
create_parser.set_defaults(func=self.execute_create)
|
||||
create_parser.add_argument('args', help='Filename', metavar='file_name', nargs='*')
|
||||
create_parser.add_argument('--skip-tty-check', default=False, help='allows editor to be opened when no tty attached',
|
||||
dest='skip_tty_check', action='store_true')
|
||||
|
||||
decrypt_parser = subparsers.add_parser('decrypt', help='Decrypt vault encrypted file', parents=[output, common])
|
||||
decrypt_parser.set_defaults(func=self.execute_decrypt)
|
||||
decrypt_parser.add_argument('args', help='Filename', metavar='file_name', nargs='*')
|
||||
|
||||
edit_parser = subparsers.add_parser('edit', help='Edit vault encrypted file', parents=[vault_id, common])
|
||||
edit_parser.set_defaults(func=self.execute_edit)
|
||||
edit_parser.add_argument('args', help='Filename', metavar='file_name', nargs='*')
|
||||
|
||||
view_parser = subparsers.add_parser('view', help='View vault encrypted file', parents=[common])
|
||||
view_parser.set_defaults(func=self.execute_view)
|
||||
view_parser.add_argument('args', help='Filename', metavar='file_name', nargs='*')
|
||||
|
||||
encrypt_parser = subparsers.add_parser('encrypt', help='Encrypt YAML file', parents=[common, output, vault_id])
|
||||
encrypt_parser.set_defaults(func=self.execute_encrypt)
|
||||
encrypt_parser.add_argument('args', help='Filename', metavar='file_name', nargs='*')
|
||||
|
||||
enc_str_parser = subparsers.add_parser('encrypt_string', help='Encrypt a string', parents=[common, output, vault_id])
|
||||
enc_str_parser.set_defaults(func=self.execute_encrypt_string)
|
||||
enc_str_parser.add_argument('args', help='String to encrypt', metavar='string_to_encrypt', nargs='*')
|
||||
enc_str_parser.add_argument('-p', '--prompt', dest='encrypt_string_prompt',
|
||||
action='store_true',
|
||||
help="Prompt for the string to encrypt")
|
||||
enc_str_parser.add_argument('--show-input', dest='show_string_input', default=False, action='store_true',
|
||||
help='Do not hide input when prompted for the string to encrypt')
|
||||
enc_str_parser.add_argument('-n', '--name', dest='encrypt_string_names',
|
||||
action='append',
|
||||
help="Specify the variable name")
|
||||
enc_str_parser.add_argument('--stdin-name', dest='encrypt_string_stdin_name',
|
||||
default=None,
|
||||
help="Specify the variable name for stdin")
|
||||
|
||||
rekey_parser = subparsers.add_parser('rekey', help='Re-key a vault encrypted file', parents=[common, vault_id])
|
||||
rekey_parser.set_defaults(func=self.execute_rekey)
|
||||
rekey_new_group = rekey_parser.add_mutually_exclusive_group()
|
||||
rekey_new_group.add_argument('--new-vault-password-file', default=None, dest='new_vault_password_file',
|
||||
help="new vault password file for rekey", type=opt_help.unfrack_path())
|
||||
rekey_new_group.add_argument('--new-vault-id', default=None, dest='new_vault_id', type=str,
|
||||
help='the new vault identity to use for rekey')
|
||||
rekey_parser.add_argument('args', help='Filename', metavar='file_name', nargs='*')
|
||||
|
||||
def post_process_args(self, options):
|
||||
options = super(VaultCLI, self).post_process_args(options)
|
||||
|
||||
display.verbosity = options.verbosity
|
||||
|
||||
if options.vault_ids:
|
||||
for vault_id in options.vault_ids:
|
||||
if u';' in vault_id:
|
||||
raise AnsibleOptionsError("'%s' is not a valid vault id. The character ';' is not allowed in vault ids" % vault_id)
|
||||
|
||||
if getattr(options, 'output_file', None) and len(options.args) > 1:
|
||||
raise AnsibleOptionsError("At most one input file may be used with the --output option")
|
||||
|
||||
if options.action == 'encrypt_string':
|
||||
if '-' in options.args or options.encrypt_string_stdin_name or (not options.args and not options.encrypt_string_prompt):
|
||||
# prompting from stdin and reading from stdin are mutually exclusive, if stdin is still provided, it is ignored
|
||||
self.encrypt_string_read_stdin = True
|
||||
|
||||
if options.encrypt_string_prompt and self.encrypt_string_read_stdin:
|
||||
# should only trigger if prompt + either - or encrypt string stdin name were provided
|
||||
raise AnsibleOptionsError('The --prompt option is not supported if also reading input from stdin')
|
||||
|
||||
return options
|
||||
|
||||
def run(self):
|
||||
super(VaultCLI, self).run()
|
||||
loader = DataLoader()
|
||||
|
||||
# set default restrictive umask
|
||||
old_umask = os.umask(0o077)
|
||||
|
||||
vault_ids = list(context.CLIARGS['vault_ids'])
|
||||
|
||||
# there are 3 types of actions, those that just 'read' (decrypt, view) and only
|
||||
# need to ask for a password once, and those that 'write' (create, encrypt) that
|
||||
# ask for a new password and confirm it, and 'read/write (rekey) that asks for the
|
||||
# old password, then asks for a new one and confirms it.
|
||||
|
||||
default_vault_ids = C.DEFAULT_VAULT_IDENTITY_LIST
|
||||
vault_ids = default_vault_ids + vault_ids
|
||||
|
||||
action = context.CLIARGS['action']
|
||||
|
||||
# TODO: instead of prompting for these before, we could let VaultEditor
|
||||
# call a callback when it needs it.
|
||||
if action in ['decrypt', 'view', 'rekey', 'edit']:
|
||||
vault_secrets = self.setup_vault_secrets(loader, vault_ids=vault_ids,
|
||||
vault_password_files=list(context.CLIARGS['vault_password_files']),
|
||||
ask_vault_pass=context.CLIARGS['ask_vault_pass'])
|
||||
if not vault_secrets:
|
||||
raise AnsibleOptionsError("A vault password is required to use Ansible's Vault")
|
||||
|
||||
if action in ['encrypt', 'encrypt_string', 'create']:
|
||||
|
||||
encrypt_vault_id = None
|
||||
# no --encrypt-vault-id context.CLIARGS['encrypt_vault_id'] for 'edit'
|
||||
if action not in ['edit']:
|
||||
encrypt_vault_id = context.CLIARGS['encrypt_vault_id'] or C.DEFAULT_VAULT_ENCRYPT_IDENTITY
|
||||
|
||||
vault_secrets = None
|
||||
vault_secrets = \
|
||||
self.setup_vault_secrets(loader,
|
||||
vault_ids=vault_ids,
|
||||
vault_password_files=list(context.CLIARGS['vault_password_files']),
|
||||
ask_vault_pass=context.CLIARGS['ask_vault_pass'],
|
||||
create_new_password=True)
|
||||
|
||||
if len(vault_secrets) > 1 and not encrypt_vault_id:
|
||||
raise AnsibleOptionsError("The vault-ids %s are available to encrypt. Specify the vault-id to encrypt with --encrypt-vault-id" %
|
||||
','.join([x[0] for x in vault_secrets]))
|
||||
|
||||
if not vault_secrets:
|
||||
raise AnsibleOptionsError("A vault password is required to use Ansible's Vault")
|
||||
|
||||
encrypt_secret = match_encrypt_secret(vault_secrets,
|
||||
encrypt_vault_id=encrypt_vault_id)
|
||||
|
||||
# only one secret for encrypt for now, use the first vault_id and use its first secret
|
||||
# TODO: exception if more than one?
|
||||
self.encrypt_vault_id = encrypt_secret[0]
|
||||
self.encrypt_secret = encrypt_secret[1]
|
||||
|
||||
if action in ['rekey']:
|
||||
encrypt_vault_id = context.CLIARGS['encrypt_vault_id'] or C.DEFAULT_VAULT_ENCRYPT_IDENTITY
|
||||
# print('encrypt_vault_id: %s' % encrypt_vault_id)
|
||||
# print('default_encrypt_vault_id: %s' % default_encrypt_vault_id)
|
||||
|
||||
# new_vault_ids should only ever be one item, from
|
||||
# load the default vault ids if we are using encrypt-vault-id
|
||||
new_vault_ids = []
|
||||
if encrypt_vault_id:
|
||||
new_vault_ids = default_vault_ids
|
||||
if context.CLIARGS['new_vault_id']:
|
||||
new_vault_ids.append(context.CLIARGS['new_vault_id'])
|
||||
|
||||
new_vault_password_files = []
|
||||
if context.CLIARGS['new_vault_password_file']:
|
||||
new_vault_password_files.append(context.CLIARGS['new_vault_password_file'])
|
||||
|
||||
new_vault_secrets = \
|
||||
self.setup_vault_secrets(loader,
|
||||
vault_ids=new_vault_ids,
|
||||
vault_password_files=new_vault_password_files,
|
||||
ask_vault_pass=context.CLIARGS['ask_vault_pass'],
|
||||
create_new_password=True)
|
||||
|
||||
if not new_vault_secrets:
|
||||
raise AnsibleOptionsError("A new vault password is required to use Ansible's Vault rekey")
|
||||
|
||||
# There is only one new_vault_id currently and one new_vault_secret, or we
|
||||
# use the id specified in --encrypt-vault-id
|
||||
new_encrypt_secret = match_encrypt_secret(new_vault_secrets,
|
||||
encrypt_vault_id=encrypt_vault_id)
|
||||
|
||||
self.new_encrypt_vault_id = new_encrypt_secret[0]
|
||||
self.new_encrypt_secret = new_encrypt_secret[1]
|
||||
|
||||
loader.set_vault_secrets(vault_secrets)
|
||||
|
||||
# FIXME: do we need to create VaultEditor here? its not reused
|
||||
vault = VaultLib(vault_secrets)
|
||||
self.editor = VaultEditor(vault)
|
||||
|
||||
context.CLIARGS['func']()
|
||||
|
||||
# and restore umask
|
||||
os.umask(old_umask)
|
||||
|
||||
def execute_encrypt(self):
|
||||
''' encrypt the supplied file using the provided vault secret '''
|
||||
|
||||
if not context.CLIARGS['args'] and sys.stdin.isatty():
|
||||
display.display("Reading plaintext input from stdin", stderr=True)
|
||||
|
||||
for f in context.CLIARGS['args'] or ['-']:
|
||||
# Fixme: use the correct vau
|
||||
self.editor.encrypt_file(f, self.encrypt_secret,
|
||||
vault_id=self.encrypt_vault_id,
|
||||
output_file=context.CLIARGS['output_file'])
|
||||
|
||||
if sys.stdout.isatty():
|
||||
display.display("Encryption successful", stderr=True)
|
||||
|
||||
@staticmethod
|
||||
def format_ciphertext_yaml(b_ciphertext, indent=None, name=None):
|
||||
indent = indent or 10
|
||||
|
||||
block_format_var_name = ""
|
||||
if name:
|
||||
block_format_var_name = "%s: " % name
|
||||
|
||||
block_format_header = "%s!vault |" % block_format_var_name
|
||||
lines = []
|
||||
vault_ciphertext = to_text(b_ciphertext)
|
||||
|
||||
lines.append(block_format_header)
|
||||
for line in vault_ciphertext.splitlines():
|
||||
lines.append('%s%s' % (' ' * indent, line))
|
||||
|
||||
yaml_ciphertext = '\n'.join(lines)
|
||||
return yaml_ciphertext
|
||||
|
||||
def execute_encrypt_string(self):
|
||||
''' encrypt the supplied string using the provided vault secret '''
|
||||
b_plaintext = None
|
||||
|
||||
# Holds tuples (the_text, the_source_of_the_string, the variable name if its provided).
|
||||
b_plaintext_list = []
|
||||
|
||||
# remove the non-option '-' arg (used to indicate 'read from stdin') from the candidate args so
|
||||
# we don't add it to the plaintext list
|
||||
args = [x for x in context.CLIARGS['args'] if x != '-']
|
||||
|
||||
# We can prompt and read input, or read from stdin, but not both.
|
||||
if context.CLIARGS['encrypt_string_prompt']:
|
||||
msg = "String to encrypt: "
|
||||
|
||||
name = None
|
||||
name_prompt_response = display.prompt('Variable name (enter for no name): ')
|
||||
|
||||
# TODO: enforce var naming rules?
|
||||
if name_prompt_response != "":
|
||||
name = name_prompt_response
|
||||
|
||||
# TODO: could prompt for which vault_id to use for each plaintext string
|
||||
# currently, it will just be the default
|
||||
hide_input = not context.CLIARGS['show_string_input']
|
||||
if hide_input:
|
||||
msg = "String to encrypt (hidden): "
|
||||
else:
|
||||
msg = "String to encrypt:"
|
||||
|
||||
prompt_response = display.prompt(msg, private=hide_input)
|
||||
|
||||
if prompt_response == '':
|
||||
raise AnsibleOptionsError('The plaintext provided from the prompt was empty, not encrypting')
|
||||
|
||||
b_plaintext = to_bytes(prompt_response)
|
||||
b_plaintext_list.append((b_plaintext, self.FROM_PROMPT, name))
|
||||
|
||||
# read from stdin
|
||||
if self.encrypt_string_read_stdin:
|
||||
if sys.stdout.isatty():
|
||||
display.display("Reading plaintext input from stdin. (ctrl-d to end input, twice if your content does not already have a newline)", stderr=True)
|
||||
|
||||
stdin_text = sys.stdin.read()
|
||||
if stdin_text == '':
|
||||
raise AnsibleOptionsError('stdin was empty, not encrypting')
|
||||
|
||||
if sys.stdout.isatty() and not stdin_text.endswith("\n"):
|
||||
display.display("\n")
|
||||
|
||||
b_plaintext = to_bytes(stdin_text)
|
||||
|
||||
# defaults to None
|
||||
name = context.CLIARGS['encrypt_string_stdin_name']
|
||||
b_plaintext_list.append((b_plaintext, self.FROM_STDIN, name))
|
||||
|
||||
# use any leftover args as strings to encrypt
|
||||
# Try to match args up to --name options
|
||||
if context.CLIARGS.get('encrypt_string_names', False):
|
||||
name_and_text_list = list(zip(context.CLIARGS['encrypt_string_names'], args))
|
||||
|
||||
# Some but not enough --name's to name each var
|
||||
if len(args) > len(name_and_text_list):
|
||||
# Trying to avoid ever showing the plaintext in the output, so this warning is vague to avoid that.
|
||||
display.display('The number of --name options do not match the number of args.',
|
||||
stderr=True)
|
||||
display.display('The last named variable will be "%s". The rest will not have'
|
||||
' names.' % context.CLIARGS['encrypt_string_names'][-1],
|
||||
stderr=True)
|
||||
|
||||
# Add the rest of the args without specifying a name
|
||||
for extra_arg in args[len(name_and_text_list):]:
|
||||
name_and_text_list.append((None, extra_arg))
|
||||
|
||||
# if no --names are provided, just use the args without a name.
|
||||
else:
|
||||
name_and_text_list = [(None, x) for x in args]
|
||||
|
||||
# Convert the plaintext text objects to bytestrings and collect
|
||||
for name_and_text in name_and_text_list:
|
||||
name, plaintext = name_and_text
|
||||
|
||||
if plaintext == '':
|
||||
raise AnsibleOptionsError('The plaintext provided from the command line args was empty, not encrypting')
|
||||
|
||||
b_plaintext = to_bytes(plaintext)
|
||||
b_plaintext_list.append((b_plaintext, self.FROM_ARGS, name))
|
||||
|
||||
# TODO: specify vault_id per string?
|
||||
# Format the encrypted strings and any corresponding stderr output
|
||||
outputs = self._format_output_vault_strings(b_plaintext_list, vault_id=self.encrypt_vault_id)
|
||||
|
||||
b_outs = []
|
||||
for output in outputs:
|
||||
err = output.get('err', None)
|
||||
out = output.get('out', '')
|
||||
if err:
|
||||
sys.stderr.write(err)
|
||||
b_outs.append(to_bytes(out))
|
||||
|
||||
# The output must end with a newline to play nice with terminal representation.
|
||||
# Refs:
|
||||
# * https://stackoverflow.com/a/729795/595220
|
||||
# * https://github.com/ansible/ansible/issues/78932
|
||||
b_outs.append(b'')
|
||||
self.editor.write_data(b'\n'.join(b_outs), context.CLIARGS['output_file'] or '-')
|
||||
|
||||
if sys.stdout.isatty():
|
||||
display.display("Encryption successful", stderr=True)
|
||||
|
||||
# TODO: offer block or string ala eyaml
|
||||
|
||||
def _format_output_vault_strings(self, b_plaintext_list, vault_id=None):
|
||||
# If we are only showing one item in the output, we don't need to included commented
|
||||
# delimiters in the text
|
||||
show_delimiter = False
|
||||
if len(b_plaintext_list) > 1:
|
||||
show_delimiter = True
|
||||
|
||||
# list of dicts {'out': '', 'err': ''}
|
||||
output = []
|
||||
|
||||
# Encrypt the plaintext, and format it into a yaml block that can be pasted into a playbook.
|
||||
# For more than one input, show some differentiating info in the stderr output so we can tell them
|
||||
# apart. If we have a var name, we include that in the yaml
|
||||
for index, b_plaintext_info in enumerate(b_plaintext_list):
|
||||
# (the text itself, which input it came from, its name)
|
||||
b_plaintext, src, name = b_plaintext_info
|
||||
|
||||
b_ciphertext = self.editor.encrypt_bytes(b_plaintext, self.encrypt_secret, vault_id=vault_id)
|
||||
|
||||
# block formatting
|
||||
yaml_text = self.format_ciphertext_yaml(b_ciphertext, name=name)
|
||||
|
||||
err_msg = None
|
||||
if show_delimiter:
|
||||
human_index = index + 1
|
||||
if name:
|
||||
err_msg = '# The encrypted version of variable ("%s", the string #%d from %s).\n' % (name, human_index, src)
|
||||
else:
|
||||
err_msg = '# The encrypted version of the string #%d from %s.)\n' % (human_index, src)
|
||||
output.append({'out': yaml_text, 'err': err_msg})
|
||||
|
||||
return output
|
||||
|
||||
def execute_decrypt(self):
|
||||
''' decrypt the supplied file using the provided vault secret '''
|
||||
|
||||
if not context.CLIARGS['args'] and sys.stdin.isatty():
|
||||
display.display("Reading ciphertext input from stdin", stderr=True)
|
||||
|
||||
for f in context.CLIARGS['args'] or ['-']:
|
||||
self.editor.decrypt_file(f, output_file=context.CLIARGS['output_file'])
|
||||
|
||||
if sys.stdout.isatty():
|
||||
display.display("Decryption successful", stderr=True)
|
||||
|
||||
def execute_create(self):
|
||||
''' create and open a file in an editor that will be encrypted with the provided vault secret when closed'''
|
||||
|
||||
if len(context.CLIARGS['args']) != 1:
|
||||
raise AnsibleOptionsError("ansible-vault create can take only one filename argument")
|
||||
|
||||
if sys.stdout.isatty() or context.CLIARGS['skip_tty_check']:
|
||||
self.editor.create_file(context.CLIARGS['args'][0], self.encrypt_secret,
|
||||
vault_id=self.encrypt_vault_id)
|
||||
else:
|
||||
raise AnsibleOptionsError("not a tty, editor cannot be opened")
|
||||
|
||||
def execute_edit(self):
|
||||
''' open and decrypt an existing vaulted file in an editor, that will be encrypted again when closed'''
|
||||
for f in context.CLIARGS['args']:
|
||||
self.editor.edit_file(f)
|
||||
|
||||
def execute_view(self):
|
||||
''' open, decrypt and view an existing vaulted file using a pager using the supplied vault secret '''
|
||||
|
||||
for f in context.CLIARGS['args']:
|
||||
# Note: vault should return byte strings because it could encrypt
|
||||
# and decrypt binary files. We are responsible for changing it to
|
||||
# unicode here because we are displaying it and therefore can make
|
||||
# the decision that the display doesn't have to be precisely what
|
||||
# the input was (leave that to decrypt instead)
|
||||
plaintext = self.editor.plaintext(f)
|
||||
self.pager(to_text(plaintext))
|
||||
|
||||
def execute_rekey(self):
|
||||
''' re-encrypt a vaulted file with a new secret, the previous secret is required '''
|
||||
for f in context.CLIARGS['args']:
|
||||
# FIXME: plumb in vault_id, use the default new_vault_secret for now
|
||||
self.editor.rekey_file(f, self.new_encrypt_secret,
|
||||
self.new_encrypt_vault_id)
|
||||
|
||||
display.display("Rekey successful", stderr=True)
|
||||
|
||||
|
||||
def main(args=None):
|
||||
VaultCLI.cli_executor(args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Binary file not shown.
Binary file not shown.
@ -0,0 +1,63 @@
|
||||
# (c) 2019 Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import annotations
|
||||
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.cli.galaxy import with_collection_artifacts_manager
|
||||
from ansible.galaxy.collection import find_existing_collections
|
||||
from ansible.module_utils.common.text.converters import to_bytes
|
||||
from ansible.utils.collection_loader._collection_finder import _get_collection_name_from_path
|
||||
from ansible.utils.display import Display
|
||||
|
||||
display = Display()
|
||||
|
||||
|
||||
@with_collection_artifacts_manager
|
||||
def list_collections(coll_filter=None, search_paths=None, dedupe=True, artifacts_manager=None):
|
||||
|
||||
collections = {}
|
||||
for candidate in list_collection_dirs(search_paths=search_paths, coll_filter=coll_filter, artifacts_manager=artifacts_manager, dedupe=dedupe):
|
||||
collection = _get_collection_name_from_path(candidate)
|
||||
collections[collection] = candidate
|
||||
return collections
|
||||
|
||||
|
||||
@with_collection_artifacts_manager
|
||||
def list_collection_dirs(search_paths=None, coll_filter=None, artifacts_manager=None, dedupe=True):
|
||||
"""
|
||||
Return paths for the specific collections found in passed or configured search paths
|
||||
:param search_paths: list of text-string paths, if none load default config
|
||||
:param coll_filter: limit collections to just the specific namespace or collection, if None all are returned
|
||||
:return: list of collection directory paths
|
||||
"""
|
||||
|
||||
namespace_filter = None
|
||||
collection_filter = None
|
||||
has_pure_namespace_filter = False # whether at least one coll_filter is a namespace-only filter
|
||||
if coll_filter is not None:
|
||||
if isinstance(coll_filter, str):
|
||||
coll_filter = [coll_filter]
|
||||
namespace_filter = set()
|
||||
for coll_name in coll_filter:
|
||||
if '.' in coll_name:
|
||||
try:
|
||||
namespace, collection = coll_name.split('.')
|
||||
except ValueError:
|
||||
raise AnsibleError("Invalid collection pattern supplied: %s" % coll_name)
|
||||
namespace_filter.add(namespace)
|
||||
if not has_pure_namespace_filter:
|
||||
if collection_filter is None:
|
||||
collection_filter = []
|
||||
collection_filter.append(collection)
|
||||
else:
|
||||
namespace_filter.add(coll_name)
|
||||
has_pure_namespace_filter = True
|
||||
collection_filter = None
|
||||
namespace_filter = sorted(namespace_filter)
|
||||
|
||||
for req in find_existing_collections(search_paths, artifacts_manager, namespace_filter=namespace_filter,
|
||||
collection_filter=collection_filter, dedupe=dedupe):
|
||||
|
||||
if not has_pure_namespace_filter and coll_filter is not None and req.fqcn not in coll_filter:
|
||||
continue
|
||||
yield to_bytes(req.src)
|
||||
@ -0,0 +1,23 @@
|
||||
# (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
'''
|
||||
Compat library for ansible. This contains compatibility definitions for older python
|
||||
When we need to import a module differently depending on python version, do it
|
||||
here. Then in the code we can simply import from compat in order to get what we want.
|
||||
'''
|
||||
from __future__ import annotations
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,19 @@
|
||||
# Copyright: Contributors to the Ansible project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
HAS_IMPORTLIB_RESOURCES = False
|
||||
|
||||
if sys.version_info < (3, 10):
|
||||
try:
|
||||
from importlib_resources import files # type: ignore[import] # pylint: disable=unused-import
|
||||
except ImportError:
|
||||
files = None # type: ignore[assignment]
|
||||
else:
|
||||
HAS_IMPORTLIB_RESOURCES = True
|
||||
else:
|
||||
from importlib.resources import files
|
||||
HAS_IMPORTLIB_RESOURCES = True
|
||||
@ -0,0 +1,32 @@
|
||||
# (c) 2014, 2017 Toshio Kuratomi <tkuratomi@ansible.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import selectors
|
||||
|
||||
from ansible.module_utils.common.warnings import deprecate
|
||||
|
||||
|
||||
sys.modules['ansible.compat.selectors'] = selectors
|
||||
|
||||
|
||||
deprecate(
|
||||
msg='The `ansible.module_utils.compat.selectors` module is deprecated.',
|
||||
version='2.19',
|
||||
)
|
||||
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
2177
ansible/lib/python3.11/site-packages/ansible/config/base.yml
Normal file
2177
ansible/lib/python3.11/site-packages/ansible/config/base.yml
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user