Compare commits

...

24 Commits
main ... main

Author SHA1 Message Date
99dd122262 docs: add gogo usage guide 2024-12-12 23:45:19 +09:00
b84ef86cd0 docs: add --de into help msg 2024-12-12 22:54:47 +09:00
ba6dd53f6c update: new config support DE go server and --test option 2024-12-12 21:29:22 +09:00
5815c84b07 Fix: help msg missing auth subcommand 2024-07-22 21:36:20 +09:00
6c6df42e7f Fix: help message, init func and ssh-add cmd in --crt 2024-06-09 16:43:31 +09:00
da3e61eccb Hotfix: some instances use dashes rather than underscores in container names 2024-06-09 16:41:55 +09:00
e0de805af7 Add: gogo.sh script for quick access from employee side 2024-06-08 16:47:12 +09:00
4cdf3b4539 Fix: platform_dummy import bugs 2024-06-08 14:40:36 +09:00
d4de07c340 Add: dci6 auth cli commands 2024-06-07 00:04:35 +09:00
dc6e61dcbe Fix: mgrctl vm6 cli help messages 2024-06-07 00:03:44 +09:00
f773898b71 Update: BaseAPI class now has error handler 2024-06-06 20:59:03 +09:00
cdf3a92527 Update: access funcs 2024-06-06 16:01:21 +09:00
8ad5e2a230 Refactoring: move API init statments to settings 2024-06-06 00:08:10 +09:00
5405679640 Fix: docker-compose docs and network name 2024-06-06 00:07:10 +09:00
72bfeb9193 Add: auth cmd prototype, dummy_platform for api 2024-06-05 00:38:17 +09:00
bb3b20e6c8 Add: depends (tabulate) 2024-06-05 00:36:36 +09:00
287216d975 Add: devtools, Makefile 2024-06-05 00:35:21 +09:00
0f96d0f956 Update: dockerize app, rename pkg to mgrctl, fix imports 2024-06-03 18:58:27 +09:00
34e8118327 Add: sh lib 2024-06-03 10:12:55 +09:00
45f9204c6c Refactoring: remove core pkg, all subpkgs now in root pkg 2024-05-31 13:51:15 +09:00
1d72b8d015 Create: api handlers 2024-05-26 23:06:58 +09:00
f9a213fe3e Update: models now follow PEP, add connection func, example app for show all vm6 users 2024-02-11 23:10:29 +09:00
867cd1c022 Edit: gitignore 2024-02-11 12:00:46 +05:00
2bc9b2e94f Add: db models vm6,dci, and Docker file 2024-02-11 12:00:46 +05:00
68 changed files with 7463 additions and 113 deletions

5
.dockerignore Normal file
View File

@ -0,0 +1,5 @@
docker-compose.yml
.gitignore
.env
.git
.cache

4
.gitignore vendored
View File

@ -158,7 +158,9 @@ cython_debug/
# 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
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
.idea/
.vscode/
# Project specific
config.json
**/dummy_platform

59
Dockerfile Normal file
View File

@ -0,0 +1,59 @@
# app/Dockerfile
# pull the official docker image
FROM python:3.11-alpine as poetry-base
# default build args
ARG APP_VERSION=0.1.0 \
APP_DIR=/app \
SRC_DIR=./mgrctl \
PKG_NAME=mgrctl \
PKG_VERSION=0.1.0
# 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=1.7.1
# 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.11-alpine
# copy app and dependences
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
View 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
View File

