%global _empty_manifest_terminate_build 0 Name: python-automatix Version: 1.11.2 Release: 1 Summary: Automation wrapper for bash and python commands License: MIT URL: https://github.com/seibert-media/automatix Source0: https://mirrors.aliyun.com/pypi/web/packages/ba/aa/c43000d059eb5297515067d532febc10e9d81c3a43d9e579a7e3539aa087/automatix-1.11.2.tar.gz BuildArch: noarch Requires: python3-pyyaml Requires: python3-importlib-metadata Requires: python3-argcomplete %description # automatix Automation wrapper for bash and python commands # DESCRIPTION **automatix** is a wrapper for scripted sysadmin tasks. It offers some useful functionality for easier scripting and having full control over the automated process. The idea of **automatix** is to write down all the commands you would normally type to your commandline or python console into a YAML file. Then use **automatix** to execute these commands. There are different modes for **automatix** to work. Without any parameters automatix will try to execute the specified command pipeline from the script file until an error occurs or the pipeline is done. The interactive mode (**-i**) asks for every single commandline step whether to execute, skip or abort. Forced mode (**-f**) will also proceed if errors occur. **automatix** is originally designed for internal //SEIBERT/MEDIA use. It comes therefore with bundlewrap and teamvault support as well as the possibility to use your own logging library. ## Warning: Beware that this tool cannot substitute the system administrators brain and it needs a responsible handling, since you can do (and destroy) almost everything with it. **Automatix** evaluates YAML files and executes defined commands as shell or python commands. There is no check for harmful commands. Be aware that this can cause critical damage to your system. Please use the interactive mode and doublecheck commands before executing. Usage of automatix is at your own risk! # INSTALLATION Automatix requires Python ≥ 3.6. ``` pip install automatix ``` # CONFIGURATION You can specify a path to a configuration YAML file via the environment variable **AUTOMATIX_CONFIG**. Default location is "~/.automatix.cfg.yaml". ### Example: .automatix.cfg.yaml # Path to scripts directory script_dir: ~/automatix_script_files # Global constants for use in pipeline scripts constants: apt_update: 'apt-get -qy update' apt_upgrade: 'DEBIAN_FRONTEND=noninteractive apt-get -qy -o Dpkg::Options::=--force-confold --no-install-recommends upgrade' apt_full_upgrade: 'DEBIAN_FRONTEND=noninteractive apt-get -qy -o Dpkg::Options::=--force-confold --no-install-recommends full-upgrade' # Encoding encoding: utf-8 # Path for shell imports import_path: '.' # SSH Command used for remote connections ssh_cmd: 'ssh {hostname} sudo ' # Temporary directory on remote machines for shell imports remote_tmp_dir: 'automatix_tmp' # Logger logger: mylogger # Logging library (has to implement the init_logger method) logging_lib: mylib.logging # Bundlewrap support, bundlewrap has to be installed (default: false) bundlewrap: true # Teamvault / Secret support, bundlewrap-teamvault has to be installed (default: false) teamvault: true # SYNOPSIS **automatix** \[**--help**|**-h**\] \[**--systems** \[_SYSTEM1=ADDRESS_OR_NODENAME_ ...\]\] \[**--vars** \[_VAR1=VALUE1_ ...\]\] \[**--secrets** \[_SECRET1=SECRETID_ ...\]\] \[**--vars-file** _VARS_FILE_PATH_ \] \[**--print-overview**|**-p**\] \[**--jump-to**|**-j** _JUMP_TO_\] \[**--interactive**|**-i**\] \[**--force**|**-f**\] \[**--debug**|**-d**\] \[**--**\] **scriptfile** ## OPTIONS **scriptfile** : The only required parameter for this tool to work. Use " -- " if needed to delimit this from argument fields. See **SCRIPTFILE** section for more information. **-h**, **--help** : View help message and exit. **--systems** _SYSTEM1=ADDRESS_OR_NODENAME_ : Use this to set systems without adding them to the scriptfile or to overwrite them. You can specify multiple systems like: --systems v1=string1 v2=string2 v3=string3 **--vars** _VAR1=VALUE1_ : Use this to set vars without adding them to the scriptfile or to overwrite them. You can specify multiple vars like: --vars v1=string1 v2=string2 v3=string3 **--secrets** _SECRET1=SECRETID_ : Use this to set secrets without adding them to the scriptfile or to overwrite them. You can specify multiple secrets like: --secrets v1=string1 v2=string2 v3=string3 *(only if teamvault is enabled)* **--vars-file** _VARS_FILE_PATH_ : Use this to specify a CSV file from where **automatix** reads systems, variables and secrets. First row must contain the field types and names. You may also specify an `label` field. Example: `label,systems:mysystem,vars:myvar`. The automatix script will be processed for each row sequentially. **--print-overview**, **-p** : Just print command pipeline overview with indices then exit without executing the commandline. Note that the *always pipeline* will be executed anyway. **--jump-to** _JUMP_TO_, **-j** _JUMP_TO_ : Jump to step with index _JUMP_TO_ instead of starting at the beginning. Use **-p** or the output messages to determine the desired step index. You can use negative numbers to start counting from the end. **--interactive**, **-i** : Confirm actions before executing. **--force**, **-f** : Try always to proceed (except manual steps), even if errors occur (no retries). **--debug**, **-d** : Activate debug log level. ### EXAMPLE: Usage automatix -i --systems source=sourcesystem.com target=targetsystem.org -- scriptfile.yaml ## SCRIPTFILE The **scriptfile** describes your automated process. Therefore it contains information about systems, variables, secrets and the command pipeline. You can provide a path to your **scriptfile** or place your scriptfile in the predefined directory (see **CONFIGURATION** section, _script_dir_). The path has precedence over the predefined directory, if the file exists at both locations. The **scriptfile** has to contain valid YAML. ### EXAMPLE: scriptfile name: Migration Server XY # Systems you like to refer to in pipeline (accessible via 'SYSTEMS.source') # If Bundlewrap support is activated use node names instead of hostnames or add preceeding 'hostname!'. require_version: '1.5.0' systems: source: sourcesystem.com target: targetsystem.org # Custom vars to use in pipeline vars: version: 1.2.3 domain: 'bla.mein-test-system' # Teamvault Secrets, if activated (left: like vars, right: SECRETID_FIELD, FIELD=username|password|file) secrets: web_user: v6GQag_username web_pw: v6GQag_password # Imports for functions you like to use (path may be modified in configuration) imports: - myfunctions.sh # like command pipeline but will be exectuted always beforehand always: - python: | import mylib as nc PERSISTENT_VARS.update(locals()) pipeline: - remote@target: systemctl stop server - remote@source: zfs snapshot -r tank@before-migration - manual: Please trigger preparing tasks via webinterface - myvar=local: curl -L -vvv -k https://{domain}/ - local: echo "1.1.1.1 {domain}" >> /etc/hosts - sla=python: NODES.source.metadata.get('sla') - python: | sla = '{sla}' if sla == 'gold': print('Wow that\'s pretty cool. You have SLA Gold.') else: print('Oh. Running out of money? SLA Gold is worth it. You should check your wallet.') PERSISTENT_VARS['sla'] = sla - cond=python: sla == 'gold' - cond?local: echo "This command is only executed if sla is gold." cleanup: - local: rm temp_files ### FIELDS **name** _(string)_ : Just a name for the process. Does not do anything. **require_version** _(string)_ : Minimum required Automatix version for this script to run. **systems** _(associative array)_ : Define some systems. Value has to be a valid SSH destination like an IP address or hostname. If Bundlewrap support is enabled, it has to be a valid and existing Bundlewrap nodename or you can preceed your IP or hostname with `hostname!` to define a non-Bundlewrap system. You can refer to these systems in the command pipeline in multiple ways: 1) remote@systemname as your command action (see below) 2) via {SYSTEMS.systemname} which will be replaced with the value 3) via NODES.systemname in python actions to use the Bundlewrap node object (Bundlewrap systems only) **vars** _(associative array)_ : Define some vars. These are accessible in the command pipeline via {varname}. Note: Only valid Python variable names are allowed. You can use "*FILE_*" prefix followed by a file path to assign the file content to the variable. **secrets** _(associative array)_ : Define teamvault secrets. Value has to be in this format: _SECRETID_FIELD_. _FIELD_ must be one of username, password or file. The resolved secret values are accessible in command line via {secretname}. *(only if teamvault is enabled)* **imports** _(list)_ : Listed shell files (see **CONFIGURATION** section, _import_path_) will be sourced before every local or remote command execution. For remote commands, these files are transferred via tar and ssh to your home directory on the remote system beforehand and deleted afterwards. This is meant to define some functions you may need. **always**, **cleanup** _(list of associative arrays)_ : See **ALWAYS / CLEANUP PIPELINE** section. **pipeline** _(list of associative arrays)_ : See **PIPELINE** section. ### PIPELINE Here you define the commands automatix shall execute. **KEY**: One of these possible command actions: 1) **manual**: Some manual instruction for the user. The user has to confirm, that automatix may proceed. 2) **local**: Local shell command to execute. Imports will be sourced beforehand. /bin/bash will be used for execution. 3) **remote@systemname**: Remote shell command to execute. Systemname has to be a defined system. The command will be run via SSH (without pseudo-terminal allocation). It uses the standard SSH command. Therefore your .ssh/config should be respected. 4) **python**: Python code to execute. * Notice that there are some modules, constants and functions which are already imported (check command.py): e.g. `re, subprocess, quote(from shlex)`. The variable `vars` is used to store the Automatix variables as a dictionary. You can use it to access or change the variables directly. * If bundlewrap is enabled, the Bundlewrap repository object is available via AUTOMATIX_BW_REPO and system node objects are available via NODES.systemname. Use `AUTOMATIX_BW_REPO.reload()` to reinitialize the Bundlewrap repository from the file system. This can be useful for using newly created nodes (e.g. remote commands). **ASSIGNMENT**: For **local**, **remote** and **python** action you can also define a variable to which the output will be assigned. To do this prefix the desired variablename and = before the action key, e.g. `myvar=python: NODES.system.hostname`. Be careful when working with multiline statements. In **python** the first line is likely to set the variable. All variables will be converted to strings when used to build commands in following steps. **CONDITIONS**: You can define the command only to be executed if your condition variable evaluates to "True" in Python. To achieve this write the variable name followed by a question mark at the very beginning like `cond?python: destroy_system()`. Be aware that all output from **local** or **remote** commands will lead to a non-empty string which evaluates to "True" in Python, but empty output will evaluate to "False". **VALUE**: Your command. Variables will be replaced with Python format function. Therefore, use curly brackets to refer to variables, systems, secrets and constants. Constants are available via CONST.KEY, where KEY is the key of your constants in your **CONFIGURATION** file. There you can define some widely used constants. In most cases its a good idea to define your command in quotes to avoid parsing errors, but it is not always necessary. Another way is to use '|' to indicate a _literal scalar block_. There you can even define whole program structures for python (see example). #### Escaping in Pipeline Because automatix uses Python's format() function: `{` -> `{{` `}` -> `}}` Standard YAML escapes (see also https://yaml.org/spec/1.2/spec.html): `'` -> `''` `"` -> `\"` `\ ` -> `\\` `:` -> Please use quotes (double or single). ### ALWAYS / CLEANUP PIPELINE Same usage as the 'normal' command pipeline, but will be executed every time at start of automatix (**always**) or at the end (**cleanup**) even if aborted (a). The commands are executed without --interactive flag, independend of the specified parameters. Intended use case for **always**: python imports or informations that are needed afterwards and do not change anything on systems. You want to have these available even if using --jump|-j feature. Intended use case for **cleanup**: Remove temporary files or artifacts. ## ENVIRONMENT **AUTOMATIX_CONFIG**: Specify the path to the configuration file. Default is "~/.automatix.cfg.yaml". **AUTOMATIX_TIME**: Set this to an arbitrary value to print the times for the single steps and the whole script. **ENCODING**: Specify output encoding. Default is "UTF-8". Additionally you can modify the environment to adjust things to your needs. # TIPS & TRICKS If you want to access variables in **python** action you defined in preceeding command, you can use the **PERSISTENT_VARS** dictionary (shortcut: **PVARS**). This is added to the local scope of **python** actions and the dictonary keys are also available as attributes. Examples: - To make all local variables of the actual command persistent use `PERSISTENT_VARS.update(locals())`. - To delete one persistent variable named "myvar" use `del PERSISTENT_VARS['myvar']` - To make variable "v2" persistent use `PERSISTENT_VARS['v2'] = v2` or `PERSISTENT_VARS.v2 = v2` - Use the shortcut like `PVARS.v2 = v2` You can use variables in PERSISTENT_VARS also as condition by using the shortcut and the attribute notation: - python: PVARS.cond = some_function() - PVARS.cond?local: echo 'This is only printed if "some_function" evaluates to "True"' An alternative is to make variables global, but in most cases using PERSISTENT_VARS is more clean. _**CAUTION: Choosing already existing (Python) variable names may lead to unexpected behaviour!!!**_ Maybe you want to check the source code (command.py). Explanation: automatix is written in Python and uses 'exec' to execute the command in function context. If you declare variables globally they remain across commands. To abort the current automatix and jump to the next batch item you can raise the `SkipBatchItemException`. For aborting the whole automatix process raise `AbortException(return_code: int)`. In both cases the cleanup pipeline is executed. Same is the case for selecting `a`:abort or `c`:continue when asked (interactive or error). # BEST PRACTISES There are different ways to start scripting with **automatix**. The author's approach is mainly to consider the process and simply write down, what to do (manual steps for complex or not automated steps) and which commands to use. Then start **automatix** in interactive mode (-i) and adjust the single steps one by one. Replace manual steps, if suitable. Whenever adjustment is needed, abort, adjust and restart **automatix** with jump (-j) to the adjusted step. Repeat this procedure to automate more and more and increase quality, whenever you feel like it. Consider to put often used paths or code sequences in automatix variables for better readability. Do the same with variable content like URLs, to make it possible to overwrite it by command line options. Where ever possible prefer to use functions to determine already available information, such as BW metadata, instead of defining things explicitly. This will make things easier when using the script with different systems / parameters. Preferred way of using **automatix** is to put often used and complex algorithms in shell functions or python libraries (shelllib/pylib) and import them. Advantage of this approach is that you can use your implemented functions multiple times and build up a toolbox of nice functionality over time. # NOTES **Manual steps** will always cause automatix to stop and wait for user input. Be careful with **assignments** containing line breaks (echo, ...). Using the variables may lead to unexpected behaviour or errors. Assignments containing **null bytes** are currently not supported. Because the **always** pipeline should not change anything, aborting while running this pipeline will not trigger a cleanup. If you want to abort the **pipeline** without triggering the **cleanup** pipeline, use CRTL+C. While **aborting remote functions** (via imports), automatix is not able to determine still running processes invoked by the function, because it only checks the processes for the commands (in this case the function name) which is called in the pipeline. # EXTRAS ## Bash completion (experimental) Automatix supports bash completion for parameters and the script directory via [argcomplete](https://github.com/kislyuk/argcomplete). Therefor follow the installation instructions for argcomplete, which is at the current time pip install argcomplete and either global activation via executing activate-global-python-argcomplete or activation for automatix (e.g. in `.bashrc`) eval "$(register-python-argcomplete automatix)" Automatix will recognize the installed module and offer the completion automatically. %package -n python3-automatix Summary: Automation wrapper for bash and python commands Provides: python-automatix BuildRequires: python3-devel BuildRequires: python3-setuptools BuildRequires: python3-pip %description -n python3-automatix # automatix Automation wrapper for bash and python commands # DESCRIPTION **automatix** is a wrapper for scripted sysadmin tasks. It offers some useful functionality for easier scripting and having full control over the automated process. The idea of **automatix** is to write down all the commands you would normally type to your commandline or python console into a YAML file. Then use **automatix** to execute these commands. There are different modes for **automatix** to work. Without any parameters automatix will try to execute the specified command pipeline from the script file until an error occurs or the pipeline is done. The interactive mode (**-i**) asks for every single commandline step whether to execute, skip or abort. Forced mode (**-f**) will also proceed if errors occur. **automatix** is originally designed for internal //SEIBERT/MEDIA use. It comes therefore with bundlewrap and teamvault support as well as the possibility to use your own logging library. ## Warning: Beware that this tool cannot substitute the system administrators brain and it needs a responsible handling, since you can do (and destroy) almost everything with it. **Automatix** evaluates YAML files and executes defined commands as shell or python commands. There is no check for harmful commands. Be aware that this can cause critical damage to your system. Please use the interactive mode and doublecheck commands before executing. Usage of automatix is at your own risk! # INSTALLATION Automatix requires Python ≥ 3.6. ``` pip install automatix ``` # CONFIGURATION You can specify a path to a configuration YAML file via the environment variable **AUTOMATIX_CONFIG**. Default location is "~/.automatix.cfg.yaml". ### Example: .automatix.cfg.yaml # Path to scripts directory script_dir: ~/automatix_script_files # Global constants for use in pipeline scripts constants: apt_update: 'apt-get -qy update' apt_upgrade: 'DEBIAN_FRONTEND=noninteractive apt-get -qy -o Dpkg::Options::=--force-confold --no-install-recommends upgrade' apt_full_upgrade: 'DEBIAN_FRONTEND=noninteractive apt-get -qy -o Dpkg::Options::=--force-confold --no-install-recommends full-upgrade' # Encoding encoding: utf-8 # Path for shell imports import_path: '.' # SSH Command used for remote connections ssh_cmd: 'ssh {hostname} sudo ' # Temporary directory on remote machines for shell imports remote_tmp_dir: 'automatix_tmp' # Logger logger: mylogger # Logging library (has to implement the init_logger method) logging_lib: mylib.logging # Bundlewrap support, bundlewrap has to be installed (default: false) bundlewrap: true # Teamvault / Secret support, bundlewrap-teamvault has to be installed (default: false) teamvault: true # SYNOPSIS **automatix** \[**--help**|**-h**\] \[**--systems** \[_SYSTEM1=ADDRESS_OR_NODENAME_ ...\]\] \[**--vars** \[_VAR1=VALUE1_ ...\]\] \[**--secrets** \[_SECRET1=SECRETID_ ...\]\] \[**--vars-file** _VARS_FILE_PATH_ \] \[**--print-overview**|**-p**\] \[**--jump-to**|**-j** _JUMP_TO_\] \[**--interactive**|**-i**\] \[**--force**|**-f**\] \[**--debug**|**-d**\] \[**--**\] **scriptfile** ## OPTIONS **scriptfile** : The only required parameter for this tool to work. Use " -- " if needed to delimit this from argument fields. See **SCRIPTFILE** section for more information. **-h**, **--help** : View help message and exit. **--systems** _SYSTEM1=ADDRESS_OR_NODENAME_ : Use this to set systems without adding them to the scriptfile or to overwrite them. You can specify multiple systems like: --systems v1=string1 v2=string2 v3=string3 **--vars** _VAR1=VALUE1_ : Use this to set vars without adding them to the scriptfile or to overwrite them. You can specify multiple vars like: --vars v1=string1 v2=string2 v3=string3 **--secrets** _SECRET1=SECRETID_ : Use this to set secrets without adding them to the scriptfile or to overwrite them. You can specify multiple secrets like: --secrets v1=string1 v2=string2 v3=string3 *(only if teamvault is enabled)* **--vars-file** _VARS_FILE_PATH_ : Use this to specify a CSV file from where **automatix** reads systems, variables and secrets. First row must contain the field types and names. You may also specify an `label` field. Example: `label,systems:mysystem,vars:myvar`. The automatix script will be processed for each row sequentially. **--print-overview**, **-p** : Just print command pipeline overview with indices then exit without executing the commandline. Note that the *always pipeline* will be executed anyway. **--jump-to** _JUMP_TO_, **-j** _JUMP_TO_ : Jump to step with index _JUMP_TO_ instead of starting at the beginning. Use **-p** or the output messages to determine the desired step index. You can use negative numbers to start counting from the end. **--interactive**, **-i** : Confirm actions before executing. **--force**, **-f** : Try always to proceed (except manual steps), even if errors occur (no retries). **--debug**, **-d** : Activate debug log level. ### EXAMPLE: Usage automatix -i --systems source=sourcesystem.com target=targetsystem.org -- scriptfile.yaml ## SCRIPTFILE The **scriptfile** describes your automated process. Therefore it contains information about systems, variables, secrets and the command pipeline. You can provide a path to your **scriptfile** or place your scriptfile in the predefined directory (see **CONFIGURATION** section, _script_dir_). The path has precedence over the predefined directory, if the file exists at both locations. The **scriptfile** has to contain valid YAML. ### EXAMPLE: scriptfile name: Migration Server XY # Systems you like to refer to in pipeline (accessible via 'SYSTEMS.source') # If Bundlewrap support is activated use node names instead of hostnames or add preceeding 'hostname!'. require_version: '1.5.0' systems: source: sourcesystem.com target: targetsystem.org # Custom vars to use in pipeline vars: version: 1.2.3 domain: 'bla.mein-test-system' # Teamvault Secrets, if activated (left: like vars, right: SECRETID_FIELD, FIELD=username|password|file) secrets: web_user: v6GQag_username web_pw: v6GQag_password # Imports for functions you like to use (path may be modified in configuration) imports: - myfunctions.sh # like command pipeline but will be exectuted always beforehand always: - python: | import mylib as nc PERSISTENT_VARS.update(locals()) pipeline: - remote@target: systemctl stop server - remote@source: zfs snapshot -r tank@before-migration - manual: Please trigger preparing tasks via webinterface - myvar=local: curl -L -vvv -k https://{domain}/ - local: echo "1.1.1.1 {domain}" >> /etc/hosts - sla=python: NODES.source.metadata.get('sla') - python: | sla = '{sla}' if sla == 'gold': print('Wow that\'s pretty cool. You have SLA Gold.') else: print('Oh. Running out of money? SLA Gold is worth it. You should check your wallet.') PERSISTENT_VARS['sla'] = sla - cond=python: sla == 'gold' - cond?local: echo "This command is only executed if sla is gold." cleanup: - local: rm temp_files ### FIELDS **name** _(string)_ : Just a name for the process. Does not do anything. **require_version** _(string)_ : Minimum required Automatix version for this script to run. **systems** _(associative array)_ : Define some systems. Value has to be a valid SSH destination like an IP address or hostname. If Bundlewrap support is enabled, it has to be a valid and existing Bundlewrap nodename or you can preceed your IP or hostname with `hostname!` to define a non-Bundlewrap system. You can refer to these systems in the command pipeline in multiple ways: 1) remote@systemname as your command action (see below) 2) via {SYSTEMS.systemname} which will be replaced with the value 3) via NODES.systemname in python actions to use the Bundlewrap node object (Bundlewrap systems only) **vars** _(associative array)_ : Define some vars. These are accessible in the command pipeline via {varname}. Note: Only valid Python variable names are allowed. You can use "*FILE_*" prefix followed by a file path to assign the file content to the variable. **secrets** _(associative array)_ : Define teamvault secrets. Value has to be in this format: _SECRETID_FIELD_. _FIELD_ must be one of username, password or file. The resolved secret values are accessible in command line via {secretname}. *(only if teamvault is enabled)* **imports** _(list)_ : Listed shell files (see **CONFIGURATION** section, _import_path_) will be sourced before every local or remote command execution. For remote commands, these files are transferred via tar and ssh to your home directory on the remote system beforehand and deleted afterwards. This is meant to define some functions you may need. **always**, **cleanup** _(list of associative arrays)_ : See **ALWAYS / CLEANUP PIPELINE** section. **pipeline** _(list of associative arrays)_ : See **PIPELINE** section. ### PIPELINE Here you define the commands automatix shall execute. **KEY**: One of these possible command actions: 1) **manual**: Some manual instruction for the user. The user has to confirm, that automatix may proceed. 2) **local**: Local shell command to execute. Imports will be sourced beforehand. /bin/bash will be used for execution. 3) **remote@systemname**: Remote shell command to execute. Systemname has to be a defined system. The command will be run via SSH (without pseudo-terminal allocation). It uses the standard SSH command. Therefore your .ssh/config should be respected. 4) **python**: Python code to execute. * Notice that there are some modules, constants and functions which are already imported (check command.py): e.g. `re, subprocess, quote(from shlex)`. The variable `vars` is used to store the Automatix variables as a dictionary. You can use it to access or change the variables directly. * If bundlewrap is enabled, the Bundlewrap repository object is available via AUTOMATIX_BW_REPO and system node objects are available via NODES.systemname. Use `AUTOMATIX_BW_REPO.reload()` to reinitialize the Bundlewrap repository from the file system. This can be useful for using newly created nodes (e.g. remote commands). **ASSIGNMENT**: For **local**, **remote** and **python** action you can also define a variable to which the output will be assigned. To do this prefix the desired variablename and = before the action key, e.g. `myvar=python: NODES.system.hostname`. Be careful when working with multiline statements. In **python** the first line is likely to set the variable. All variables will be converted to strings when used to build commands in following steps. **CONDITIONS**: You can define the command only to be executed if your condition variable evaluates to "True" in Python. To achieve this write the variable name followed by a question mark at the very beginning like `cond?python: destroy_system()`. Be aware that all output from **local** or **remote** commands will lead to a non-empty string which evaluates to "True" in Python, but empty output will evaluate to "False". **VALUE**: Your command. Variables will be replaced with Python format function. Therefore, use curly brackets to refer to variables, systems, secrets and constants. Constants are available via CONST.KEY, where KEY is the key of your constants in your **CONFIGURATION** file. There you can define some widely used constants. In most cases its a good idea to define your command in quotes to avoid parsing errors, but it is not always necessary. Another way is to use '|' to indicate a _literal scalar block_. There you can even define whole program structures for python (see example). #### Escaping in Pipeline Because automatix uses Python's format() function: `{` -> `{{` `}` -> `}}` Standard YAML escapes (see also https://yaml.org/spec/1.2/spec.html): `'` -> `''` `"` -> `\"` `\ ` -> `\\` `:` -> Please use quotes (double or single). ### ALWAYS / CLEANUP PIPELINE Same usage as the 'normal' command pipeline, but will be executed every time at start of automatix (**always**) or at the end (**cleanup**) even if aborted (a). The commands are executed without --interactive flag, independend of the specified parameters. Intended use case for **always**: python imports or informations that are needed afterwards and do not change anything on systems. You want to have these available even if using --jump|-j feature. Intended use case for **cleanup**: Remove temporary files or artifacts. ## ENVIRONMENT **AUTOMATIX_CONFIG**: Specify the path to the configuration file. Default is "~/.automatix.cfg.yaml". **AUTOMATIX_TIME**: Set this to an arbitrary value to print the times for the single steps and the whole script. **ENCODING**: Specify output encoding. Default is "UTF-8". Additionally you can modify the environment to adjust things to your needs. # TIPS & TRICKS If you want to access variables in **python** action you defined in preceeding command, you can use the **PERSISTENT_VARS** dictionary (shortcut: **PVARS**). This is added to the local scope of **python** actions and the dictonary keys are also available as attributes. Examples: - To make all local variables of the actual command persistent use `PERSISTENT_VARS.update(locals())`. - To delete one persistent variable named "myvar" use `del PERSISTENT_VARS['myvar']` - To make variable "v2" persistent use `PERSISTENT_VARS['v2'] = v2` or `PERSISTENT_VARS.v2 = v2` - Use the shortcut like `PVARS.v2 = v2` You can use variables in PERSISTENT_VARS also as condition by using the shortcut and the attribute notation: - python: PVARS.cond = some_function() - PVARS.cond?local: echo 'This is only printed if "some_function" evaluates to "True"' An alternative is to make variables global, but in most cases using PERSISTENT_VARS is more clean. _**CAUTION: Choosing already existing (Python) variable names may lead to unexpected behaviour!!!**_ Maybe you want to check the source code (command.py). Explanation: automatix is written in Python and uses 'exec' to execute the command in function context. If you declare variables globally they remain across commands. To abort the current automatix and jump to the next batch item you can raise the `SkipBatchItemException`. For aborting the whole automatix process raise `AbortException(return_code: int)`. In both cases the cleanup pipeline is executed. Same is the case for selecting `a`:abort or `c`:continue when asked (interactive or error). # BEST PRACTISES There are different ways to start scripting with **automatix**. The author's approach is mainly to consider the process and simply write down, what to do (manual steps for complex or not automated steps) and which commands to use. Then start **automatix** in interactive mode (-i) and adjust the single steps one by one. Replace manual steps, if suitable. Whenever adjustment is needed, abort, adjust and restart **automatix** with jump (-j) to the adjusted step. Repeat this procedure to automate more and more and increase quality, whenever you feel like it. Consider to put often used paths or code sequences in automatix variables for better readability. Do the same with variable content like URLs, to make it possible to overwrite it by command line options. Where ever possible prefer to use functions to determine already available information, such as BW metadata, instead of defining things explicitly. This will make things easier when using the script with different systems / parameters. Preferred way of using **automatix** is to put often used and complex algorithms in shell functions or python libraries (shelllib/pylib) and import them. Advantage of this approach is that you can use your implemented functions multiple times and build up a toolbox of nice functionality over time. # NOTES **Manual steps** will always cause automatix to stop and wait for user input. Be careful with **assignments** containing line breaks (echo, ...). Using the variables may lead to unexpected behaviour or errors. Assignments containing **null bytes** are currently not supported. Because the **always** pipeline should not change anything, aborting while running this pipeline will not trigger a cleanup. If you want to abort the **pipeline** without triggering the **cleanup** pipeline, use CRTL+C. While **aborting remote functions** (via imports), automatix is not able to determine still running processes invoked by the function, because it only checks the processes for the commands (in this case the function name) which is called in the pipeline. # EXTRAS ## Bash completion (experimental) Automatix supports bash completion for parameters and the script directory via [argcomplete](https://github.com/kislyuk/argcomplete). Therefor follow the installation instructions for argcomplete, which is at the current time pip install argcomplete and either global activation via executing activate-global-python-argcomplete or activation for automatix (e.g. in `.bashrc`) eval "$(register-python-argcomplete automatix)" Automatix will recognize the installed module and offer the completion automatically. %package help Summary: Development documents and examples for automatix Provides: python3-automatix-doc %description help # automatix Automation wrapper for bash and python commands # DESCRIPTION **automatix** is a wrapper for scripted sysadmin tasks. It offers some useful functionality for easier scripting and having full control over the automated process. The idea of **automatix** is to write down all the commands you would normally type to your commandline or python console into a YAML file. Then use **automatix** to execute these commands. There are different modes for **automatix** to work. Without any parameters automatix will try to execute the specified command pipeline from the script file until an error occurs or the pipeline is done. The interactive mode (**-i**) asks for every single commandline step whether to execute, skip or abort. Forced mode (**-f**) will also proceed if errors occur. **automatix** is originally designed for internal //SEIBERT/MEDIA use. It comes therefore with bundlewrap and teamvault support as well as the possibility to use your own logging library. ## Warning: Beware that this tool cannot substitute the system administrators brain and it needs a responsible handling, since you can do (and destroy) almost everything with it. **Automatix** evaluates YAML files and executes defined commands as shell or python commands. There is no check for harmful commands. Be aware that this can cause critical damage to your system. Please use the interactive mode and doublecheck commands before executing. Usage of automatix is at your own risk! # INSTALLATION Automatix requires Python ≥ 3.6. ``` pip install automatix ``` # CONFIGURATION You can specify a path to a configuration YAML file via the environment variable **AUTOMATIX_CONFIG**. Default location is "~/.automatix.cfg.yaml". ### Example: .automatix.cfg.yaml # Path to scripts directory script_dir: ~/automatix_script_files # Global constants for use in pipeline scripts constants: apt_update: 'apt-get -qy update' apt_upgrade: 'DEBIAN_FRONTEND=noninteractive apt-get -qy -o Dpkg::Options::=--force-confold --no-install-recommends upgrade' apt_full_upgrade: 'DEBIAN_FRONTEND=noninteractive apt-get -qy -o Dpkg::Options::=--force-confold --no-install-recommends full-upgrade' # Encoding encoding: utf-8 # Path for shell imports import_path: '.' # SSH Command used for remote connections ssh_cmd: 'ssh {hostname} sudo ' # Temporary directory on remote machines for shell imports remote_tmp_dir: 'automatix_tmp' # Logger logger: mylogger # Logging library (has to implement the init_logger method) logging_lib: mylib.logging # Bundlewrap support, bundlewrap has to be installed (default: false) bundlewrap: true # Teamvault / Secret support, bundlewrap-teamvault has to be installed (default: false) teamvault: true # SYNOPSIS **automatix** \[**--help**|**-h**\] \[**--systems** \[_SYSTEM1=ADDRESS_OR_NODENAME_ ...\]\] \[**--vars** \[_VAR1=VALUE1_ ...\]\] \[**--secrets** \[_SECRET1=SECRETID_ ...\]\] \[**--vars-file** _VARS_FILE_PATH_ \] \[**--print-overview**|**-p**\] \[**--jump-to**|**-j** _JUMP_TO_\] \[**--interactive**|**-i**\] \[**--force**|**-f**\] \[**--debug**|**-d**\] \[**--**\] **scriptfile** ## OPTIONS **scriptfile** : The only required parameter for this tool to work. Use " -- " if needed to delimit this from argument fields. See **SCRIPTFILE** section for more information. **-h**, **--help** : View help message and exit. **--systems** _SYSTEM1=ADDRESS_OR_NODENAME_ : Use this to set systems without adding them to the scriptfile or to overwrite them. You can specify multiple systems like: --systems v1=string1 v2=string2 v3=string3 **--vars** _VAR1=VALUE1_ : Use this to set vars without adding them to the scriptfile or to overwrite them. You can specify multiple vars like: --vars v1=string1 v2=string2 v3=string3 **--secrets** _SECRET1=SECRETID_ : Use this to set secrets without adding them to the scriptfile or to overwrite them. You can specify multiple secrets like: --secrets v1=string1 v2=string2 v3=string3 *(only if teamvault is enabled)* **--vars-file** _VARS_FILE_PATH_ : Use this to specify a CSV file from where **automatix** reads systems, variables and secrets. First row must contain the field types and names. You may also specify an `label` field. Example: `label,systems:mysystem,vars:myvar`. The automatix script will be processed for each row sequentially. **--print-overview**, **-p** : Just print command pipeline overview with indices then exit without executing the commandline. Note that the *always pipeline* will be executed anyway. **--jump-to** _JUMP_TO_, **-j** _JUMP_TO_ : Jump to step with index _JUMP_TO_ instead of starting at the beginning. Use **-p** or the output messages to determine the desired step index. You can use negative numbers to start counting from the end. **--interactive**, **-i** : Confirm actions before executing. **--force**, **-f** : Try always to proceed (except manual steps), even if errors occur (no retries). **--debug**, **-d** : Activate debug log level. ### EXAMPLE: Usage automatix -i --systems source=sourcesystem.com target=targetsystem.org -- scriptfile.yaml ## SCRIPTFILE The **scriptfile** describes your automated process. Therefore it contains information about systems, variables, secrets and the command pipeline. You can provide a path to your **scriptfile** or place your scriptfile in the predefined directory (see **CONFIGURATION** section, _script_dir_). The path has precedence over the predefined directory, if the file exists at both locations. The **scriptfile** has to contain valid YAML. ### EXAMPLE: scriptfile name: Migration Server XY # Systems you like to refer to in pipeline (accessible via 'SYSTEMS.source') # If Bundlewrap support is activated use node names instead of hostnames or add preceeding 'hostname!'. require_version: '1.5.0' systems: source: sourcesystem.com target: targetsystem.org # Custom vars to use in pipeline vars: version: 1.2.3 domain: 'bla.mein-test-system' # Teamvault Secrets, if activated (left: like vars, right: SECRETID_FIELD, FIELD=username|password|file) secrets: web_user: v6GQag_username web_pw: v6GQag_password # Imports for functions you like to use (path may be modified in configuration) imports: - myfunctions.sh # like command pipeline but will be exectuted always beforehand always: - python: | import mylib as nc PERSISTENT_VARS.update(locals()) pipeline: - remote@target: systemctl stop server - remote@source: zfs snapshot -r tank@before-migration - manual: Please trigger preparing tasks via webinterface - myvar=local: curl -L -vvv -k https://{domain}/ - local: echo "1.1.1.1 {domain}" >> /etc/hosts - sla=python: NODES.source.metadata.get('sla') - python: | sla = '{sla}' if sla == 'gold': print('Wow that\'s pretty cool. You have SLA Gold.') else: print('Oh. Running out of money? SLA Gold is worth it. You should check your wallet.') PERSISTENT_VARS['sla'] = sla - cond=python: sla == 'gold' - cond?local: echo "This command is only executed if sla is gold." cleanup: - local: rm temp_files ### FIELDS **name** _(string)_ : Just a name for the process. Does not do anything. **require_version** _(string)_ : Minimum required Automatix version for this script to run. **systems** _(associative array)_ : Define some systems. Value has to be a valid SSH destination like an IP address or hostname. If Bundlewrap support is enabled, it has to be a valid and existing Bundlewrap nodename or you can preceed your IP or hostname with `hostname!` to define a non-Bundlewrap system. You can refer to these systems in the command pipeline in multiple ways: 1) remote@systemname as your command action (see below) 2) via {SYSTEMS.systemname} which will be replaced with the value 3) via NODES.systemname in python actions to use the Bundlewrap node object (Bundlewrap systems only) **vars** _(associative array)_ : Define some vars. These are accessible in the command pipeline via {varname}. Note: Only valid Python variable names are allowed. You can use "*FILE_*" prefix followed by a file path to assign the file content to the variable. **secrets** _(associative array)_ : Define teamvault secrets. Value has to be in this format: _SECRETID_FIELD_. _FIELD_ must be one of username, password or file. The resolved secret values are accessible in command line via {secretname}. *(only if teamvault is enabled)* **imports** _(list)_ : Listed shell files (see **CONFIGURATION** section, _import_path_) will be sourced before every local or remote command execution. For remote commands, these files are transferred via tar and ssh to your home directory on the remote system beforehand and deleted afterwards. This is meant to define some functions you may need. **always**, **cleanup** _(list of associative arrays)_ : See **ALWAYS / CLEANUP PIPELINE** section. **pipeline** _(list of associative arrays)_ : See **PIPELINE** section. ### PIPELINE Here you define the commands automatix shall execute. **KEY**: One of these possible command actions: 1) **manual**: Some manual instruction for the user. The user has to confirm, that automatix may proceed. 2) **local**: Local shell command to execute. Imports will be sourced beforehand. /bin/bash will be used for execution. 3) **remote@systemname**: Remote shell command to execute. Systemname has to be a defined system. The command will be run via SSH (without pseudo-terminal allocation). It uses the standard SSH command. Therefore your .ssh/config should be respected. 4) **python**: Python code to execute. * Notice that there are some modules, constants and functions which are already imported (check command.py): e.g. `re, subprocess, quote(from shlex)`. The variable `vars` is used to store the Automatix variables as a dictionary. You can use it to access or change the variables directly. * If bundlewrap is enabled, the Bundlewrap repository object is available via AUTOMATIX_BW_REPO and system node objects are available via NODES.systemname. Use `AUTOMATIX_BW_REPO.reload()` to reinitialize the Bundlewrap repository from the file system. This can be useful for using newly created nodes (e.g. remote commands). **ASSIGNMENT**: For **local**, **remote** and **python** action you can also define a variable to which the output will be assigned. To do this prefix the desired variablename and = before the action key, e.g. `myvar=python: NODES.system.hostname`. Be careful when working with multiline statements. In **python** the first line is likely to set the variable. All variables will be converted to strings when used to build commands in following steps. **CONDITIONS**: You can define the command only to be executed if your condition variable evaluates to "True" in Python. To achieve this write the variable name followed by a question mark at the very beginning like `cond?python: destroy_system()`. Be aware that all output from **local** or **remote** commands will lead to a non-empty string which evaluates to "True" in Python, but empty output will evaluate to "False". **VALUE**: Your command. Variables will be replaced with Python format function. Therefore, use curly brackets to refer to variables, systems, secrets and constants. Constants are available via CONST.KEY, where KEY is the key of your constants in your **CONFIGURATION** file. There you can define some widely used constants. In most cases its a good idea to define your command in quotes to avoid parsing errors, but it is not always necessary. Another way is to use '|' to indicate a _literal scalar block_. There you can even define whole program structures for python (see example). #### Escaping in Pipeline Because automatix uses Python's format() function: `{` -> `{{` `}` -> `}}` Standard YAML escapes (see also https://yaml.org/spec/1.2/spec.html): `'` -> `''` `"` -> `\"` `\ ` -> `\\` `:` -> Please use quotes (double or single). ### ALWAYS / CLEANUP PIPELINE Same usage as the 'normal' command pipeline, but will be executed every time at start of automatix (**always**) or at the end (**cleanup**) even if aborted (a). The commands are executed without --interactive flag, independend of the specified parameters. Intended use case for **always**: python imports or informations that are needed afterwards and do not change anything on systems. You want to have these available even if using --jump|-j feature. Intended use case for **cleanup**: Remove temporary files or artifacts. ## ENVIRONMENT **AUTOMATIX_CONFIG**: Specify the path to the configuration file. Default is "~/.automatix.cfg.yaml". **AUTOMATIX_TIME**: Set this to an arbitrary value to print the times for the single steps and the whole script. **ENCODING**: Specify output encoding. Default is "UTF-8". Additionally you can modify the environment to adjust things to your needs. # TIPS & TRICKS If you want to access variables in **python** action you defined in preceeding command, you can use the **PERSISTENT_VARS** dictionary (shortcut: **PVARS**). This is added to the local scope of **python** actions and the dictonary keys are also available as attributes. Examples: - To make all local variables of the actual command persistent use `PERSISTENT_VARS.update(locals())`. - To delete one persistent variable named "myvar" use `del PERSISTENT_VARS['myvar']` - To make variable "v2" persistent use `PERSISTENT_VARS['v2'] = v2` or `PERSISTENT_VARS.v2 = v2` - Use the shortcut like `PVARS.v2 = v2` You can use variables in PERSISTENT_VARS also as condition by using the shortcut and the attribute notation: - python: PVARS.cond = some_function() - PVARS.cond?local: echo 'This is only printed if "some_function" evaluates to "True"' An alternative is to make variables global, but in most cases using PERSISTENT_VARS is more clean. _**CAUTION: Choosing already existing (Python) variable names may lead to unexpected behaviour!!!**_ Maybe you want to check the source code (command.py). Explanation: automatix is written in Python and uses 'exec' to execute the command in function context. If you declare variables globally they remain across commands. To abort the current automatix and jump to the next batch item you can raise the `SkipBatchItemException`. For aborting the whole automatix process raise `AbortException(return_code: int)`. In both cases the cleanup pipeline is executed. Same is the case for selecting `a`:abort or `c`:continue when asked (interactive or error). # BEST PRACTISES There are different ways to start scripting with **automatix**. The author's approach is mainly to consider the process and simply write down, what to do (manual steps for complex or not automated steps) and which commands to use. Then start **automatix** in interactive mode (-i) and adjust the single steps one by one. Replace manual steps, if suitable. Whenever adjustment is needed, abort, adjust and restart **automatix** with jump (-j) to the adjusted step. Repeat this procedure to automate more and more and increase quality, whenever you feel like it. Consider to put often used paths or code sequences in automatix variables for better readability. Do the same with variable content like URLs, to make it possible to overwrite it by command line options. Where ever possible prefer to use functions to determine already available information, such as BW metadata, instead of defining things explicitly. This will make things easier when using the script with different systems / parameters. Preferred way of using **automatix** is to put often used and complex algorithms in shell functions or python libraries (shelllib/pylib) and import them. Advantage of this approach is that you can use your implemented functions multiple times and build up a toolbox of nice functionality over time. # NOTES **Manual steps** will always cause automatix to stop and wait for user input. Be careful with **assignments** containing line breaks (echo, ...). Using the variables may lead to unexpected behaviour or errors. Assignments containing **null bytes** are currently not supported. Because the **always** pipeline should not change anything, aborting while running this pipeline will not trigger a cleanup. If you want to abort the **pipeline** without triggering the **cleanup** pipeline, use CRTL+C. While **aborting remote functions** (via imports), automatix is not able to determine still running processes invoked by the function, because it only checks the processes for the commands (in this case the function name) which is called in the pipeline. # EXTRAS ## Bash completion (experimental) Automatix supports bash completion for parameters and the script directory via [argcomplete](https://github.com/kislyuk/argcomplete). Therefor follow the installation instructions for argcomplete, which is at the current time pip install argcomplete and either global activation via executing activate-global-python-argcomplete or activation for automatix (e.g. in `.bashrc`) eval "$(register-python-argcomplete automatix)" Automatix will recognize the installed module and offer the completion automatically. %prep %autosetup -n automatix-1.11.2 %build %py3_build %install %py3_install install -d -m755 %{buildroot}/%{_pkgdocdir} if [ -d doc ]; then cp -arf doc %{buildroot}/%{_pkgdocdir}; fi if [ -d docs ]; then cp -arf docs %{buildroot}/%{_pkgdocdir}; fi if [ -d example ]; then cp -arf example %{buildroot}/%{_pkgdocdir}; fi if [ -d examples ]; then cp -arf examples %{buildroot}/%{_pkgdocdir}; fi pushd %{buildroot} if [ -d usr/lib ]; then find usr/lib -type f -printf "\"/%h/%f\"\n" >> filelist.lst fi if [ -d usr/lib64 ]; then find usr/lib64 -type f -printf "\"/%h/%f\"\n" >> filelist.lst fi if [ -d usr/bin ]; then find usr/bin -type f -printf "\"/%h/%f\"\n" >> filelist.lst fi if [ -d usr/sbin ]; then find usr/sbin -type f -printf "\"/%h/%f\"\n" >> filelist.lst fi touch doclist.lst if [ -d usr/share/man ]; then find usr/share/man -type f -printf "\"/%h/%f.gz\"\n" >> doclist.lst fi popd mv %{buildroot}/filelist.lst . mv %{buildroot}/doclist.lst . %files -n python3-automatix -f filelist.lst %dir %{python3_sitelib}/* %files help -f doclist.lst %{_docdir}/* %changelog * Fri Jun 09 2023 Python_Bot - 1.11.2-1 - Package Spec generated