forked from ISPsystem/isp-maintenance
		
	Compare commits
	
		
			27 Commits
		
	
	
		
			platform_s
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| e87c1679e4 | |||
| 6bbebd5a7a | |||
| 99dd122262 | |||
| b84ef86cd0 | |||
| ba6dd53f6c | |||
| 5815c84b07 | |||
| 6c6df42e7f | |||
| da3e61eccb | |||
| e0de805af7 | |||
| 4cdf3b4539 | |||
| d4de07c340 | |||
| dc6e61dcbe | |||
| f773898b71 | |||
| cdf3a92527 | |||
| 8ad5e2a230 | |||
| 5405679640 | |||
| 72bfeb9193 | |||
| bb3b20e6c8 | |||
| 287216d975 | |||
| 0f96d0f956 | |||
| 34e8118327 | |||
| 45f9204c6c | |||
| 1d72b8d015 | |||
| f9a213fe3e | |||
| 867cd1c022 | |||
| 2bc9b2e94f | |||
| c776632195 | 
							
								
								
									
										5
									
								
								.dockerignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.dockerignore
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | |||||||
|  | docker-compose.yml | ||||||
|  | .gitignore | ||||||
|  | .env | ||||||
|  | .git | ||||||
|  | .cache | ||||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -158,7 +158,9 @@ cython_debug/ | |||||||
| #  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore | #  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore | ||||||
| #  and can be added to the global gitignore or merged into this file.  For a more nuclear | #  and can be added to the global gitignore or merged into this file.  For a more nuclear | ||||||
| #  option (not recommended) you can uncomment the following to ignore the entire idea folder. | #  option (not recommended) you can uncomment the following to ignore the entire idea folder. | ||||||
| #.idea/ | .idea/ | ||||||
|  | .vscode/ | ||||||
| 
 | 
 | ||||||
| # Project specific | # Project specific | ||||||
| config.json | config.json | ||||||
|  | **/dummy_platform | ||||||
|  | |||||||
							
								
								
									
										21
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | |||||||
|  | # Changelog | ||||||
|  | 
 | ||||||
|  | ## [0.1.0] - 2024-06-09 | ||||||
|  | ### Added | ||||||
|  | - mgrctl core mvp | ||||||
|  | - auth apps for vm6/dci6 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ## [0.1.1] - 2025-05-07 | ||||||
|  | ### Updated | ||||||
|  | - dependencies | ||||||
|  | - docker hub latest image | ||||||
|  | 
 | ||||||
|  | ### Fixed | ||||||
|  | - dci input container name | ||||||
|  | 
 | ||||||
|  | ### Changed | ||||||
|  | - remove peewee deps | ||||||
|  | - remove data base management | ||||||
|  | - remove mysql container for dev env | ||||||
|  | - remove \__version__ from sub app | ||||||
							
								
								
									
										59
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | |||||||
|  | # app/Dockerfile | ||||||
|  | 
 | ||||||
|  | # pull the official docker image | ||||||
|  | FROM python:3.12-alpine AS poetry-base | ||||||
|  | 
 | ||||||
|  | # default build args | ||||||
|  | ARG APP_VERSION=0.1.1 \ | ||||||
|  |     APP_DIR=/app \ | ||||||
|  |     SRC_DIR=./mgrctl \ | ||||||
|  |     PKG_NAME=mgrctl \ | ||||||
|  |     PKG_VERSION=0.1.1 | ||||||
|  | 
 | ||||||
|  | # set env variables | ||||||
|  | ENV APP_NAME=mgrctl \ | ||||||
|  |     # Python3: | ||||||
|  |     PYTHONFAULTHANDLER=1 \ | ||||||
|  |     PYTHONUNBUFFERED=1 \ | ||||||
|  |     PYTHONHASHSEED=random \ | ||||||
|  |     # Pip: | ||||||
|  |     PIP_NO_CACHE_DIR=on \ | ||||||
|  |     PIP_DISABLE_PIP_VERSION_CHECK=on \ | ||||||
|  |     PIP_ROOT_USER_ACTION=ignore \ | ||||||
|  |     PIP_DEFAULT_TIMEOUT=100 \ | ||||||
|  |     # Poetry: | ||||||
|  |     POETRY_NO_INTERACTION=1 \ | ||||||
|  |     POETRY_VIRTUALENVS_CREATE=false \ | ||||||
|  |     POETRY_CACHE_DIR='/var/cache/pypoetry' \ | ||||||
|  |     POETRY_HOME='/usr/local' \ | ||||||
|  |     POETRY_VERSION=2.1.2 | ||||||
|  | 
 | ||||||
|  | # install system deps | ||||||
|  | RUN apk --no-cache add curl \ | ||||||
|  |     && curl -sSL https://install.python-poetry.org | python3 - \ | ||||||
|  |     && mkdir ${APP_DIR} | ||||||
|  | 
 | ||||||
|  | COPY ./pyproject.toml ./poetry.lock ./README.md ${APP_DIR} | ||||||
|  | COPY ${SRC_DIR} ${APP_DIR}/${PKG_NAME} | ||||||
|  | 
 | ||||||
|  | # set workdir | ||||||
|  | WORKDIR ${APP_DIR} | ||||||
|  | 
 | ||||||
|  | # build pkg whl | ||||||
|  | RUN poetry build --format wheel \ | ||||||
|  |     && pip install ${APP_DIR}/dist/${PKG_NAME}-${PKG_VERSION}-py3-none-any.whl \ | ||||||
|  |     && rm -r /usr/local/venv | ||||||
|  | 
 | ||||||
|  | # now multistage builds | ||||||
|  | FROM python:3.12-alpine | ||||||
|  | 
 | ||||||
|  | # copy app and dependencies | ||||||
|  | COPY --from=poetry-base /usr/local/ /usr/local/ | ||||||
|  | 
 | ||||||
|  | # install bash and mgrctl shell completion | ||||||
|  | RUN apk --no-cache add bash \ | ||||||
|  |     && echo 'eval "$(_MGRCTL_COMPLETE=bash_source mgrctl)"' > ~/.bashrc | ||||||
|  | 
 | ||||||
|  | # "demonize" container | ||||||
|  | # use docker attach mgrctl or docker exec -it mgrctl mgrctl --help for example | ||||||
|  | CMD [ "bash"] | ||||||
							
								
								
									
										13
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | build: | ||||||
|  | 	python3 ./scripts/devtools/dev.py builder docker-compose build | ||||||
|  | destroy: | ||||||
|  | 	python3 ./scripts/devtools/dev.py builder docker-compose destroy | ||||||
|  | up: | ||||||
|  | 	python3 ./scripts/devtools/dev.py runner docker-compose up | ||||||
|  | down: | ||||||
|  | 	python3 ./scripts/devtools/dev.py runner docker-compose down | ||||||
|  | restart: | ||||||
|  | 	python3 ./scripts/devtools/dev.py runner docker-compose restart | ||||||
|  | setup: build up | ||||||
|  | 
 | ||||||
|  | .PHONY: exec setup build | ||||||
							
								
								
									
										172
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										172
									
								
								README.md
									
									
									
									
									
								
							| @ -6,3 +6,175 @@ Maintenance application for quick access, check and resolve issues VM/DCImanager | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| ### See [CONTRIBUTING.md](CONTRIBUTING.md) for a general overview of how to contribute | ### See [CONTRIBUTING.md](CONTRIBUTING.md) for a general overview of how to contribute | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ## How to use gogo companion: | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | #### Dependencies | ||||||
|  | 
 | ||||||
|  | ОС: Linux, Mac, Windows | ||||||
|  | Shell: bash | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | #### Installation | ||||||
|  | 
 | ||||||
|  | - download or copy the script from the root of the repository `/scripts/gogo/gogo.sh` | ||||||
|  | - to call the script without specifying the full path, put it in the directory that is in $PATH (in the future, I assume that you will have it `~/.local/bin/`) | ||||||
|  | 
 | ||||||
|  | ```console | ||||||
|  | curl https://git.isptech.ru/ISPsystem/isp-maintenance/raw/branch/main/scripts/gogo/gogo.sh -o ~/.local/bin/gogo && chmod u+x ~/.local/bin/gogo | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | #### Configuration | ||||||
|  | 
 | ||||||
|  | For the script to work, you need a configuration file with settings `~/.config/gogo/gogo.conf` | ||||||
|  | 
 | ||||||
|  | This config contains the following fields that are read every time you start | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | GO_SERVER_ADDR_RUSSIAN= | ||||||
|  | GO_SERVER_ADDR_GERMANY= | ||||||
|  | GO_SERVER_ADDR_TEST= | ||||||
|  | VAULT_SERVER_ADDR= | ||||||
|  | SSH_PRIVATE_KEY_USER= | ||||||
|  | SSH_PRIVATE_KEY_PATH= | ||||||
|  | SSH_PUBLIC_KEY_PATH= | ||||||
|  | SSH_CRT_FILE= | ||||||
|  | MGRCTL_IMAGE=mois3y/mgrctl:latest | ||||||
|  | DEBUG_MODE=false | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Please fill in the fields with current data, write the path to the keys and certificate in full `/home/username/.ssh/id_ecdsa` and so on. | ||||||
|  | For security reasons, server addresses and paths to keys are not indicated here. | ||||||
|  | 
 | ||||||
|  | For your convenience, the config can be filled out interactively | ||||||
|  | 
 | ||||||
|  | ```console | ||||||
|  | gogo --init | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | #### Usage | ||||||
|  | 
 | ||||||
|  | - Get a temporary ssh certificate | ||||||
|  | 
 | ||||||
|  | ```console | ||||||
|  | gogo --crt | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | - Check your connection to test servers in both locations | ||||||
|  | 
 | ||||||
|  | ```console | ||||||
|  | gogo --test | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | - If the connection is successful, you can use the script. You are beautiful! | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | #### Examples | ||||||
|  | 
 | ||||||
|  | - Connect to BILLmanager 6 via ssh port 22 and interface port 443 | ||||||
|  | 
 | ||||||
|  | ```console | ||||||
|  | gogo --bill my.example.com | ||||||
|  | ``` | ||||||
|  | or | ||||||
|  | ```console | ||||||
|  | gogo --bill my.example.com -p 22 -wp 443 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | - Connect to BILLmanager 6 again without generating an access key to the interface | ||||||
|  | 
 | ||||||
|  | ```console | ||||||
|  | gogo --bill my.example.com --ssh | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | - Connect to DNSmanager 6 via ssh port 22 and interface port 1501 | ||||||
|  | 
 | ||||||
|  | ```console | ||||||
|  | gogo --dns my.example.com -p 22 -wp 1501 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | - Connect to DCImanager 6 via ssh port 2222 and interface 443 port | ||||||
|  | 
 | ||||||
|  | ```console | ||||||
|  | gogo --dci vm.example.com -p 2222 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | - Connect to VMmanager 6 via ssh port 2222 and interface port 443 | ||||||
|  | 
 | ||||||
|  | ```console | ||||||
|  | gogo --vm 228.228.228.228 -p 2222 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | - Connect to VMmanager 6 via ssh port 22 and interface port 443, indicating the id of a specific user and generate 3 keys | ||||||
|  | 
 | ||||||
|  | ```console | ||||||
|  | gogo --vm vm.example.com --mgrctl auth user access --id 1488 --count 1 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | - The same thing, just print the command that is executed on the server side into the console, you can run it later simply by copying it | ||||||
|  | 
 | ||||||
|  | ```console | ||||||
|  | gogo --vm vm.example.com --tty --mgrctl auth user access --id 1488 --count 1 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Connect to VMmanager 6 via ssh port 22 and interface port 443 via DE go3 server | ||||||
|  | 
 | ||||||
|  | ```console | ||||||
|  | gogo --vm vm.example.com -p 22 --de | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Connect to DCImanager 6 via ssh port 22 and interface port 443 via the old go method | ||||||
|  | 
 | ||||||
|  | ```console | ||||||
|  | gogo --dci dci.example.com -p 22 --go | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ##### There are also examples in `help` | ||||||
|  | 
 | ||||||
|  | ```console | ||||||
|  | gogo --help | ||||||
|  | 
 | ||||||
|  | Usage: gogo [options [parameters]] | ||||||
|  | 
 | ||||||
|  | Examples: | ||||||
|  | 
 | ||||||
|  | gogo --init | init config file | ||||||
|  | gogo --crt  | get ssh certificate for go3 connections | ||||||
|  | gogo --test | check go3 connection availability | ||||||
|  | 
 | ||||||
|  | gogo --bill my.example.com | ||||||
|  | gogo --vm my.example.com --de  | connect throw DE go3 server | ||||||
|  | gogo --vm 0.0.0.0 --ssh | only ssh access | ||||||
|  | gogo --vm 0.0.0.0 --tty | use mgrctl interactive | ||||||
|  | 
 | ||||||
|  | gogo --dci 0.0.0.0 --mgrctl auth user access --id 3 --count 5 | ||||||
|  | gogo --dci 0.0.0.0 --mgrctl auth user ls --admins | ||||||
|  | gogo --vm 0.0.0.0 --port 22122 --mgrctl auth user ls --admins | ||||||
|  | gogo --vm 0.0.0.0 --tty --mgrctl auth user ls --admins | ||||||
|  | gogo --dns ns1.example.com --web-port 1501 | ||||||
|  | gogo --dns ns1.example.com --port 22122 --web-port 1501 | ||||||
|  | gogo --bill my.example.com --port 22 --web-port 1501 | ||||||
|  | 
 | ||||||