@ -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
## 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
```

60
docker-compose.yml Normal file
View File

@ -0,0 +1,60 @@
# ? 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
# ? ./dummy_platform/opt/ispsystem/vm/mysql - database directory
# ? DCI6:
# ? ./dummy_platform/opt/ispsystem/dci/config.json - configuration file
# ? ./dummy_platform/opt/ispsystem/dci/mysql - database directory
# ? Create ./.env file and fill it with required vars:
# ? PLATFORM_TYPE='vm'
# ? Database container:
# ? MYSQL_DATABASE="database name"
# ? MYSQL_ROOT_PASSWORD="super secret password from config.json"
# ? 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
mysql:
container_name: mysql
image: docker-registry.ispsystem.com/mysql:5
volumes:
- ./dummy_platform/opt/ispsystem/${PLATFORM_TYPE}/mysql:/var/lib/mysql
env_file:
- ./.env
labels:
autoconf_mysql: "true"
networks:
vm_box_net: null
command: --group-concat-max-len=131072 --max-connections=1000 --optimizer-search-depth=0
networks:
vm_box_net:
name: vm_box_net
driver: bridge

View File

@ -1,12 +0,0 @@
import click
@click.group(help='access command for lazy example')
@click.option('--debug/--no-debug', default=False)
def cli(debug):
click.echo(f"Debug mode is {'on' if debug else 'off'}")
@cli.command()
def enable():
click.echo('Access granted')

View File

@ -1,13 +0,0 @@
import click
from core.cli.lazy_group import LazyGroup
from settings.general import INSTALLED_APPS
@click.group(
cls=LazyGroup,
lazy_subcommands=INSTALLED_APPS['dci6'],
help='dci6 command for lazy example',
)
def cli():
pass

View File

@ -1,12 +0,0 @@
import click
@click.group(help='access command for lazy example')
@click.option('--debug/--no-debug', default=False)
def cli(debug):
click.echo(f"Debug mode is {'on' if debug else 'off'}")
@cli.command()
def enable():
click.echo('Access granted')

View File

@ -1,12 +0,0 @@
import click
from core.cli.lazy_group import LazyGroup
from settings.general import INSTALLED_APPS
@click.group(
cls=LazyGroup,
lazy_subcommands=INSTALLED_APPS['vm6'],
help='vm6 command for lazy example',
)
def cli():
pass

View File

@ -1,13 +0,0 @@
import click
@click.group(help='nodes command for lazy example')
def cli():
pass
@cli.command(name='list')
def nodes_list():
click.echo('NODES LIST: etc...')
for num in range(1, 10):
click.echo(num)

View File

@ -1,21 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import click
from core.cli.lazy_group import LazyGroup
@click.group(
cls=LazyGroup,
lazy_subcommands={
'vm6': 'apps.vm6.commands.cli',
'dci6': 'apps.dci6.commands.cli',
},
help='main CLI command for lazy example',
)
def cli():
pass
if __name__ == '__main__':
cli()

View File

@ -1,13 +0,0 @@
from settings.environment import env
from settings.general import 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')}"
)

15
mgrctl/__init__.py Normal file
View File

@ -0,0 +1,15 @@
# █▀▄▀█ █▀▀ ▀█▀ ▄▀█ ▀
# █░▀░█ ██▄ ░█░ █▀█ ▄
# -- -- -- -- -- -- -
__author__ = "MOIS3Y, a.garaev, Failak3, Ann_M"
__credits__ = [
"Stepan Zhukovsky",
"Arthur Garaev",
"Vladislav Shmidt",
"Anna Moskovkina"
]
__license__ = "MIT"
__version__ = "0.1.0"
__maintainer__ = "Stepan Zhukovsky"
__email__ = "stepan@zhukovsky.me"
__status__ = "Development"

153
mgrctl/api/base.py Normal file
View File

@ -0,0 +1,153 @@
import sys
import json
import click
import urllib
import requests
from time import sleep
from mgrctl.settings.api import (
API_INPUT_PORT,
API_URL,
API_HEADERS,
API_EMAIL,
API_PASSWORD,
API_VERIFY_SSL,
API_COUNT_TRY_CONNECTIONS
)
from mgrctl.settings.platform import PLATFORM_TYPE
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={}):
attempt = API_COUNT_TRY_CONNECTIONS
while attempt:
attempt -= 1
try:
uri = self._gen_request_url(url)
headers = self.API_HEADERS if not headers else headers
params_str = urllib.parse.urlencode(data, safe="+'()")
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 and PLATFORM_TYPE == 'dci':
# ? workaround if new docker version use dashes
# TODO: ISPsystem developers must set container_name !!!
self.API_URL = f'http://dci-input-1:{API_INPUT_PORT}'
if attempt == 0:
click.echo(f'Error: {type(error).__name__}')
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
View 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
View 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'

15
mgrctl/apps/__init__.py Normal file
View File

@ -0,0 +1,15 @@
# █▀▄▀█ █▀▀ ▀█▀ ▄▀█ ▀
# █░▀░█ ██▄ ░█░ █▀█ ▄
# -- -- -- -- -- -- -
__author__ = "MOIS3Y, a.garaev, Failak3, Ann_M"
__credits__ = [
"Stepan Zhukovsky",
"Arthur Garaev",
"Vladislav Shmidt",
"Anna Moskovkina"
]
__license__ = "MIT"
__version__ = "0.1.0"
__maintainer__ = "Stepan Zhukovsky"
__email__ = "stepan@zhukovsky.me"
__status__ = "Development"

View File

@ -0,0 +1,15 @@
# █▀▄▀█ █▀▀ ▀█▀ ▄▀█ ▀
# █░▀░█ ██▄ ░█░ █▀█ ▄
# -- -- -- -- -- -- -
__author__ = "MOIS3Y, a.garaev, Failak3, Ann_M"
__credits__ = [
"Stepan Zhukovsky",
"Arthur Garaev",
"Vladislav Shmidt",
"Anna Moskovkina"
]
__license__ = "MIT"
__version__ = "0.1.0"
__maintainer__ = "Stepan Zhukovsky"
__email__ = "stepan@zhukovsky.me"
__status__ = "Development"

View 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"

View File

@ -0,0 +1,99 @@
import click
from mgrctl.apps.dci6.auth import __version__
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')
@click.version_option(
version=__version__,
package_name='mgrctl.apps.dci6.auth',
message=__version__
)
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)

View 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

View File

@ -0,0 +1,15 @@
# █▀▄▀█ █▀▀ ▀█▀ ▄▀█ ▀
# █░▀░█ ██▄ ░█░ █▀█ ▄
# -- -- -- -- -- -- -
__author__ = "MOIS3Y, a.garaev, Failak3, Ann_M"
__credits__ = [
"Stepan Zhukovsky",
"Arthur Garaev",
"Vladislav Shmidt",
"Anna Moskovkina"
]
__license__ = "MIT"
__version__ = "0.1.0"
__maintainer__ = "Stepan Zhukovsky"
__email__ = "stepan@zhukovsky.me"
__status__ = "Development"

View 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"

View File

@ -0,0 +1,99 @@
import click
from mgrctl.apps.vm6.auth import __version__
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')
@click.version_option(
version=__version__,
package_name='mgrctl.apps.vm6.auth',
message=__version__
)
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)

View File

@ -0,0 +1,19 @@
import click
from mgrctl.cli.lazy_group import LazyGroup
from mgrctl.settings.general import INSTALLED_APPS
from mgrctl.apps.vm6 import __version__
@click.group(
cls=LazyGroup,
lazy_subcommands=INSTALLED_APPS['vm6'],
help='vm6 command for VM6manager management',
)
@click.version_option(
version=__version__,
package_name='mgrctl',
message=__version__
)
def cli():
pass

20
mgrctl/db/common.py Normal file
View File

@ -0,0 +1,20 @@
import json
from peewee import TextField
class JSONField(TextField):
"""
Class to "fake" a JSON field with a text field.
Not efficient but works nicely
"""
def db_value(self, value):
"""Convert the python value for storage in the database."""
return value if value is None else json.dumps(value)
def python_value(self, value):
"""Convert the database value to a pythonic value."""
return value if value is None else json.loads(value)
class UnknownField(object):
def __init__(self, *_, **__): pass

46
mgrctl/db/connection.py Normal file
View File

@ -0,0 +1,46 @@
from peewee import MySQLDatabase, PostgresqlDatabase
from mgrctl.settings.db import (
DB_ENGINE,
DB_HOST,
DB_PORT,
DB_USER,
DB_PASSWORD
)
def guess_database(db_name):
"""
function checks DB_ENGINE and set correct connection class
with connection params. Expected database name in input.
Args:
db_name (_str_): database name
Returns:
(_MySQLDatabase_or_PostgresqlDatabase_ class):
contains db connection params
"""
if DB_ENGINE == 'mysql':
return MySQLDatabase(
db_name, **{
'charset': 'utf8',
'sql_mode': 'PIPES_AS_CONCAT',
'use_unicode': True,
'host': DB_HOST,
'port': DB_PORT,
'user': DB_USER,
'password': DB_PASSWORD
}
)
if DB_ENGINE == 'psql':
return PostgresqlDatabase(
db_name, **{
'charset': 'utf8',
'sql_mode': 'PIPES_AS_CONCAT',
'use_unicode': True,
'host': DB_HOST,
'port': DB_PORT,
'user': DB_USER,
'password': DB_PASSWORD
}
)

View File

@ -0,0 +1,9 @@
from mgrctl.db.connection import guess_database
# The variable will contain an object of
# the MySQLDatabase or PostgresqlDatabase class as well as all connection data
# If in the future there will be more databases in the platform,
# they should be added here
auth_database = guess_database('auth')
dci_1_database = guess_database('dci_1')

980
mgrctl/db/dci6/models.py Normal file
View File

@ -0,0 +1,980 @@
from peewee import (
AutoField,
BigIntegerField,
CharField,
CompositeKey,
DateTimeField,
FloatField,
ForeignKeyField,
IntegerField,
Model,
TextField,
SQL
)
from mgrctl.db.common import UnknownField
from mgrctl.db.dci6.databases import auth_database
class BaseModel(Model):
class Meta:
database = auth_database
class AlertSetting(BaseModel):
channel = UnknownField(null=True) # json
enabled = IntegerField(constraints=[SQL("DEFAULT 1")])
metric = UnknownField(null=True) # json
metric_id = CharField(null=True)
name = CharField(null=True)
class Meta:
table_name = 'alert_setting'
class AuthAcl(BaseModel):
ip_list = UnknownField() # json
name = CharField(constraints=[SQL("DEFAULT ''")], unique=True)
class Meta:
table_name = 'auth_acl'
class AuthUser(BaseModel):
auth_source = CharField(constraints=[SQL("DEFAULT 'local'")])
avatar = TextField(null=True)
avatar_content_type = CharField(null=True)
avatar_filename = CharField(null=True)
created_date = DateTimeField(null=True)
email = CharField(constraints=[SQL("DEFAULT ''")], unique=True)
email_confirm = IntegerField(constraints=[SQL("DEFAULT 0")])
full_name = CharField(null=True)
ip_list = UnknownField(null=True) # json
lang = CharField(constraints=[SQL("DEFAULT 'en'")])
password = CharField(null=True)
phone_number = CharField(null=True)
property = UnknownField(null=True) # json
reserve_codes = UnknownField(null=True) # json
roles = UnknownField() # json
ssh_priv_key = TextField(null=True)
ssh_pub_key = TextField(null=True)
state = CharField(constraints=[SQL("DEFAULT 'active'")])
timezone = CharField(null=True)
totp = CharField(null=True)
uuid = CharField(null=True, unique=True)
class Meta:
table_name = 'auth_user'
class AuthConfirmToken(BaseModel):
expires_at = DateTimeField(null=True)
resend_token_after = DateTimeField(null=True)
token = CharField(null=True, unique=True)
user = ForeignKeyField(
column_name='user',
field='id',
model=AuthUser,
null=True,
unique=True
)
class Meta:
table_name = 'auth_confirm_token'
class AuthGdprDoc(BaseModel):
change_date = DateTimeField(null=True)
desc_condition = CharField(null=True)
entry_date = DateTimeField(null=True)
locale = CharField(null=True)
name = CharField(unique=True)
required = IntegerField(constraints=[SQL("DEFAULT 0")])
state = CharField()
url = CharField(null=True)
class Meta:
table_name = 'auth_gdpr_doc'
class AuthGdprJournal(BaseModel):
action_date = DateTimeField(null=True)
action_type = CharField()
doc = ForeignKeyField(
column_name='doc',
field='id',
model=AuthGdprDoc,
null=True
)
ip = CharField(null=True)
user_email = CharField()
class Meta:
table_name = 'auth_gdpr_journal'
class AuthGeneratedSshkey(BaseModel):
generation_date = DateTimeField(null=True)
privkey = TextField(null=True)
pubkey = TextField(null=True)
class Meta:
table_name = 'auth_generated_sshkey'
class AuthProduct(BaseModel):
docker_compose_file = TextField(null=True)
name = CharField(null=True)
script = TextField(null=True)
version = CharField(null=True)
class Meta:
table_name = 'auth_product'
indexes = (
(('name', 'version'), True),
)
class AuthInstance(BaseModel):
conn_params = UnknownField(null=True) # json
dbkey = TextField(null=True)
demo = IntegerField(constraints=[SQL("DEFAULT 0")])
failure_reason = UnknownField(null=True) # json
limits = UnknownField(null=True) # json
name = CharField(null=True)
owner = ForeignKeyField(
column_name='owner',
field='id',
model=AuthUser,
null=True
)
product = ForeignKeyField(
column_name='product',
field='id',
model=AuthProduct,
null=True
)
roles = UnknownField(null=True) # json
started = DateTimeField(null=True)
status = CharField()
class Meta:
table_name = 'auth_instance'
class AuthInstanceHistory(BaseModel):
create_time = DateTimeField(constraints=[SQL("DEFAULT CURRENT_TIMESTAMP")])
event_info = UnknownField(null=True) # json
event_type = CharField(null=True)
prev_time = DateTimeField(null=True)
ref = ForeignKeyField(
column_name='ref',
field='id',
model=AuthInstance,
null=True
)
request_id = CharField(null=True)
request_ip = CharField(null=True)
request_owner = CharField(null=True)
request_user = CharField(null=True)
class Meta:
table_name = 'auth_instance_history'
class AuthKey(BaseModel):
expires_at = DateTimeField(null=True)
name = CharField(constraints=[SQL("DEFAULT ''")], unique=True)
user = ForeignKeyField(column_name='user', field='id', model=AuthUser)
class Meta:
table_name = 'auth_key'
class AuthLicense(BaseModel):
instance = ForeignKeyField(
column_name='instance',
field='id',
model=AuthInstance,
null=True,
unique=True
)
last_update = DateTimeField(null=True)
lic_data = UnknownField(null=True) # json
lic_request = UnknownField(null=True) # json
product = CharField(null=True)
signature_expire_date = DateTimeField(null=True)
status = CharField()
class Meta:
table_name = 'auth_license'
class AuthPermission(BaseModel):
data = UnknownField() # json
plugin = CharField(constraints=[SQL("DEFAULT ''")])
service = CharField(constraints=[SQL("DEFAULT ''")])
class Meta:
table_name = 'auth_permission'
indexes = (
(('service', 'plugin'), True),
)
class AuthPricelist(BaseModel):
bill_id = IntegerField(null=True, unique=True)
name = CharField(null=True)
params = UnknownField(null=True) # json
class Meta:
table_name = 'auth_pricelist'
class AuthRestrictions(BaseModel):
attempts_counting_duration = BigIntegerField()
attempts_max_count = IntegerField(null=True)
role = CharField(constraints=[SQL("DEFAULT ''")], unique=True)
time_between_attempts = BigIntegerField()
time_to_unban = BigIntegerField()
class Meta:
table_name = 'auth_restrictions'
class AuthRestrictionsBans(BaseModel):
banned_until = DateTimeField()
user = ForeignKeyField(column_name='user', field='id', model=AuthUser)
user_ip = CharField(null=True)
class Meta:
table_name = 'auth_restrictions_bans'
indexes = (
(('user', 'user_ip'), True),
)
class AuthRole(BaseModel):
data = UnknownField() # json
human_descr = CharField(null=True)
human_name = CharField(null=True)
name = CharField(constraints=[SQL("DEFAULT ''")], unique=True)
class Meta:
table_name = 'auth_role'
class AuthServer(BaseModel):
demo = IntegerField(constraints=[SQL("DEFAULT 0")])
host = CharField(null=True)
instance = ForeignKeyField(
column_name='instance',
field='id',
model=AuthInstance,
null=True,
unique=True
)
login = CharField(null=True)
machine_id = CharField(null=True)
password = CharField(null=True)
port = IntegerField(null=True)
class Meta:
table_name = 'auth_server'
indexes = (
(('host', 'port'), True),
)
class AuthService(BaseModel):
bill_id = IntegerField(null=True, unique=True)
info = UnknownField(null=True) # json
instance = ForeignKeyField(
column_name='instance',
field='id',
model=AuthInstance,
null=True,
unique=True
)
params = UnknownField(null=True) # json
payment_form = TextField(null=True)
status = CharField(null=True)
class Meta:
table_name = 'auth_service'
indexes = (
(('instance', 'bill_id'), True),
)
class AuthServiceDbkey(BaseModel):
dbkey = TextField(null=True)
name = CharField(null=True, unique=True)
class Meta:
table_name = 'auth_service_dbkey'
class AuthSession(BaseModel):
expires_at = DateTimeField(null=True)
is_internal = IntegerField(constraints=[SQL("DEFAULT 0")])
name = CharField(null=True, unique=True)
owner = ForeignKeyField(
column_name='owner',
field='id',
model=AuthUser,
null=True
)
trustee = ForeignKeyField(
backref='auth_user_trustee_set',
column_name='trustee',
field='id',
model=AuthUser,
null=True
)
updated_at = DateTimeField(null=True)
xsrf_token = CharField(null=True)
class Meta:
table_name = 'auth_session'
class AuthToken(BaseModel):
client_ip = CharField(null=True)
description = CharField(constraints=[SQL("DEFAULT ''")])
expires_at = DateTimeField(null=True)
last_used = DateTimeField()
name = CharField(constraints=[SQL("DEFAULT ''")], unique=True)
need_2fa = IntegerField(constraints=[SQL("DEFAULT 0")])
owner = ForeignKeyField(column_name='owner', field='id', model=AuthUser)
trustee = ForeignKeyField(
backref='auth_user_trustee_set',
column_name='trustee',
field='id',
model=AuthUser
)
class Meta:
table_name = 'auth_token'
class AuthTrusteeUser(BaseModel):
instance = ForeignKeyField(
column_name='instance',
field='id',
model=AuthInstance,
null=True
)
role = CharField(null=True)
trustee = ForeignKeyField(
column_name='trustee',
field='id',
model=AuthUser,
null=True
)
user = ForeignKeyField(
backref='auth_user_user_set',
column_name='user',
field='id',
model=AuthUser,
null=True
)
class Meta:
table_name = 'auth_trustee_user'
indexes = (
(('user', 'trustee', 'instance'), True),
)
class AuthUser2Acl(BaseModel):
acl = ForeignKeyField(column_name='acl', field='id', model=AuthAcl)
user = ForeignKeyField(column_name='user', field='id', model=AuthUser)
class Meta:
table_name = 'auth_user2acl'
indexes = (
(('user', 'acl'), True),
)
class AuthUserRole(BaseModel):
instance = ForeignKeyField(
column_name='instance',
field='id',
model=AuthInstance,
null=True
)
roles = UnknownField() # json
state = CharField(constraints=[SQL("DEFAULT 'active'")])
trustee = ForeignKeyField(
column_name='trustee',
field='id',
model=AuthUser
)
user = ForeignKeyField(
backref='auth_user_user_set',
column_name='user',
field='id',
model=AuthUser
)
class Meta:
table_name = 'auth_user_role'
indexes = (
(('user', 'trustee', 'instance'), True),
)
class AuthUserSetting(BaseModel):
data = UnknownField(null=True) # json
instance = ForeignKeyField(
column_name='instance',
field='id',
model=AuthInstance,
null=True
)
name = CharField(null=True)
user = ForeignKeyField(
column_name='user',
field='id',
model=AuthUser
)
class Meta:
table_name = 'auth_user_setting'
indexes = (
(('user', 'name', 'instance'), True),
)
class AuthUserSshkey(BaseModel):
name = CharField(constraints=[SQL("DEFAULT ''")])
owner = ForeignKeyField(
column_name='owner',
field='id',
model=AuthUser,
null=True
)
ssh_pub_key = TextField()
class Meta:
table_name = 'auth_user_sshkey'
indexes = (
(('name', 'owner'), True),
)
class AuthUsersLocks(BaseModel):
description = CharField(constraints=[SQL("DEFAULT ''")])
service_name = CharField()
user = ForeignKeyField(column_name='user', field='id', model=AuthUser)
class Meta:
table_name = 'auth_users_locks'
indexes = (
(('user', 'service_name'), True),
)
primary_key = CompositeKey('service_name', 'user')
class BackupAlembicVersion(BaseModel):
version_num = CharField(primary_key=True)
class Meta:
table_name = 'backup_alembic_version'
class BackupTask(BaseModel):
connection_params = TextField()
cron_expression = CharField()
enabled = IntegerField()
limit_count = IntegerField()
limit_size_mib = IntegerField(null=True)
name = CharField()
note = TextField()
schedule_type = CharField()
storage_type = CharField()
class Meta:
table_name = 'backup_task'
class BackupFile(BaseModel):
date = DateTimeField()
downloadable = IntegerField()
name = CharField()
size = FloatField()
storage_type = CharField()
task = ForeignKeyField(
column_name='task',
field='id',
model=BackupTask,
null=True
)
class Meta:
table_name = 'backup_file'
class CsDocuments(BaseModel):
lang = CharField(constraints=[SQL("DEFAULT ''")])
name = CharField(constraints=[SQL("DEFAULT ''")])
product = CharField(constraints=[SQL("DEFAULT ''")])
required = IntegerField(constraints=[SQL("DEFAULT 0")])
url = CharField(constraints=[SQL("DEFAULT ''")])
class Meta:
table_name = 'cs_documents'
indexes = (
(('product', 'lang', 'name'), True),
)
class CsSettings(BaseModel):
lang = CharField(constraints=[SQL("DEFAULT ''")])
product = CharField(constraints=[SQL("DEFAULT ''")])
prop_key = CharField(constraints=[SQL("DEFAULT ''")])
prop_value = CharField(constraints=[SQL("DEFAULT ''")])
class Meta:
table_name = 'cs_settings'
indexes = (
(('product', 'lang', 'prop_key'), True),
)
class EserviceAlembicVersion(BaseModel):
version_num = CharField(primary_key=True)
class Meta:
table_name = 'eservice_alembic_version'
class EserviceCustomEquipment(BaseModel):
data = TextField(null=True)
device_type = CharField()
features = UnknownField(null=True) # json
handler = CharField(unique=True)
handler_import = CharField(null=True)
name = CharField(unique=True)
protocol = UnknownField(null=True) # json
update_time = DateTimeField()
version = CharField()
class Meta:
table_name = 'eservice_custom_equipment'
class EserviceIpmiFru(BaseModel):
fru_info = UnknownField(null=True) # json
ipmi_id = AutoField()
update_time = DateTimeField()
class Meta:
table_name = 'eservice_ipmi_fru'
class EserviceIpmiInfo(BaseModel):
ipmi_id = AutoField()
model = CharField()
class Meta:
table_name = 'eservice_ipmi_info'
class EserviceIpmiSensors(BaseModel):
ipmi_id = AutoField()
sensors_info = UnknownField(null=True) # json
update_time = DateTimeField()
class Meta:
table_name = 'eservice_ipmi_sensors'
class IpmiProxyLocation(BaseModel):
docker_compose = TextField(null=True)
instance = IntegerField(null=True)
location = IntegerField(null=True)
status = CharField(constraints=[SQL("DEFAULT 'setting_up'")])
status_info = UnknownField(null=True) # json
supported_ipmi_consoles = UnknownField(null=True) # json
class Meta:
table_name = 'ipmi_proxy_location'
indexes = (
(('instance', 'location'), True),
)
class IpmiProxyConnection(BaseModel):
close_time = DateTimeField(null=True)
connection_data = UnknownField(null=True) # json
device_data = UnknownField(null=True) # json
intel_amt = IntegerField(null=True)
ipmi = IntegerField(null=True)
listen_web_port = IntegerField(null=True, unique=True)
location = ForeignKeyField(
column_name='location_id',
field='id',
model=IpmiProxyLocation
)
type = CharField(constraints=[SQL("DEFAULT 'web'")])
user = IntegerField(null=True)
vnc_port = IntegerField(null=True)
class Meta:
table_name = 'ipmi_proxy_connection'
class IpmiProxyJavaAgreement(BaseModel):
instance = IntegerField(null=True, unique=True)
java_agree = IntegerField(constraints=[SQL("DEFAULT 1")])
class Meta:
table_name = 'ipmi_proxy_java_agreement'
class IpmiProxyPlugin(BaseModel):
instance = IntegerField(null=True, unique=True)
class Meta:
table_name = 'ipmi_proxy_plugin'
class IspSettings(BaseModel):
name = CharField(primary_key=True)
value = TextField()
class Meta:
table_name = 'isp_settings'
class JournalAlembicVersion(BaseModel):
version_num = CharField(primary_key=True)
class Meta:
table_name = 'journal_alembic_version'
class JournalEvents(BaseModel):
data = UnknownField(null=True) # json
date_time = DateTimeField(
constraints=[SQL("DEFAULT CURRENT_TIMESTAMP(3)")]
)
entity_id = IntegerField()
entity_name = CharField()
entity_owner = IntegerField()
entity_type = CharField()
ip = CharField()
roles = UnknownField(null=True) # json
trustee_email = CharField(index=True)
trustee_id = IntegerField()
type = CharField()
user_email = CharField(index=True)
user_id = IntegerField()
class Meta:
table_name = 'journal_events'
class MsgsChannel(BaseModel):
comment = TextField(null=True)
creation_date = DateTimeField(null=True)
delivery_method = CharField()
enabled = IntegerField(constraints=[SQL("DEFAULT 1")])
language = CharField()
name = CharField()
params = UnknownField(null=True) # json
state = CharField(null=True)
class Meta:
table_name = 'msgs_channel'
class MsgsDeliveryMethod(BaseModel):
delivery_method = CharField(primary_key=True)
dm_params = UnknownField(null=True) # json
class Meta:
table_name = 'msgs_delivery_method'
class MsgsMessageDesign(BaseModel):
design = CharField(primary_key=True)
class Meta:
table_name = 'msgs_message_design'
class MsgsMessageDesignVariant(BaseModel):
delivery_method = CharField()
design = CharField()
design_content = TextField(null=True)
language = CharField()
class Meta:
table_name = 'msgs_message_design_variant'
indexes = (
(('design', 'delivery_method', 'language'), True),
(('design', 'delivery_method', 'language'), True),
)
primary_key = CompositeKey('delivery_method', 'design', 'language')
class MsgsNoticeReceiver(BaseModel):
delivery_method = CharField()
receivers = UnknownField(null=True) # json
user = IntegerField(null=True)
class Meta:
table_name = 'msgs_notice_receiver'
indexes = (
(('user', 'delivery_method'), True),
)
class MsgsTemplate(BaseModel):
priority = CharField()
template = CharField(primary_key=True)
class Meta:
table_name = 'msgs_template'
class MsgsTemplate2Category(BaseModel):
category = CharField()
template = CharField()
class Meta:
table_name = 'msgs_template2category'
indexes = (
(('template', 'category'), True),
(('template', 'category'), True),
)
primary_key = CompositeKey('category', 'template')
class MsgsTemplateVariant(BaseModel):
delivery_method = CharField()
language = CharField()
template = CharField()
tv_params = UnknownField(null=True) # json
class Meta:
table_name = 'msgs_template_variant'
indexes = (
(('template', 'delivery_method', 'language'), True),
(('template', 'delivery_method', 'language'), True),
)
primary_key = CompositeKey('delivery_method', 'language', 'template')
class MsgsUser(BaseModel):
language = CharField()
user = AutoField()
class Meta:
table_name = 'msgs_user'
class MsgsUser2DeliveryMethod(BaseModel):
delivery_method = CharField()
u2dm_params = UnknownField(null=True) # json
user = IntegerField()
class Meta:
table_name = 'msgs_user2delivery_method'
indexes = (
(('user', 'delivery_method'), True),
(('user', 'delivery_method'), True),
)
primary_key = CompositeKey('delivery_method', 'user')
class MsgsUserSubscription(BaseModel):
category = CharField()
delivery_method = CharField()
user = IntegerField()
class Meta:
table_name = 'msgs_user_subscription'
indexes = (
(('user', 'delivery_method', 'category'), True),
)
primary_key = CompositeKey('category', 'delivery_method', 'user')
class NcNotice(BaseModel):
create_timestamp = IntegerField(null=True)
entity = UnknownField(null=True) # json
params = UnknownField(null=True) # json
status = CharField(constraints=[SQL("DEFAULT 'sended'")])
timestamp = IntegerField(null=True)
type = CharField(null=True)
class Meta:
table_name = 'nc_notice'
class NcUser(BaseModel):
last_notice_timestamp = IntegerField(constraints=[SQL("DEFAULT 0")])
read_timestamp = IntegerField(constraints=[SQL("DEFAULT 0")])
class Meta:
table_name = 'nc_user'
class NcNotice2User(BaseModel):
notice = ForeignKeyField(
column_name='notice',
field='id',
model=NcNotice,
null=True
)
read_timestamp = IntegerField(constraints=[SQL("DEFAULT 0")])
user = ForeignKeyField(
column_name='user',
field='id',
model=NcUser,
null=True
)
class Meta:
table_name = 'nc_notice2user'
indexes = (
(('user', 'notice'), True),
)
class NotifierNotify(BaseModel):
additional_info = UnknownField() # json
description = UnknownField() # json
instance_id = IntegerField(null=True)
modify_index = BigIntegerField()
name = CharField(null=True)
notify_type = CharField()
owner = IntegerField(null=True)
request_id = CharField(null=True)
request_ip = CharField(null=True)
time = DateTimeField(
constraints=[SQL("DEFAULT CURRENT_TIMESTAMP")],
index=True
)
trustee = IntegerField(null=True)
class Meta:
table_name = 'notifier_notify'
class PsPlugin(BaseModel):
content = TextField()
current_version = CharField(null=True)
deprecated = IntegerField(constraints=[SQL("DEFAULT 0")])
filename = CharField(null=True)
intname = CharField(null=True, unique=True)
metadata = UnknownField() # json
name = CharField(null=True, unique=True)
obsoletes = UnknownField(null=True) # json
plugin_requirement = UnknownField(null=True) # json
product = CharField()
remote = IntegerField(constraints=[SQL("DEFAULT 0")])
status = CharField(constraints=[SQL("DEFAULT 'disabled'")])
version = CharField(null=True)
class Meta:
table_name = 'ps_plugin'
class PsPluginLicense(BaseModel):
instance = IntegerField(null=True)
license = UnknownField(null=True) # json
plugin = IntegerField(null=True)
class Meta:
table_name = 'ps_plugin_license'
indexes = (
(('instance', 'plugin'), True),
)
class ReportAlembicVersion(BaseModel):
version_num = CharField(primary_key=True)
class Meta:
table_name = 'report_alembic_version'
class ReportFile(BaseModel):
additional_info = UnknownField(null=True) # json
content = TextField(null=True)
create_datetime = DateTimeField()
entity = CharField()
entity_ids = UnknownField() # json
filename = CharField(null=True)
is_read = IntegerField()
status = CharField()
status_info = UnknownField(null=True) # json
type = CharField()
class Meta:
table_name = 'report_file'
class TaskmgrTask(BaseModel):
before_execute = UnknownField(null=True) # json
bin_args = UnknownField(null=True) # json
bin_path = CharField(constraints=[SQL("DEFAULT ''")])
callback_params = UnknownField(null=True) # json
defer_resolve = UnknownField(null=True) # json
defer_wait = UnknownField(null=True) # json
depends_on = UnknownField(null=True) # json
env = UnknownField(null=True) # json
instance_id = CharField(null=True)
lock_tag = UnknownField(null=True) # json
name = CharField(constraints=[SQL("DEFAULT ''")])
notify = UnknownField(null=True) # json
on_start = UnknownField(null=True) # json
output = TextField()
queue = UnknownField(null=True) # json
registration_time = DateTimeField(
constraints=[SQL("DEFAULT CURRENT_TIMESTAMP")]
)
request_info = UnknownField(null=True) # json
return_code = IntegerField()
service = CharField(constraints=[SQL("DEFAULT ''")])
status = CharField(constraints=[SQL("DEFAULT 'created'")], index=True)
stdin = UnknownField(null=True) # json
ttl = IntegerField()
work_dir = CharField(constraints=[SQL("DEFAULT ''")])
worker_id = CharField(null=True)
class Meta:
table_name = 'taskmgr_task'
class Vault(BaseModel):
key = CharField(unique=True)
value = TextField()
class Meta:
table_name = 'vault'
class VaultAlembicVersion(BaseModel):
version_num = CharField(primary_key=True)
class Meta:
table_name = 'vault_alembic_version'
class VaultQueryLog(BaseModel):
date = DateTimeField()
owner_email = CharField()
owner_id = IntegerField()
request_id = CharField()
request_ip = CharField()
service = CharField()
trustee_id = IntegerField()
class Meta:
table_name = 'vault_query_log'

View File

@ -0,0 +1,8 @@
from mgrctl.db.connection import guess_database
# The variable will contain an object of
# the MySQLDatabase or PostgresqlDatabase class as well as all connection data
# If in the future there will be more databases in the platform,
# they should be added here
isp_database = guess_database('isp')

3156
mgrctl/db/vm6/models.py Normal file

File diff suppressed because it is too large Load Diff

32
mgrctl/mgrctl.py Executable file
View 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()

View File

@ -1,12 +1,12 @@
from settings.general import BASE_DIR
from mgrctl.settings.general import BASE_DIR
from settings.platform import (
from mgrctl.settings.platform import (
PLATFORM_TYPE,
PLATFORM_URL,
PLATFORM_CONFIG
)
from settings.db import(
from mgrctl.settings.db import(
DB_ENGINE,
DB_HOST,
DB_PORT,

47
mgrctl/settings/api.py Normal file
View 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' if PLATFORM_TYPE == 'vm' else 'dci_input_1'
# 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

View File

@ -1,5 +1,5 @@
from settings.environment import env
from settings.platform import PLATFORM_CONFIG
from mgrctl.settings.environment import env
from mgrctl.settings.platform import PLATFORM_CONFIG
# ! Required because some instance use psql db:

View File

@ -6,10 +6,9 @@ BASE_DIR = pathlib.Path(__file__).resolve().parent.parent
INSTALLED_APPS = {
'vm6': {
'access': 'apps.vm6.access.commands.cli',
'nodes': 'apps.vm6.nodes.commands.cli',
'auth': 'mgrctl.apps.vm6.auth.commands.cli',
},
'dci6': {
'access': 'apps.dci6.access.commands.cli',
'auth': 'mgrctl.apps.dci6.auth.commands.cli',
},
}

View 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
View 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'))

View File

@ -1,8 +1,9 @@
import json
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
@ -16,5 +17,7 @@ def parse_json_file(file_path):
with open(file_path, 'r') as f:
return json.load(f)
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)

953
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,13 @@
[tool.poetry]
name = "isp-maintenance"
name = "mgrctl"
version = "0.1.0"
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>",
"Failak3 <v.shmidt@ispsystem.com>",
"a.garaev <a.garaev@ispsystem.com>",
"Ann_M <a.moskovkina@ispsystem.com>"
]
license = "MIT"
readme = "README.md"
@ -12,11 +17,17 @@ peewee = "^3.17.0"
click = "^8.1.7"
requests = "^2.31.0"
environs = "^10.3.0"
pymysql = {extras = ["rsa"], version = "^1.1.0"}
poetry-plugin-export = "^1.6.0"
sh = "^2.0.7"
tabulate = "^0.9.0"
[tool.poetry.group.dev.dependencies]
flake8 = "^7.0.0"
[tool.poetry.scripts]
mgrctl = 'mgrctl.mgrctl:cli'
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

View File

@ -1,6 +1,65 @@
build==1.0.3 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:538aab1b64f9828977f84bc63ae570b060a8ed1be419e7870b8b4fc5e6ea553b \
--hash=sha256:589bf99a67df7c9cf07ec0ac0e5e2ea5d4b37ac63301c4986d1acb126aa83f8f
cachecontrol[filecache]==0.13.1 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:95dedbec849f46dda3137866dc28b9d133fc9af55f5b805ab1291833e4457aa4 \
--hash=sha256:f012366b79d2243a6118309ce73151bf52a38d4a5dac8ea57f09bd29087e506b
certifi==2024.2.2 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f \
--hash=sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1
cffi==1.16.0 ; python_version >= "3.11" and python_version < "4.0" and (platform_python_implementation != "PyPy" or sys_platform == "darwin") \
--hash=sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc \
--hash=sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a \
--hash=sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417 \
--hash=sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab \
--hash=sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520 \
--hash=sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36 \
--hash=sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743 \
--hash=sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8 \
--hash=sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed \
--hash=sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684 \
--hash=sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56 \
--hash=sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324 \
--hash=sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d \
--hash=sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235 \
--hash=sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e \
--hash=sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088 \
--hash=sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000 \
--hash=sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7 \
--hash=sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e \
--hash=sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673 \
--hash=sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c \
--hash=sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe \
--hash=sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2 \
--hash=sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098 \
--hash=sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8 \
--hash=sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a \
--hash=sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0 \
--hash=sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b \
--hash=sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896 \
--hash=sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e \
--hash=sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9 \
--hash=sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2 \
--hash=sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b \
--hash=sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6 \
--hash=sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404 \
--hash=sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f \
--hash=sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0 \
--hash=sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4 \
--hash=sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc \
--hash=sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936 \
--hash=sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba \
--hash=sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872 \
--hash=sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb \
--hash=sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614 \
--hash=sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1 \
--hash=sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d \
--hash=sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969 \
--hash=sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b \
--hash=sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4 \
--hash=sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627 \
--hash=sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956 \
--hash=sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357
charset-normalizer==3.3.2 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027 \
--hash=sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087 \
@ -92,32 +151,449 @@ charset-normalizer==3.3.2 ; python_version >= "3.11" and python_version < "4.0"
--hash=sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33 \
--hash=sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519 \
--hash=sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561
cleo==2.1.0 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:0b2c880b5d13660a7ea651001fb4acb527696c01f15c9ee650f377aa543fd523 \
--hash=sha256:4a31bd4dd45695a64ee3c4758f583f134267c2bc518d8ae9a29cf237d009b07e
click==8.1.7 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \
--hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de
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" or os_name == "nt") \
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
crashtest==0.4.1 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:80d7b1f316ebfbd429f648076d6275c877ba30ba48979de4191714a75266f0ce \
--hash=sha256:8d23eac5fa660409f57472e3851dab7ac18aba459a8d19cbbba86d3d5aecd2a5
cryptography==42.0.2 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:087887e55e0b9c8724cf05361357875adb5c20dec27e5816b653492980d20380 \
--hash=sha256:09a77e5b2e8ca732a19a90c5bca2d124621a1edb5438c5daa2d2738bfeb02589 \
--hash=sha256:130c0f77022b2b9c99d8cebcdd834d81705f61c68e91ddd614ce74c657f8b3ea \
--hash=sha256:141e2aa5ba100d3788c0ad7919b288f89d1fe015878b9659b307c9ef867d3a65 \
--hash=sha256:28cb2c41f131a5758d6ba6a0504150d644054fd9f3203a1e8e8d7ac3aea7f73a \
--hash=sha256:2f9f14185962e6a04ab32d1abe34eae8a9001569ee4edb64d2304bf0d65c53f3 \
--hash=sha256:320948ab49883557a256eab46149df79435a22d2fefd6a66fe6946f1b9d9d008 \
--hash=sha256:36d4b7c4be6411f58f60d9ce555a73df8406d484ba12a63549c88bd64f7967f1 \
--hash=sha256:3b15c678f27d66d247132cbf13df2f75255627bcc9b6a570f7d2fd08e8c081d2 \
--hash=sha256:3dbd37e14ce795b4af61b89b037d4bc157f2cb23e676fa16932185a04dfbf635 \
--hash=sha256:4383b47f45b14459cab66048d384614019965ba6c1a1a141f11b5a551cace1b2 \
--hash=sha256:44c95c0e96b3cb628e8452ec060413a49002a247b2b9938989e23a2c8291fc90 \
--hash=sha256:4b063d3413f853e056161eb0c7724822a9740ad3caa24b8424d776cebf98e7ee \
--hash=sha256:52ed9ebf8ac602385126c9a2fe951db36f2cb0c2538d22971487f89d0de4065a \
--hash=sha256:55d1580e2d7e17f45d19d3b12098e352f3a37fe86d380bf45846ef257054b242 \
--hash=sha256:5ef9bc3d046ce83c4bbf4c25e1e0547b9c441c01d30922d812e887dc5f125c12 \
--hash=sha256:5fa82a26f92871eca593b53359c12ad7949772462f887c35edaf36f87953c0e2 \
--hash=sha256:61321672b3ac7aade25c40449ccedbc6db72c7f5f0fdf34def5e2f8b51ca530d \
--hash=sha256:701171f825dcab90969596ce2af253143b93b08f1a716d4b2a9d2db5084ef7be \
--hash=sha256:841ec8af7a8491ac76ec5a9522226e287187a3107e12b7d686ad354bb78facee \
--hash=sha256:8a06641fb07d4e8f6c7dda4fc3f8871d327803ab6542e33831c7ccfdcb4d0ad6 \
--hash=sha256:8e88bb9eafbf6a4014d55fb222e7360eef53e613215085e65a13290577394529 \
--hash=sha256:a00aee5d1b6c20620161984f8ab2ab69134466c51f58c052c11b076715e72929 \
--hash=sha256:a047682d324ba56e61b7ea7c7299d51e61fd3bca7dad2ccc39b72bd0118d60a1 \
--hash=sha256:a7ef8dd0bf2e1d0a27042b231a3baac6883cdd5557036f5e8df7139255feaac6 \
--hash=sha256:ad28cff53f60d99a928dfcf1e861e0b2ceb2bc1f08a074fdd601b314e1cc9e0a \
--hash=sha256:b9097a208875fc7bbeb1286d0125d90bdfed961f61f214d3f5be62cd4ed8a446 \
--hash=sha256:b97fe7d7991c25e6a31e5d5e795986b18fbbb3107b873d5f3ae6dc9a103278e9 \
--hash=sha256:e0ec52ba3c7f1b7d813cd52649a5b3ef1fc0d433219dc8c93827c57eab6cf888 \
--hash=sha256:ea2c3ffb662fec8bbbfce5602e2c159ff097a4631d96235fcf0fb00e59e3ece4 \
--hash=sha256:fa3dec4ba8fb6e662770b74f62f1a0c7d4e37e25b58b2bf2c1be4c95372b4a33 \
--hash=sha256:fbeb725c9dc799a574518109336acccaf1303c30d45c075c665c0793c2f79a7f
distlib==0.3.8 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784 \
--hash=sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64
dulwich==0.21.7 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:0fc3078a1ba04c588fabb0969d3530efd5cd1ce2cf248eefb6baf7cbc15fc285 \
--hash=sha256:10893105c6566fc95bc2a67b61df7cc1e8f9126d02a1df6a8b2b82eb59db8ab9 \
--hash=sha256:12d61334a575474e707614f2e93d6ed4cdae9eb47214f9277076d9e5615171d3 \
--hash=sha256:2590e9b431efa94fc356ae33b38f5e64f1834ec3a94a6ac3a64283b206d07aa3 \
--hash=sha256:25c3ab8fb2e201ad2031ddd32e4c68b7c03cb34b24a5ff477b7a7dcef86372f5 \
--hash=sha256:274c18ec3599a92a9b67abaf110e4f181a4f779ee1aaab9e23a72e89d71b2bd9 \
--hash=sha256:29bb5c1d70eba155ded41ed8a62be2f72edbb3c77b08f65b89c03976292f6d1b \
--hash=sha256:2bc12697f0918bee324c18836053644035362bb3983dc1b210318f2fed1d7132 \
--hash=sha256:2e2c66888207b71cd1daa2acb06d3984a6bc13787b837397a64117aa9fc5936a \
--hash=sha256:404b8edeb3c3a86c47c0a498699fc064c93fa1f8bab2ffe919e8ab03eafaaad3 \
--hash=sha256:40dcbd29ba30ba2c5bfbab07a61a5f20095541d5ac66d813056c122244df4ac0 \
--hash=sha256:460b3849d5c3d3818a80743b4f7a0094c893c559f678e56a02fff570b49a644a \
--hash=sha256:460ba74bdb19f8d498786ae7776745875059b1178066208c0fd509792d7f7bfc \
--hash=sha256:4637cbd8ed1012f67e1068aaed19fcc8b649bcf3e9e26649826a303298c89b9d \
--hash=sha256:471305af74790827fcbafe330fc2e8bdcee4fb56ca1177c8c481b1c8f806c4a4 \
--hash=sha256:4a043b90958cec866b4edc6aef5fe3c2c96a664d0b357e1682a46f6c477273c4 \
--hash=sha256:4b09bc3a64fb70132ec14326ecbe6e0555381108caff3496898962c4136a48c6 \
--hash=sha256:4bc4c5366eaf26dda3fdffe160a3b515666ed27c2419f1d483da285ac1411de0 \
--hash=sha256:4c51058ec4c0b45dc5189225b9e0c671b96ca9713c1daf71d622c13b0ab07681 \
--hash=sha256:4f18f0a311fb7734b033a3101292b932158cade54b74d1c44db519e42825e5a2 \
--hash=sha256:61e3451bd3d3844f2dca53f131982553be4d1b1e1ebd9db701843dd76c4dba31 \
--hash=sha256:62bfb26bdce869cd40be443dfd93143caea7089b165d2dcc33de40f6ac9d812a \
--hash=sha256:675a612ce913081beb0f37b286891e795d905691dfccfb9bf73721dca6757cde \
--hash=sha256:6bd69921fdd813b7469a3c77bc75c1783cc1d8d72ab15a406598e5a3ba1a1503 \
--hash=sha256:6c589468e5c0cd84e97eb7ec209ab005a2cb69399e8c5861c3edfe38989ac3a8 \
--hash=sha256:6de6f8de4a453fdbae8062a6faa652255d22a3d8bce0cd6d2d6701305c75f2b3 \
--hash=sha256:739b191f61e1c4ce18ac7d520e7a7cbda00e182c3489552408237200ce8411ad \
--hash=sha256:74700e4c7d532877355743336c36f51b414d01e92ba7d304c4f8d9a5946dbc81 \
--hash=sha256:7836da3f4110ce684dcd53489015fb7fa94ed33c5276e3318b8b1cbcb5b71e08 \
--hash=sha256:7bca4b86e96d6ef18c5bc39828ea349efb5be2f9b1f6ac9863f90589bac1084d \
--hash=sha256:7d8ab29c660125db52106775caa1f8f7f77a69ed1fe8bc4b42bdf115731a25bf \
--hash=sha256:808e8b9cc0aa9ac74870b49db4f9f39a52fb61694573f84b9c0613c928d4caf8 \
--hash=sha256:817822f970e196e757ae01281ecbf21369383285b9f4a83496312204cf889b8c \
--hash=sha256:8278835e168dd097089f9e53088c7a69c6ca0841aef580d9603eafe9aea8c358 \
--hash=sha256:858842b30ad6486aacaa607d60bab9c9a29e7c59dc2d9cb77ae5a94053878c08 \
--hash=sha256:869eb7be48243e695673b07905d18b73d1054a85e1f6e298fe63ba2843bb2ca1 \
--hash=sha256:8869fc8ec3dda743e03d06d698ad489b3705775fe62825e00fa95aa158097fc0 \
--hash=sha256:8929c37986c83deb4eb500c766ee28b6670285b512402647ee02a857320e377c \
--hash=sha256:a0650ec77d89cb947e3e4bbd4841c96f74e52b4650830112c3057a8ca891dc2f \
--hash=sha256:a7b5624b02ef808cdc62dabd47eb10cd4ac15e8ac6df9e2e88b6ac6b40133673 \
--hash=sha256:a9e9c66833cea580c3ac12927e4b9711985d76afca98da971405d414de60e968 \
--hash=sha256:b0d2e4485b98695bf95350ce9d38b1bb0aaac2c34ad00a0df789aa33c934469b \
--hash=sha256:c01a735b9a171dcb634a97a3cec1b174cfbfa8e840156870384b633da0460f18 \
--hash=sha256:c3a539b4696a42fbdb7412cb7b66a4d4d332761299d3613d90a642923c7560e1 \
--hash=sha256:c3d1685f320907a52c40fd5890627945c51f3a5fa4bcfe10edb24fec79caadec \
--hash=sha256:c92e72c43c9e9e936b01a57167e0ea77d3fd2d82416edf9489faa87278a1cdf7 \
--hash=sha256:cc1e11be527ac06316539b57a7688bcb1b6a3e53933bc2f844397bc50734e9ae \
--hash=sha256:ce8db196e79c1f381469410d26fb1d8b89c6b87a4e7f00ff418c22a35121405c \
--hash=sha256:d05d3c781bc74e2c2a2a8f4e4e2ed693540fbe88e6ac36df81deac574a6dad99 \
--hash=sha256:d097e963eb6b9fa53266146471531ad9c6765bf390849230311514546ed64db2 \
--hash=sha256:d4a2d76c96426e791556836ef43542b639def81be4f1d6d4322cd886c115eae1 \
--hash=sha256:d4c0110798099bb7d36a110090f2688050703065448895c4f53ade808d889dd3 \
--hash=sha256:d54c9d0e845be26f65f954dff13a1cd3f2b9739820c19064257b8fd7435ab263 \
--hash=sha256:d5882e70b74ac3c736a42d3fdd4f5f2e6570637f59ad5d3e684760290b58f041 \
--hash=sha256:d62446797163317a397a10080c6397ffaaca51a7804c0120b334f8165736c56a \
--hash=sha256:d96ca5e0dde49376fbcb44f10eddb6c30284a87bd03bb577c59bb0a1f63903fa \
--hash=sha256:e0064363bd5e814359657ae32517fa8001e8573d9d040bd997908d488ab886ed \
--hash=sha256:e138d516baa6b5bafbe8f030eccc544d0d486d6819b82387fc0e285e62ef5261 \
--hash=sha256:e1957b65f96e36c301e419d7adaadcff47647c30eb072468901bb683b1000bc5 \
--hash=sha256:e25953c7acbbe4e19650d0225af1c0c0e6882f8bddd2056f75c1cc2b109b88ad \
--hash=sha256:e274cebaf345f0b1e3b70197f2651de92b652386b68020cfd3bf61bc30f6eaaa \
--hash=sha256:e598d743c6c0548ebcd2baf94aa9c8bfacb787ea671eeeb5828cfbd7d56b552f \
--hash=sha256:e84cc606b1f581733df4350ca4070e6a8b30be3662bbb81a590b177d0c996c91 \
--hash=sha256:ecd315847dea406a4decfa39d388a2521e4e31acde3bd9c2609c989e817c6d62 \
--hash=sha256:ed60d1f610ef6437586f7768254c2a93820ccbd4cfdac7d182cf2d6e615969bb \
--hash=sha256:f34bf9b9fa9308376263fd9ac43143c7c09da9bc75037bb75c6c2423a151b92c \
--hash=sha256:f6c88acb60a1f4d31bd6d13bfba465853b3df940ee4a0f2a3d6c7a0778c705b7 \
--hash=sha256:fa4d14767cf7a49c9231c2e52cb2a3e90d0c83f843eb6a2ca2b5d81d254cf6b9 \
--hash=sha256:ffc27fb063f740712e02b4d2f826aee8bbed737ed799962fef625e2ce56e2d29
environs==10.3.0 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:cc421ddb143fa30183568164755aa113a160e555cd19e97e664c478662032c24 \
--hash=sha256:feeaf28f17fd0499f9cd7c0fcf408c6d82c308e69e335eb92d09322fc9ed8138
fastjsonschema==2.19.1 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:3672b47bc94178c9f23dbb654bf47440155d4db9df5f7bc47643315f9c405cd0 \
--hash=sha256:e3126a94bdc4623d3de4485f8d468a12f02a67921315ddc87836d6e456dc789d
filelock==3.13.1 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e \
--hash=sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c
idna==3.6 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \
--hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f
importlib-metadata==7.0.1 ; python_version >= "3.11" and python_version < "3.12" \
--hash=sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e \
--hash=sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc
installer==0.7.0 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53 \
--hash=sha256:a26d3e3116289bb08216e0d0f7d925fcef0b0194eedfa0c944bcaaa106c4b631
jaraco-classes==3.3.1 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:86b534de565381f6b3c1c830d13f931d7be1a75f0081c57dff615578676e2206 \
--hash=sha256:cb28a5ebda8bc47d8c8015307d93163464f9f2b91ab4006e09ff0ce07e8bfb30
jeepney==0.8.0 ; python_version >= "3.11" and python_version < "4.0" and sys_platform == "linux" \
--hash=sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806 \
--hash=sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755
keyring==24.3.0 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:4446d35d636e6a10b8bce7caa66913dd9eca5fd222ca03a3d42c38608ac30836 \
--hash=sha256:e730ecffd309658a08ee82535a3b5ec4b4c8669a9be11efb66249d8e0aeb9a25
marshmallow==3.20.2 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:4c1daff273513dc5eb24b219a8035559dc573c8f322558ef85f5438ddd1236dd \
--hash=sha256:c21d4b98fee747c130e6bc8f45c4b3199ea66bc00c12ee1f639f0aeca034d5e9
more-itertools==10.2.0 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684 \
--hash=sha256:8fccb480c43d3e99a00087634c06dd02b0d50fbf088b380de5a41a015ec239e1
msgpack==1.0.7 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:04ad6069c86e531682f9e1e71b71c1c3937d6014a7c3e9edd2aa81ad58842862 \
--hash=sha256:0bfdd914e55e0d2c9e1526de210f6fe8ffe9705f2b1dfcc4aecc92a4cb4b533d \
--hash=sha256:1dc93e8e4653bdb5910aed79f11e165c85732067614f180f70534f056da97db3 \
--hash=sha256:1e2d69948e4132813b8d1131f29f9101bc2c915f26089a6d632001a5c1349672 \
--hash=sha256:235a31ec7db685f5c82233bddf9858748b89b8119bf4538d514536c485c15fe0 \
--hash=sha256:27dcd6f46a21c18fa5e5deed92a43d4554e3df8d8ca5a47bf0615d6a5f39dbc9 \
--hash=sha256:28efb066cde83c479dfe5a48141a53bc7e5f13f785b92ddde336c716663039ee \
--hash=sha256:3476fae43db72bd11f29a5147ae2f3cb22e2f1a91d575ef130d2bf49afd21c46 \
--hash=sha256:36e17c4592231a7dbd2ed09027823ab295d2791b3b1efb2aee874b10548b7524 \
--hash=sha256:384d779f0d6f1b110eae74cb0659d9aa6ff35aaf547b3955abf2ab4c901c4819 \
--hash=sha256:38949d30b11ae5f95c3c91917ee7a6b239f5ec276f271f28638dec9156f82cfc \
--hash=sha256:3967e4ad1aa9da62fd53e346ed17d7b2e922cba5ab93bdd46febcac39be636fc \
--hash=sha256:3e7bf4442b310ff154b7bb9d81eb2c016b7d597e364f97d72b1acc3817a0fdc1 \
--hash=sha256:3f0c8c6dfa6605ab8ff0611995ee30d4f9fcff89966cf562733b4008a3d60d82 \
--hash=sha256:484ae3240666ad34cfa31eea7b8c6cd2f1fdaae21d73ce2974211df099a95d81 \
--hash=sha256:4a7b4f35de6a304b5533c238bee86b670b75b03d31b7797929caa7a624b5dda6 \
--hash=sha256:4cb14ce54d9b857be9591ac364cb08dc2d6a5c4318c1182cb1d02274029d590d \
--hash=sha256:4e71bc4416de195d6e9b4ee93ad3f2f6b2ce11d042b4d7a7ee00bbe0358bd0c2 \
--hash=sha256:52700dc63a4676669b341ba33520f4d6e43d3ca58d422e22ba66d1736b0a6e4c \
--hash=sha256:572efc93db7a4d27e404501975ca6d2d9775705c2d922390d878fcf768d92c87 \
--hash=sha256:576eb384292b139821c41995523654ad82d1916da6a60cff129c715a6223ea84 \
--hash=sha256:5b0bf0effb196ed76b7ad883848143427a73c355ae8e569fa538365064188b8e \
--hash=sha256:5b6ccc0c85916998d788b295765ea0e9cb9aac7e4a8ed71d12e7d8ac31c23c95 \
--hash=sha256:5ed82f5a7af3697b1c4786053736f24a0efd0a1b8a130d4c7bfee4b9ded0f08f \
--hash=sha256:6d4c80667de2e36970ebf74f42d1088cc9ee7ef5f4e8c35eee1b40eafd33ca5b \
--hash=sha256:730076207cb816138cf1af7f7237b208340a2c5e749707457d70705715c93b93 \
--hash=sha256:7687e22a31e976a0e7fc99c2f4d11ca45eff652a81eb8c8085e9609298916dcf \
--hash=sha256:822ea70dc4018c7e6223f13affd1c5c30c0f5c12ac1f96cd8e9949acddb48a61 \
--hash=sha256:84b0daf226913133f899ea9b30618722d45feffa67e4fe867b0b5ae83a34060c \
--hash=sha256:85765fdf4b27eb5086f05ac0491090fc76f4f2b28e09d9350c31aac25a5aaff8 \
--hash=sha256:8dd178c4c80706546702c59529ffc005681bd6dc2ea234c450661b205445a34d \
--hash=sha256:8f5b234f567cf76ee489502ceb7165c2a5cecec081db2b37e35332b537f8157c \
--hash=sha256:98bbd754a422a0b123c66a4c341de0474cad4a5c10c164ceed6ea090f3563db4 \
--hash=sha256:993584fc821c58d5993521bfdcd31a4adf025c7d745bbd4d12ccfecf695af5ba \
--hash=sha256:a40821a89dc373d6427e2b44b572efc36a2778d3f543299e2f24eb1a5de65415 \
--hash=sha256:b291f0ee7961a597cbbcc77709374087fa2a9afe7bdb6a40dbbd9b127e79afee \
--hash=sha256:b573a43ef7c368ba4ea06050a957c2a7550f729c31f11dd616d2ac4aba99888d \
--hash=sha256:b610ff0f24e9f11c9ae653c67ff8cc03c075131401b3e5ef4b82570d1728f8a9 \
--hash=sha256:bdf38ba2d393c7911ae989c3bbba510ebbcdf4ecbdbfec36272abe350c454075 \
--hash=sha256:bfef2bb6ef068827bbd021017a107194956918ab43ce4d6dc945ffa13efbc25f \
--hash=sha256:cab3db8bab4b7e635c1c97270d7a4b2a90c070b33cbc00c99ef3f9be03d3e1f7 \
--hash=sha256:cb70766519500281815dfd7a87d3a178acf7ce95390544b8c90587d76b227681 \
--hash=sha256:cca1b62fe70d761a282496b96a5e51c44c213e410a964bdffe0928e611368329 \
--hash=sha256:ccf9a39706b604d884d2cb1e27fe973bc55f2890c52f38df742bc1d79ab9f5e1 \
--hash=sha256:dc43f1ec66eb8440567186ae2f8c447d91e0372d793dfe8c222aec857b81a8cf \
--hash=sha256:dd632777ff3beaaf629f1ab4396caf7ba0bdd075d948a69460d13d44357aca4c \
--hash=sha256:e45ae4927759289c30ccba8d9fdce62bb414977ba158286b5ddaf8df2cddb5c5 \
--hash=sha256:e50ebce52f41370707f1e21a59514e3375e3edd6e1832f5e5235237db933c98b \
--hash=sha256:ebbbba226f0a108a7366bf4b59bf0f30a12fd5e75100c630267d94d7f0ad20e5 \
--hash=sha256:ec79ff6159dffcc30853b2ad612ed572af86c92b5168aa3fc01a67b0fa40665e \
--hash=sha256:f0936e08e0003f66bfd97e74ee530427707297b0d0361247e9b4f59ab78ddc8b \
--hash=sha256:f26a07a6e877c76a88e3cecac8531908d980d3d5067ff69213653649ec0f60ad \
--hash=sha256:f64e376cd20d3f030190e8c32e1c64582eba56ac6dc7d5b0b49a9d44021b52fd \
--hash=sha256:f6ffbc252eb0d229aeb2f9ad051200668fc3a9aaa8994e49f0cb2ffe2b7867e7 \
--hash=sha256:f9a7c509542db4eceed3dcf21ee5267ab565a83555c9b88a8109dcecc4709002 \
--hash=sha256:ff1d0899f104f3921d94579a5638847f783c9b04f2d5f229392ca77fba5b82fc
packaging==23.2 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5 \
--hash=sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7
peewee==3.17.1 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:e009ac4227c4fdc0058a56e822ad5987684f0a1fbb20fed577200785102581c3
pexpect==4.9.0 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523 \
--hash=sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f
pkginfo==1.9.6 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546 \
--hash=sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046
platformdirs==3.11.0 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3 \
--hash=sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e
poetry-core==1.8.1 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:194832b24f3283e01c5402eae71a6aae850ecdfe53f50a979c76bf7aa5010ffa \
--hash=sha256:67a76c671da2a70e55047cddda83566035b701f7e463b32a2abfeac6e2a16376
poetry-plugin-export==1.6.0 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:091939434984267a91abf2f916a26b00cff4eee8da63ec2a24ba4b17cf969a59 \
--hash=sha256:2dce6204c9318f1f6509a11a03921fb3f461b201840b59f1c237b6ab454dabcf
poetry==1.7.1 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:03d3807a0fb3bc1028cc3707dfd646aae629d58e476f7e7f062437680741c561 \
--hash=sha256:b348a70e7d67ad9c0bd3d0ea255bc6df84c24cf4b16f8d104adb30b425d6ff32
ptyprocess==0.7.0 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35 \
--hash=sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220
pycparser==2.21 ; python_version >= "3.11" and python_version < "4.0" and (platform_python_implementation != "PyPy" or sys_platform == "darwin") \
--hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \
--hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206
pymysql[rsa]==1.1.0 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:4f13a7df8bf36a51e81dd9f3605fede45a4878fe02f9236349fd82a3f0612f96 \
--hash=sha256:8969ec6d763c856f7073c4c64662882675702efcb114b4bcbb955aea3a069fa7
pyproject-hooks==1.0.0 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8 \
--hash=sha256:f271b298b97f5955d53fb12b72c1fb1948c22c1a6b70b315c54cedaca0264ef5
python-dotenv==1.0.1 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca \
--hash=sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a
pywin32-ctypes==0.2.2 ; python_version >= "3.11" and python_version < "4.0" and sys_platform == "win32" \
--hash=sha256:3426e063bdd5fd4df74a14fa3cf80a0b42845a87e1d1e81f6549f9daec593a60 \
--hash=sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7
rapidfuzz==3.6.1 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:01835d02acd5d95c1071e1da1bb27fe213c84a013b899aba96380ca9962364bc \
--hash=sha256:01eb03cd880a294d1bf1a583fdd00b87169b9cc9c9f52587411506658c864d73 \
--hash=sha256:03f73b381bdeccb331a12c3c60f1e41943931461cdb52987f2ecf46bfc22f50d \
--hash=sha256:0402f1629e91a4b2e4aee68043a30191e5e1b7cd2aa8dacf50b1a1bcf6b7d3ab \
--hash=sha256:060bd7277dc794279fa95522af355034a29c90b42adcb7aa1da358fc839cdb11 \
--hash=sha256:064c1d66c40b3a0f488db1f319a6e75616b2e5fe5430a59f93a9a5e40a656d15 \
--hash=sha256:06e98ff000e2619e7cfe552d086815671ed09b6899408c2c1b5103658261f6f3 \
--hash=sha256:08b6fb47dd889c69fbc0b915d782aaed43e025df6979b6b7f92084ba55edd526 \
--hash=sha256:0a9fc714b8c290261669f22808913aad49553b686115ad0ee999d1cb3df0cd66 \
--hash=sha256:0bbfae35ce4de4c574b386c43c78a0be176eeddfdae148cb2136f4605bebab89 \
--hash=sha256:12ff8eaf4a9399eb2bebd838f16e2d1ded0955230283b07376d68947bbc2d33d \
--hash=sha256:1936d134b6c513fbe934aeb668b0fee1ffd4729a3c9d8d373f3e404fbb0ce8a0 \
--hash=sha256:1c47d592e447738744905c18dda47ed155620204714e6df20eb1941bb1ba315e \
--hash=sha256:1dfc557c0454ad22382373ec1b7df530b4bbd974335efe97a04caec936f2956a \
--hash=sha256:1e12319c6b304cd4c32d5db00b7a1e36bdc66179c44c5707f6faa5a889a317c0 \
--hash=sha256:23de71e7f05518b0bbeef55d67b5dbce3bcd3e2c81e7e533051a2e9401354eb0 \
--hash=sha256:266dd630f12696ea7119f31d8b8e4959ef45ee2cbedae54417d71ae6f47b9848 \
--hash=sha256:2963f4a3f763870a16ee076796be31a4a0958fbae133dbc43fc55c3968564cf5 \
--hash=sha256:2a791168e119cfddf4b5a40470620c872812042f0621e6a293983a2d52372db0 \
--hash=sha256:2b155e67fff215c09f130555002e42f7517d0ea72cbd58050abb83cb7c880cec \
--hash=sha256:2b19795b26b979c845dba407fe79d66975d520947b74a8ab6cee1d22686f7967 \
--hash=sha256:2e03038bfa66d2d7cffa05d81c2f18fd6acbb25e7e3c068d52bb7469e07ff382 \
--hash=sha256:3028ee8ecc48250607fa8a0adce37b56275ec3b1acaccd84aee1f68487c8557b \
--hash=sha256:35660bee3ce1204872574fa041c7ad7ec5175b3053a4cb6e181463fc07013de7 \
--hash=sha256:3c772d04fb0ebeece3109d91f6122b1503023086a9591a0b63d6ee7326bd73d9 \
--hash=sha256:3c84294f4470fcabd7830795d754d808133329e0a81d62fcc2e65886164be83b \
--hash=sha256:40cced1a8852652813f30fb5d4b8f9b237112a0bbaeebb0f4cc3611502556764 \
--hash=sha256:4243a9c35667a349788461aae6471efde8d8800175b7db5148a6ab929628047f \
--hash=sha256:42f211e366e026de110a4246801d43a907cd1a10948082f47e8a4e6da76fef52 \
--hash=sha256:4381023fa1ff32fd5076f5d8321249a9aa62128eb3f21d7ee6a55373e672b261 \
--hash=sha256:484759b5dbc5559e76fefaa9170147d1254468f555fd9649aea3bad46162a88b \
--hash=sha256:49b9ed2472394d306d5dc967a7de48b0aab599016aa4477127b20c2ed982dbf9 \
--hash=sha256:53251e256017e2b87f7000aee0353ba42392c442ae0bafd0f6b948593d3f68c6 \
--hash=sha256:588c4b20fa2fae79d60a4e438cf7133d6773915df3cc0a7f1351da19eb90f720 \
--hash=sha256:5a2f3e9df346145c2be94e4d9eeffb82fab0cbfee85bd4a06810e834fe7c03fa \
--hash=sha256:5d82b9651e3d34b23e4e8e201ecd3477c2baa17b638979deeabbb585bcb8ba74 \
--hash=sha256:5dd95b6b7bfb1584f806db89e1e0c8dbb9d25a30a4683880c195cc7f197eaf0c \
--hash=sha256:692c9a50bea7a8537442834f9bc6b7d29d8729a5b6379df17c31b6ab4df948c2 \
--hash=sha256:6b0ccc2ec1781c7e5370d96aef0573dd1f97335343e4982bdb3a44c133e27786 \
--hash=sha256:6dede83a6b903e3ebcd7e8137e7ff46907ce9316e9d7e7f917d7e7cdc570ee05 \
--hash=sha256:7142ee354e9c06e29a2636b9bbcb592bb00600a88f02aa5e70e4f230347b373e \
--hash=sha256:7183157edf0c982c0b8592686535c8b3e107f13904b36d85219c77be5cefd0d8 \
--hash=sha256:7420e801b00dee4a344ae2ee10e837d603461eb180e41d063699fb7efe08faf0 \
--hash=sha256:757dfd7392ec6346bd004f8826afb3bf01d18a723c97cbe9958c733ab1a51791 \
--hash=sha256:76c23ceaea27e790ddd35ef88b84cf9d721806ca366199a76fd47cfc0457a81b \
--hash=sha256:7fec74c234d3097612ea80f2a80c60720eec34947066d33d34dc07a3092e8105 \
--hash=sha256:82300e5f8945d601c2daaaac139d5524d7c1fdf719aa799a9439927739917460 \
--hash=sha256:841eafba6913c4dfd53045835545ba01a41e9644e60920c65b89c8f7e60c00a9 \
--hash=sha256:8d7a072f10ee57c8413c8ab9593086d42aaff6ee65df4aa6663eecdb7c398dca \
--hash=sha256:8e4da90e4c2b444d0a171d7444ea10152e07e95972bb40b834a13bdd6de1110c \
--hash=sha256:96cd19934f76a1264e8ecfed9d9f5291fde04ecb667faef5f33bdbfd95fe2d1f \
--hash=sha256:a03863714fa6936f90caa7b4b50ea59ea32bb498cc91f74dc25485b3f8fccfe9 \
--hash=sha256:a1788ebb5f5b655a15777e654ea433d198f593230277e74d51a2a1e29a986283 \
--hash=sha256:a3ee4f8f076aa92184e80308fc1a079ac356b99c39408fa422bbd00145be9854 \
--hash=sha256:a490cd645ef9d8524090551016f05f052e416c8adb2d8b85d35c9baa9d0428ab \
--hash=sha256:a553cc1a80d97459d587529cc43a4c7c5ecf835f572b671107692fe9eddf3e24 \
--hash=sha256:a59472b43879012b90989603aa5a6937a869a72723b1bf2ff1a0d1edee2cc8e6 \
--hash=sha256:ac434fc71edda30d45db4a92ba5e7a42c7405e1a54cb4ec01d03cc668c6dcd40 \
--hash=sha256:ad9d74ef7c619b5b0577e909582a1928d93e07d271af18ba43e428dc3512c2a1 \
--hash=sha256:ae598a172e3a95df3383634589660d6b170cc1336fe7578115c584a99e0ba64d \
--hash=sha256:b2ef4c0fd3256e357b70591ffb9e8ed1d439fb1f481ba03016e751a55261d7c1 \
--hash=sha256:b3e5af946f419c30f5cb98b69d40997fe8580efe78fc83c2f0f25b60d0e56efb \
--hash=sha256:b53137d81e770c82189e07a8f32722d9e4260f13a0aec9914029206ead38cac3 \
--hash=sha256:b7e3375e4f2bfec77f907680328e4cd16cc64e137c84b1886d547ab340ba6928 \
--hash=sha256:bcc957c0a8bde8007f1a8a413a632a1a409890f31f73fe764ef4eac55f59ca87 \
--hash=sha256:be156f51f3a4f369e758505ed4ae64ea88900dcb2f89d5aabb5752676d3f3d7e \
--hash=sha256:be368573255f8fbb0125a78330a1a40c65e9ba3c5ad129a426ff4289099bfb41 \
--hash=sha256:c1a23eee225dfb21c07f25c9fcf23eb055d0056b48e740fe241cbb4b22284379 \
--hash=sha256:c65f92881753aa1098c77818e2b04a95048f30edbe9c3094dc3707d67df4598b \
--hash=sha256:ca3dfcf74f2b6962f411c33dd95b0adf3901266e770da6281bc96bb5a8b20de9 \
--hash=sha256:cd4ba4c18b149da11e7f1b3584813159f189dc20833709de5f3df8b1342a9759 \
--hash=sha256:d056e342989248d2bdd67f1955bb7c3b0ecfa239d8f67a8dfe6477b30872c607 \
--hash=sha256:d2f0274595cc5b2b929c80d4e71b35041104b577e118cf789b3fe0a77b37a4c5 \
--hash=sha256:d73dcfe789d37c6c8b108bf1e203e027714a239e50ad55572ced3c004424ed3b \
--hash=sha256:d79aec8aeee02ab55d0ddb33cea3ecd7b69813a48e423c966a26d7aab025cdfe \
--hash=sha256:da3e8c9f7e64bb17faefda085ff6862ecb3ad8b79b0f618a6cf4452028aa2222 \
--hash=sha256:dad55a514868dae4543ca48c4e1fc0fac704ead038dafedf8f1fc0cc263746c1 \
--hash=sha256:dec307b57ec2d5054d77d03ee4f654afcd2c18aee00c48014cb70bfed79597d6 \
--hash=sha256:e06c4242a1354cf9d48ee01f6f4e6e19c511d50bb1e8d7d20bcadbb83a2aea90 \
--hash=sha256:e19d519386e9db4a5335a4b29f25b8183a1c3f78cecb4c9c3112e7f86470e37f \
--hash=sha256:e49b9575d16c56c696bc7b06a06bf0c3d4ef01e89137b3ddd4e2ce709af9fe06 \
--hash=sha256:ebcfb5bfd0a733514352cfc94224faad8791e576a80ffe2fd40b2177bf0e7198 \
--hash=sha256:ed0f712e0bb5fea327e92aec8a937afd07ba8de4c529735d82e4c4124c10d5a0 \
--hash=sha256:edf97c321fd641fea2793abce0e48fa4f91f3c202092672f8b5b4e781960b891 \
--hash=sha256:eef8b346ab331bec12bbc83ac75641249e6167fab3d84d8f5ca37fd8e6c7a08c \
--hash=sha256:f056ba42fd2f32e06b2c2ba2443594873cfccc0c90c8b6327904fc2ddf6d5799 \
--hash=sha256:f382f7ffe384ce34345e1c0b2065451267d3453cadde78946fbd99a59f0cc23c \
--hash=sha256:f59d19078cc332dbdf3b7b210852ba1f5db8c0a2cd8cc4c0ed84cc00c76e6802 \
--hash=sha256:fbc07e2e4ac696497c5f66ec35c21ddab3fc7a406640bffed64c26ab2f7ce6d6 \
--hash=sha256:fde9b14302a31af7bdafbf5cfbb100201ba21519be2b9dedcf4f1048e4fbe65d
requests-toolbelt==1.0.0 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6 \
--hash=sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06
requests==2.31.0 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \
--hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1
secretstorage==3.3.3 ; python_version >= "3.11" and python_version < "4.0" and sys_platform == "linux" \
--hash=sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77 \
--hash=sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99
sh==2.0.7 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:029d45198902bfb967391eccfd13a88d92f7cebd200411e93f99ebacc6afbb35 \
--hash=sha256:2f2f79a65abd00696cf2e9ad26508cf8abb6dba5745f40255f1c0ded2876926d
shellingham==1.5.4 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686 \
--hash=sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de
tabulate==0.9.0 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c \
--hash=sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f
tomlkit==0.12.3 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:75baf5012d06501f07bee5bf8e801b9f343e7aac5a92581f20f80ce632e6b5a4 \
--hash=sha256:b0a645a9156dc7cb5d3a1f0d4bab66db287fcb8e0430bdd4664a095ea16414ba
trove-classifiers==2024.1.31 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:854aba3358f3cf10e5c0916aa533f5a39e27aadd8ade26a54cdc2a93257e39c4 \
--hash=sha256:bfdfe60bbf64985c524416afb637ecc79c558e0beb4b7f52b0039e01044b0229
urllib3==2.2.0 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20 \
--hash=sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224
virtualenv==20.25.0 ; python_version >= "3.11" and python_version < "4.0" \
--hash=sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3 \
--hash=sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b
xattr==0.10.1 ; python_version >= "3.11" and python_version < "4.0" and sys_platform == "darwin" \
--hash=sha256:042ad818cda6013162c0bfd3816f6b74b7700e73c908cde6768da824686885f8 \
--hash=sha256:0aedf55b116beb6427e6f7958ccd80a8cbc80e82f87a4cd975ccb61a8d27b2ee \
--hash=sha256:0e14bd5965d3db173d6983abdc1241c22219385c22df8b0eb8f1846c15ce1fee \
--hash=sha256:13279fe8f7982e3cdb0e088d5cb340ce9cbe5ef92504b1fd80a0d3591d662f68 \
--hash=sha256:148466e5bb168aba98f80850cf976e931469a3c6eb11e9880d9f6f8b1e66bd06 \
--hash=sha256:16a660a883e703b311d1bbbcafc74fa877585ec081cd96e8dd9302c028408ab1 \
--hash=sha256:183ad611a2d70b5a3f5f7aadef0fcef604ea33dcf508228765fd4ddac2c7321d \
--hash=sha256:199b20301b6acc9022661412346714ce764d322068ef387c4de38062474db76c \
--hash=sha256:1dc9b9f580ef4b8ac5e2c04c16b4d5086a611889ac14ecb2e7e87170623a0b75 \
--hash=sha256:1e2973e72faa87ca29d61c23b58c3c89fe102d1b68e091848b0e21a104123503 \
--hash=sha256:1f0563196ee54756fe2047627d316977dc77d11acd7a07970336e1a711e934db \
--hash=sha256:209fb84c09b41c2e4cf16dd2f481bb4a6e2e81f659a47a60091b9bcb2e388840 \
--hash=sha256:2677d40b95636f3482bdaf64ed9138fb4d8376fb7933f434614744780e46e42d \
--hash=sha256:295b3ab335fcd06ca0a9114439b34120968732e3f5e9d16f456d5ec4fa47a0a2 \
--hash=sha256:3725746a6502f40f72ef27e0c7bfc31052a239503ff3eefa807d6b02a249be22 \
--hash=sha256:3e5825b5fc99ecdd493b0cc09ec35391e7a451394fdf623a88b24726011c950d \
--hash=sha256:3e739d624491267ec5bb740f4eada93491de429d38d2fcdfb97b25efe1288eca \
--hash=sha256:3ff0dbe4a6ce2ce065c6de08f415bcb270ecfd7bf1655a633ddeac695ce8b250 \
--hash=sha256:40039f1532c4456fd0f4c54e9d4e01eb8201248c321c6c6856262d87e9a99593 \
--hash=sha256:436e1aaf23c07e15bed63115f1712d2097e207214fc6bcde147c1efede37e2c5 \
--hash=sha256:46c32cd605673606b9388a313b0050ee7877a0640d7561eea243ace4fa2cc5a6 \
--hash=sha256:475c38da0d3614cc5564467c4efece1e38bd0705a4dbecf8deeb0564a86fb010 \
--hash=sha256:485539262c2b1f5acd6b6ea56e0da2bc281a51f74335c351ea609c23d82c9a79 \
--hash=sha256:49626096ddd72dcc1654aadd84b103577d8424f26524a48d199847b5d55612d0 \
--hash=sha256:4abef557028c551d59cf2fb3bf63f2a0c89f00d77e54c1c15282ecdd56943496 \
--hash=sha256:5267e5f9435c840d2674194150b511bef929fa7d3bc942a4a75b9eddef18d8d8 \
--hash=sha256:5b49d591cf34cda2079fd7a5cb2a7a1519f54dc2e62abe3e0720036f6ed41a85 \
--hash=sha256:5bc40570155beb85e963ae45300a530223d9822edfdf09991b880e69625ba38a \
--hash=sha256:5dc6099e76e33fa3082a905fe59df766b196534c705cf7a2e3ad9bed2b8a180e \
--hash=sha256:636ebdde0277bce4d12d2ef2550885804834418fee0eb456b69be928e604ecc4 \
--hash=sha256:6b8705ac6791426559c1a5c2b88bb2f0e83dc5616a09b4500899bfff6a929302 \
--hash=sha256:6b905e808df61b677eb972f915f8a751960284358b520d0601c8cbc476ba2df6 \
--hash=sha256:7298455ccf3a922d403339781b10299b858bb5ec76435445f2da46fb768e31a5 \
--hash=sha256:772b22c4ff791fe5816a7c2a1c9fcba83f9ab9bea138eb44d4d70f34676232b4 \
--hash=sha256:7880c8a54c18bc091a4ce0adc5c6d81da1c748aec2fe7ac586d204d6ec7eca5b \
--hash=sha256:789bd406d1aad6735e97b20c6d6a1701e1c0661136be9be862e6a04564da771f \
--hash=sha256:7f9be588a4b6043b03777d50654c6079af3da60cc37527dbb80d36ec98842b1e \
--hash=sha256:80638d1ce7189dc52f26c234cee3522f060fadab6a8bc3562fe0ddcbe11ba5a4 \
--hash=sha256:8068df3ebdfa9411e58d5ae4a05d807ec5994645bb01af66ec9f6da718b65c5b \
--hash=sha256:827b5a97673b9997067fde383a7f7dc67342403093b94ea3c24ae0f4f1fec649 \
--hash=sha256:89c93b42c3ba8aedbc29da759f152731196c2492a2154371c0aae3ef8ba8301b \
--hash=sha256:8faaacf311e2b5cc67c030c999167a78a9906073e6abf08eaa8cf05b0416515c \
--hash=sha256:925284a4a28e369459b2b7481ea22840eed3e0573a4a4c06b6b0614ecd27d0a7 \
--hash=sha256:986c2305c6c1a08f78611eb38ef9f1f47682774ce954efb5a4f3715e8da00d5f \
--hash=sha256:9d4c306828a45b41b76ca17adc26ac3dc00a80e01a5ba85d71df2a3e948828f2 \
--hash=sha256:a126eb38e14a2f273d584a692fe36cff760395bf7fc061ef059224efdb4eb62c \
--hash=sha256:a3878e1aff8eca64badad8f6d896cb98c52984b1e9cd9668a3ab70294d1ef92d \
--hash=sha256:a5ea974930e876bc5c146f54ac0f85bb39b7b5de2b6fc63f90364712ae368ebe \
--hash=sha256:a606280b0c9071ef52572434ecd3648407b20df3d27af02c6592e84486b05894 \
--hash=sha256:a9a7a807ab538210ff8532220d8fc5e2d51c212681f63dbd4e7ede32543b070f \
--hash=sha256:aa32f1b45fed9122bed911de0fcc654da349e1f04fa4a9c8ef9b53e1cc98b91e \
--hash=sha256:b0e919c24f5b74428afa91507b15e7d2ef63aba98e704ad13d33bed1288dca81 \
--hash=sha256:b27dfc13b193cb290d5d9e62f806bb9a99b00cd73bb6370d556116ad7bb5dc12 \
--hash=sha256:b34df5aad035d0343bd740a95ca30db99b776e2630dca9cc1ba8e682c9cc25ea \
--hash=sha256:b7bc4ae264aa679aacf964abf3ea88e147eb4a22aea6af8c6d03ebdebd64cfd6 \
--hash=sha256:c0cd2d02ef2fb45ecf2b0da066a58472d54682c6d4f0452dfe7ae2f3a76a42ea \
--hash=sha256:c12e7d81ffaa0605b3ac8c22c2994a8e18a9cf1c59287a1b7722a2289c952ec5 \
--hash=sha256:c3024a9ff157247c8190dd0eb54db4a64277f21361b2f756319d9d3cf20e475f \
--hash=sha256:c4120090dac33eddffc27e487f9c8f16b29ff3f3f8bcb2251b2c6c3f974ca1e1 \
--hash=sha256:c5d3d0e728bace64b74c475eb4da6148cd172b2d23021a1dcd055d92f17619ac \
--hash=sha256:cc6b8d5ca452674e1a96e246a3d2db5f477aecbc7c945c73f890f56323e75203 \
--hash=sha256:ceaa26bef8fcb17eb59d92a7481c2d15d20211e217772fb43c08c859b01afc6a \
--hash=sha256:d1ef954d0655f93a34d07d0cc7e02765ec779ff0b59dc898ee08c6326ad614d5 \
--hash=sha256:d60c27922ec80310b45574351f71e0dd3a139c5295e8f8b19d19c0010196544f \
--hash=sha256:e31d062cfe1aaeab6ba3db6bd255f012d105271018e647645941d6609376af18 \
--hash=sha256:e8c014c371391f28f8cd27d73ea59f42b30772cd640b5a2538ad4f440fd9190b \
--hash=sha256:ec0956a8ab0f0d3f9011ba480f1e1271b703d11542375ef73eb8695a6bd4b78b \
--hash=sha256:f1be6e733e9698f645dbb98565bb8df9b75e80e15a21eb52787d7d96800e823b \
--hash=sha256:f24a7c04ff666d0fe905dfee0a84bc899d624aeb6dccd1ea86b5c347f15c20c1 \
--hash=sha256:f55a2dd73a12a1ae5113c5d9cd4b4ab6bf7950f4d76d0a1a0c0c4264d50da61d \
--hash=sha256:fc354f086f926a1c7f04886f97880fed1a26d20e3bc338d0d965fd161dbdb8ab \
--hash=sha256:ffcb57ca1be338d69edad93cf59aac7c6bb4dbb92fd7bf8d456c69ea42f7e6d2
zipp==3.17.0 ; python_version >= "3.11" and python_version < "3.12" \
--hash=sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31 \
--hash=sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0

View 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"

View 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'
)

View 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

View 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'
)

View 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)

View 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
View 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
View 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 $@