|  | Options: | ||||||
|  | 
 | ||||||
|  |  --vm[dci|bill|dns|ip] expected ip_addr             required | ||||||
|  |  --port     | -p    ssh port,   default 22 | ||||||
|  |  --web-port | -wp   web port,   default 443 | ||||||
|  |  --go/--go3         go version, default go3 | ||||||
|  |  --de               connect throw DE go3 server | ||||||
|  |  --ssh              open only ssh session | ||||||
|  |  --tty              for vm6/dci6 echo cmd for run container | ||||||
|  |  --mgrctl [args]    for vm6/dci6 customize access params | ||||||
|  | 
 | ||||||
|  | Single options: | ||||||
|  |  --init     | -i    generate configuration | ||||||
|  |  --crt      | -c    generate ssh cert | ||||||
|  |  --test     | -t    check go3 connection availability | ||||||
|  |  --version  | -v    print version | ||||||
|  |  --help     | -h    print this message and exit | ||||||
|  | ``` | ||||||
							
								
								
									
										43
									
								
								docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | |||||||
|  | # ? docker-compose.yml for development environment | ||||||
|  | 
 | ||||||
|  | # ! To start development you need to create a directory ./dummy_platform. | ||||||
|  | # ? Place files from the test platform into it: | ||||||
|  | # ? VM6: | ||||||
|  | # ?    ./dummy_platform/opt/ispsystem/vm/config.json - configuration file | ||||||
|  | # ? DCI6: | ||||||
|  | # ?    ./dummy_platform/opt/ispsystem/dci/config.json - configuration file | ||||||
|  | 
 | ||||||
|  | # ? Create ./.env file and fill it with required vars: | ||||||
|  | # ? PLATFORM_TYPE='vm' or PLATFORM_TYPE='dci' | ||||||
|  | 
 | ||||||
|  | # ? Launch:  | ||||||
|  | # ?     docker-compose up -d --force-recreate | ||||||
|  | # ?     docker attach mgrctl | ||||||
|  | 
 | ||||||
|  | services: | ||||||
|  |   mgrctl: | ||||||
|  |     container_name: mgrctl | ||||||
|  |     restart: unless-stopped | ||||||
|  |     build: | ||||||
|  |       context: . | ||||||
|  |       args: | ||||||
|  |         - APP_VERSION=${APP_VERSION} | ||||||
|  |         - APP_DIR=${APP_DIR} | ||||||
|  |         - SRC_DIR=${SRC_DIR} | ||||||
|  |         - PKG_NAME=${PKG_NAME} | ||||||
|  |         - PKG_VERSION=${PKG_VERSION} | ||||||
|  |     networks: | ||||||
|  |       vm_box_net: null | ||||||
|  |     volumes: | ||||||
|  |       - type: bind | ||||||
|  |         source: ./dummy_platform/opt/ispsystem/${PLATFORM_TYPE}/config.json | ||||||
|  |         target: /opt/ispsystem/${PLATFORM_TYPE}/config.json | ||||||
|  |     env_file: | ||||||
|  |       - ./.env | ||||||
|  |     tty: true | ||||||
|  |     stdin_open: true | ||||||
|  | 
 | ||||||
|  | networks: | ||||||
|  |   vm_box_net: | ||||||
|  |     name: vm_box_net | ||||||
|  |     driver: bridge | ||||||
| @ -1,9 +0,0 @@ | |||||||
| #!/usr/bin/env python3 |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
| from settings.environment import env |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| # TODO: delete this demo |  | ||||||
| # Just show you which env get application or default values |  | ||||||
| for key, value in env.dump().items(): |  | ||||||
|     print(key, '|', value) |  | ||||||
| @ -1,15 +0,0 @@ | |||||||
| from settings.environment import BASE_DIR |  | ||||||
| 
 |  | ||||||
| from settings.platform import ( |  | ||||||
|     PLATFORM_TYPE, |  | ||||||
|     PLATFORM_URL, |  | ||||||
|     PLATFORM_CONFIG |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| from settings.db import( |  | ||||||
|     DB_ENGINE, |  | ||||||
|     DB_HOST, |  | ||||||
|     DB_PORT, |  | ||||||
|     DB_USER, |  | ||||||
|     DB_PASSWORD |  | ||||||
| ) |  | ||||||
| @ -1,20 +0,0 @@ | |||||||
| from settings.environment import env |  | ||||||
| from settings.platform import PLATFORM_CONFIG |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| #! Required because some instance use psql db: |  | ||||||
| DB_ENGINE = env.str( |  | ||||||
|     'DB_ENGINE', |  | ||||||
|     PLATFORM_CONFIG.get('DatabaseType', 'mysql') |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| # Connection parameters: |  | ||||||
| DB_HOST = env.str('DB_HOST', 'mysql') |  | ||||||
| DB_PORT = env.int('DB_PORT', 3306) |  | ||||||
| DB_USER = env.str('DB_USER', 'root') |  | ||||||
| 
 |  | ||||||
| #! Do not pass password on production. Use value from config.json |  | ||||||
| DB_PASSWORD = env.str( |  | ||||||
|     'DB_PASSWORD', |  | ||||||
|     PLATFORM_CONFIG.get('MysqlRootPassword', '') |  | ||||||
| ) |  | ||||||
| @ -1,12 +0,0 @@ | |||||||
| from settings.environment import env, BASE_DIR |  | ||||||
| from utils.helpers import parse_json_file |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| PLATFORM_TYPE = env.str('PLATFORM_TYPE', 'vm') |  | ||||||
| 
 |  | ||||||
| PLATFORM_CONFIG = parse_json_file(f'{BASE_DIR}/config.json') |  | ||||||
| 
 |  | ||||||
| PLATFORM_URL = env.url( |  | ||||||
|     'PLATFORM_URL', |  | ||||||
|     f"https://{PLATFORM_CONFIG.get('DomainName' ,'replace.me')}" |  | ||||||
| ) |  | ||||||
							
								
								
									
										13
									
								
								mgrctl/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								mgrctl/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | # █▀▄▀█ █▀▀ ▀█▀ ▄▀█ ▀ | ||||||
|  | # █░▀░█ ██▄ ░█░ █▀█ ▄ | ||||||
|  | # -- -- -- -- -- -- - | ||||||
|  | __author__ = "MOIS3Y, a.garaev" | ||||||
|  | __credits__ = [ | ||||||
|  |     "Stepan Zhukovsky", | ||||||
|  |     "Arthur Garaev", | ||||||
|  | ] | ||||||
|  | __license__ = "MIT" | ||||||
|  | __version__ = "0.1.1" | ||||||
|  | __maintainer__ = "Stepan Zhukovsky" | ||||||
|  | __email__ = "stepan@zhukovsky.me" | ||||||
|  | __status__ = "Development" | ||||||
							
								
								
									
										150
									
								
								mgrctl/api/base.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								mgrctl/api/base.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,150 @@ | |||||||
|  | import sys | ||||||
|  | import json | ||||||
|  | import click | ||||||
|  | import urllib | ||||||
|  | import requests | ||||||
|  | 
 | ||||||
|  | from time import sleep | ||||||
|  | from mgrctl.settings.api import ( | ||||||
|  |     API_URL, | ||||||
|  |     API_HEADERS, | ||||||
|  |     API_EMAIL, | ||||||
|  |     API_PASSWORD, | ||||||
|  |     API_VERIFY_SSL, | ||||||
|  |     API_COUNT_TRY_CONNECTIONS | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class BaseAPI(object): | ||||||
|  |     def __init__(self): | ||||||
|  |         """Announces required parameters""" | ||||||
|  |         self.API_URL = API_URL | ||||||
|  |         self.API_HEADERS = API_HEADERS | ||||||
|  |         self.API_EMAIL = API_EMAIL | ||||||
|  |         self.API_PASSWORD = API_PASSWORD | ||||||
|  |         self.API_VERIFY_SSL = API_VERIFY_SSL | ||||||
|  |         self.API_VERSION = 'v3' | ||||||
|  |         self.API_DEFINITION = 'api' | ||||||
|  | 
 | ||||||
|  |     def _gen_request_url(self, url): | ||||||
|  |         return f'{self.API_URL}/{self.API_DEFINITION}/{self.API_VERSION}{url}' | ||||||
|  | 
 | ||||||
|  |     def call_api(self, url, method='GET', headers={}, data={}): | ||||||
|  |         # set conn params | ||||||
|  |         attempt = API_COUNT_TRY_CONNECTIONS | ||||||
|  |         uri = self._gen_request_url(url) | ||||||
|  |         headers = self.API_HEADERS if not headers else headers | ||||||
|  |         params_str = urllib.parse.urlencode(data, safe="+'()") | ||||||
|  |         # connect | ||||||
|  |         while attempt: | ||||||
|  |             attempt -= 1 | ||||||
|  |             try: | ||||||
|  |                 if method == 'POST': | ||||||
|  |                     api_request = requests.post( | ||||||
|  |                         url=uri, | ||||||
|  |                         json=data, | ||||||
|  |                         headers=headers, | ||||||
|  |                         verify=self.API_VERIFY_SSL | ||||||
|  |                     ) | ||||||
|  |                 if method == 'GET': | ||||||
|  |                     uri = f'{uri}?{params_str}' if params_str else uri | ||||||
|  |                     api_request = requests.get( | ||||||
|  |                         url=uri, | ||||||
|  |                         headers=headers, | ||||||
|  |                         verify=self.API_VERIFY_SSL | ||||||
|  |                     ) | ||||||
|  |             except Exception as error: | ||||||
|  |                 ConnectionError = requests.exceptions.ConnectionError | ||||||
|  |                 if type(error) is ConnectionError: | ||||||
|  |                     if attempt == 0: | ||||||
|  |                         click.echo(f'Error: {type(error).__name__}') | ||||||
|  |                         click.echo(f'mgrctl cannot connect to {self.API_URL}') | ||||||
|  |                         sys.exit() | ||||||
|  |                     else: | ||||||
|  |                         continue | ||||||
|  |                 else: | ||||||
|  |                     click.echo(f'Error: {type(error).__name__}') | ||||||
|  |                     sys.exit() | ||||||
|  | 
 | ||||||
|  |             # Get response: | ||||||
|  |             response = self._parse_response(api_request) | ||||||
|  | 
 | ||||||
|  |             # Validate response: | ||||||
|  |             if self._error_handler(response): | ||||||
|  |                 continue  # new attempt connection | ||||||
|  | 
 | ||||||
|  |             return response | ||||||
|  | 
 | ||||||
|  |     def _parse_response(self, api_request): | ||||||
|  |         try: | ||||||
|  |             response = json.loads(api_request.text) | ||||||
|  |             return response | ||||||
|  |         except json.decoder.JSONDecodeError: | ||||||
|  |             click.echo('Error: Invalid API response') | ||||||
|  |             raise sys.exit() | ||||||
|  | 
 | ||||||
|  |     def _is_error(self, response): | ||||||
|  |         if response.get('error'): | ||||||
|  |             return True | ||||||
|  | 
 | ||||||
|  |     def _is_error_3004(self, error): | ||||||
|  |         if error.get('code') == 3004: | ||||||
|  |             sleep(2)  # wait 2 second timeout | ||||||
|  |             return True | ||||||
|  | 
 | ||||||
|  |     def _error_handler(self, response): | ||||||
|  |         if self._is_error(response): | ||||||
|  |             error = response.get('error') | ||||||
|  |             if self._is_error_3004(error): | ||||||
|  |                 return True | ||||||
|  |             else: | ||||||
|  |                 click.echo(f'Error: API return {response}') | ||||||
|  |                 raise sys.exit() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class BaseAuthAPI(BaseAPI): | ||||||
|  |     def __init__(self): | ||||||
|  |         super().__init__() | ||||||
|  |         self.API_VERSION = 'v4' | ||||||
|  |         self.API_DEFINITION = 'auth' | ||||||
|  | 
 | ||||||
|  |     def get_auth_token(self, email=None, password=None) -> dict: | ||||||
|  |         email = self.API_EMAIL if not email else email | ||||||
|  |         password = self.API_PASSWORD if not password else password | ||||||
|  |         return self.call_api( | ||||||
|  |             url='/public/token', | ||||||
|  |             method='POST', | ||||||
|  |             data={'email': email, 'password': password} | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     def get_auth_key(self, token=None, user=None) -> dict: | ||||||
|  |         headers = {} | ||||||
|  |         user = self.API_EMAIL if not user else user | ||||||
|  |         if token: | ||||||
|  |             headers = self.make_auth_header(token) | ||||||
|  |         return self.call_api( | ||||||
|  |             url=f'/user/{user}/key', | ||||||
|  |             method='POST', | ||||||
|  |             headers=headers | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     def make_auth_header(self, token: str) -> dict: | ||||||
|  |         return {'x-xsrf-token': token} | ||||||
|  | 
 | ||||||
|  |     def whoami(self) -> dict: | ||||||
|  |         return self.call_api( | ||||||
|  |             url='/whoami', | ||||||
|  |             method='GET' | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class BaseIpAPI(BaseAPI): | ||||||
|  |     def __init__(self): | ||||||
|  |         super().__init__() | ||||||
|  |         self.API_DEFINITION = 'ip' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class BaseDnsProxyAPI(BaseAPI): | ||||||
|  |     def __init__(self): | ||||||
|  |         super().__init__() | ||||||
|  |         self.API_DEFINITION = 'dnsproxy' | ||||||
							
								
								
									
										51
									
								
								mgrctl/api/dci6.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								mgrctl/api/dci6.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,51 @@ | |||||||
|  | from mgrctl.api.base import BaseAPI, BaseAuthAPI, BaseDnsProxyAPI, BaseIpAPI | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class AuthAPI(BaseAuthAPI): | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class BackupAPI(BaseAPI): | ||||||
|  |     def __init__(self): | ||||||
|  |         super().__init__() | ||||||
|  |         self.API_VERSION = 'v4' | ||||||
|  |         self.API_DEFINITION = 'backup' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class DnsProxyAPI(BaseDnsProxyAPI): | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class EserviceAPI(BaseAPI): | ||||||
|  |     def __init__(self): | ||||||
|  |         super().__init__() | ||||||
|  |         self.API_DEFINITION = 'eservice' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class IsoAPI(BaseAPI): | ||||||
|  |     def __init__(self): | ||||||
|  |         super().__init__() | ||||||
|  |         self.API_DEFINITION = 'iso' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class IpmiAPI(BaseAPI): | ||||||
|  |     def __init__(self): | ||||||
|  |         super().__init__() | ||||||
|  |         self.API_DEFINITION = 'ipmiproxy' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class IpAPI(BaseIpAPI): | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class ReportAPI(BaseAPI): | ||||||
|  |     def __init__(self): | ||||||
|  |         super().__init__() | ||||||
|  |         self.API_VERSION = 'v4' | ||||||
|  |         self.API_DEFINITION = 'report' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class UpdaterAPI(BaseAPI): | ||||||
|  |     def __init__(self): | ||||||
|  |         super().__init__() | ||||||
|  |         self.API_DEFINITION = 'updater' | ||||||
							
								
								
									
										19
									
								
								mgrctl/api/vm6.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								mgrctl/api/vm6.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | |||||||
|  | from mgrctl.api.base import BaseAPI, BaseAuthAPI, BaseDnsProxyAPI, BaseIpAPI | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class AuthAPI(BaseAuthAPI): | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class DnsProxyAPI(BaseDnsProxyAPI): | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class IpAPI(BaseIpAPI): | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class VmAPI(BaseAPI): | ||||||
|  |     def __init__(self): | ||||||
|  |         super().__init__() | ||||||
|  |         self.API_DEFINITION = 'vm' | ||||||
							
								
								
									
										93
									
								
								mgrctl/apps/dci6/auth/commands.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								mgrctl/apps/dci6/auth/commands.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,93 @@ | |||||||
|  | import click | ||||||
|  | 
 | ||||||
|  | from mgrctl.api.dci6 import AuthAPI | ||||||
|  | from mgrctl.utils.api_users import UserAPI | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | user_cursor = UserAPI(callback_class=AuthAPI) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @click.group(help='auth cmd for auth in DCImanager 6') | ||||||
|  | def cli(): | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @cli.group(help='Manage users') | ||||||
|  | def user(): | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @user.command(help='List users') | ||||||
|  | @click.option( | ||||||
|  |     '--all', | ||||||
|  |     is_flag=True, | ||||||
|  |     required=False, | ||||||
|  |     help='Show all users' | ||||||
|  | ) | ||||||
|  | @click.option( | ||||||
|  |     '--admins', | ||||||
|  |     is_flag=True, | ||||||
|  |     required=False, | ||||||
|  |     help='Show all active admins', | ||||||
|  | ) | ||||||
|  | def ls(all, admins): | ||||||
|  |     if all: | ||||||
|  |         users = user_cursor.get_users(role='all') | ||||||
|  |     elif admins: | ||||||
|  |         users = user_cursor.get_users(role='admin') | ||||||
|  |     else: | ||||||
|  |         users = user_cursor.get_users(role='all') | ||||||
|  |     # print users: | ||||||
|  |     user_cursor.echo_users(users) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @user.command( | ||||||
|  |     help='Generate an access key and return auth link(s)', | ||||||
|  |     no_args_is_help=True | ||||||
|  | ) | ||||||
|  | @click.option( | ||||||
|  |     '--id', | ||||||
|  |     '_id', | ||||||
|  |     required=False, | ||||||
|  |     type=int, | ||||||
|  |     help='User id' | ||||||
|  | ) | ||||||
|  | @click.option( | ||||||
|  |     '--count', | ||||||
|  |     required=False, | ||||||
|  |     type=int, | ||||||
|  |     help='Number of access keys generated', | ||||||
|  | ) | ||||||
|  | @click.option( | ||||||
|  |     '--random', | ||||||
|  |     is_flag=True, | ||||||
|  |     required=False, | ||||||
|  |     help='Generate access key for the first available admin' | ||||||
|  | ) | ||||||
|  | @click.option( | ||||||
|  |     '--interactive', | ||||||
|  |     is_flag=True, | ||||||
|  |     required=False, | ||||||
|  |     help='Interactive mode, ignores other keys' | ||||||
|  | ) | ||||||
|  | def access(_id, count, interactive, random): | ||||||
|  |     if _id and not count: | ||||||
|  |         keys = user_cursor.get_access_keys(user=_id, count=1) | ||||||
|  |     elif _id and count: | ||||||
|  |         keys = user_cursor.get_access_keys(user=_id, count=count) | ||||||
|  |     elif random: | ||||||
|  |         admin = user_cursor.get_first_random_admin() | ||||||
|  |         keys = user_cursor.get_access_keys(user=admin.get('id', 3)) | ||||||
|  |     elif interactive: | ||||||
|  |         user_cursor.gen_access_links_interactive() | ||||||
|  |         return  # exit from func | ||||||
|  |     else: | ||||||
|  |         pass | ||||||
|  |     links = user_cursor.gen_access_links(keys) | ||||||
|  |     user_cursor.echo_access_links(links) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @user.command(help='Generate API token for mgrctl user') | ||||||
|  | def token(): | ||||||
|  |     token = user_cursor.gen_api_token() | ||||||
|  |     user_cursor.echo_api_token(token) | ||||||
							
								
								
									
										13
									
								
								mgrctl/apps/dci6/commands.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								mgrctl/apps/dci6/commands.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | import click | ||||||
|  | 
 | ||||||
|  | from mgrctl.cli.lazy_group import LazyGroup | ||||||
|  | from mgrctl.settings.general import INSTALLED_APPS | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @click.group( | ||||||
|  |     cls=LazyGroup, | ||||||
|  |     lazy_subcommands=INSTALLED_APPS['dci6'], | ||||||
|  |     help='dci6 command for DCI6manager management', | ||||||
|  | ) | ||||||
|  | def cli(): | ||||||
|  |     pass | ||||||
							
								
								
									
										93
									
								
								mgrctl/apps/vm6/auth/commands.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								mgrctl/apps/vm6/auth/commands.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,93 @@ | |||||||
|  | import click | ||||||
|  | 
 | ||||||
|  | from mgrctl.api.vm6 import AuthAPI | ||||||
|  | from mgrctl.utils.api_users import UserAPI | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | user_cursor = UserAPI(callback_class=AuthAPI) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @click.group(help='auth cmd for auth in VMmanager 6') | ||||||
|  | def cli(): | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @cli.group(help='Manage users') | ||||||
|  | def user(): | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @user.command(help='List users') | ||||||
|  | @click.option( | ||||||
|  |     '--all', | ||||||
|  |     is_flag=True, | ||||||
|  |     required=False, | ||||||
|  |     help='Show all users' | ||||||
|  | ) | ||||||
|  | @click.option( | ||||||
|  |     '--admins', | ||||||
|  |     is_flag=True, | ||||||
|  |     required=False, | ||||||
|  |     help='Show all active admins', | ||||||
|  | ) | ||||||
|  | def ls(all, admins): | ||||||
|  |     if all: | ||||||
|  |         users = user_cursor.get_users(role='all') | ||||||
|  |     elif admins: | ||||||
|  |         users = user_cursor.get_users(role='admin') | ||||||
|  |     else: | ||||||
|  |         users = user_cursor.get_users(role='all') | ||||||
|  |     # print users: | ||||||
|  |     user_cursor.echo_users(users) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @user.command( | ||||||
|  |     help='Generate an access key and return auth link(s)', | ||||||
|  |     no_args_is_help=True | ||||||
|  | ) | ||||||
|  | @click.option( | ||||||
|  |     '--id', | ||||||
|  |     '_id', | ||||||
|  |     required=False, | ||||||
|  |     type=int, | ||||||
|  |     help='User id' | ||||||
|  | ) | ||||||
|  | @click.option( | ||||||
|  |     '--count', | ||||||
|  |     required=False, | ||||||
|  |     type=int, | ||||||
|  |     help='Number of access keys generated', | ||||||
|  | ) | ||||||
|  | @click.option( | ||||||
|  |     '--random', | ||||||
|  |     is_flag=True, | ||||||
|  |     required=False, | ||||||
|  |     help='Generate access key for the first available admin' | ||||||
|  | ) | ||||||
|  | @click.option( | ||||||
|  |     '--interactive', | ||||||
|  |     is_flag=True, | ||||||
|  |     required=False, | ||||||
|  |     help='Interactive mode, ignores other keys' | ||||||
|  | ) | ||||||
|  | def access(_id, count, interactive, random): | ||||||
|  |     if _id and not count: | ||||||
|  |         keys = user_cursor.get_access_keys(user=_id, count=1) | ||||||
|  |     elif _id and count: | ||||||
|  |         keys = user_cursor.get_access_keys(user=_id, count=count) | ||||||
|  |     elif random: | ||||||
|  |         admin = user_cursor.get_first_random_admin() | ||||||
|  |         keys = user_cursor.get_access_keys(user=admin.get('id', 3)) | ||||||
|  |     elif interactive: | ||||||
|  |         user_cursor.gen_access_links_interactive() | ||||||
|  |         return  # exit from func | ||||||
|  |     else: | ||||||
|  |         pass | ||||||
|  |     links = user_cursor.gen_access_links(keys) | ||||||
|  |     user_cursor.echo_access_links(links) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @user.command(help='Generate API token for mgrctl user') | ||||||
|  | def token(): | ||||||
|  |     token = user_cursor.gen_api_token() | ||||||
|  |     user_cursor.echo_api_token(token) | ||||||
							
								
								
									
										13
									
								
								mgrctl/apps/vm6/commands.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								mgrctl/apps/vm6/commands.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | import click | ||||||
|  | 
 | ||||||
|  | from mgrctl.cli.lazy_group import LazyGroup | ||||||
|  | from mgrctl.settings.general import INSTALLED_APPS | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @click.group( | ||||||
|  |     cls=LazyGroup, | ||||||
|  |     lazy_subcommands=INSTALLED_APPS['vm6'], | ||||||
|  |     help='vm6 command for VM6manager management', | ||||||
|  | ) | ||||||
|  | def cli(): | ||||||
|  |     pass | ||||||
							
								
								
									
										39
									
								
								mgrctl/cli/lazy_group.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								mgrctl/cli/lazy_group.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,39 @@ | |||||||
|  | import importlib | ||||||
|  | import click | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class LazyGroup(click.Group): | ||||||
|  |     def __init__(self, *args, lazy_subcommands=None, **kwargs): | ||||||
|  |         super().__init__(*args, **kwargs) | ||||||
|  |         # lazy_subcommands is a map of the form: | ||||||
|  |         # | ||||||
|  |         #   {command-name} -> {module-name}.{command-object-name} | ||||||
|  |         # | ||||||
|  |         self.lazy_subcommands = lazy_subcommands or {} | ||||||
|  | 
 | ||||||
|  |     def list_commands(self, ctx): | ||||||
|  |         base = super().list_commands(ctx) | ||||||
|  |         lazy = sorted(self.lazy_subcommands.keys()) | ||||||
|  |         return base + lazy | ||||||
|  | 
 | ||||||
|  |     def get_command(self, ctx, cmd_name): | ||||||
|  |         if cmd_name in self.lazy_subcommands: | ||||||
|  |             return self._lazy_load(cmd_name) | ||||||
|  |         return super().get_command(ctx, cmd_name) | ||||||
|  | 
 | ||||||
|  |     def _lazy_load(self, cmd_name): | ||||||
|  |         # lazily loading a command, | ||||||
|  |         # first get the module name and attribute name | ||||||
|  |         import_path = self.lazy_subcommands[cmd_name] | ||||||
|  |         modname, cmd_object_name = import_path.rsplit(".", 1) | ||||||
|  |         # do the import | ||||||
|  |         mod = importlib.import_module(modname) | ||||||
|  |         # get the Command object from that module | ||||||
|  |         cmd_object = getattr(mod, cmd_object_name) | ||||||
|  |         # check the result to make debugging easier | ||||||
|  |         if not isinstance(cmd_object, click.BaseCommand): | ||||||
|  |             raise ValueError( | ||||||
|  |                 f"Lazy loading of {import_path} failed by returning " | ||||||
|  |                 "a non-command object" | ||||||
|  |             ) | ||||||
|  |         return cmd_object | ||||||
							
								
								
									
										32
									
								
								mgrctl/mgrctl.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										32
									
								
								mgrctl/mgrctl.py
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,32 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  | 
 | ||||||
|  | # █▀▄▀█ █▀▀ █▀█ █▀▀ ▀█▀ █░░ ▀ | ||||||
|  | # █░▀░█ █▄█ █▀▄ █▄▄ ░█░ █▄▄ ▄ | ||||||
|  | # -- -- -- -- -- -- -- -- -- | ||||||
|  | 
 | ||||||
|  | import click | ||||||
|  | 
 | ||||||
|  | from mgrctl import __version__ | ||||||
|  | from mgrctl.cli.lazy_group import LazyGroup | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @click.group( | ||||||
|  |     cls=LazyGroup, | ||||||
|  |     lazy_subcommands={ | ||||||
|  |         'vm6': 'mgrctl.apps.vm6.commands.cli', | ||||||
|  |         'dci6': 'mgrctl.apps.dci6.commands.cli', | ||||||
|  |     }, | ||||||
|  |     help='main CLI command for mgrctl app', | ||||||
|  | ) | ||||||
|  | @click.version_option( | ||||||
|  |     version=__version__, | ||||||
|  |     package_name='mgrctl', | ||||||
|  |     message=__version__ | ||||||
|  | ) | ||||||
|  | def cli(): | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     cli() | ||||||
							
								
								
									
										7
									
								
								mgrctl/settings/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								mgrctl/settings/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | from mgrctl.settings.general import BASE_DIR | ||||||
|  | 
 | ||||||
|  | from mgrctl.settings.platform import ( | ||||||
|  |     PLATFORM_TYPE, | ||||||
|  |     PLATFORM_URL, | ||||||
|  |     PLATFORM_CONFIG | ||||||
|  | ) | ||||||
							
								
								
									
										47
									
								
								mgrctl/settings/api.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								mgrctl/settings/api.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | |||||||
|  | from requests.packages import urllib3 | ||||||
|  | 
 | ||||||
|  | from mgrctl.settings.environment import env | ||||||
|  | from mgrctl.settings.platform import ( | ||||||
|  |     PLATFORM_TYPE, | ||||||
|  |     PLATFORM_VERIFY_SSL, | ||||||
|  |     PLATFORM_VERIFY_SSL_WARNING, | ||||||
|  |     PLATFORM_DUMMY, | ||||||
|  |     PLATFORM_DUMMY_API_URL, | ||||||
|  |     PLATFORM_DUMMY_EMAIL, | ||||||
|  |     PLATFORM_DUMMY_PASSWORD, | ||||||
|  |     PLATFORM_DUMMY_TOKEN, | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | # Name of nginx container: | ||||||
|  | API_INPUT_HOSTNAME = 'input' | ||||||
|  | 
 | ||||||
|  | # Port that nginx container is listening: | ||||||
|  | API_INPUT_PORT = '1500' | ||||||
|  | 
 | ||||||
|  | # Internal API url: | ||||||
|  | API_URL = f'http://{API_INPUT_HOSTNAME}:{API_INPUT_PORT}' | ||||||
|  | 
 | ||||||
|  | # Headers for internal auth: | ||||||
|  | API_HEADERS = {"Internal-Auth": "on", "Accept": "application/json"} | ||||||
|  | 
 | ||||||
|  | # Alias for import: | ||||||
|  | API_VERIFY_SSL = PLATFORM_VERIFY_SSL | ||||||
|  | 
 | ||||||
|  | # API 3004 Unavailable error handler: | ||||||
|  | API_COUNT_TRY_CONNECTIONS = env.int('API_COUNT_TRY_CONNECTIONS', 3) | ||||||
|  | 
 | ||||||
|  | # Suppress warning from urllib3: | ||||||
|  | if not PLATFORM_VERIFY_SSL_WARNING: | ||||||
|  |     # ! This is not recommended, | ||||||
|  |     # ! the user will not see a message about an untrusted SSL connection | ||||||
|  |     urllib3.disable_warnings( | ||||||
|  |         category=urllib3.exceptions.InsecureRequestWarning | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | # Development mode: | ||||||
|  | if PLATFORM_DUMMY: | ||||||
|  |     API_URL = PLATFORM_DUMMY_API_URL | ||||||
|  |     API_HEADERS = {'x-xsrf-token': PLATFORM_DUMMY_TOKEN} | ||||||
|  | 
 | ||||||
|  | API_EMAIL = PLATFORM_DUMMY_EMAIL | ||||||
|  | API_PASSWORD = PLATFORM_DUMMY_PASSWORD | ||||||
| @ -1,11 +1,6 @@ | |||||||
| import pathlib |  | ||||||
| from environs import Env | from environs import Env | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # Build paths inside the project like this: BASE_DIR / 'subdir'. |  | ||||||
| BASE_DIR = pathlib.Path(__file__).resolve().parent.parent |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| # Init environment: | # Init environment: | ||||||
| env = Env() | env = Env() | ||||||
| 
 | 
 | ||||||
							
								
								
									
										14
									
								
								mgrctl/settings/general.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								mgrctl/settings/general.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | |||||||
|  | import pathlib | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Build paths inside the project like this: BASE_DIR / 'subdir'. | ||||||
|  | BASE_DIR = pathlib.Path(__file__).resolve().parent.parent | ||||||
|  | 
 | ||||||
|  | INSTALLED_APPS = { | ||||||
|  |     'vm6': { | ||||||
|  |         'auth': 'mgrctl.apps.vm6.auth.commands.cli', | ||||||
|  |     }, | ||||||
|  |     'dci6': { | ||||||
|  |         'auth': 'mgrctl.apps.dci6.auth.commands.cli', | ||||||
|  |     }, | ||||||
|  | } | ||||||
							
								
								
									
										41
									
								
								mgrctl/settings/platform.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								mgrctl/settings/platform.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,41 @@ | |||||||
|  | from mgrctl.settings.environment import env | ||||||
|  | from mgrctl.utils.helpers import parse_json_file | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | PLATFORM_TYPE = env.str('PLATFORM_TYPE', 'vm') | ||||||
|  | 
 | ||||||
|  | PLATFORM_VERIFY_SSL = env.bool('PLATFORM_VERIFY_SSL', True) | ||||||
|  | PLATFORM_VERIFY_SSL_WARNING = env.bool('PLATFORM_VERIFY_SSL_WARNING', True) | ||||||
|  | 
 | ||||||
|  | PLATFORM_CONFIG = env.str( | ||||||
|  |     'PLATFORM_CONFIG', | ||||||
|  |     f'/opt/ispsystem/{PLATFORM_TYPE}/config.json' | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | PLATFORM_CONFIG = parse_json_file(PLATFORM_CONFIG) | ||||||
|  | 
 | ||||||
|  | PLATFORM_URL = env.str( | ||||||
|  |     'PLATFORM_URL', | ||||||
|  |     f"https://{PLATFORM_CONFIG.get('DomainName' ,'replace.me')}" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | # Development mode: | ||||||
|  | PLATFORM_DUMMY = env.bool('PLATFORM_DUMMY', False) | ||||||
|  | 
 | ||||||
|  | if PLATFORM_TYPE == 'vm': | ||||||
|  |     PLATFORM_DUMMY_API_URL = env.str('PLATFORM_DUMMY_VM6_API_URL', '') | ||||||
|  |     PLATFORM_DUMMY_EMAIL = env.str('PLATFORM_DUMMY_VM6_EMAIL', '') | ||||||
|  |     PLATFORM_DUMMY_PASSWORD = env.str('PLATFORM_DUMMY_VM6_PASSWORD', '') | ||||||
|  |     PLATFORM_DUMMY_TOKEN = env.str('PLATFORM_DUMMY_VM6_TOKEN', '') | ||||||
|  | elif PLATFORM_TYPE == 'dci': | ||||||
|  |     PLATFORM_DUMMY_API_URL = env.str('PLATFORM_DUMMY_DCI6_API_URL', '') | ||||||
|  |     PLATFORM_DUMMY_EMAIL = env.str('PLATFORM_DUMMY_DCI6_EMAIL', '') | ||||||
|  |     PLATFORM_DUMMY_PASSWORD = env.str('PLATFORM_DUMMY_DCI6_PASSWORD', '') | ||||||
|  |     PLATFORM_DUMMY_TOKEN = env.str('PLATFORM_DUMMY_DCI6_TOKEN', '') | ||||||
|  | else: | ||||||
|  |     # ? guarantees that constants exist for import | ||||||
|  |     # ? if the user has set the wrong PLATFORM_TYPE: | ||||||
|  |     PLATFORM_DUMMY_API_URL = env.str('PLATFORM_DUMMY_API_URL', '') | ||||||
|  |     PLATFORM_DUMMY_EMAIL = env.str('PLATFORM_DUMMY_EMAIL', '') | ||||||
|  |     PLATFORM_DUMMY_PASSWORD = env.str('PLATFORM_DUMMY_PASSWORD', '') | ||||||
|  |     PLATFORM_DUMMY_TOKEN = env.str('PLATFORM_DUMMY_TOKEN', '') | ||||||
							
								
								
									
										92
									
								
								mgrctl/utils/api_users.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								mgrctl/utils/api_users.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,92 @@ | |||||||
|  | import click | ||||||
|  | from tabulate import tabulate | ||||||
|  | 
 | ||||||
|  | from mgrctl.settings.platform import PLATFORM_URL | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class UserAPI(object): | ||||||
|  |     def __init__(self, callback_class): | ||||||
|  |         """Announces required parameters""" | ||||||
|  |         self.callback_class = callback_class | ||||||
|  |         self.callback = callback_class() | ||||||
|  | 
 | ||||||
|  |     def get_users(self, role: str) -> list: | ||||||
|  |         data = {} | ||||||
|  |         if role == 'admin': | ||||||
|  |             data = {"where": "((roles+CP+'%@admin%')+AND+(state+EQ+'active'))"} | ||||||
|  |         response = self.callback.call_api( | ||||||
|  |             url='/user', | ||||||
|  |             method='GET', | ||||||
|  |             data=data | ||||||
|  |         ) | ||||||
|  |         users = self._extract_users(users=response) | ||||||
|  |         return users | ||||||
|  | 
 | ||||||
|  |     def _extract_users(self, users: dict) -> list: | ||||||
|  |         return users.get('list', []) | ||||||
|  | 
 | ||||||
|  |     def _format_users(self, users: list) -> list: | ||||||
|  |         output = [] | ||||||
|  |         for user in users: | ||||||
|  |             output.append({ | ||||||
|  |                 'id': user.get('id', ''), | ||||||
|  |                 'email': user.get('email', ''), | ||||||
|  |                 'roles': user.get('roles', []), | ||||||
|  |                 'state': user.get('state', ''), | ||||||
|  |                 # add more fields here... | ||||||
|  |             }) | ||||||
|  |         return output | ||||||
|  | 
 | ||||||
|  |     def get_first_random_admin(self): | ||||||
|  |         users = self.get_users(role='admin') | ||||||
|  |         admin = {} | ||||||
|  |         for user in users: | ||||||
|  |             if '@admin' in user.get('roles', []): | ||||||
|  |                 admin = user | ||||||
|  |                 break | ||||||
|  |         return admin | ||||||
|  | 
 | ||||||
|  |     def echo_users(self, users: list) -> None: | ||||||
|  |         output = self._format_users(users) | ||||||
|  |         click.echo(tabulate(output, headers='keys')) | ||||||
|  | 
 | ||||||
|  |     def get_access_keys(self, user, count=1): | ||||||
|  |         keys = [] | ||||||
|  |         while count >= 1: | ||||||
|  |             count -= 1 | ||||||
|  |             key = self.callback.get_auth_key(user=user) | ||||||
|  |             check = key.get('key', '') | ||||||
|  |             if check: | ||||||
|  |                 keys.append(key) | ||||||
|  |         return keys | ||||||
|  | 
 | ||||||
|  |     def gen_access_links(self, keys: list) -> list: | ||||||
|  |         links = [] | ||||||
|  |         for key in keys: | ||||||
|  |             _id = key.get('id', '') | ||||||
|  |             link = f"{PLATFORM_URL}/auth/key/{key.get('key', '')}" | ||||||
|  |             links.append({'id': _id, 'link': link}) | ||||||
|  |         return links | ||||||
|  | 
 | ||||||
|  |     def echo_access_links(self, links: list) -> None: | ||||||
|  |         click.echo(tabulate(links, headers='keys')) | ||||||
|  | 
 | ||||||
|  |     def gen_access_links_interactive(self) -> None: | ||||||
|  |         users = self.get_users(role='admin') | ||||||
|  |         self.echo_users(users) | ||||||
|  |         try: | ||||||
|  |             click.echo('Choose user id and count of keys') | ||||||
|  |             _id = int(input('User ID: ')) | ||||||
|  |             count = int(input('Count of keys: ')) | ||||||
|  |             keys = self.get_access_keys(user=_id, count=count) | ||||||
|  |             links = self.gen_access_links(keys) | ||||||
|  |             self.echo_access_links(links) | ||||||
|  |         except ValueError: | ||||||
|  |             click.echo('Error: Invalid, value is not a valid integer') | ||||||
|  | 
 | ||||||
|  |     def gen_api_token(self, email=None, password=None): | ||||||
|  |         token = self.callback.get_auth_token(email, password) | ||||||
|  |         return token | ||||||
|  | 
 | ||||||
|  |     def echo_api_token(self, token: dict) -> None: | ||||||
|  |         click.echo(tabulate([token], headers='keys')) | ||||||
| @ -1,8 +1,9 @@ | |||||||
| import json | import json | ||||||
| import sys | import sys | ||||||
|  | import click | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def parse_json_file(file_path):  | def parse_json_file(file_path: str) -> dict: | ||||||
|     """ |     """ | ||||||
|     Function read json file as usual config.json then parse it to python dict |     Function read json file as usual config.json then parse it to python dict | ||||||
| 
 | 
 | ||||||
| @ -16,5 +17,7 @@ def parse_json_file(file_path): | |||||||
|         with open(file_path, 'r') as f: |         with open(file_path, 'r') as f: | ||||||
|             return json.load(f) |             return json.load(f) | ||||||
|     except Exception as error: |     except Exception as error: | ||||||
|         print(error) |         click.echo(error) | ||||||
|  |         click.echo("Required: /opt/ispsystem/PLATFORM_TYPE/config.json") | ||||||
|  |         click.echo("Note: don't forget to mount this file into the container") | ||||||
|         sys.exit(1) |         sys.exit(1) | ||||||
							
								
								
									
										1634
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1634
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,22 +1,29 @@ | |||||||
| [tool.poetry] | [tool.poetry] | ||||||
| name = "isp-maintenance" | name = "mgrctl" | ||||||
| version = "0.1.0" | version = "0.1.1" | ||||||
| description = "Maintenance service for ISPsystem platforms" | description = "Maintenance service for ISPsystem platforms" | ||||||
| authors = ["MOIS3Y <s.zhukovskii@ispsystem.com>", "Failak3 <v.shmidt@ispsystem.com>", "a.garaev <a.garaev@ispsystem.com>", "Ann_M <a.moskovkina@ispsystem.com>"] | authors = [ | ||||||
|  |     "MOIS3Y <s.zhukovskii@ispsystem.com>", | ||||||
|  |     "a.garaev <a.garaev@ispsystem.com>", | ||||||
|  | ] | ||||||
| license = "MIT" | license = "MIT" | ||||||
| readme = "README.md" | readme = "README.md" | ||||||
| 
 | 
 | ||||||
| [tool.poetry.dependencies] | [tool.poetry.dependencies] | ||||||
| python = "^3.11" | python = "^3.11" | ||||||
| peewee = "^3.17.0" |  | ||||||
| click = "^8.1.7" | click = "^8.1.7" | ||||||
| requests = "^2.31.0" | requests = "^2.31.0" | ||||||
| environs = "^10.3.0" | environs = "^12.0.0" | ||||||
|  | sh = "^2.0.7" | ||||||
|  | tabulate = "^0.9.0" | ||||||
| 
 | 
 | ||||||
| [tool.poetry.group.dev.dependencies] | [tool.poetry.group.dev.dependencies] | ||||||
| flake8 = "^7.0.0" | flake8 = "^7.0.0" | ||||||
|  | poetry-plugin-export = "^1.6.0" | ||||||
|  | 
 | ||||||
|  | [tool.poetry.scripts] | ||||||
|  | mgrctl = 'mgrctl.mgrctl:cli' | ||||||
| 
 | 
 | ||||||
| [build-system] | [build-system] | ||||||
| requires = ["poetry-core"] | requires = ["poetry-core"] | ||||||
| build-backend = "poetry.core.masonry.api" | build-backend = "poetry.core.masonry.api" | ||||||
| 
 |  | ||||||
|  | |||||||
							
								
								
									
										243
									
								
								requirements.txt
									
									
									
									
									
								
							
							
						
						
									
										243
									
								
								requirements.txt
									
									
									
									
									
								
							| @ -1,123 +1,126 @@ | |||||||
| certifi==2024.2.2 ; python_version >= "3.11" and python_version < "4.0" \ | certifi==2025.4.26 ; python_version >= "3.11" and python_version < "4.0" \ | ||||||
|     --hash=sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f \ |     --hash=sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6 \ | ||||||
|     --hash=sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1 |     --hash=sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3 | ||||||
| charset-normalizer==3.3.2 ; python_version >= "3.11" and python_version < "4.0" \ | charset-normalizer==3.4.2 ; python_version >= "3.11" and python_version < "4.0" \ | ||||||
|     --hash=sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027 \ |     --hash=sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4 \ | ||||||
|     --hash=sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087 \ |     --hash=sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45 \ | ||||||
|     --hash=sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786 \ |     --hash=sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7 \ | ||||||
|     --hash=sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8 \ |     --hash=sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0 \ | ||||||
|     --hash=sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09 \ |     --hash=sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7 \ | ||||||
|     --hash=sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185 \ |     --hash=sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d \ | ||||||
|     --hash=sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574 \ |     --hash=sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d \ | ||||||
|     --hash=sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e \ |     --hash=sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0 \ | ||||||
|     --hash=sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519 \ |     --hash=sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184 \ | ||||||
|     --hash=sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898 \ |     --hash=sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db \ | ||||||
|     --hash=sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269 \ |     --hash=sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b \ | ||||||
|     --hash=sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3 \ |     --hash=sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64 \ | ||||||
|     --hash=sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f \ |     --hash=sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b \ | ||||||
|     --hash=sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6 \ |     --hash=sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8 \ | ||||||
|     --hash=sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8 \ |     --hash=sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff \ | ||||||
|     --hash=sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a \ |     --hash=sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344 \ | ||||||
|     --hash=sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73 \ |     --hash=sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58 \ | ||||||
|     --hash=sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc \ |     --hash=sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e \ | ||||||
|     --hash=sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714 \ |     --hash=sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471 \ | ||||||
|     --hash=sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2 \ |     --hash=sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148 \ | ||||||
|     --hash=sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc \ |     --hash=sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a \ | ||||||
|     --hash=sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce \ |     --hash=sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836 \ | ||||||
|     --hash=sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d \ |     --hash=sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e \ | ||||||
|     --hash=sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e \ |     --hash=sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63 \ | ||||||
|     --hash=sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6 \ |     --hash=sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c \ | ||||||
|     --hash=sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269 \ |     --hash=sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1 \ | ||||||
|     --hash=sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96 \ |     --hash=sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01 \ | ||||||
|     --hash=sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d \ |     --hash=sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366 \ | ||||||
|     --hash=sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a \ |     --hash=sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58 \ | ||||||
|     --hash=sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4 \ |     --hash=sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5 \ | ||||||
|     --hash=sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77 \ |     --hash=sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c \ | ||||||
|     --hash=sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d \ |     --hash=sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2 \ | ||||||
|     --hash=sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0 \ |     --hash=sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a \ | ||||||
|     --hash=sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed \ |     --hash=sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597 \ | ||||||
|     --hash=sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068 \ |     --hash=sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b \ | ||||||
|     --hash=sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac \ |     --hash=sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5 \ | ||||||
|     --hash=sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25 \ |     --hash=sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb \ | ||||||
|     --hash=sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8 \ |     --hash=sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f \ | ||||||
|     --hash=sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab \ |     --hash=sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0 \ | ||||||
|     --hash=sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26 \ |     --hash=sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941 \ | ||||||
|     --hash=sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2 \ |     --hash=sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0 \ | ||||||
|     --hash=sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db \ |     --hash=sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86 \ | ||||||
|     --hash=sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f \ |     --hash=sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7 \ | ||||||
|     --hash=sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5 \ |     --hash=sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7 \ | ||||||
|     --hash=sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99 \ |     --hash=sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455 \ | ||||||
|     --hash=sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c \ |     --hash=sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6 \ | ||||||
|     --hash=sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d \ |     --hash=sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4 \ | ||||||
|     --hash=sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811 \ |     --hash=sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0 \ | ||||||
|     --hash=sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa \ |     --hash=sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3 \ | ||||||
|     --hash=sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a \ |     --hash=sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1 \ | ||||||
|     --hash=sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03 \ |     --hash=sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6 \ | ||||||
|     --hash=sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b \ |     --hash=sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981 \ | ||||||
|     --hash=sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04 \ |     --hash=sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c \ | ||||||
|     --hash=sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c \ |     --hash=sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980 \ | ||||||
|     --hash=sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001 \ |     --hash=sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645 \ | ||||||
|     --hash=sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458 \ |     --hash=sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7 \ | ||||||
|     --hash=sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389 \ |     --hash=sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12 \ | ||||||
|     --hash=sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99 \ |     --hash=sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa \ | ||||||
|     --hash=sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985 \ |     --hash=sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd \ | ||||||
|     --hash=sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537 \ |     --hash=sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef \ | ||||||
|     --hash=sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238 \ |     --hash=sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f \ | ||||||
|     --hash=sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f \ |     --hash=sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2 \ | ||||||
|     --hash=sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d \ |     --hash=sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d \ | ||||||
|     --hash=sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796 \ |     --hash=sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5 \ | ||||||
|     --hash=sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a \ |     --hash=sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02 \ | ||||||
|     --hash=sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143 \ |     --hash=sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3 \ | ||||||
|     --hash=sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8 \ |     --hash=sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd \ | ||||||
|     --hash=sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c \ |     --hash=sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e \ | ||||||
|     --hash=sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5 \ |     --hash=sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214 \ | ||||||
|     --hash=sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5 \ |     --hash=sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd \ | ||||||
|     --hash=sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711 \ |     --hash=sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a \ | ||||||
|     --hash=sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4 \ |     --hash=sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c \ | ||||||
|     --hash=sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6 \ |     --hash=sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681 \ | ||||||
|     --hash=sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c \ |     --hash=sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba \ | ||||||
|     --hash=sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7 \ |     --hash=sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f \ | ||||||
|     --hash=sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4 \ |     --hash=sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a \ | ||||||
|     --hash=sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b \ |     --hash=sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28 \ | ||||||
|     --hash=sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae \ |     --hash=sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691 \ | ||||||
|     --hash=sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12 \ |     --hash=sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82 \ | ||||||
|     --hash=sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c \ |     --hash=sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a \ | ||||||
|     --hash=sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae \ |     --hash=sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027 \ | ||||||
|     --hash=sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8 \ |     --hash=sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7 \ | ||||||
|     --hash=sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887 \ |     --hash=sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518 \ | ||||||
|     --hash=sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b \ |     --hash=sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf \ | ||||||
|     --hash=sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4 \ |     --hash=sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b \ | ||||||
|     --hash=sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f \ |     --hash=sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9 \ | ||||||
|     --hash=sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5 \ |     --hash=sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544 \ | ||||||
|     --hash=sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33 \ |     --hash=sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da \ | ||||||
|     --hash=sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519 \ |     --hash=sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509 \ | ||||||
|     --hash=sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561 |     --hash=sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f \ | ||||||
| click==8.1.7 ; python_version >= "3.11" and python_version < "4.0" \ |     --hash=sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a \ | ||||||
|     --hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \ |     --hash=sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f | ||||||
|     --hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de | click==8.1.8 ; python_version >= "3.11" and python_version < "4.0" \ | ||||||
|  |     --hash=sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2 \ | ||||||
|  |     --hash=sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a | ||||||
| colorama==0.4.6 ; python_version >= "3.11" and python_version < "4.0" and platform_system == "Windows" \ | colorama==0.4.6 ; python_version >= "3.11" and python_version < "4.0" and platform_system == "Windows" \ | ||||||
|     --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ |     --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ | ||||||
|     --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 |     --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 | ||||||
| environs==10.3.0 ; python_version >= "3.11" and python_version < "4.0" \ | environs==12.0.0 ; python_version >= "3.11" and python_version < "4.0" \ | ||||||
|     --hash=sha256:cc421ddb143fa30183568164755aa113a160e555cd19e97e664c478662032c24 \ |     --hash=sha256:152068ba62185b99d87df5f6d6044c0e533261777119037e718c1397fc7d697f \ | ||||||
|     --hash=sha256:feeaf28f17fd0499f9cd7c0fcf408c6d82c308e69e335eb92d09322fc9ed8138 |     --hash=sha256:6150ddeb05562a80cc789b308c650d33b98a5f9c3f4c63d9ed465c433f1cb9e2 | ||||||
| idna==3.6 ; python_version >= "3.11" and python_version < "4.0" \ | idna==3.10 ; python_version >= "3.11" and python_version < "4.0" \ | ||||||
|     --hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \ |     --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ | ||||||
|     --hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f |     --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 | ||||||
| marshmallow==3.20.2 ; python_version >= "3.11" and python_version < "4.0" \ | marshmallow==4.0.0 ; python_version >= "3.11" and python_version < "4.0" \ | ||||||
|     --hash=sha256:4c1daff273513dc5eb24b219a8035559dc573c8f322558ef85f5438ddd1236dd \ |     --hash=sha256:3b6e80aac299a7935cfb97ed01d1854fb90b5079430969af92118ea1b12a8d55 \ | ||||||
|     --hash=sha256:c21d4b98fee747c130e6bc8f45c4b3199ea66bc00c12ee1f639f0aeca034d5e9 |     --hash=sha256:e7b0528337e9990fd64950f8a6b3a1baabed09ad17a0dfb844d701151f92d203 | ||||||
| packaging==23.2 ; python_version >= "3.11" and python_version < "4.0" \ | python-dotenv==1.1.0 ; python_version >= "3.11" and python_version < "4.0" \ | ||||||
|     --hash=sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5 \ |     --hash=sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5 \ | ||||||
|     --hash=sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7 |     --hash=sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d | ||||||
| peewee==3.17.1 ; python_version >= "3.11" and python_version < "4.0" \ | requests==2.32.3 ; python_version >= "3.11" and python_version < "4.0" \ | ||||||
|     --hash=sha256:e009ac4227c4fdc0058a56e822ad5987684f0a1fbb20fed577200785102581c3 |     --hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \ | ||||||
| python-dotenv==1.0.1 ; python_version >= "3.11" and python_version < "4.0" \ |     --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6 | ||||||
|     --hash=sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca \ | sh==2.2.2 ; python_version >= "3.11" and python_version < "4.0" \ | ||||||
|     --hash=sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a |     --hash=sha256:653227a7c41a284ec5302173fbc044ee817c7bad5e6e4d8d55741b9aeb9eb65b \ | ||||||
| requests==2.31.0 ; python_version >= "3.11" and python_version < "4.0" \ |     --hash=sha256:e0b15b4ae8ffcd399bc8ffddcbd770a43c7a70a24b16773fbb34c001ad5d52af | ||||||
|     --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ | tabulate==0.9.0 ; python_version >= "3.11" and python_version < "4.0" \ | ||||||
|     --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 |     --hash=sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c \ | ||||||
| urllib3==2.2.0 ; python_version >= "3.11" and python_version < "4.0" \ |     --hash=sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f | ||||||
|     --hash=sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20 \ | urllib3==2.4.0 ; python_version >= "3.11" and python_version < "4.0" \ | ||||||
|     --hash=sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224 |     --hash=sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466 \ | ||||||
|  |     --hash=sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813 | ||||||
|  | |||||||
							
								
								
									
										10
									
								
								scripts/devtools/cli/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								scripts/devtools/cli/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | |||||||
|  | # █▀▄▀█ █▀▀ ▀█▀ ▄▀█ ▀ | ||||||
|  | # █░▀░█ ██▄ ░█░ █▀█ ▄ | ||||||
|  | # -- -- -- -- -- -- - | ||||||
|  | __author__ = "MOIS3Y" | ||||||
|  | __credits__ = ["Stepan Zhukovsky"] | ||||||
|  | __license__ = "MIT" | ||||||
|  | __version__ = "0.1.0" | ||||||
|  | __maintainer__ = "Stepan Zhukovsky" | ||||||
|  | __email__ = "stepan@zhukovsky.me" | ||||||
|  | __status__ = "Development" | ||||||
							
								
								
									
										50
									
								
								scripts/devtools/cli/builder.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								scripts/devtools/cli/builder.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,50 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  | import click | ||||||
|  | import sh | ||||||
|  | 
 | ||||||
|  | from .lazy_group import LazyGroup | ||||||
|  | from .utils import abort_if_false | ||||||
|  | from . import settings | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @click.group() | ||||||
|  | def cli1(): | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @click.group( | ||||||
|  |     cls=LazyGroup, | ||||||
|  |     lazy_subcommands={'docker-compose': 'cli.builder.docker_compose'}, | ||||||
|  | ) | ||||||
|  | def cli2(): | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @click.group(help='cmd for build/destroy project in docker containers') | ||||||
|  | def docker_compose(): | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @docker_compose.command(help='cmd for build project in docker containers') | ||||||
|  | def build(): | ||||||
|  |     with sh.contrib.sudo(password=settings.SUDO_PASSWORD, _with=True): | ||||||
|  |         sh.docker_compose('-f', settings.COMPOSE_FILE, 'build', _fg=True) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @docker_compose.command(help='cmd for destroy project in docker containers') | ||||||
|  | @click.option( | ||||||
|  |     '--yes', | ||||||
|  |     is_flag=True, | ||||||
|  |     callback=abort_if_false, | ||||||
|  |     expose_value=False, | ||||||
|  |     prompt='Are you sure you want to destroy docker containers') | ||||||
|  | def destroy(): | ||||||
|  |     with sh.contrib.sudo(password=settings.SUDO_PASSWORD, _with=True): | ||||||
|  |         sh.docker_compose('-f', settings.COMPOSE_FILE, 'down',  _fg=True) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | cli = click.CommandCollection( | ||||||
|  |     sources=[cli1, cli2], | ||||||
|  |     help='cmd for building the project' | ||||||
|  | ) | ||||||
							
								
								
									
										39
									
								
								scripts/devtools/cli/lazy_group.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								scripts/devtools/cli/lazy_group.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,39 @@ | |||||||
|  | import importlib | ||||||
|  | import click | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class LazyGroup(click.Group): | ||||||
|  |     def __init__(self, *args, lazy_subcommands=None, **kwargs): | ||||||
|  |         super().__init__(*args, **kwargs) | ||||||
|  |         # lazy_subcommands is a map of the form: | ||||||
|  |         # | ||||||
|  |         #   {command-name} -> {module-name}.{command-object-name} | ||||||
|  |         # | ||||||
|  |         self.lazy_subcommands = lazy_subcommands or {} | ||||||
|  | 
 | ||||||
|  |     def list_commands(self, ctx): | ||||||
|  |         base = super().list_commands(ctx) | ||||||
|  |         lazy = sorted(self.lazy_subcommands.keys()) | ||||||
|  |         return base + lazy | ||||||
|  | 
 | ||||||
|  |     def get_command(self, ctx, cmd_name): | ||||||
|  |         if cmd_name in self.lazy_subcommands: | ||||||
|  |             return self._lazy_load(cmd_name) | ||||||
|  |         return super().get_command(ctx, cmd_name) | ||||||
|  | 
 | ||||||
|  |     def _lazy_load(self, cmd_name): | ||||||
|  |         # lazily loading a command, | ||||||
|  |         # first get the module name and attribute name | ||||||
|  |         import_path = self.lazy_subcommands[cmd_name] | ||||||
|  |         modname, cmd_object_name = import_path.rsplit(".", 1) | ||||||
|  |         # do the import | ||||||
|  |         mod = importlib.import_module(modname) | ||||||
|  |         # get the Command object from that module | ||||||
|  |         cmd_object = getattr(mod, cmd_object_name) | ||||||
|  |         # check the result to make debugging easier | ||||||
|  |         if not isinstance(cmd_object, click.BaseCommand): | ||||||
|  |             raise ValueError( | ||||||
|  |                 f"Lazy loading of {import_path} failed by returning " | ||||||
|  |                 "a non-command object" | ||||||
|  |             ) | ||||||
|  |         return cmd_object | ||||||
							
								
								
									
										57
									
								
								scripts/devtools/cli/runner.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								scripts/devtools/cli/runner.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,57 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  | import click | ||||||
|  | import sh | ||||||
|  | 
 | ||||||
|  | from .lazy_group import LazyGroup | ||||||
|  | from . import settings | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @click.group() | ||||||
|  | def cli1(): | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @click.group( | ||||||
|  |     cls=LazyGroup, | ||||||
|  |     lazy_subcommands={'docker-compose': 'cli.runner.docker_compose'}, | ||||||
|  | ) | ||||||
|  | def cli2(): | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @click.group(help='cmd for run/stop/restart project in docker containers') | ||||||
|  | def docker_compose(): | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @docker_compose.command(help='cmd for run project docker containers') | ||||||
|  | def up(): | ||||||
|  |     with sh.contrib.sudo(password=settings.SUDO_PASSWORD, _with=True): | ||||||
|  |         sh.docker_compose('-f', settings.COMPOSE_FILE, 'up', '-d', _fg=True) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @docker_compose.command(help='cmd for down project docker containers') | ||||||
|  | def down(): | ||||||
|  |     with sh.contrib.sudo(password=settings.SUDO_PASSWORD, _with=True): | ||||||
|  |         sh.docker_compose('-f', settings.COMPOSE_FILE, 'down', _fg=True) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @docker_compose.command(help='cmd for restart project docker containers') | ||||||
|  | def restart(): | ||||||
|  |     with sh.contrib.sudo(password=settings.SUDO_PASSWORD, _with=True): | ||||||
|  |         sh.docker_compose( | ||||||
|  |             '-f', | ||||||
|  |             settings.COMPOSE_FILE, | ||||||
|  |             'up', | ||||||
|  |             '-d', | ||||||
|  |             '--build', | ||||||
|  |             '--force-recreate', | ||||||
|  |             _fg=True | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | cli = click.CommandCollection( | ||||||
|  |     sources=[cli1, cli2], | ||||||
|  |     help='cmd for running the project standalone or docker-compose' | ||||||
|  | ) | ||||||
							
								
								
									
										18
									
								
								scripts/devtools/cli/settings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								scripts/devtools/cli/settings.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | |||||||
|  | import pathlib | ||||||
|  | from environs import Env | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Path: | ||||||
|  | ROOT_DIR = pathlib.Path(__file__).resolve().parent.parent.parent.parent | ||||||
|  | PROJECT_DIR = ROOT_DIR / 'mgrctl' | ||||||
|  | COMPOSE_FILE = ROOT_DIR / 'docker-compose.yml' | ||||||
|  | 
 | ||||||
|  | # Init environment: | ||||||
|  | env = Env() | ||||||
|  | 
 | ||||||
|  | # read .env file, if it exists | ||||||
|  | # reed more about .env file here: https://github.com/sloria/environs | ||||||
|  | env.read_env(path=ROOT_DIR / '.env') | ||||||
|  | 
 | ||||||
|  | # ! insecure save sudo password wherever | ||||||
|  | SUDO_PASSWORD = env.str('SUDO_PASSWORD', None) | ||||||
							
								
								
									
										15
									
								
								scripts/devtools/cli/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								scripts/devtools/cli/utils.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | |||||||
|  | import click | ||||||
|  | 
 | ||||||
|  | from . import __version__ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def print_version(ctx, param, value): | ||||||
|  |     if not value or ctx.resilient_parsing: | ||||||
|  |         return | ||||||
|  |     click.echo(__version__) | ||||||
|  |     ctx.exit() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def abort_if_false(ctx, param, value): | ||||||
|  |     if not value: | ||||||
|  |         ctx.abort() | ||||||
							
								
								
									
										30
									
								
								scripts/devtools/dev.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										30
									
								
								scripts/devtools/dev.py
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,30 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  | import click | ||||||
|  | 
 | ||||||
|  | from cli.lazy_group import LazyGroup | ||||||
|  | from cli.utils import print_version | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @click.group( | ||||||
|  |     cls=LazyGroup, | ||||||
|  |     lazy_subcommands={ | ||||||
|  |         'builder': 'cli.builder.cli', | ||||||
|  |         'runner': 'cli.runner.cli', | ||||||
|  |     }, | ||||||
|  |     help='dev.py CLI tool for dev actions', | ||||||
|  | ) | ||||||
|  | @click.option( | ||||||
|  |     '--version', | ||||||
|  |     is_flag=True, | ||||||
|  |     callback=print_version, | ||||||
|  |     expose_value=False, | ||||||
|  |     is_eager=True, | ||||||
|  |     help='Print version and exit' | ||||||
|  | ) | ||||||
|  | def cli(): | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     cli() | ||||||
							
								
								
									
										512
									
								
								scripts/gogo/gogo.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										512
									
								
								scripts/gogo/gogo.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,512 @@ | |||||||
|  | #!/usr/bin/env bash | ||||||
|  | 
 | ||||||
|  | # █▀▀ █▀█ █▀▀ █▀█ ▀ | ||||||
|  | # █▄█ █▄█ █▄█ █▄█ ▄ | ||||||
|  | # -- -- -- -- -- -- | ||||||
|  | 
 | ||||||
|  | # INIT GLOBAL VARIABLES: | ||||||
|  | _VERSION="0.1.1" | ||||||
|  | _SCRIPT_NAME="$(basename $0)" | ||||||
|  | _GO_CMD="go3" | ||||||
|  | _DEBUG_MODE=false | ||||||
|  | 
 | ||||||
|  | _CONFIG_DIR="${HOME}/.config/gogo" | ||||||
|  | _CONFIG="${_CONFIG_DIR}/gogo.conf" | ||||||
|  | 
 | ||||||
|  | _IS_TTY=false | ||||||
|  | _IS_SSH_ONLY=false | ||||||
|  | _IS_MGRCTL_ARGS=false | ||||||
|  | _MGRCTL_ARGS="" | ||||||
|  | _MGRCTL_BIN="mgrctl" | ||||||
|  | _MGRCTL_CMD="" | ||||||
|  | _MGRCTL_RUN="" | ||||||
|  | _MGRCTL_KEY="" | ||||||
|  | 
 | ||||||
|  | _PLATFORM_TYPE="" | ||||||
|  | _PLATFORM_GENERATION=6 | ||||||
|  | _PLATFORM_SSH_PORT=22 | ||||||
|  | _PLATFORM_WEB_PORT=443 | ||||||
|  | _PLATFORM_IP_ADDR="" | ||||||
|  | _PLATFORM_CONFIG_FILE="" | ||||||
|  | _PLATFORM_NETWORK_NAME="" | ||||||
|  | 
 | ||||||
|  | _SSH_CONNECT_CMD="" | ||||||
|  | _SSH_REMOTE_CMD="" | ||||||
|  | 
 | ||||||
|  | _ACCESS_LINK="" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Colorize output | ||||||
|  | # Usage - $(colorize CYAN "Hello, friend!") | ||||||
|  | colorize() { | ||||||
|  | 	local RED="\033[0;31m" | ||||||
|  | 	local GREEN="\033[0;32m"  # <-- [0 means not bold | ||||||
|  | 	local YELLOW="\033[1;33m" # <-- [1 means bold | ||||||
|  | 	local BLUE="\033[0;34m" | ||||||
|  | 	local MAGNETA="\033[0;35" | ||||||
|  | 	local CYAN="\033[1;36m" | ||||||
|  | 	# ... Add more colors if you like | ||||||
|  | 
 | ||||||
|  | 	local NC="\033[0m" # No Color | ||||||
|  | 
 | ||||||
|  | 	# printf "${(P)1}${2} ${NC}\n" # <-- zsh | ||||||
|  | 	# printf "${!1}${2} ${NC}\n"   # <-- bash | ||||||
|  | 	echo -e "${!1}${2}${NC}"       # <-- all-purpose | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Print help message how used it script | ||||||
|  | help() { | ||||||
|  | 	# colorize value | ||||||
|  | 	local script=$(colorize GREEN "$_SCRIPT_NAME") | ||||||
|  | 	local required=$(colorize RED "required") | ||||||
|  | 	# help message | ||||||
|  | 	printf "Usage: $script [options [parameters]]                              \n" | ||||||
|  | 	printf "                                                                   \n" | ||||||
|  | 	printf "Examples:                                                          \n" | ||||||
|  | 	printf "                                                                   \n" | ||||||
|  | 	printf "${script} --init | init config file                                \n" | ||||||
|  | 	printf "${script} --crt  | get ssh certificate for go3 connections         \n" | ||||||
|  | 	printf "${script} --test | check go3 connection availability               \n" | ||||||
|  | 	printf "                                                                   \n" | ||||||
|  | 	printf "${script} --bill my.example.com                                    \n" | ||||||
|  | 	printf "${script} --vm my.example.com --de  | connect throw DE go3 server  \n" | ||||||
|  | 	printf "${script} --vm 0.0.0.0 --ssh | only ssh access                     \n" | ||||||
|  | 	printf "${script} --vm 0.0.0.0 --tty | use mgrctl interactive              \n" | ||||||
|  | 	printf "                                                                   \n" | ||||||
|  | 	printf "${script} --dci 0.0.0.0 --mgrctl auth user access --id 3 --count 5 \n" | ||||||
|  | 	printf "${script} --dci 0.0.0.0 --mgrctl auth user ls --admins             \n" | ||||||
|  | 	printf "${script} --vm 0.0.0.0 --port 22122 --mgrctl auth user ls --admins \n" | ||||||
|  | 	printf "${script} --vm 0.0.0.0 --tty --mgrctl auth user ls --admins        \n" | ||||||
|  | 	printf "${script} --dns ns1.example.com --web-port 1501                    \n" | ||||||
|  | 	printf "${script} --dns ns1.example.com --port 22122 --web-port 1501       \n" | ||||||
|  | 	printf "${script} --bill my.example.com --port 22 --web-port 1501          \n" | ||||||
|  | 	printf "                                                                   \n" | ||||||
|  | 	printf "Options:                                                           \n" | ||||||
|  | 	printf "                                                                   \n" | ||||||
|  | 	printf " --vm[dci|bill|dns|ip] expected ip_addr             $required      \n" | ||||||
|  | 	printf " --port     | -p    ssh port,   default 22                         \n" | ||||||
|  | 	printf " --web-port | -wp   web port,   default 443                        \n" | ||||||
|  | 	printf " --go/--go3         go version, default go3                        \n" | ||||||
|  | 	printf " --de               connect throw DE go3 server                    \n" | ||||||
|  | 	printf " --ssh              open only ssh session                          \n" | ||||||
|  | 	printf " --tty              for vm6/dci6 echo cmd for run container        \n" | ||||||
|  | 	printf " --mgrctl [args]    for vm6/dci6 customize access params           \n" | ||||||
|  | 	printf "                                                                   \n" | ||||||
|  | 	printf "Single options:                                                    \n" | ||||||
|  | 	printf " --init     | -i    generate configuration                         \n" | ||||||
|  | 	printf " --crt      | -c    generate ssh cert                              \n" | ||||||
|  | 	printf " --test     | -t    check go3 connection availability              \n" | ||||||
|  | 	printf " --version  | -v    print version                                  \n" | ||||||
|  | 	printf " --help     | -h    print this message and exit                    \n" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # Ask confirmation user if No - exit with 1 state | ||||||
|  | continue_handler() { | ||||||
|  | 	read -p "Continue? (Y/N): " confirm \ | ||||||
|  | 	&& [[ $confirm == [yY] || $confirm == [yY][eE][sS] ]] || exit 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # Init script configuration file: | ||||||
|  | init_config() { | ||||||
|  | 	# Lables: | ||||||
|  | 	local warning=$(colorize RED "WARNING!") | ||||||
|  | 	local success=$(colorize GREEN "SUCCESS!") | ||||||
|  | 	local script_name=$(colorize GREEN "${_SCRIPT_NAME}") | ||||||
|  | 	# check if config file exists: | ||||||
|  | 	if [ -f $_CONFIG ]; then | ||||||
|  | 		echo "${warning} Config file is already exists" | ||||||
|  | 		echo "New initialization rewrites current config" | ||||||
|  | 		continue_handler | ||||||
|  | 	fi | ||||||
|  | 	# get user unputs: | ||||||
|  | 	read -p "Enter russian go server address: "    _GO_SERVER_ADDR_RUSSIAN | ||||||
|  | 	read -p "Enter germany go server address: "    _GO_SERVER_ADDR_GERMANY | ||||||
|  | 	read -p "Enter test go server address: "       _GO_SERVER_ADDR_TEST | ||||||
|  | 	read -p "Enter vault server address: "         _VAULT_SERVER_ADDR | ||||||
|  | 	read -p "Enter username: "                     _SSH_PRIVATE_KEY_USER | ||||||
|  | 	read -p "Enter full path to ssh private key: " _SSH_PRIVATE_KEY_PATH | ||||||
|  | 	read -p "Enter full path to ssh public key: "  _SSH_PUBLIC_KEY_PATH | ||||||
|  | 	read -p "Enter full path to ssh certificate: " _SSH_CRT_FILE | ||||||
|  | 	read -p "Enter mgrctl image name: "            _MGRCTL_IMAGE | ||||||
|  | 	# save config: | ||||||
|  | 	mkdir -p $_CONFIG_DIR | ||||||
|  | 	cat << EOF > "${_CONFIG}" | ||||||
|  | GO_SERVER_ADDR_RUSSIAN=$_GO_SERVER_ADDR_RUSSIAN | ||||||
|  | GO_SERVER_ADDR_GERMANY=$_GO_SERVER_ADDR_GERMANY | ||||||
|  | GO_SERVER_ADDR_TEST=$_GO_SERVER_ADDR_TEST | ||||||
|  | VAULT_SERVER_ADDR=$_VAULT_SERVER_ADDR | ||||||
|  | SSH_PRIVATE_KEY_USER=$_SSH_PRIVATE_KEY_USER | ||||||
|  | SSH_PRIVATE_KEY_PATH=$_SSH_PRIVATE_KEY_PATH | ||||||
|  | SSH_PUBLIC_KEY_PATH=$_SSH_PUBLIC_KEY_PATH | ||||||
|  | SSH_CRT_FILE=$_SSH_CRT_FILE | ||||||
|  | MGRCTL_IMAGE=$_MGRCTL_IMAGE | ||||||
|  | DEBUG_MODE=false | ||||||
|  | EOF | ||||||
|  | 	echo "" | ||||||
|  | 	echo "${success} Config file was created, run ${script_name} again" | ||||||
|  | 	echo "" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Read config file that contains key=value params | ||||||
|  | load_config() { | ||||||
|  | 	local file="$_CONFIG" | ||||||
|  | 
 | ||||||
|  | 	if ! [ -f $_CONFIG ]; then | ||||||
|  | 		help | ||||||
|  | 		local warning=$(colorize RED "WARNING!") | ||||||
|  | 		echo "" | ||||||
|  | 		echo "${warning} Config file doesn't exist" | ||||||
|  | 		echo "Init new config: ${_CONFIG}" | ||||||
|  | 		continue_handler | ||||||
|  | 		init_config | ||||||
|  | 	fi | ||||||
|  | 	 | ||||||
|  | 	while IFS="=" read -r key value; do | ||||||
|  | 		case "$key" in | ||||||
|  | 			"GO_SERVER_ADDR_RUSSIAN") | ||||||
|  | 				_GO_SERVER_ADDR_RUSSIAN="$value" | ||||||
|  | 				_GO_SERVER_ADDR="$value" | ||||||
|  | 				;; | ||||||
|  | 			"GO_SERVER_ADDR_GERMANY") | ||||||
|  | 				_GO_SERVER_ADDR_GERMANY="$value" | ||||||
|  | 				;; | ||||||
|  | 			"GO_SERVER_ADDR_TEST") | ||||||
|  | 				_GO_SERVER_ADDR_TEST="$value" | ||||||
|  | 				;; | ||||||
|  | 			"VAULT_SERVER_ADDR") | ||||||
|  | 				_VAULT_SERVER_ADDR="$value" | ||||||
|  | 				;; | ||||||
|  | 			"SSH_PRIVATE_KEY_USER") | ||||||
|  | 				_SSH_PRIVATE_KEY_USER="$value" | ||||||
|  | 				;; | ||||||
|  | 			"SSH_PRIVATE_KEY_PATH") | ||||||
|  | 				_SSH_PRIVATE_KEY_PATH="$value" | ||||||
|  | 				;; | ||||||
|  | 			"SSH_PUBLIC_KEY_PATH") | ||||||
|  | 				_SSH_PUBLIC_KEY_PATH="$value" | ||||||
|  | 				_VAULT_SSH_PUBLIC_KEY="@$value"  # @ sybol is important | ||||||
|  | 				;; | ||||||
|  | 			"SSH_CRT_FILE") | ||||||
|  | 				_SSH_CRT_FILE="$value" | ||||||
|  | 				;; | ||||||
|  | 			"MGRCTL_IMAGE") | ||||||
|  | 				_MGRCTL_IMAGE="$value" | ||||||
|  | 				;; | ||||||
|  | 			"DEBUG_MODE") | ||||||
|  | 				_DEBUG_MODE="$value" | ||||||
|  | 				;; | ||||||
|  | 		esac | ||||||
|  | 	done < "$file" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # Generate key for coremgr based platrorms access link: | ||||||
|  | gen_random_key() { | ||||||
|  | 	_MGRCTL_KEY=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | gen_coremgr_access_params() { | ||||||
|  | 	# get opt name: | ||||||
|  | 	local opt=$1 | ||||||
|  | 	# gen access key: | ||||||
|  | 	gen_random_key | ||||||
|  | 	# fill current parametrs: | ||||||
|  | 	_PLATFORM_TYPE=$(sed 's~[^[:alpha:]/]\+~~g' <<< "$opt") | ||||||
|  | 	_PLATFORM_GENERATION=5 | ||||||
|  | 	_MGRCTL_BIN="/usr/local/mgr5/sbin/mgrctl" | ||||||
|  | 	_MGRCTL_ARGS="-m ${_PLATFORM_TYPE}mgr session.newkey key=$_MGRCTL_KEY"	 | ||||||
|  | 
 | ||||||
|  | 	# override _PLATFORM_GENERATION for bill6 or dns6 | ||||||
|  | 	if [[ $opt == "--bill" ]] || [[ $opt == "--dns" ]]; then | ||||||
|  | 		_PLATFORM_GENERATION=6 | ||||||
|  | 	fi | ||||||
|  | 	# override _MGRCTL_BIN _MGRCTL_ARGS for dns6 | ||||||
|  | 	if [[ $opt == "--dns" ]]; then | ||||||
|  | 		_MGRCTL_BIN="/opt/ispsystem/${_PLATFORM_TYPE}manager6/sbin/mgrctl" | ||||||
|  | 		_MGRCTL_ARGS="-m ${_PLATFORM_TYPE}mgr session.newkey key=$_MGRCTL_KEY"	 | ||||||
|  | 	fi | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | gen_docker_access_params(){ | ||||||
|  | 	# get opt name: | ||||||
|  | 	local opt=$1 | ||||||
|  | 	# fill current parametrs: | ||||||
|  | 	_PLATFORM_TYPE=$(sed 's~[^[:alpha:]/]\+~~g' <<< "$opt") | ||||||
|  | 	_PLATFORM_GENERATION=6 | ||||||
|  | 	_PLATFORM_CONFIG_FILE="/opt/ispsystem/${_PLATFORM_TYPE}/config.json" | ||||||
|  | 	# set platform docker network name: | ||||||
|  | 	if [[ $_PLATFORM_TYPE == "vm" ]]; then | ||||||
|  | 		_PLATFORM_NETWORK_NAME="vm_vm_box_net" | ||||||
|  | 	else | ||||||
|  | 		_PLATFORM_NETWORK_NAME="dci_auth" | ||||||
|  | 	fi | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | gen_ssh_connect_cmd(){ | ||||||
|  | 	# get params: | ||||||
|  | 	local go_server="${_GO_SERVER_ADDR}" | ||||||
|  | 	local go_cmd="${_GO_CMD}" | ||||||
|  | 	local address="${_PLATFORM_IP_ADDR}" | ||||||
|  | 	local port="${_PLATFORM_SSH_PORT}" | ||||||
|  | 	local key_path="${_SSH_PRIVATE_KEY_PATH}" | ||||||
|  | 	local key_user="${_SSH_PRIVATE_KEY_USER}" | ||||||
|  | 	local ssh_args="${key_user}@${go_server} ${go_cmd} ${address} -p ${port}" | ||||||
|  | 	# generate cmd: | ||||||
|  | 	_SSH_CONNECT_CMD="ssh -A -t -i ${key_path} ${ssh_args}" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | gen_ssh_remote_cmd() { | ||||||
|  | 	# ? VMmanager6 || DCImanager6: | ||||||
|  | 	if [[ $_PLATFORM_TYPE == "vm" ]] || \ | ||||||
|  | 		[[ $_PLATFORM_TYPE == "dci" ]] && \ | ||||||
|  | 		[[ $_PLATFORM_GENERATION -eq 6 ]]; then | ||||||
|  | 		# use default mgrctl cmd if not set args: | ||||||
|  | 		if [ -z "${_MGRCTL_ARGS}" ]; then | ||||||
|  | 			_MGRCTL_ARGS="${_PLATFORM_TYPE}6 auth user access --random" | ||||||
|  | 			_MGRCTL_CMD="${_MGRCTL_BIN} ${_MGRCTL_ARGS}" | ||||||
|  | 		else | ||||||
|  | 			_MGRCTL_CMD="${_MGRCTL_BIN} ${_PLATFORM_TYPE}6 ${_MGRCTL_ARGS}" | ||||||
|  | 		fi | ||||||
|  | 		# silent mode: | ||||||
|  | 		local hide_output=">> /dev/null" | ||||||
|  | 		if $_DEBUG_MODE; then | ||||||
|  | 			hide_output="" | ||||||
|  | 		fi | ||||||
|  | 		# image: | ||||||
|  | 		local image=${_MGRCTL_IMAGE} | ||||||
|  | 		# docker cmd: | ||||||
|  | 		local docker_bin="/usr/bin/docker" | ||||||
|  | 		local docker_pull="${docker_bin} pull ${image} ${hide_output}" | ||||||
|  | 		local docker_rm="${docker_bin} image rm -f ${image} ${hide_output}" | ||||||
|  | 		local docker_run="${docker_bin} run" | ||||||
|  | 		# mount config: | ||||||
|  | 		local mount_src="source=${_PLATFORM_CONFIG_FILE}" | ||||||
|  | 		local mount_trg="target=${_PLATFORM_CONFIG_FILE}" | ||||||
|  | 		local mount_opt="type=bind,${mount_src},${mount_trg},readonly" | ||||||
|  | 		local mount="--mount ${mount_opt}" | ||||||
|  | 		# network config: | ||||||
|  | 		local network="--network=${_PLATFORM_NETWORK_NAME}" | ||||||
|  | 		# environment config: | ||||||
|  | 		local envs="-e PLATFORM_TYPE=${_PLATFORM_TYPE}" | ||||||
|  | 		# container args: | ||||||
|  | 		local args="${_MGRCTL_CMD}" | ||||||
|  | 		# mgrctl container params: | ||||||
|  | 		local container="${network} ${mount} ${envs} --rm ${image} ${args}" | ||||||
|  | 		# docker commands: | ||||||
|  | 		local cmd="${docker_pull} && ${docker_run} ${container} && ${docker_rm}" | ||||||
|  | 		# final cmd: | ||||||
|  | 		_SSH_REMOTE_CMD="${cmd}" | ||||||
|  | 		# set cmd for manual start container: | ||||||
|  | 		if $_IS_TTY; then | ||||||
|  | 			# override parammetrs if DEBUG_MODE=false add -it flag: | ||||||
|  | 			docker_pull="${docker_bin} pull ${image}" | ||||||
|  | 			docker_rm="${docker_bin} image rm -f ${image}" | ||||||
|  | 			container="${network} ${mount} ${envs} --rm -i -t ${image}" | ||||||
|  | 			cmd="${docker_pull} && ${docker_run} ${container} && ${docker_rm}" | ||||||
|  | 			_MGRCTL_RUN="${cmd}" | ||||||
|  | 		fi | ||||||
|  | 	# ? BILLmanager6 ||  DNSmanager6 || IP/DNS/DCI/VMmanager5: | ||||||
|  | 	else  | ||||||
|  | 		# final cmd: | ||||||
|  | 		_SSH_REMOTE_CMD="${_MGRCTL_BIN} ${_MGRCTL_ARGS}" | ||||||
|  | 		echo_access_link | ||||||
|  | 	fi | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | gen_access_link() { | ||||||
|  | 	local url="https://${_PLATFORM_IP_ADDR}" | ||||||
|  | 	local port="${_PLATFORM_WEB_PORT}" | ||||||
|  | 	local platform="${_PLATFORM_TYPE}mgr" | ||||||
|  | 	local func="func=auth&key=${_MGRCTL_KEY}" | ||||||
|  | 	_ACCESS_LINK="${url}:${port}/${platform}?${func}" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | echo_access_link() { | ||||||
|  | 	gen_access_link | ||||||
|  | 	echo "mgr    link" | ||||||
|  | 	echo "-----  -------------------------------------------------------------" | ||||||
|  | 	echo "${_PLATFORM_TYPE}${_PLATFORM_GENERATION}  ${_ACCESS_LINK}" | ||||||
|  | 	echo "" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | echo_mgrctl_run_msg() { | ||||||
|  | 	echo "--------------------------------------------------------------------" | ||||||
|  | 	echo "To run the mgrctl container manually on the client server:"  | ||||||
|  | 	echo "copy and paste the command into the terminal." | ||||||
|  | 	echo "This will download the image and run the container interactively." | ||||||
|  | 	echo "After exiting the container and its image will be deleted." | ||||||
|  | 	echo "--------------------------------------------------------------------" | ||||||
|  | 	echo "${_MGRCTL_RUN}" | ||||||
|  | 	echo "--------------------------------------------------------------------" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | get_access() { | ||||||
|  | 	gen_ssh_connect_cmd | ||||||
|  | 	if $_IS_SSH_ONLY; then | ||||||
|  | 		# run connection: | ||||||
|  | 		$_SSH_CONNECT_CMD | ||||||
|  | 	else | ||||||
|  | 		gen_ssh_remote_cmd | ||||||
|  | 		# run connection send remote cmd: | ||||||
|  | 		$_SSH_CONNECT_CMD "${_SSH_REMOTE_CMD}" | ||||||
|  | 		if [[ $_PLATFORM_TYPE == "vm" ]] || \ | ||||||
|  | 			[[ $_PLATFORM_TYPE == "dci" ]] && \ | ||||||
|  | 			[[ $_PLATFORM_GENERATION -eq 6 ]] && \ | ||||||
|  | 			$_IS_TTY; then | ||||||
|  | 			echo_mgrctl_run_msg | ||||||
|  | 		fi | ||||||
|  | 		# use default mgrctl cmd if not set args: | ||||||
|  | 		# run connection again for ssh tty session: | ||||||
|  | 		$_SSH_CONNECT_CMD | ||||||
|  | 	fi | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | get_vault_crt() { | ||||||
|  | 	local public_key=$1 | ||||||
|  | 	local crt_file=$2 | ||||||
|  | 	vault login -method=oidc | ||||||
|  | 	if [ ! -f $crt_file ]; then | ||||||
|  | 		touch $crt_file | ||||||
|  | 	fi | ||||||
|  | 	vault write -field=signed_key ssh/sign/support \ | ||||||
|  | 	public_key=$public_key valid_principals=root > $crt_file | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | set_ssh_agent() { | ||||||
|  | 	local secret_key=$1 | ||||||
|  | 	ssh-add -D | ||||||
|  | 	ssh-add $secret_key | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | renewal_crt() { | ||||||
|  | 	export VAULT_ADDR=$_VAULT_SERVER_ADDR | ||||||
|  | 	get_vault_crt $_VAULT_SSH_PUBLIC_KEY $_SSH_CRT_FILE | ||||||
|  | 	set_ssh_agent $_SSH_PRIVATE_KEY_PATH | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | test_go3_connection() { | ||||||
|  | 	# force only ssh connections without platform features: | ||||||
|  | 	_IS_SSH_ONLY=true | ||||||
|  | 	# set fake client address: | ||||||
|  | 	_PLATFORM_IP_ADDR="${_GO_SERVER_ADDR_TEST}" | ||||||
|  | 	_PLATFORM_SSH_PORT=22 | ||||||
|  | 
 | ||||||
|  | 	echo "Run TEST: $_GO_SERVER_ADDR_RUSSIAN connection" | ||||||
|  | 	get_access | ||||||
|  | 
 | ||||||
|  | 	echo "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --" | ||||||
|  | 
 | ||||||
|  | 	echo "Run TEST: $_GO_SERVER_ADDR_GERMANY connection" | ||||||
|  | 	_GO_SERVER_ADDR="${_GO_SERVER_ADDR_GERMANY}" | ||||||
|  | 	get_access | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Parse user options | ||||||
|  | optparser() { | ||||||
|  | 	# count user-passed options: | ||||||
|  | 	local count_options=$# | ||||||
|  | 	# run help if empty and exit: | ||||||
|  | 	if [[ count_options -eq 0 ]]; then | ||||||
|  | 		help | ||||||
|  | 		exit 2 | ||||||
|  | 	fi | ||||||
|  | 	# run init config if flag --init and exit: | ||||||
|  | 	if [[ "$1" == "--init" ]]; then | ||||||
|  | 		init_config | ||||||
|  | 		exit 0 | ||||||
|  | 	fi | ||||||
|  | 	# load config from config file: | ||||||
|  | 	load_config | ||||||
|  | 	# parse opts: | ||||||
|  | 	while [ ! -z "$1" ]; do | ||||||
|  | 		case "$1" in | ||||||
|  | 			--vm|--dci) | ||||||
|  | 				gen_docker_access_params "$1" | ||||||
|  | 				shift | ||||||
|  | 				_PLATFORM_IP_ADDR="$1" | ||||||
|  | 				;; | ||||||
|  | 			--bill|--dns|--bill5|--ip5|--dns5|--vm5|--dci5) | ||||||
|  | 				gen_coremgr_access_params "$1" | ||||||
|  | 				shift | ||||||
|  | 				_PLATFORM_IP_ADDR="$1" | ||||||
|  | 				;; | ||||||
|  | 			--port|-p) | ||||||
|  | 				shift | ||||||
|  | 				_PLATFORM_SSH_PORT="$1" | ||||||
|  | 				;; | ||||||
|  | 			--web-port|-wp) | ||||||
|  | 				shift | ||||||
|  | 				_PLATFORM_WEB_PORT="$1" | ||||||
|  | 				;; | ||||||
|  | 			--go|--go3) | ||||||
|  | 				_GO_CMD=$(sed 's~[^[:alnum:]/]\+~~g' <<< "$1") | ||||||
|  | 				;; | ||||||
|  | 			--de) | ||||||
|  | 				_GO_SERVER_ADDR="${_GO_SERVER_ADDR_GERMANY}" | ||||||
|  | 				;; | ||||||
|  | 			--mgrctl|--tty|--ssh) | ||||||
|  | 				if [[ "$1" == "--mgrctl" ]]; then | ||||||
|  | 					_IS_MGRCTL_ARGS=true | ||||||
|  | 					shift | ||||||
|  | 					_MGRCTL_ARGS=$@ | ||||||
|  | 				elif [[ "$1" == "--tty" ]]; then | ||||||
|  | 					if $_IS_MGRCTL_ARGS; then | ||||||
|  | 						local error=$(colorize RED "ERROR!") | ||||||
|  | 						echo "${error} $1 must be in before --mgrctl not after" | ||||||
|  | 						exit 1 | ||||||
|  | 					fi | ||||||
|  | 					_IS_TTY=true | ||||||
|  | 				elif [[ "$1" == "--ssh" ]]; then | ||||||
|  | 					_IS_SSH_ONLY=true | ||||||
|  | 				fi | ||||||
|  | 				;; | ||||||
|  | 			--crt|-c) | ||||||
|  | 				renewal_crt | ||||||
|  | 				exit 0 | ||||||
|  | 				;; | ||||||
|  | 			--test|-t) | ||||||
|  | 				test_go3_connection | ||||||
|  | 				exit 0 | ||||||
|  | 				;; | ||||||
|  | 			--help|-h) | ||||||
|  | 				help | ||||||
|  | 				exit 0 | ||||||
|  | 				;; | ||||||
|  | 			--version|-v) | ||||||
|  | 				printf "$_VERSION\n" | ||||||
|  | 				exit 0 | ||||||
|  | 				;; | ||||||
|  | 			*) | ||||||
|  | 				if ! $_IS_MGRCTL_ARGS; then | ||||||
|  | 					help | ||||||
|  | 					exit 1 | ||||||
|  | 				fi | ||||||
|  | 				;; | ||||||
|  | 		esac | ||||||
|  | 	shift | ||||||
|  | 	done | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Entrypoint: | ||||||
|  | main() { | ||||||
|  | 	optparser $@ | ||||||
|  | 	get_access | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # RUN IT: | ||||||
|  | main $@ | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user