From 8034bd95e5ef95b80e307658c6b1db08c52a322a Mon Sep 17 00:00:00 2001 From: Sergey Klyuykov Date: Fri, 21 Jul 2023 22:06:12 -0700 Subject: [PATCH 01/18] Fix(CI): Build docs. --- autorelease.sh | 2 +- doc/conf.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/autorelease.sh b/autorelease.sh index 6344dc57..a73052cc 100755 --- a/autorelease.sh +++ b/autorelease.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -CURRENT_VERSION=$(python3 setup.py --version | tr -d '\n') +CURRENT_VERSION=$(python3.8 setup.py --version | tr -d '\n') TAG=$(git tag -l $CURRENT_VERSION) if [ -z "${TAG}" ]; then diff --git a/doc/conf.py b/doc/conf.py index 6778c0ec..f1510be2 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -27,6 +27,7 @@ import polemarch except ImportError: import pmlib as polemarch + sys.modules['polemarch'] = sys.modules['pmlib'] prepare_environment() setup() @@ -117,7 +118,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static', polemarch.__path__[0]+'/static', vstutils.__path__[0]+'/static'] +html_static_path = ['_static', polemarch.__path__[0]+'/static'] html_logo = 'img/html_logo.png' From 11bf6b6af0b1bd2c3ccc563bf8fb0a0848146c9c Mon Sep 17 00:00:00 2001 From: Sergei Kliuikov Date: Mon, 21 Aug 2023 13:56:17 +1000 Subject: [PATCH 02/18] Refactoring(build): Migrate to pyproject --- .coveragerc | 35 -- .pep8 | 4 - Dockerfile | 2 +- frontend_src/inventory/InventoryFieldEdit.vue | 2 +- .../inventory/InventoryFieldReadonly.vue | 2 +- polemarch/api/fields.py | 1 + polemarch/api/filters.py | 2 + polemarch/api/permissions.py | 1 + polemarch/api/schema.py | 1 + polemarch/api/swagger.py | 1 + polemarch/api/v4/ansible.py | 1 + polemarch/api/v4/base.py | 1 + polemarch/api/v4/execution_templates.py | 1 + polemarch/api/v4/history.py | 1 + polemarch/api/v4/hooks.py | 3 +- polemarch/api/v4/hosts.py | 1 + polemarch/api/v4/projects.py | 22 +- polemarch/api/v4/statistics.py | 1 + polemarch/api/v4/users.py | 2 + polemarch/main/ci.py | 2 + polemarch/main/constants.py | 2 + polemarch/main/hooks/base.py | 3 +- polemarch/main/hooks/http.py | 4 +- polemarch/main/hooks/script.py | 9 +- polemarch/main/management/base.py | 2 + .../commands/update_ansible_modules.py | 1 + .../main/management/commands/webserver.py | 1 + polemarch/main/models/base.py | 5 +- polemarch/main/models/execution_templates.py | 9 +- polemarch/main/models/history.py | 29 +- polemarch/main/models/hooks.py | 9 +- polemarch/main/models/hosts.py | 10 +- polemarch/main/models/projects.py | 33 +- polemarch/main/models/users.py | 7 +- polemarch/main/models/vars.py | 16 +- polemarch/main/repo/_base.py | 26 +- polemarch/main/repo/manual.py | 5 +- polemarch/main/repo/tar.py | 4 +- polemarch/main/repo/vcs.py | 11 +- polemarch/main/tasks/tasks.py | 6 +- polemarch/main/utils.py | 16 +- polemarch/main/validators.py | 4 +- polemarch/metrics.py | 4 +- polemarch/plugins/execution/ansible.py | 12 +- polemarch/plugins/execution/base.py | 3 +- polemarch/plugins/history/base.py | 4 +- polemarch/plugins/history/database.py | 1 + polemarch/plugins/inventory/ansible.py | 10 +- polemarch/plugins/inventory/base.py | 3 +- pyproject.toml | 106 +++++ requirements-doc.txt | 2 +- requirements.txt | 4 +- settings.ini | 6 + setup.cfg | 41 -- setup.py | 394 +----------------- tests.py | 212 +++++----- tox.ini | 65 +-- tox_build.ini | 15 +- 58 files changed, 429 insertions(+), 751 deletions(-) delete mode 100644 .coveragerc delete mode 100644 .pep8 create mode 100644 pyproject.toml diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 94f44ec3..00000000 --- a/.coveragerc +++ /dev/null @@ -1,35 +0,0 @@ -[run] -source = polemarch -parallel = True -concurrency = - thread - multiprocessing -omit = - .tox/* - */main/management/daemon.py - */*/migrations/* - */main/wsgi.py - */wsgi.py - */manage.py - *ihsctl - *setup.py - test.py - upload_big.py - polemarch/__init__.py - polemarch/__main__.py - polemarch/wapp.py - polemarch/main/management/commands/webserver.py - polemarch/main/tests/repos.py - polemarch/main/tests/tasks.py - polemarch/main/tests/project.py - polemarch/main/tests/ansible.py - polemarch/api/v1/* - *celery_beat_scheduler.py - -[report] -fail_under = 100 -show_missing = True -exclude_lines = - pragma: no cover - nocv - noce diff --git a/.pep8 b/.pep8 deleted file mode 100644 index 63e7849d..00000000 --- a/.pep8 +++ /dev/null @@ -1,4 +0,0 @@ -[flake8] -ignore = E221,E222,E121,E123,E126,E226,E24,E704,E116,E731,E722,E741,E402,W504,W503,W605 -exclude = ./polemarch/*/migrations/*,./polemarch/main/settings*.py,.tox/*,./etc/*,./*/__init__.py,*__main.py,./t_openstack.py,./polemarch/projects/*,./env/* -max-line-length=120 diff --git a/Dockerfile b/Dockerfile index 2193aff6..65cb962c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -FROM registry.gitlab.com/vstconsulting/images:ubuntu AS build +FROM registry.gitlab.com/vstconsulting/images:ubuntu-v2 AS build RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache WORKDIR /usr/local/polemarch diff --git a/frontend_src/inventory/InventoryFieldEdit.vue b/frontend_src/inventory/InventoryFieldEdit.vue index cd91dc03..73eb8e32 100644 --- a/frontend_src/inventory/InventoryFieldEdit.vue +++ b/frontend_src/inventory/InventoryFieldEdit.vue @@ -34,7 +34,7 @@ HTTP: list of URLs, separated by "|".
SCRIPT: script files, separated by "|" ' 'Files must be in HOOKS_DIR directory.' - ),), + )), }, 'permission_classes': (StaffPermission,), 'filterset_fields': { diff --git a/polemarch/api/v4/hosts.py b/polemarch/api/v4/hosts.py index 6cb5ddd4..9dd6cffe 100644 --- a/polemarch/api/v4/hosts.py +++ b/polemarch/api/v4/hosts.py @@ -9,6 +9,7 @@ from vstutils.api import fields as vstfields from vstutils.api.serializers import VSTSerializer, BaseSerializer from vstutils.utils import translate as _, lazy_translate as __ + from ...main.models import Host, Group, Inventory, InventoryState from .base import ( VariablesCopyViewMixin, diff --git a/polemarch/api/v4/projects.py b/polemarch/api/v4/projects.py index fb2e17b7..162db594 100644 --- a/polemarch/api/v4/projects.py +++ b/polemarch/api/v4/projects.py @@ -1,18 +1,19 @@ -from uuid import uuid1 from functools import partial +from uuid import uuid1 + from django.db import transaction from django.db.models import F, Value from django.db.models.functions import Concat -from rest_framework import fields as drffields from django_filters import CharFilter -from vstutils.utils import create_view, lazy_translate as __, translate as _ +from rest_framework import fields as drffields from vstutils.api import fields as vstfields +from vstutils.api.actions import EmptyAction, Action from vstutils.api.filter_backends import VSTFilterBackend from vstutils.api.filters import extra_filter -from vstutils.api.actions import EmptyAction, Action from vstutils.api.serializers import DataSerializer, BaseSerializer -from ...main.models import Project, ProjectCommunityTemplate, ExecutionTemplateOption -from ...main.constants import ProjectStatus, ProjectType, ProjectRepoAuthType, ProjectVariablesEnum +from vstutils.utils import create_view, lazy_translate as __, translate as _ + +from .ansible import AnsiblePlaybookViewSet, AnsibleModuleViewSet from .base import ( VariablesCopyViewMixin, ExecuteResponseSerializer, @@ -20,12 +21,13 @@ _VariableViewSet, _VariableSerializer, ) +from .execution_templates import ExecutionTemplateViewSet +from .history import HistoryViewSet +from .hosts import InventoryViewSet, ImportInventorySerializer from .users import OwnerViewMixin, UserSerializer +from ...main.constants import ProjectStatus, ProjectType, ProjectRepoAuthType, ProjectVariablesEnum from ...main.executions import PLUGIN_HANDLERS -from .ansible import AnsiblePlaybookViewSet, AnsibleModuleViewSet -from .hosts import InventoryViewSet, ImportInventorySerializer -from .history import HistoryViewSet -from .execution_templates import ExecutionTemplateViewSet +from ...main.models import Project, ProjectCommunityTemplate, ExecutionTemplateOption class CreateProjectSerializer(BaseSerializer): diff --git a/polemarch/api/v4/statistics.py b/polemarch/api/v4/statistics.py index 4b245e4b..7c1eb064 100644 --- a/polemarch/api/v4/statistics.py +++ b/polemarch/api/v4/statistics.py @@ -5,6 +5,7 @@ from vstutils.api.base import NonModelsViewSet from vstutils.api.serializers import BaseSerializer from vstutils.api.responses import HTTP_200_OK + from ...main.models import ( Project, Inventory, diff --git a/polemarch/api/v4/users.py b/polemarch/api/v4/users.py index efeaac84..087f8e92 100644 --- a/polemarch/api/v4/users.py +++ b/polemarch/api/v4/users.py @@ -1,4 +1,5 @@ import re + from rest_framework.authtoken.views import ObtainAuthToken from rest_framework.exceptions import ParseError from rest_framework import fields as drffields @@ -13,6 +14,7 @@ from vstutils.api.base import CopyMixin from vstutils.api.actions import SimpleAction from vstutils.api.serializers import BaseSerializer + from ..permissions import CreateUsersPermission, SetOwnerPermission diff --git a/polemarch/main/ci.py b/polemarch/main/ci.py index e6f2bc1d..966d56ed 100644 --- a/polemarch/main/ci.py +++ b/polemarch/main/ci.py @@ -1,7 +1,9 @@ from typing import Text, Optional import logging import traceback + from vstutils.models import BModel + from .models.execution_templates import ExecutionTemplateOption diff --git a/polemarch/main/constants.py b/polemarch/main/constants.py index 114f8996..776660b1 100644 --- a/polemarch/main/constants.py +++ b/polemarch/main/constants.py @@ -1,8 +1,10 @@ from functools import lru_cache from typing import Dict, Optional + from rest_framework import fields as drffields from vstutils.utils import BaseEnum as VSTBaseEnum from vstutils.api import fields as vstfields + from .utils import AnsibleArgumentsReference diff --git a/polemarch/main/hooks/base.py b/polemarch/main/hooks/base.py index da61edca..7d11c3a0 100644 --- a/polemarch/main/hooks/base.py +++ b/polemarch/main/hooks/base.py @@ -1,4 +1,5 @@ from typing import Dict + from django.conf import settings @@ -39,7 +40,7 @@ def execute(self, recipient, when, message): # nocv raise NotImplementedError def execute_many(self, recipients, when, message) -> str: - return '\n'.join(map(lambda r: self.execute(r, when, message), recipients)) + return '\n'.join(self.execute(r, when, message) for r in recipients) def send(self, message, when: str) -> str: self.when = when diff --git a/polemarch/main/hooks/http.py b/polemarch/main/hooks/http.py index 08842e2b..22e2bfea 100644 --- a/polemarch/main/hooks/http.py +++ b/polemarch/main/hooks/http.py @@ -1,7 +1,9 @@ import logging import traceback + import requests import orjson + from .base import BaseHook @@ -12,7 +14,7 @@ class Backend(BaseHook): __slots__ = () def execute(self, url, when, message) -> str: # pylint: disable=arguments-renamed - data = dict(type=when, payload=message) + data = {'type': when, 'payload': message} try: response = requests.post(url, data=orjson.dumps(data)) # pylint: disable=no-member return "{} {}: {}".format( diff --git a/polemarch/main/hooks/script.py b/polemarch/main/hooks/script.py index fc35a6d4..3f9b5df2 100644 --- a/polemarch/main/hooks/script.py +++ b/polemarch/main/hooks/script.py @@ -1,12 +1,13 @@ from __future__ import unicode_literals -from typing import Dict -import os + import json -import traceback import logging +import os import subprocess -from .base import BaseHook +import traceback +from typing import Dict +from .base import BaseHook logger = logging.getLogger("polemarch") diff --git a/polemarch/main/management/base.py b/polemarch/main/management/base.py index d4d33704..dcefcefc 100644 --- a/polemarch/main/management/base.py +++ b/polemarch/main/management/base.py @@ -1,7 +1,9 @@ # pylint: disable=no-member,invalid-name,broad-except,import-error from __future__ import absolute_import, unicode_literals + import celery from vstutils.management.commands._base import BaseCommand as _BaseCommand + from ..constants import ANSIBLE_REFERENCE diff --git a/polemarch/main/management/commands/update_ansible_modules.py b/polemarch/main/management/commands/update_ansible_modules.py index 94f50918..04a41149 100644 --- a/polemarch/main/management/commands/update_ansible_modules.py +++ b/polemarch/main/management/commands/update_ansible_modules.py @@ -1,4 +1,5 @@ from django.db import transaction + from ..base import ServiceCommand from ...utils import AnsibleModules from ...models import Module diff --git a/polemarch/main/management/commands/webserver.py b/polemarch/main/management/commands/webserver.py index 3165eb2e..72fa0200 100644 --- a/polemarch/main/management/commands/webserver.py +++ b/polemarch/main/management/commands/webserver.py @@ -1,4 +1,5 @@ from vstutils.management.commands import web + from ..base import BaseCommand diff --git a/polemarch/main/models/base.py b/polemarch/main/models/base.py index 33ebdf4c..e4ce6db5 100644 --- a/polemarch/main/models/base.py +++ b/polemarch/main/models/base.py @@ -1,11 +1,12 @@ # pylint: disable=no-name-in-module from __future__ import unicode_literals + from typing import Any -from django.db import models + from django.contrib.auth import get_user_model +from django.db import models from vstutils.models import BModel - User = get_user_model() diff --git a/polemarch/main/models/execution_templates.py b/polemarch/main/models/execution_templates.py index 915a3170..24093c86 100644 --- a/polemarch/main/models/execution_templates.py +++ b/polemarch/main/models/execution_templates.py @@ -1,4 +1,7 @@ from uuid import uuid4 + +from celery.schedules import crontab +from django.contrib.auth import get_user_model from django.db.models import ( JSONField, CharField, @@ -9,15 +12,13 @@ AutoField, CASCADE, ) -from django.contrib.auth import get_user_model -from celery.schedules import crontab -from vstutils.models.model import BaseModel from vstutils.models.fields import FkModelField +from vstutils.models.model import BaseModel + from .projects import Project from ..constants import CrontabTimeType, PeriodicTaskScheduleType, HistoryInitiatorType from ..executions import PLUGIN_HANDLERS - User = get_user_model() diff --git a/polemarch/main/models/history.py b/polemarch/main/models/history.py index 6ffb6157..fab7a5b2 100644 --- a/polemarch/main/models/history.py +++ b/polemarch/main/models/history.py @@ -1,29 +1,28 @@ # pylint: disable=protected-access,no-member from __future__ import unicode_literals -from typing import Any, Dict, List, TypeVar -import logging -from collections import OrderedDict -from datetime import timedelta, datetime import json +import logging import re import signal +from collections import OrderedDict +from datetime import timedelta, datetime +from typing import Any, Dict, List, TypeVar +from django.contrib.auth import get_user_model from django.core.exceptions import ValidationError from django.db import models -from django.utils import timezone -from django.contrib.auth import get_user_model from django.db.models import functions as dbfunc, Count +from django.utils import timezone from django.utils.timezone import now - from vstutils.environment import get_celery_app from vstutils.models import BaseModel, BModel, BQuerySet + from . import Inventory +from .execution_templates import ExecutionTemplate, TemplatePeriodicTask from .projects import Project from ..constants import HiddenArgumentsEnum, HistoryInitiatorType, HistoryStatus from ..exceptions import NotApplicable -from .execution_templates import ExecutionTemplate, TemplatePeriodicTask - logger = logging.getLogger("polemarch") InvOrString = TypeVar('InvOrString', str, int, Inventory, None) @@ -118,10 +117,10 @@ def get_hook_data(self, when: str) -> OrderedDict: if when == "after_execution": data['stop_time'] = self.stop_time.isoformat() data['status'] = self.status - data["initiator"] = dict( - initiator_type=self.initiator_type, - initiator_id=self.initiator, - ) + data["initiator"] = { + 'initiator_type': self.initiator_type, + 'initiator_id': self.initiator, + } if self.initiator_type in ["template", "scheduler"]: data["initiator"]['name'] = self.initiator_object.name return data @@ -142,7 +141,7 @@ def execute_args(self) -> Dict[str, Any]: @execute_args.setter def execute_args(self, value: Dict) -> None: if not isinstance(value, dict): - raise ValidationError(dict(args="Should be a dict.")) + raise ValidationError({'args': "Should be a dict."}) data = {k: v for k, v in value.items() if k not in ['group']} HiddenArgumentsEnum.hide_values(data) self.json_args = json.dumps(data) @@ -155,7 +154,7 @@ def options(self) -> Dict: @options.setter def options(self, value: Dict) -> None: if not isinstance(value, dict): - raise ValidationError(dict(args="Should be a dict.")) # nocv + raise ValidationError({'args': "Should be a dict."}) # nocv self.json_options = json.dumps(value) @property diff --git a/polemarch/main/models/hooks.py b/polemarch/main/models/hooks.py index 76a025f9..4c817fdc 100644 --- a/polemarch/main/models/hooks.py +++ b/polemarch/main/models/hooks.py @@ -1,12 +1,13 @@ from __future__ import unicode_literals -from typing import Text, Any, List -import logging + import collections +import logging import uuid +from typing import Text, Any, List + from django.db import models -from vstutils.utils import raise_context, ModelHandlers from vstutils.models import BModel, BQuerySet - +from vstutils.utils import raise_context, ModelHandlers logger = logging.getLogger('polemarch') diff --git a/polemarch/main/models/hosts.py b/polemarch/main/models/hosts.py index 028e2370..7a7fdd53 100644 --- a/polemarch/main/models/hosts.py +++ b/polemarch/main/models/hosts.py @@ -1,18 +1,20 @@ # pylint: disable=protected-access,no-member from __future__ import unicode_literals + import logging from functools import cached_property + from django.db.models import Q from vstutils.models import BModel from vstutils.utils import translate as _ -from .base import models + from .base import ManyToManyFieldACL, ManyToManyFieldACLReverse +from .base import models from .vars import AbstractModel, AbstractVarsQuerySet -from ...main import exceptions as ex -from ...main.utils import InventoryPluginHandlers from ..validators import RegexValidator +from ...main import exceptions as ex from ...main.exceptions import NotApplicable - +from ...main.utils import InventoryPluginHandlers logger = logging.getLogger("polemarch") diff --git a/polemarch/main/models/projects.py b/polemarch/main/models/projects.py index a8b0bcdb..75309360 100644 --- a/polemarch/main/models/projects.py +++ b/polemarch/main/models/projects.py @@ -1,21 +1,22 @@ # pylint: disable=protected-access,no-member,unused-argument from __future__ import unicode_literals -from typing import Any, Dict, List, Tuple, Iterable -import os import logging +import os import traceback import uuid +from typing import Any, Dict, List, Tuple, Iterable + import requests -from docutils.core import publish_parts as rst_gen -from markdown2 import Markdown from django.conf import settings -from django.db.models import Q from django.core.cache import caches -from vstutils.utils import ModelHandlers, raise_context_decorator_with_default, classproperty, raise_context +from django.db.models import Q +from docutils.core import publish_parts as rst_gen +from markdown2 import Markdown +from vstutils.models import BQuerySet, BModel # pylint: disable=no-name-in-module from vstutils.models import custom_model -from vstutils.models import BQuerySet, BModel +from vstutils.utils import ModelHandlers, raise_context_decorator_with_default, classproperty, raise_context from yaml import load try: @@ -124,7 +125,7 @@ class ReadMe(ReadMe): @classproperty def PROJECTS_DIR(cls) -> str: # pylint: disable=invalid-name - return getattr(settings, 'PROJECTS_DIR') + return settings.PROJECTS_DIR def __unicode__(self): return str(self.name) # pragma: no cover @@ -181,10 +182,10 @@ def __parse_yaml_view(self, data: Dict[str, Any]) -> Dict[str, dict]: parsed_data = {'fields': {}, 'playbooks': {}} # Parse fields for fieldname, field_data in data['fields'].items(): - parsed_data['fields'][fieldname] = dict( - title=field_data.get('title', fieldname.upper()), - help=field_data.get('help', ''), - ) + parsed_data['fields'][fieldname] = { + 'title': field_data.get('title', fieldname.upper()), + 'help': field_data.get('help', ''), + } field_format = field_data.get('format', 'string') if field_format not in valid_formats.keys(): field_format = 'unknown' @@ -200,10 +201,10 @@ def __parse_yaml_view(self, data: Dict[str, Any]) -> Dict[str, dict]: del parsed_data['fields'][fieldname]['format'] # Parse playbooks for execution for playbook, pb_data in data['playbooks'].items(): - parsed_data['playbooks'][playbook] = dict( - title=pb_data.get('title', playbook.replace('.yml', '')), - help=pb_data.get('help', ''), - ) + parsed_data['playbooks'][playbook] = { + 'title': pb_data.get('title', playbook.replace('.yml', '')), + 'help': pb_data.get('help', ''), + } return parsed_data @property diff --git a/polemarch/main/models/users.py b/polemarch/main/models/users.py index dd5a9fbb..d828f135 100644 --- a/polemarch/main/models/users.py +++ b/polemarch/main/models/users.py @@ -1,16 +1,17 @@ # pylint: disable=protected-access,no-member from __future__ import unicode_literals -from typing import Text + import logging +from typing import Text -from django.contrib.auth.models import Group as BaseGroup from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group as BaseGroup from django.db import models from vstutils.models import BModel, BQuerySet + from .base import ACLModel from ..constants import MemberType - logger = logging.getLogger("polemarch") User = get_user_model() diff --git a/polemarch/main/models/vars.py b/polemarch/main/models/vars.py index 10d53cd1..dfbd92e2 100644 --- a/polemarch/main/models/vars.py +++ b/polemarch/main/models/vars.py @@ -1,18 +1,20 @@ # pylint: disable=protected-access,no-member from __future__ import unicode_literals -from typing import Any, Tuple, Dict, List, Text, Union + import logging import uuid - -from functools import reduce from collections import OrderedDict +from functools import reduce +from typing import Any, Tuple, Dict, List, Text, Union + +from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation +from django.contrib.contenttypes.models import ContentType from django.db import transaction, models from django.db.models import Case, When, Value -from django.contrib.contenttypes.models import ContentType -from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation -from vstutils.utils import tmp_file -from vstutils.models import BQuerySet, BModel from vstutils.api.decorators import cache_method_result +from vstutils.models import BQuerySet, BModel +from vstutils.utils import tmp_file + from .base import ACLModel from ..constants import CYPHER, InventoryVariablesEnum diff --git a/polemarch/main/repo/_base.py b/polemarch/main/repo/_base.py index e90805ec..71f7c1f6 100644 --- a/polemarch/main/repo/_base.py +++ b/polemarch/main/repo/_base.py @@ -1,21 +1,24 @@ # pylint: disable=expression-not-assigned,abstract-method,import-error from __future__ import unicode_literals + import io -from typing import Any, Text, Dict, List, Tuple, Union, Iterable, Callable, TypeVar +import logging import os -import shutil import pathlib -import logging +import shutil import traceback from itertools import chain +from typing import Any, Text, Dict, List, Tuple, Union, Iterable, Callable, TypeVar + import requests -from django.db import transaction from django.conf import settings +from django.db import transaction from vstutils.utils import raise_context, import_class -from ..utils import AnsibleModules + from ..models.projects import Project -from ...main.exceptions import MaxContentLengthExceeded, SyncOnRunTimeout +from ..utils import AnsibleModules from ...main.constants import TEMPLATE_KIND_PLUGIN_MAP +from ...main.exceptions import MaxContentLengthExceeded, SyncOnRunTimeout logger = logging.getLogger("polemarch") FILENAME = TypeVar('FILENAME', Text, str) @@ -59,15 +62,16 @@ def message(self, message: Any, level: Text = 'debug') -> None: ) def pm_handle_sync_on_run(self, feature: Text, data: bool) -> None: - ''' + """ Set sync_on_run if it is setted in `.polemarch.yaml`. :param feature: feature name :param data: all data from file - ''' + """ value = str(data[feature]) _, created = self.proj.variables.update_or_create( - key='repo_sync_on_run', defaults=dict(value=value) + key='repo_sync_on_run', + defaults={'value': value}, ) self.message( '{} repo_sync_on_run to {}'.format('Set' if created else 'Update', value) @@ -166,7 +170,7 @@ def _handle_yaml(self, data: Union[Dict, None]) -> None: Loads and returns data from `.polemarch.yaml` file """ for feature in data.keys(): - if feature in ['templates_rewrite', ]: + if feature in ['templates_rewrite']: continue self.message('Set settings from ".polemarch.yaml" - {}.'.format(feature)) feature_name = 'pm_handle_{}'.format(feature) @@ -227,7 +231,7 @@ def _update_tasks(self, files: Iterable[pathlib.Path]) -> None: def search_files(self, repo: Any = None, pattern: Text = '**/*') -> Iterable[pathlib.Path]: # pylint: disable=unused-argument path = pathlib.Path(self.path) - return map(lambda x: x.relative_to(self.path), path.glob(pattern)) + return (x.relative_to(self.path) for x in path.glob(pattern)) def _operate(self, operation: Callable, **kwargs) -> Any: return operation(kwargs) diff --git a/polemarch/main/repo/manual.py b/polemarch/main/repo/manual.py index d32d94c1..c194a007 100644 --- a/polemarch/main/repo/manual.py +++ b/polemarch/main/repo/manual.py @@ -1,10 +1,13 @@ # pylint: disable=expression-not-assigned,abstract-method,import-error from __future__ import unicode_literals -from typing import Tuple, Text + import errno from pathlib import Path +from typing import Tuple, Text + from django.conf import settings from vstutils.utils import get_render, raise_context + from ._base import _Base, os diff --git a/polemarch/main/repo/tar.py b/polemarch/main/repo/tar.py index 730b9c55..8cbdc879 100644 --- a/polemarch/main/repo/tar.py +++ b/polemarch/main/repo/tar.py @@ -1,9 +1,11 @@ # pylint: disable=expression-not-assigned,abstract-method,import-error from __future__ import unicode_literals + import io -from typing import Text, Tuple import tarfile from pathlib import Path +from typing import Text, Tuple + from ._base import _ArchiveRepo, shutil diff --git a/polemarch/main/repo/vcs.py b/polemarch/main/repo/vcs.py index 3379e8e1..8b4ec05c 100644 --- a/polemarch/main/repo/vcs.py +++ b/polemarch/main/repo/vcs.py @@ -1,13 +1,16 @@ # pylint: disable=expression-not-assigned,abstract-method,import-error from __future__ import unicode_literals -from typing import Tuple, Dict, Text, Union, Any, Iterable + import warnings +from typing import Tuple, Dict, Text, Union, Any, Iterable + from vstutils.utils import tmp_file_context, raise_context -from ...main.exceptions import SyncOnRunTimeout try: import git except: # nocv - warnings.warn("Git is not installed or have problems.") + warnings.warn("Git is not installed or have problems.") # noqa: B028 + +from ...main.exceptions import SyncOnRunTimeout from ._base import _Base, os, logger, pathlib ENV_VARS_TYPE = Dict[Text, Union[Text, bool]] # pylint: disable=invalid-name @@ -135,7 +138,7 @@ def make_clone(self, env: ENV_VARS_TYPE) -> Tuple[git.Repo, None]: # pylint: di if not no_update: with raise_context(): self.proj.variables.update_or_create( - key='repo_branch', defaults=dict(value=repo.active_branch.name) + key='repo_branch', defaults={'value': repo.active_branch.name} ) return repo, None diff --git a/polemarch/main/tasks/tasks.py b/polemarch/main/tasks/tasks.py index 74605130..5abc297e 100644 --- a/polemarch/main/tasks/tasks.py +++ b/polemarch/main/tasks/tasks.py @@ -1,12 +1,14 @@ # pylint: disable=broad-except,no-member,redefined-outer-name import logging import traceback + from django.conf import settings from vstutils.utils import import_class -from ..utils import task, BaseTask, TaskClass + from .exceptions import TaskError -from ..models import TemplatePeriodicTask, Project, History, Inventory from ..executions import PLUGIN_HANDLERS +from ..models import TemplatePeriodicTask, Project, History, Inventory +from ..utils import task, BaseTask, TaskClass logger = logging.getLogger("polemarch") clone_retry = getattr(settings, 'CLONE_RETRY', 5) diff --git a/polemarch/main/utils.py b/polemarch/main/utils.py index 6653d705..48bb9282 100644 --- a/polemarch/main/utils.py +++ b/polemarch/main/utils.py @@ -1,24 +1,22 @@ # pylint: disable=invalid-name,ungrouped-imports from __future__ import unicode_literals -from functools import partial +import json import logging import os +import re import subprocess - import sys -import re -import json +from functools import partial from os.path import dirname -from django.conf import settings -from django.utils import timezone -from django.db import transaction try: from yaml import CLoader as Loader, CDumper as Dumper, load, dump except ImportError: # nocv from yaml import Loader, Dumper, load, dump - +from django.conf import settings +from django.db import transaction +from django.utils import timezone from vstutils.tasks import TaskClass from vstutils.utils import ( tmp_file_context, @@ -167,7 +165,7 @@ class AnsibleCache(SubCacheInterface): class PMAnsible(PMObject): - __slots__ = ('execute_path', 'cache',) + __slots__ = ('execute_path', 'cache') # Json regex _regex = re.compile(r"([\{\[\"]{1}.*[\}\]\"]{1})", re.MULTILINE | re.DOTALL) ref_name = 'object' diff --git a/polemarch/main/validators.py b/polemarch/main/validators.py index ce35417a..c5d0180f 100644 --- a/polemarch/main/validators.py +++ b/polemarch/main/validators.py @@ -1,6 +1,8 @@ from __future__ import unicode_literals -from typing import AnyStr + import re +from typing import AnyStr + from django.core.validators import ( RegexValidator, URLValidator as OldURLValidator diff --git a/polemarch/metrics.py b/polemarch/metrics.py index 864acb64..1e1a2523 100644 --- a/polemarch/metrics.py +++ b/polemarch/metrics.py @@ -1,8 +1,10 @@ from datetime import timedelta + from django.conf import settings -from django.utils.timezone import now from django.db.models import Count +from django.utils.timezone import now from vstutils.api.metrics import DefaultBackend + from .main.models import History, Project diff --git a/polemarch/plugins/execution/ansible.py b/polemarch/plugins/execution/ansible.py index 26e7868e..09cc1253 100644 --- a/polemarch/plugins/execution/ansible.py +++ b/polemarch/plugins/execution/ansible.py @@ -1,17 +1,19 @@ +import contextlib import os import re -import contextlib -from uuid import uuid1 from functools import lru_cache from typing import Type, Mapping, Optional +from uuid import uuid1 + from django.conf import settings from rest_framework import fields -from vstutils.api.serializers import BaseSerializer from vstutils.api.fields import VSTCharField, SecretFileInString, AutoCompletionField +from vstutils.api.serializers import BaseSerializer + +from .base import BasePlugin +from ...api.fields import InventoryAutoCompletionField from ...main.constants import ANSIBLE_REFERENCE, HiddenArgumentsEnum, HiddenVariablesEnum, HistoryStatus from ...main.models import Inventory -from ...api.fields import InventoryAutoCompletionField -from .base import BasePlugin from ...plugins.inventory.ansible import BaseAnsiblePlugin as BaseAnsibleInventoryPlugin diff --git a/polemarch/plugins/execution/base.py b/polemarch/plugins/execution/base.py index bdc971ef..1275b3a3 100644 --- a/polemarch/plugins/execution/base.py +++ b/polemarch/plugins/execution/base.py @@ -1,5 +1,6 @@ -from typing import Callable, Mapping, List, Tuple, Type, Optional, Any from pathlib import Path +from typing import Callable, Mapping, List, Tuple, Type, Optional, Any + from rest_framework.fields import Field from vstutils.api.serializers import BaseSerializer diff --git a/polemarch/plugins/history/base.py b/polemarch/plugins/history/base.py index 1f94228c..13def35e 100644 --- a/polemarch/plugins/history/base.py +++ b/polemarch/plugins/history/base.py @@ -56,8 +56,8 @@ def get_facts(self): r" \{\s([^\r]*?\"[\w]{1,}\"\: .*?\s)\}\s{0,1}" ) subst = '"\\1": {\n\t"status": "\\2", \n\\3},' - result = re.sub(regex, subst, data, 0, re.MULTILINE) - result = re.findall(r'^".*":[\s\S]*$', result, re.MULTILINE)[0] + result = re.sub(regex, subst, data, count=0, flags=re.MULTILINE) + result = re.findall(r'^".*":[\s\S]*$', result, flags=re.MULTILINE)[0] result = "{" + result[:-1] + "\n}" return orjson.loads(result) # pylint: disable=no-member diff --git a/polemarch/plugins/history/database.py b/polemarch/plugins/history/database.py index 77c6cb37..deb048f6 100644 --- a/polemarch/plugins/history/database.py +++ b/polemarch/plugins/history/database.py @@ -1,4 +1,5 @@ from django.db import transaction, models + from .base import BasePlugin from ...main.models.history import HistoryLines diff --git a/polemarch/plugins/inventory/ansible.py b/polemarch/plugins/inventory/ansible.py index c56852eb..1694efc1 100644 --- a/polemarch/plugins/inventory/ansible.py +++ b/polemarch/plugins/inventory/ansible.py @@ -1,20 +1,20 @@ -import re import base64 import mimetypes -from uuid import uuid1 -from typing import Tuple, Any +import re from pathlib import Path -import orjson +from typing import Tuple, Any +from uuid import uuid1 try: from yaml import dump as to_yaml, CDumper as Dumper, ScalarNode except ImportError: # nocv from yaml import dump as to_yaml, Dumper, ScalarNode - +import orjson from django.db import transaction from rest_framework import fields as drffields from vstutils.api import fields as vstfields from vstutils.api.validators import RegularExpressionValidator + from .base import BasePlugin from ...main.constants import HiddenVariablesEnum, CYPHER from ...main.utils import AnsibleInventoryParser diff --git a/polemarch/plugins/inventory/base.py b/polemarch/plugins/inventory/base.py index d2b2761a..e4a3283e 100644 --- a/polemarch/plugins/inventory/base.py +++ b/polemarch/plugins/inventory/base.py @@ -1,10 +1,11 @@ import typing as _t from pathlib import Path + from django.conf import settings from rest_framework.fields import Field from vstutils.api.serializers import BaseSerializer -from ...main.exceptions import NotSupported +from ...main.exceptions import NotSupported InventoryType = f"{settings.VST_PROJECT_LIB_NAME}.main.models.hosts.Inventory" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..7611b0e9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,106 @@ +[build-system] +requires = ["setuptools>=61.2", "vstcompile~=2.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "polemarch" +authors = [{name = "VST Consulting", email = "sergey.k@vstconsulting.net"}] +license = {text = "AGPLv3+"} +description = "Polemarch is ansible based service for orchestration infrastructure." +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Web Environment", + "Framework :: Django", + "Framework :: Django :: 4.2", + "Operating System :: POSIX", + "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: Implementation :: CPython", + "Topic :: Internet :: WWW/HTTP", + "Topic :: Internet :: WWW/HTTP :: Dynamic Content", + "Topic :: Software Development :: User Interfaces", + "Topic :: System :: Systems Administration", + "Topic :: Utilities", +] +requires-python = ">=3.8" +dynamic = ["version", "dependencies", "optional-dependencies"] + +[project.urls] +Home = "https://gitlab.com/vstconsulting/polemarch" +"Web Site" = "https://polemarch.org/" +"Issue Tracker" = "https://gitlab.com/vstconsulting/polemarch/issues" +"Source Code" = "https://gitlab.com/vstconsulting/polemarch" +Releases = "https://github.com/vstconsulting/polemarch/releases" +DockerHub = "https://hub.docker.com/r/vstconsulting/polemarch/" +Documentation = "https://about.polemarch.org/" + +[project.readme] +file = "README.rst" +content-type = "text/x-rst" + +[tool.setuptools] +zip-safe = false +include-package-data = true +license-files = ["LICENSE"] + +[tool.setuptools.dynamic] +version = {attr = "polemarch.__version__"} + +[tool.setuptools.packages.find] +include = ['polemarch', 'polemarch.*'] +namespaces = false + +[tool.flake8] +ignore = "E221,E222,E121,E123,E126,E226,E24,E704,E116,E731,E722,E741,W503,W504,B001,B008,B010,B023,C812,C815,CFQ002,CFQ004,B019,I100,DJ01" +exclude = "./polemarch/*/migrations/*,./polemarch/main/settings*.py,.tox/*,./etc/*,./*/__init__.py,*__main.py,./t_openstack.py,./polemarch/projects/*,./env/*" +max-line-length = 120 +import-order-style = 'pep8' + +[tool.coverage.run] +# branch = true +source = [ + 'polemarch', +] +parallel = true +concurrency = [ + 'thread', + 'multiprocessing', +] +omit = [ + '.tox/*', + '*/main/management/daemon.py', + '*/*/migrations/*', + '*/main/wsgi.py', + '*/wsgi.py', + '*/manage.py', + '*ihsctl', + '*setup.py', + 'test.py', + 'upload_big.py', + 'polemarch/__init__.py', + 'polemarch/__main__.py', + 'polemarch/wapp.py', + 'polemarch/main/management/commands/webserver.py', + 'polemarch/main/tests/repos.py', + 'polemarch/main/tests/tasks.py', + 'polemarch/main/tests/project.py', + 'polemarch/main/tests/ansible.py', + 'polemarch/api/v1/*', + '*celery_beat_scheduler.py', +] + +[tool.coverage.report] +fail_under = 100 +show_missing = true +exclude_lines = [ + 'pragma: no cover', + 'nocv', + 'noce', + 'raise NotImplementedError', +] diff --git a/requirements-doc.txt b/requirements-doc.txt index 34b07a87..8029ee99 100644 --- a/requirements-doc.txt +++ b/requirements-doc.txt @@ -1,4 +1,4 @@ # Docs -rrequirements.txt -vstutils[doc]~=5.6.4 +vstutils[doc]~=5.7.2 sphinxcontrib-openapi~=0.8.1 diff --git a/requirements.txt b/requirements.txt index 3713a53f..eadde1cb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ # Main -vstutils[rpc,prod]~=5.6.4 -markdown2~=2.4.9 +vstutils[rpc,prod]~=5.7.2 +markdown2~=2.4.10 # Repo types gitpython~=3.1.32 diff --git a/settings.ini b/settings.ini index 1e38b5a2..223be065 100644 --- a/settings.ini +++ b/settings.ini @@ -41,6 +41,12 @@ pidfile = /tmp/{PROG_NAME}_worker.pid backend = django.core.cache.backends.redis.RedisCache location = redis://redis:6379/2 +[cache.options] +pool_class = redis.BlockingConnectionPool +health_check_interval = 2 +socket_timeout = 3 +socket_keepalive = true + [uwsgi] daemon = false pidfile = /tmp/web.pid diff --git a/setup.cfg b/setup.cfg index 663b972c..51d1eec1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,44 +1,3 @@ -[metadata] -version = attr: polemarch.__version__ -description = Polemarch is ansible based service for orchestration infrastructure. -long_description = file: README.rst -long_description_content_type = text/x-rst -license = AGPLv3+ -author = VST Consulting -author_email = sergey.k@vstconsulting.net -url = https://gitlab.com/vstconsulting/polemarch -keywords = - ansible - polemarch - infrastructure - devops -classifiers = - Development Status :: 5 - Production/Stable - Environment :: Web Environment - Framework :: Django - Framework :: Django :: 4.1 - Operating System :: POSIX - License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+) - Programming Language :: Python - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.11 - Programming Language :: Python :: 3 :: Only - Topic :: Internet :: WWW/HTTP - Topic :: Internet :: WWW/HTTP :: Dynamic Content - Topic :: System :: Systems Administration - Topic :: Utilities - -[options] -zip_safe = False -include_package_data = True -python_requires = >=3.8, <4.0 - -[build_sphinx] -project = 'Polemarch' - [githubrelease] repo = vstconsulting/polemarch assets = diff --git a/setup.py b/setup.py index 62a13b63..841039de 100644 --- a/setup.py +++ b/setup.py @@ -1,392 +1,9 @@ -# Compilation block -######################################################################################## -import re import os import sys -import subprocess -import fnmatch -import codecs -import gzip -import shutil +from vstcompile import make_setup, load_requirements # allow setup.py to be run from any path os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) - -from setuptools import find_packages, setup, errors, Command -from setuptools.extension import Extension -from setuptools.command.sdist import sdist as _sdist -from setuptools.command.build_py import build_py as build_py_orig -from setuptools.command.install_lib import install_lib as _install_lib -try: - from Cython.Build import cythonize, build_ext as _build_ext -except ImportError: - has_cython = False -else: - has_cython = True - -try: - from sphinx.setup_command import BuildDoc - import sphinx # noqa: F401 - has_sphinx = True -except ImportError: - has_sphinx = False - - -PY_MAJOR, PY_MINOR = sys.version_info[0:2] -ignored_keys = ['-h', '--help', '--version'] -is_help = any([a for a in ignored_keys if a in sys.argv]) -is_develop = 'develop' in sys.argv -is_build = (any([a for a in ['compile', 'bdist_wheel', 'bdist', 'sdist'] if a in sys.argv]) or is_develop) and not is_help - - -def get_discription(file_path='README.rst', folder=os.getcwd()): - with codecs.open("{}/{}".format(folder, file_path), 'r', encoding='utf-8') as readme: - return readme.read() - - -def load_requirements(file_name, folder=os.getcwd()): - with codecs.open(os.path.join(folder, file_name), 'r', encoding='utf-8')as req_file: - return req_file.read().strip().split('\n') - - -def get_file_ext(ext): - file_types = [".py", ".pyx", ".c", '.cpp'] if has_cython else [".c", '.cpp', ".py"] - for ftype in file_types: - fname = ext.replace(".", "/") + ftype - if os.path.exists(fname): - return fname - return None - - -def listfiles(folder): - if not isinstance(folder, (list, tuple)): - folder = [folder] - folder = filter(lambda p: os.path.isdir(p), folder) - for one_folder in folder: - for root, folders, files in os.walk(one_folder): - for filename in folders + files: - yield os.path.join(root, filename) - - -def clear_old_extentions(extensions_list, packages): - for filename in listfiles(packages): - _filename, _f_ext = os.path.splitext(filename) - if os.path.isdir(_filename) or _f_ext not in ['.c', '.cpp']: - continue - has_py = ( - os.path.exists('{}.py'.format(_filename)) or - os.path.exists('{}.pyx'.format(_filename)) - ) - - if has_py and filename.replace('/', '.').replace(_f_ext, '') in extensions_list: - print('Removing old extention [{}].'.format(filename)) - os.remove(filename) - - -def make_extention(module_name, files, extra_compile_args, main_include_dir=os.path.join(os.getcwd(), 'include')): - include_dirs = list(filter( - lambda f: bool(f) and os.path.exists(f) and os.path.isdir(f), - [os.path.join(module_name.split('.')[0], 'include'), main_include_dir] - )) - - return Extension( - module_name, files, - extra_compile_args=extra_compile_args, - include_dirs=include_dirs - ) - - -def make_extensions(extensions_list, packages): - if not isinstance(extensions_list, list): - raise Exception("Extension list should be `list`.") - - if not is_help: - clear_old_extentions(extensions_list, packages) - - extensions_dict = {} - for ext in extensions_list: - files = [] - module_name = ext - if isinstance(ext, (list, tuple)): - module_name = ext[0] - for file_module in ext[1]: - file_name = get_file_ext(file_module) - files += [file_name] if file_name else [] - else: - file_name = get_file_ext(ext) - files += [file_name] if file_name else [] - if files: - extensions_dict[module_name] = files - - extra_compile_args = [ - '-g0', '-ggdb1', - "-fno-strict-aliasing", - "-fno-var-tracking-assignments" if PY_MINOR != 6 else "", - "-pipe", "-std=c99", '-Werror=sign-compare', - ] - if 'compile' in sys.argv: - extra_compile_args.append("-DBUILD_FROM_SOURCE") - extra_compile_args = list(filter(bool, extra_compile_args)) - ext_modules = list( - make_extention(m, f, extra_compile_args) - for m, f in extensions_dict.items() - ) - ext_count = len(ext_modules) - nthreads = ext_count if ext_count < 10 else 10 - - language_level = 3 - if is_help: - pass - elif has_cython and ('compile' in sys.argv or 'bdist_wheel' in sys.argv or 'build_ext' in sys.argv): - cy_kwargs = dict( - nthreads=nthreads, - force=True, - language_level=language_level, - compiler_directives=dict( - linetrace='CYTHON_TRACE_NOGIL' in sys.argv, - profile=True, - c_string_type='str', - c_string_encoding='utf8' - ), - ) - return cythonize(ext_modules, **cy_kwargs), extensions_dict - return ext_modules, extensions_dict - - -def minify_js_file(js_file, jsmin_func): - return jsmin_func(js_file, quote_chars="'\"`") - - -def minify_css_file(css_file, cssmin_func): - return cssmin_func(css_file) - - -def minify_static_files(base_dir, files, exclude=None): - exclude = exclude or [] - patterns = dict() - try: - from jsmin import jsmin as jsmin_func - patterns['*.js'] = (minify_js_file, jsmin_func) - except: - pass - try: - from csscompressor import compress as csscompressor_func - patterns['*.css'] = (minify_css_file, csscompressor_func) - except: - pass - - regex_exclude = [re.compile(r, re.MULTILINE) for r in exclude] - - for fnext, funcs in patterns.items(): - for fext_file in filter(lambda f: fnmatch.fnmatch(f, fnext), files): - if fnmatch.fnmatch(fext_file, '*.min.*'): - continue - fext_file = os.path.join(base_dir, fext_file) - if os.path.exists(fext_file): - if not any(filter(lambda fp: bool(fp.search(fext_file)), regex_exclude)): - func, subfunc = funcs - with codecs.open(fext_file, 'r', encoding='utf-8') as static_file_fd: - minified = func(static_file_fd.read(), subfunc) - with codecs.open(fext_file, 'w', encoding='utf-8') as static_file_fd: - static_file_fd.write(minified) - print('Minfied file {fext_file}.'.format(fext_file=fext_file)) - if not os.environ.get('NOT_COMPRESS', False): - with open(fext_file, 'rb') as f_in: - with gzip.open("{}.gz".format(fext_file), 'wb') as f_out: - shutil.copyfileobj(f_in, f_out) - print('Compressed file {fext_file}.'.format(fext_file=fext_file)) - - -def compile_py_func(fullname, compile_file_func): - if compile_file_func(fullname, ddir=os.path.dirname(fullname), legacy=True, optimize=0): - os.remove(fullname) - - -def compile_python_sources(base_dir, files, exclude=None): - exclude = exclude or [] - patterns = dict() - try: - from compileall import compile_file - patterns['*.py'] = (compile_py_func, compile_file) - except: - pass - - regex_exclude = [re.compile(r, re.MULTILINE) for r in exclude] - - for fnext, funcs in patterns.items(): - for fext_file in filter(lambda f: fnmatch.fnmatch(f, fnext), files): - fext_file = os.path.join(base_dir, fext_file) - if os.path.exists(fext_file): - if not any(filter(lambda fp: bool(fp.search(fext_file)), regex_exclude)): - func, subfunc = funcs - funcs[0](fext_file, funcs[1]) - print('Compiled {fext_file}.'.format(fext_file=fext_file)) - - -class _Compile(_sdist): - extensions_dict = dict() - static_exclude = [] - - def __filter_files(self, files): - for _files in self.extensions_dict.values(): - for file in _files: - if file in files: - files.remove(file) - return files - - def make_release_tree(self, base_dir, files): - if has_cython: - files = self.__filter_files(files) - _sdist.make_release_tree(self, base_dir, files) - minify_static_files(base_dir, files, self.static_exclude) - - def run(self): - return _sdist.run(self) - - -class GithubRelease(Command): - ''' - Make release on github via githubrelease - ''' - description = 'Make release on github via githubrelease' - - user_options = [ - ('body=', 'b', 'Body message.'), - ('assets=', 'a', 'Release assets patterns.'), - ('repo=', 'r', 'Repository for release.'), - ('release=', 'R', 'Release version.'), - ('dry-run=', 'd', 'Dry run.'), - ('publish=', 'p', 'Publish release or just create draft.'), - ] - - def initialize_options(self): - self.body = None or os.getenv('CI_COMMIT_DESCRIPTION', None) - self.assets = None - self.repo = None - self.dry_run = False - self.publish = False - self.release = None or self.distribution.metadata.version - - def finalize_options(self): - if self.repo is None: - raise Exception("Parameter --repo is missing") - if self.release is None: - raise Exception("Parameter --release is missing") - self._gh_args = (self.repo, self.release) - self._gh_kwargs = dict( - publish=self.publish, name=self.release, dry_run=self.dry_run - ) - if self.assets: - assets = self.assets.format(release=self.release) - assets = list(filter(bool, assets.split('\n'))) - self._gh_kwargs['asset_pattern'] = assets - if self.body: - self._gh_kwargs['body'] = self.body - - def run(self): - from github_release import gh_release_create - gh_release_create(*self._gh_args, **self._gh_kwargs) - - -class build_py(build_py_orig): - exclude = [] - compile_extentions_types = ['.py', '.pyx'] - wheel_extentions_types = ['.c', '.cpp'] + compile_extentions_types - - def _filter_modules(self, module_tuple): - pkg, mod, file = module_tuple - try: - file_name, file_ext = os.path.splitext(file) - module_name = file_name.replace('/', '.') - except: - return True - if 'bdist_wheel' in sys.argv: - exclude_list = self.wheel_extentions_types - elif 'compile' in sys.argv: - exclude_list = self.compile_extentions_types - else: - return True - if module_name in self.exclude and file_ext in exclude_list: - return False - return True - - def find_package_modules(self, package, package_dir): - modules = build_py_orig.find_package_modules(self, package, package_dir) - return list(filter(self._filter_modules, modules)) - - -class install_lib(_install_lib): - exclude = [] - static_exclude = [] - compile_exclude = [] - - def _filter_files_with_ext(self, filename): - _filename, _fext = os.path.splitext(filename) - if _fext in build_py.wheel_extentions_types: - return True - return False - - def install(self): - result = _install_lib.install(self) - files = list(listfiles(self.install_dir)) - so_extentions = list(filter(lambda f: fnmatch.fnmatch(f, '*.so'), files)) - for source in filter(self._filter_files_with_ext, files): - _source_name, _source_ext = os.path.splitext(source) - if any(filter(lambda f: fnmatch.fnmatch(f, _source_name+"*.so"), so_extentions)): - print('Removing extention sources [{}].'.format(source)) - os.remove(source) - minify_static_files('', files, self.static_exclude) - if os.getenv('BUILD_COMPILE', '') == 'true': - compile_python_sources('', files, self.compile_exclude) - return result - - -def get_compile_command(extensions_dict=None): - extensions_dict = extensions_dict or dict() - compile_class = _Compile - compile_class.extensions_dict = extensions_dict - return compile_class - - -def make_setup(**opts): - if 'packages' not in opts: - opts['packages'] = find_packages() - ext_modules_list = opts.pop('ext_modules_list', []) - ext_mod, ext_mod_dict = make_extensions(ext_modules_list, opts['packages']) - opts['ext_modules'] = opts.get('ext_modules', []) + ext_mod - cmdclass = opts.get('cmdclass', dict()) - static_exclude = opts.pop('static_exclude_min', []) - if 'compile' not in cmdclass: - compile_class = get_compile_command(ext_mod_dict) - compile_class.static_exclude = static_exclude - cmdclass.update({"compile": get_compile_command(ext_mod_dict)}) - if has_cython: - build_py.exclude = ext_modules_list - install_lib.static_exclude = static_exclude - install_lib.compile_exclude = opts.pop('compile_modules_exclude', []) - cmdclass.update({ - 'build_ext': _build_ext, - 'build_py': build_py, - 'install_lib': install_lib - }) - if has_sphinx and 'build_sphinx' not in cmdclass: - cmdclass['build_sphinx'] = BuildDoc - cmdclass['githubrelease'] = GithubRelease - opts['cmdclass'] = cmdclass - - webpack_path = os.path.join(os.getcwd(), 'webpack.config.js') - if os.path.exists(webpack_path) and is_build and os.environ.get('DONT_YARN', "") != 'true': - try: - subprocess.check_call(['yarn', 'install', '--pure-lockfile', '--mutex network'], stdout=sys.stdout, stderr=sys.stderr) - subprocess.check_call(['yarn', 'devBuild' if is_develop else 'build'], stdout=sys.stdout, stderr=sys.stderr) - except Exception as err: - raise errors.CompileError(str(err)) - - setup(**opts) - -######################################################################################## -# end block - os.environ.setdefault('NOT_COMPRESS', 'true') ext_list = [] @@ -402,7 +19,6 @@ def make_setup(**opts): kwargs = dict( name='polemarch', - packages=find_packages(), ext_modules_list=ext_list, static_exclude_min=[ 'polemarch/templates/gui/service-worker.js', @@ -420,14 +36,6 @@ def make_setup(**opts): }, dependency_links=[ ] + load_requirements('requirements-git.txt', os.getcwd()), - project_urls={ - "Issue Tracker": "https://gitlab.com/vstconsulting/polemarch/issues", - "Documentation": "https://about.polemarch.org/", - "Web site": "https://polemarch.org/", - "Source Code": "https://gitlab.com/vstconsulting/polemarch", - "Releases": "https://github.com/vstconsulting/polemarch/releases", - "Docker": "https://hub.docker.com/r/vstconsulting/polemarch/", - }, entry_points={ 'console_scripts': ['polemarchctl=polemarch:cmd_execution'] }, diff --git a/tests.py b/tests.py index ad425fba..f828840d 100644 --- a/tests.py +++ b/tests.py @@ -1,33 +1,35 @@ -import json import base64 import io +import json +import logging import os import re -import sys -import time import shutil -import logging +import sys import threading -from unittest import skipIf +import time from http.server import BaseHTTPRequestHandler, HTTPServer -from django.forms import ValidationError -from django.test import override_settings -from django_test_migrations.contrib.unittest_case import MigratorTestCase -from django.core.exceptions import FieldDoesNotExist -from rest_framework import fields as drffields -from django.conf import settings +from pathlib import Path +from tempfile import mkdtemp +from unittest import skipIf +from uuid import uuid1 + import git import yaml -from uuid import uuid1 -from tempfile import mkdtemp -from pathlib import Path -from requests import Response -from django.core.management import call_command -from django.utils import timezone +from django.conf import settings from django.contrib.auth import get_user_model from django.contrib.contenttypes.models import ContentType -from vstutils.tests import BaseTestCase as VSTBaseTestCase +from django.core.exceptions import FieldDoesNotExist +from django.core.management import call_command +from django.forms import ValidationError +from django.test import override_settings +from django.utils import timezone +from django_test_migrations.contrib.unittest_case import MigratorTestCase +from requests import Response +from rest_framework import fields as drffields from vstutils.api import fields as vstfields +from vstutils.tests import BaseTestCase as VSTBaseTestCase + try: from polemarch.main.openapi import PROJECT_MENU from polemarch.main.tasks import ScheduledTask @@ -82,7 +84,7 @@ def wrapper(*args, **kwargs): result = func(*args, temp_dir=temp_dir, **kwargs) shutil.rmtree(temp_dir) return result - except: + except BaseException: shutil.rmtree(temp_dir) raise @@ -107,7 +109,7 @@ class MockServer: Stops server after leaving context. """ - __slots__ = ('handler', 'httpd',) + __slots__ = ('handler', 'httpd') def __init__(self, handler: BaseHTTPRequestHandler): self.handler = handler @@ -238,7 +240,7 @@ def setUp(self): key='ansible_connection', value='local', object_id=self.inventory.id, - content_type=host_type + content_type=host_type, ) super().setUp() @@ -262,7 +264,7 @@ def setUp(self): shutil.copy(f'{TEST_DATA_DIR}/playbook.yml', f'{settings.PROJECTS_DIR}/{self.project.id}/playbook.yml') shutil.copy( f'{TEST_DATA_DIR}/localhost-inventory.yml', - f'{settings.PROJECTS_DIR}/{self.project.id}/localhost-inventory.yml' + f'{settings.PROJECTS_DIR}/{self.project.id}/localhost-inventory.yml', ) self.project.start_repo_task('sync') self.inventory_path = 'localhost-inventory.yml' @@ -280,7 +282,7 @@ def setUp(self): 'connection': 'local', 'inventory': 'localhost,', 'group': 'all', - } + }, ) def tearDown(self): @@ -296,7 +298,7 @@ def create_inventory_bulk_data(self, plugin='POLEMARCH_DB', project_id=None, **k 'name': 'test-inventory', 'plugin': plugin, **kwargs, - } + }, } def update_inventory_state_bulk_data(self, inventory_id, project_id=None, **kwargs): @@ -319,7 +321,7 @@ def import_inventory_bulk_data(self, plugin, data, project_id=None, name='test_i 'name': name, 'plugin': plugin, 'data': data, - } + }, } def get_inventory_state_bulk_data(self, inventory_id, project_id=None): @@ -347,7 +349,7 @@ def create_project_bulk_data(self, type='MANUAL', **kwargs): 'data': { 'name': str(uuid1()), 'type': type, - **kwargs + **kwargs, } } @@ -361,7 +363,7 @@ def create_project_variable_bulk_data(self, key, value, project_id=None): return { 'method': 'post', 'path': ['project', project_id or self.project.id, 'variables'], - 'data': {'key': key, 'value': value} + 'data': {'key': key, 'value': value}, } def get_project_bulk_data(self, project_id=None): @@ -374,7 +376,7 @@ def get_history_bulk_data(self, history_id, path_prefix=None): path_prefix = path_prefix or [] return { 'method': 'get', - 'path': [*path_prefix, 'history', history_id] + 'path': [*path_prefix, 'history', history_id], } def get_raw_history_bulk_data(self, history_id, path_prefix=None): @@ -396,7 +398,7 @@ def create_template_periodic_task_bulk_data( template_id=None, option_id=None, project_id=None, - **kwargs + **kwargs, ): option_id = option_id or self.template_option.id return { @@ -418,7 +420,7 @@ def create_template_periodic_task_bulk_data( 'save_result': True, 'notes': 'some notes', **kwargs, - } + }, } def get_default_template_option(self, template_id): @@ -430,7 +432,7 @@ def create_template_bulk_data( project_id=None, plugin='ANSIBLE_MODULE', arguments=None, - **kwargs + **kwargs, ): return { 'method': 'post', @@ -446,7 +448,7 @@ def create_template_bulk_data( 'verbose': 2, }, **kwargs, - } + }, } def create_template_option_bulk_data( @@ -470,7 +472,7 @@ def create_template_option_bulk_data( 'notes': 'some notes', 'arguments': arguments, **kwargs, - } + }, } def execute_template_bulk_data( @@ -1234,16 +1236,14 @@ def test_groups(self): def test_path_validator(self): try: from polemarch.main.validators import path_validator - except: + except BaseException: from pmlib.main.validators import path_validator valid_paths = { './.lol', './lol/kek.7z', './_l-o+l=', - 'lol/kek.7z', 'lol.yaml/', - 'lol/kek.7z', 'lol-kek_lol/_lol', '哈哈/chinese', '.lol', @@ -1447,7 +1447,7 @@ def check_update(inventory, option_id): try: template_id = check_create(inventory) check_update(inventory2, template_id) - except: + except BaseException: print(f'Failed with create {inventory}, update {inventory2}.') raise @@ -1685,7 +1685,7 @@ def test_sync_git(self, temp_dir): **results[2]['data'], 'status': 'ERROR', 'revision': 'ERROR', - 'branch': 'waiting...' + 'branch': 'waiting...', }) def password_and_key_checker(operation, **kwargs): @@ -1764,7 +1764,7 @@ def password_and_key_checker(operation, **kwargs): 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 'LOCATION': 'test_sync_after_repo_change', - } + }, }) @use_temp_dir def test_sync_after_repo_change(self, temp_dir): @@ -3884,7 +3884,7 @@ def test_edit_template_option(self): 'n': True, 'e': False, }, - } + }, }, # [1] { @@ -3900,7 +3900,7 @@ def test_edit_template_option(self): 'execution_templates', '<<0[data][id]>>', 'options', - '<<1[data][results][0][id]>>' + '<<1[data][results][0][id]>>', ], }, # [3] @@ -3912,7 +3912,7 @@ def test_edit_template_option(self): 'execution_templates', '<<0[data][id]>>', 'options', - '<<1[data][results][0][id]>>' + '<<1[data][results][0][id]>>', ], 'data': { 'arguments': { @@ -4065,18 +4065,18 @@ def test_playbook_path_variable_validation(self): { 'method': 'post', 'path': ['project', self.project.id, 'variables'], - 'data': {'key': 'playbook_path', 'value': '../kek.yaml'} + 'data': {'key': 'playbook_path', 'value': '../kek.yaml'}, }, { 'method': 'post', 'path': ['project', self.project.id, 'variables'], - 'data': {'key': 'playbook_path', 'value': './lol/kek.yaml'} + 'data': {'key': 'playbook_path', 'value': './lol/kek.yaml'}, }, ]) self.assertEqual(results[0]['status'], 400) self.assertIn( 'Invalid path. Path must not contain "..", "~" or any other special characters and must be relative.', - results[0]['data']['detail']['other_errors'][0] + results[0]['data']['detail']['other_errors'][0], ) self.assertEqual(results[1]['status'], 201) @@ -4088,7 +4088,7 @@ def ansible_host_bulk_data(value): 'data': { 'key': 'ansible_host', 'value': value, - } + }, } self.get_model_class('main.Variable').objects.all().delete() @@ -4118,13 +4118,13 @@ def test_remove_existing_variable(self): { 'method': 'post', 'path': ['host', self.host.id, 'variables'], - 'data': {'key': 'ansible_host', 'value': '1'} + 'data': {'key': 'ansible_host', 'value': '1'}, }, { 'method': 'post', 'path': ['host', self.host.id, 'variables'], - 'data': {'key': 'ansible_host', 'value': '2'} - } + 'data': {'key': 'ansible_host', 'value': '2'}, + }, ]) self.assertEqual(results[0]['status'], 201) self.assertEqual(results[1]['status'], 201) @@ -4198,13 +4198,13 @@ def test_var_filter(self): key='lol', value='kek', content_type=group_type, - object_id=self.group.id + object_id=self.group.id, ) self.get_model_class('main.Variable').objects.create( key='kek', value='lol', content_type=group_type, - object_id=another_group.id + object_id=another_group.id, ) results = self.bulk([ @@ -4215,17 +4215,17 @@ def test_var_filter(self): { # [1] should find 1 'method': 'get', 'path': 'group', - 'query': 'variables=lol:kek' + 'query': 'variables=lol:kek', }, { # [2] should find 0 'method': 'get', 'path': 'group', - 'query': 'variables=unknown:unknown' + 'query': 'variables=unknown:unknown', }, { # [3] should raise list index out of range (FIXME: really ??) 'method': 'get', 'path': 'group', - 'query': 'variables=kek' + 'query': 'variables=kek', }, ]) self.assertEqual(results[0]['status'], 200) @@ -4248,7 +4248,7 @@ def test_execution_template_option_private_vars(self): 'module': 'system.ping', 'private_key': 'key', 'vault_password_file': 'file', - } + }, ), self.create_template_bulk_data( plugin='ANSIBLE_PLAYBOOK', @@ -4256,7 +4256,7 @@ def test_execution_template_option_private_vars(self): 'playbook': 'playbook.yml', 'private_key': 'key', 'vault_password_file': 'file', - } + }, ), ]) module_template_id = results[0]['data']['id'] @@ -4297,7 +4297,7 @@ def test_history_raw_inventory_private_vars(self): { 'method': 'post', 'path': ['inventory', self.inventory.id, 'variables'], - 'data': {'key': 'ansible_ssh_pass', 'value': 'lol-pass'} + 'data': {'key': 'ansible_ssh_pass', 'value': 'lol-pass'}, }, self.execute_plugin_bulk_data( plugin='ANSIBLE_MODULE', @@ -4315,12 +4315,12 @@ def test_vars_property_caching(self): self.project.vars = { 'repo_branch': 'slave', 'repo_password': CYPHER, - 'repo_sync_on_run': True + 'repo_sync_on_run': True, } self.assertDictEqual(self.project.vars, { 'repo_branch': 'slave', 'repo_password': CYPHER, - 'repo_sync_on_run': True + 'repo_sync_on_run': True, }) def test_env_vars_on_execution(self): @@ -4332,7 +4332,7 @@ def test_env_vars_on_execution(self): plugin='ANSIBLE_MODULE', module='system.ping', inventory=self.inventory.id, - ) + ), ]) self.assertEqual(popen.call_args[-1]['env']['EXAMPLE'], '1') self.assertIn('VST_PROJECT', popen.call_args[-1]['env']) @@ -4348,8 +4348,8 @@ def create_hook_bulk_data(type, recipients, when): 'enable': True, 'recipients': recipients, 'type': type, - 'when': when - } + 'when': when, + }, } @@ -4406,26 +4406,26 @@ def check_hook(request, when, bulk_data, call_count=1, status=None): # *_execution check_hook( when='on_execution', - bulk_data=[self.execute_plugin_bulk_data(plugin='ANSIBLE_MODULE', module='system.ping')] + bulk_data=[self.execute_plugin_bulk_data(plugin='ANSIBLE_MODULE', module='system.ping')], ) check_hook( when='after_execution', - bulk_data=[self.execute_plugin_bulk_data(plugin='ANSIBLE_MODULE', module='system.ping')] + bulk_data=[self.execute_plugin_bulk_data(plugin='ANSIBLE_MODULE', module='system.ping')], ) # on_object_* check_hook( when='on_object_add', - bulk_data=[{'method': 'post', 'path': 'host'}] + bulk_data=[{'method': 'post', 'path': 'host'}], ) host = self.get_model_filter('main.Host').first() check_hook( when='on_object_upd', - bulk_data=[{'method': 'patch', 'path': ['host', host.id], 'data': {'name': 'somename'}}] + bulk_data=[{'method': 'patch', 'path': ['host', host.id], 'data': {'name': 'somename'}}], ) check_hook( when='on_object_del', - bulk_data=[{'method': 'delete', 'path': ['host', host.id]}] + bulk_data=[{'method': 'delete', 'path': ['host', host.id]}], ) # on_object_add @@ -4454,9 +4454,9 @@ def check_hook(request, when, bulk_data, call_count=1, status=None): 'username': 'username', 'password': 'password', 'password2': 'password', - } - } - ] + }, + }, + ], ) user = User.objects.get(username='username') check_hook( @@ -4465,7 +4465,7 @@ def check_hook(request, when, bulk_data, call_count=1, status=None): ) check_hook( when='on_user_del', - bulk_data=[{'method': 'delete', 'path': ['user', user.id]}] + bulk_data=[{'method': 'delete', 'path': ['user', user.id]}], ) # check successful change password triggers on_user_upd @@ -4479,9 +4479,9 @@ def check_hook(request, when, bulk_data, call_count=1, status=None): 'old_password': self.user.data['password'], 'password': 'same', 'password2': 'same', - } - } - ] + }, + }, + ], ) # check unsuccessful attempt to change password not triggers on_user_upd check_hook( @@ -4494,11 +4494,11 @@ def check_hook(request, when, bulk_data, call_count=1, status=None): 'old_password': self.user.data['password'], 'password': 'password', 'password2': 'password2', - } - } + }, + }, ], status=403, - call_count=0 + call_count=0, ) def check_output_run_script(self, check_data, *args, **kwargs): @@ -4578,7 +4578,7 @@ def test_update_ansible_modules(self): call_command('update_ansible_modules', interactive=False, stdout=out) self.assertEqual( 'The modules have been successfully updated.\n', - out.getvalue().replace('\x1b[32;1m', '').replace('\x1b[0m', '') + out.getvalue().replace('\x1b[32;1m', '').replace('\x1b[0m', ''), ) @@ -4596,8 +4596,8 @@ def test_users_create(self): 'last_name': 'Msh', 'username': 'msh', 'password': '1q2w3e', - 'password2': '1q2w3e' - }} + 'password2': '1q2w3e', + }}, ]) self.assertEqual(results[0]['status'], 201) @@ -4612,8 +4612,8 @@ def test_users_create(self): 'last_name': 'User', 'username': 'user', 'password': 'user', - 'password2': 'user' - }} + 'password2': 'user', + }}, ]) self.assertEqual(results[0]['status'], 201) @@ -4628,8 +4628,8 @@ def test_users_create(self): 'last_name': 'User1', 'username': 'user1', 'password': 'user1', - 'password2': 'user1' - }} + 'password2': 'user1', + }}, ]) self.assertEqual(results[0]['status'], 403) @@ -4637,7 +4637,7 @@ def test_users_create(self): def test_update(self): results = self.bulk([ {'method': 'patch', 'path': ['user', self.user.id], 'data': {'username': 'new_username'}}, - {'method': 'get', 'path': ['user', self.user.id]} + {'method': 'get', 'path': ['user', self.user.id]}, ]) self.assertEqual(results[0]['status'], 200) self.assertEqual(results[0]['data']['username'], 'new_username') @@ -4651,12 +4651,12 @@ def test_delete_token(self): result = self.result( self.client.post, self.get_url('token'), - data={'username': self.user.username, 'password': self.user.username.upper()} + data={'username': self.user.username, 'password': self.user.username.upper()}, ) response = self.client.delete( self.get_url('token'), HTTP_AUTHORIZATION=f'Token {result["token"]}', - content_type='application/json' + content_type='application/json', ) self.assertEqual(response.status_code, 204) @@ -4671,7 +4671,7 @@ class BaseOpenAPITestCase(VSTBaseTestCase): system_tab = { 'name': 'System', 'span_class': 'fa fa-cog', - 'sublinks': [] + 'sublinks': [], } users_sublink = { 'name': 'Users', @@ -4681,7 +4681,7 @@ class BaseOpenAPITestCase(VSTBaseTestCase): hooks_sublink = { 'name': 'Hooks', 'url': '/hook', - 'span_class': 'fa fa-plug' + 'span_class': 'fa fa-plug', } @classmethod @@ -4701,15 +4701,17 @@ def check_execution_plugins(self, values): self.assertSetEqual( set(values), self.builtin_execution_plugins, - 'External execution plugins are not allowed in generated YML schema.' + 'External execution plugins are not allowed in generated YML schema.', ) - def check_inventory_plugins(self, values, exclude=set()): + def check_inventory_plugins(self, values, exclude=None): + if exclude is None: + exclude = set() self.assertSetEqual( set(values), self.builtin_inventory_plugins - exclude, 'plugin is either not excluded or external. ' - 'External inventory plugins are not allowed in generated YML schema.' + 'External inventory plugins are not allowed in generated YML schema.', ) def get_schemas_for_test(self): @@ -4747,7 +4749,7 @@ def get_schemas_for_test(self): endpoint_schema['definitions'][key]['properties']['arguments']['x-options']['types'] = yml_types for idx, query_def in enumerate( - endpoint_schema['paths']['/project/{id}/execution_templates/']['get']['parameters'] + endpoint_schema['paths']['/project/{id}/execution_templates/']['get']['parameters'], ): if query_def['name'] == 'plugin': yml_schema['paths']['/project/{id}/execution_templates/']['get']['parameters'][idx] = query_def @@ -4839,10 +4841,10 @@ def setUp(self): } for status, count in self.history_status_count_map.items(): - for i in range(count): + for _ in range(count): History.objects.create(status=status) - Project.objects.create(name=f'test_metrics_{i}') + Project.objects.create(name='test_metrics_0') for i in range(3): Project.objects.create(name=f'test_metrics_{i}', status='OK') for i in range(4): @@ -4853,7 +4855,7 @@ def test_metrics(self): expected = (Path(__file__).parent / 'test_data' / 'metrics.txt').read_text('utf-8') expected = expected.replace( '$VERSION', - f'{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}' + f'{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}', ) self.assertEqual(result, expected) @@ -5043,7 +5045,7 @@ def prepare(self): content_type = self.old_state.apps.get_model( 'contenttypes', - 'ContentType' + 'ContentType', ).objects.create(app_label='main', model='periodictask') Variable.objects.create( @@ -5557,7 +5559,7 @@ def prepare(self): initiator=template1.id, initiator_type='template', executor=None, - json_options=json.dumps({'template_option': str(option1.id)}) + json_options=json.dumps({'template_option': str(option1.id)}), ) self.history3_id = history3.id @@ -5572,7 +5574,7 @@ def prepare(self): initiator=template1.id, initiator_type='template', executor=None, - json_options=json.dumps({'template_option': str(option2.id)}) + json_options=json.dumps({'template_option': str(option2.id)}), ) self.history4_id = history4.id @@ -5631,7 +5633,7 @@ def test_migrations(self): 'module': 'ping', 'args': 'some args', 'group': 'all', - 'vars': {'check': True} + 'vars': {'check': True}, }) self.assertDictEqual(json.loads(template1.options_data), { 'option2': { @@ -5641,8 +5643,8 @@ def test_migrations(self): 'vars': { 'check': False, 'become': True, - } - } + }, + }, }) template2 = Template.objects.get(name='template2') @@ -5655,7 +5657,7 @@ def test_migrations(self): 'vars': { 'check': True, 'private_key': example_key, - } + }, }) self.assertEqual(template2.options_data, '{}') @@ -5752,7 +5754,7 @@ def dummy_output_handler(self, message, level): def get_plugin_instance(self, options=None, execution_dir=None) -> BasePlugin: instance = self.plugin_class( options=options or {}, - output_handler=self.dummy_output_handler + output_handler=self.dummy_output_handler, ) instance.execution_dir = execution_dir or self.dummy_execution_dir return instance @@ -6002,7 +6004,7 @@ class AnsibleModuleExecutionPluginUnitTestCase(BaseExecutionPluginUnitTestCase): ) file_args = ( 'private_key', - 'vault_password_file' + 'vault_password_file', ) def test_process_arg(self): diff --git a/tox.ini b/tox.ini index 1bf988d5..95e445c2 100644 --- a/tox.ini +++ b/tox.ini @@ -27,15 +27,15 @@ commands = -pip install -U pip install: -pip uninstall polemarch -y install: rm -rfv {envdir}/dist/ - install: python setup.py bdist_wheel --dist-dir {envdir}/dist/ + install: pip wheel {toxinidir} -w {envdir}/dist/ --no-deps install: bash -c "export BUILD_COMPILE=true; pip install -U {envdir}/dist/$(ls {envdir}/dist)[test]" - coverage: python setup.py install_egg_info coverage: pip install -U -e .[test] install: rm -f {envdir}/tests.py {envdir}/tests.pyc install: rm -rf {envdir}/test_data install: ln -s {toxinidir}/test_data {envdir}/test_data install: ln -s {toxinidir}/tests.py {envdir}/tests.py install: python -m polemarch test -v 2 --failfast --parallel auto + coverage: python -m polemarch makemigrations main --check coverage: coverage debug sys coverage: coverage erase coverage: coverage run -m polemarch test -v 2 --failfast --parallel auto {posargs} @@ -50,11 +50,18 @@ deps = [testenv:flake] basepython = python3.8 deps = - flake8 - -rrequirements.txt - -rrequirements-git.txt + flake8==6.0.0 + flake8-bugbear==23.7.10 + flake8-commas==2.1.0 + flake8-comprehensions==3.14.0 + flake8-django==1.3.0 + flake8-executable==2.1.3 + flake8-functions==0.0.8 + flake8-import-order==0.18.2 + Flake8-pyproject==1.2.3 commands = - flake8 --config=.pep8 polemarch tests.py + flake8 polemarch + flake8 --ignore=CFQ001,C812 tests.py [testenv:pylint] basepython = python3.8 @@ -66,7 +73,6 @@ deps = -rrequirements-git.txt commands = pip uninstall polemarch -y - python setup.py install_egg_info pip install -U -e . pylint --rcfile=./.pylintrc {posargs} polemarch @@ -88,6 +94,7 @@ changedir = . allowlist_externals = tox rm +deps = tox~=4.0 commands = rm -rf dist build tox -c tox_build.ini -e py38-build,py38-wheel --workdir {toxworkdir} @@ -115,15 +122,13 @@ setenv = passenv = * allowlist_externals = * commands = - rm -frv {envdir}/dist - python setup.py compile_docs - python setup.py bdist_wheel --dist-dir {envdir}/dist/ - bash -c "pip wheel $(find {envdir}/dist/* | head -1)[mysql,postgresql,ansible] -w wheels" + pip wheel .[mysql,postgresql,ansible] -w wheels deps = -rrequirements-doc.txt pip>=20.2 wheel cython + vstcompile~=2.0 [testenv:release] basepython = python3.8 @@ -154,45 +159,11 @@ setenv = passenv = * allowlist_externals = * commands = - python setup.py install_egg_info pip install -U -e .[test] deps = cython>0.29,<0.30 - tox~=3.25.0 - pip~=21.3.1 - -e .[test] - -[testenv:deploy_env] -passenv = * -setenv = - LC_ALL = en_US.UTF-8 - LANG = en_US.UTF-8 -allowlist_externals = - /bin/sh -commands = - ansible-playbook -i localhost, --connection local k8s_env_deploy.yaml -vvvv -deps = - ansible~=2.9.0 - openshift>=0.6 - pyyaml>=3.11 - docker - tox - -[testenv:destroy_env] -passenv = * -setenv = - LC_ALL = en_US.UTF-8 - LANG = en_US.UTF-8 -allowlist_externals = - /bin/sh -commands = - ansible-playbook -i localhost, --connection local k8s_env_destroy.yaml -vvvv -deps = - ansible~=2.9.0 - openshift>=0.6 - pyyaml>=3.11 - docker - tox + tox~=4.0 + pip~=23.0 [testenv:release-docker] passenv = * diff --git a/tox_build.ini b/tox_build.ini index a5f4198e..e9347d30 100644 --- a/tox_build.ini +++ b/tox_build.ini @@ -11,10 +11,17 @@ allowlist_externals = bash grep mkdir + yarn commands = - rm -rf build - build: python setup.py compile -v {posargs} - wheel: python setup.py compile_docs -v {posargs} - wheel: python setup.py bdist_wheel -v {posargs} + rm -rf {toxinidir}/build + build: yarn install --pure-lockfile + build: yarn build + build: python -m build --sdist --wheel --no-isolation --skip-dependency-check --outdir {toxinidir}/dist {toxinidir} + wheel: python -m build --wheel --no-isolation --skip-dependency-check --outdir {toxinidir}/dist {toxinidir} deps = -rrequirements-doc.txt + vstcompile~=2.0 + cython>=3.0.0 + build~=0.10.0 + wheel==0.31.1 + setuptools>=40.8.0 From 4f97145b741a28b82370e12c297e3f615db3ecf7 Mon Sep 17 00:00:00 2001 From: Vladislav Korenkov Date: Tue, 22 Aug 2023 13:05:54 +1000 Subject: [PATCH 03/18] Fix(deps): Remove useless requirement of vstcompile for docker build --- tox.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/tox.ini b/tox.ini index 95e445c2..85b85baa 100644 --- a/tox.ini +++ b/tox.ini @@ -128,7 +128,6 @@ deps = pip>=20.2 wheel cython - vstcompile~=2.0 [testenv:release] basepython = python3.8 From 4c5605dea4311767e0b26325d1e73214f6765e88 Mon Sep 17 00:00:00 2001 From: Sergey Klyuykov Date: Mon, 21 Aug 2023 21:23:06 -0700 Subject: [PATCH 04/18] Fix(build): Build docs in package. --- tox.ini | 2 ++ tox_build.ini | 1 + 2 files changed, 3 insertions(+) diff --git a/tox.ini b/tox.ini index 85b85baa..0af23bf6 100644 --- a/tox.ini +++ b/tox.ini @@ -122,9 +122,11 @@ setenv = passenv = * allowlist_externals = * commands = + python setup.py compile_docs pip wheel .[mysql,postgresql,ansible] -w wheels deps = -rrequirements-doc.txt + vstcompile~=2.0 pip>=20.2 wheel cython diff --git a/tox_build.ini b/tox_build.ini index e9347d30..6638c70e 100644 --- a/tox_build.ini +++ b/tox_build.ini @@ -14,6 +14,7 @@ allowlist_externals = yarn commands = rm -rf {toxinidir}/build + python setup.py compile_docs build: yarn install --pure-lockfile build: yarn build build: python -m build --sdist --wheel --no-isolation --skip-dependency-check --outdir {toxinidir}/dist {toxinidir} From 3d77833b519bc99dfd95e0918af818de239876f8 Mon Sep 17 00:00:00 2001 From: Sergey Klyuykov Date: Tue, 22 Aug 2023 16:39:33 -0700 Subject: [PATCH 05/18] Fix(deps): Update frontend dependencies. --- package.json | 2 +- yarn.lock | 21 ++++++++------------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 204d82ea..d6600684 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,6 @@ "dependencies": { "chart.js": "^3.1.1", "codemirror": "^5.65.6", - "highlight.js": "9.15.10" + "highlight.js": "10.4.1" } } diff --git a/yarn.lock b/yarn.lock index a9f6584e..249ef3c5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2154,10 +2154,10 @@ he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -highlight.js@9.15.10: - version "9.15.10" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.15.10.tgz#7b18ed75c90348c045eef9ed08ca1319a2219ad2" - integrity sha512-RoV7OkQm0T3os3Dd2VHLNMoaoDVx77Wygln3n9l5YV172XonWG6rgQD3XnF/BuFFZw9A0TJgmMSO8FEWQgvcXw== +highlight.js@10.4.1: + version "10.4.1" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.4.1.tgz#d48fbcf4a9971c4361b3f95f302747afe19dbad0" + integrity sha512-yR5lWvNz7c85OhVAEAeFhVCc/GV4C30Fjzc/rCP0aCWzc1UUOPUk55dK/qdwTZHBvMZo+eZ2jpk62ndX/xMFlg== icss-utils@^5.0.0, icss-utils@^5.1.0: version "5.1.0" @@ -2318,18 +2318,13 @@ json-stable-stringify-without-jsonify@^1.0.1: integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== json5@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" - integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + version "1.0.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== dependencies: minimist "^1.2.0" -json5@^2.1.2: - version "2.2.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" - integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== - -json5@^2.2.2: +json5@^2.1.2, json5@^2.2.2: version "2.2.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== From 453f12511dd4cc53d58459f0af3d416b52a4990d Mon Sep 17 00:00:00 2001 From: Sergey Klyuykov Date: Tue, 22 Aug 2023 19:50:34 -0700 Subject: [PATCH 06/18] Fix(plugins): Provide model instances in custom plugin execution. --- Dockerfile | 9 ++++++++- polemarch/main/utils.py | 12 ++++++++---- tox.ini | 4 ++-- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 65cb962c..d44f31bc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -64,11 +64,18 @@ RUN --mount=type=cache,target=/var/cache/apt \ libffi7 \ libssl1.1 \ openssh-client && \ - python3.8 -m pip install cryptography paramiko 'pip<22' && \ + if [ "$PACKAGE_NAME" = "polemarchplus" ]; then \ + apt install --no-install-recommends gpg wget lsb-release -y && \ + wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg && \ + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/hashicorp.list && \ + apt update && apt install --no-install-recommends terraform -y; \ + fi && \ + python3.8 -m pip install cryptography paramiko 'pip~=23.0' && \ ln -s /usr/bin/python3.8 /usr/bin/python && \ mkdir -p /projects /hooks /run/openldap /etc/polemarch/hooks /var/lib/polemarch && \ python3.8 -m pip install --no-index --find-links /polemarch_env/wheels $PACKAGE_NAME[mysql,postgresql,ansible] && \ find /usr/lib/python3.8 -regex '.*\(*.pyc\|__pycache__\).*' -delete && \ + apt remove gpg wget lsb-release -y && \ apt autoremove -y && \ rm -rf /tmp/* \ /var/tmp/* \ diff --git a/polemarch/main/utils.py b/polemarch/main/utils.py index 48bb9282..1c553491 100644 --- a/polemarch/main/utils.py +++ b/polemarch/main/utils.py @@ -15,7 +15,7 @@ except ImportError: # nocv from yaml import Loader, Dumper, load, dump from django.conf import settings -from django.db import transaction +from django.db import transaction, models from django.utils import timezone from vstutils.tasks import TaskClass from vstutils.utils import ( @@ -338,6 +338,13 @@ def execute(self, plugin: str, project, execute_args, **kwargs): task_class = project.task_handlers.backend('EXECUTION') + if execute_args: + for key, value in execute_args.items(): + if isinstance(value, models.Model): + with raise_context(): # nocv + # Cannot be covered because persist only on production build + execute_args[key] = value.pk + history = self.create_history( project, plugin, @@ -349,9 +356,6 @@ def execute(self, plugin: str, project, execute_args, **kwargs): options=kwargs.pop('options', {}), ) - with raise_context(): - execute_args['inventory'] = execute_args['inventory'].id - task_kwargs = { 'plugin': plugin, 'project_id': project.id, diff --git a/tox.ini b/tox.ini index 0af23bf6..d5a0aa84 100644 --- a/tox.ini +++ b/tox.ini @@ -34,11 +34,11 @@ commands = install: rm -rf {envdir}/test_data install: ln -s {toxinidir}/test_data {envdir}/test_data install: ln -s {toxinidir}/tests.py {envdir}/tests.py - install: python -m polemarch test -v 2 --failfast --parallel auto + install: python -m polemarch test -v 2 --failfast --parallel 2 coverage: python -m polemarch makemigrations main --check coverage: coverage debug sys coverage: coverage erase - coverage: coverage run -m polemarch test -v 2 --failfast --parallel auto {posargs} + coverage: coverage run -m polemarch test -v 2 --failfast --parallel 2 {posargs} coverage: coverage combine coverage: coverage report rm -rf .eggs build polemarch.egg-info {envdir}/dist From d483ba1d0df7392e0241c0869067ac67b61069a3 Mon Sep 17 00:00:00 2001 From: Dmitriy Ovcharenko Date: Mon, 16 Dec 2024 15:56:22 +1000 Subject: [PATCH 07/18] Feature: Update vstutils --- .dockerignore | 1 - .gitignore | 2 +- .gitlab-ci.yml | 12 +- .pylintrc | 55 +- Dockerfile | 17 +- doc/api_schema.yaml | 1591 ++++++- docker-compose.yml | 7 +- frontend_src/.babelrc.js | 36 - frontend_src/.editorconfig | 8 - frontend_src/.eslintrc.js | 30 - frontend_src/.gitignore | 24 + frontend_src/.prettierrc | 18 - frontend_src/Home.vue | 36 +- frontend_src/TasksChart.vue | 372 +- frontend_src/assets/logo/horizontal.svg | 38 + frontend_src/assets/logo/logo.png | Bin 0 -> 44204 bytes frontend_src/assets/logo/vertical.png | Bin 0 -> 67930 bytes frontend_src/auth/AuthPagesLayout.vue | 41 + frontend_src/auth/index.ts | 8 + frontend_src/biome.jsonc | 13 + frontend_src/{index.js => common.ts} | 26 +- frontend_src/history/ExecutionTimeField.js | 1 + frontend_src/history/HistoryLine.vue | 17 +- frontend_src/history/OutputLines.vue | 248 +- frontend_src/history/index.js | 7 +- frontend_src/history/style.scss | 2 +- frontend_src/index.html | 13 + frontend_src/index.ts | 11 + frontend_src/inventory/InventoryFieldEdit.vue | 96 +- .../inventory/InventoryFieldReadonly.vue | 38 +- frontend_src/inventory/index.js | 28 +- frontend_src/jsconfig.json | 5 - frontend_src/layout/Logo.vue | 13 +- frontend_src/layout/index.js | 8 - frontend_src/layout/index.ts | 8 + frontend_src/project/detail/RunPlaybook.vue | 151 +- frontend_src/project/detail/index.js | 2 +- frontend_src/tsconfig.json | 27 + frontend_src/vite-env.d.ts | 1 + frontend_src/vite.config.ts | 52 + package.json | 55 +- polemarch/__init__.py | 4 +- polemarch/api/fields.py | 5 + polemarch/api/filters.py | 2 +- polemarch/api/v4/history.py | 14 +- polemarch/main/hooks/base.py | 2 +- polemarch/main/hooks/http.py | 14 +- polemarch/main/hooks/script.py | 2 +- .../commands/update_ansible_modules.py | 4 +- polemarch/main/models/__init__.py | 19 +- polemarch/main/models/base.py | 11 +- polemarch/main/models/hooks.py | 2 +- polemarch/main/models/hosts.py | 6 - polemarch/main/models/projects.py | 20 +- polemarch/main/models/utils.py | 39 +- polemarch/main/models/vars.py | 8 - polemarch/main/repo/_base.py | 45 +- polemarch/main/repo/manual.py | 2 +- polemarch/main/repo/tar.py | 6 +- polemarch/main/repo/vcs.py | 18 +- polemarch/main/settings.py | 6 +- polemarch/main/tasks/tasks.py | 11 +- polemarch/main/utils.py | 15 +- polemarch/main/validators.py | 4 +- polemarch/plugins/execution/base.py | 2 +- polemarch/plugins/history/base.py | 7 +- polemarch/plugins/inventory/ansible.py | 2 + polemarch/templates/auth/login.html | 7 - polemarch/templates/gui/service-worker.js | 11 - pyproject.toml | 11 +- requirements-doc.txt | 4 +- requirements.txt | 3 +- setup.py | 6 - tests.py | 175 +- tox.ini | 27 +- tox_build.ini | 8 +- webpack.config.js | 82 - yarn.lock | 3919 ++++------------- 78 files changed, 3592 insertions(+), 4049 deletions(-) delete mode 100644 frontend_src/.babelrc.js delete mode 100644 frontend_src/.editorconfig delete mode 100644 frontend_src/.eslintrc.js create mode 100644 frontend_src/.gitignore delete mode 100644 frontend_src/.prettierrc create mode 100644 frontend_src/assets/logo/horizontal.svg create mode 100644 frontend_src/assets/logo/logo.png create mode 100644 frontend_src/assets/logo/vertical.png create mode 100644 frontend_src/auth/AuthPagesLayout.vue create mode 100644 frontend_src/auth/index.ts create mode 100644 frontend_src/biome.jsonc rename frontend_src/{index.js => common.ts} (82%) create mode 100644 frontend_src/index.html create mode 100644 frontend_src/index.ts delete mode 100644 frontend_src/jsconfig.json delete mode 100644 frontend_src/layout/index.js create mode 100644 frontend_src/layout/index.ts create mode 100644 frontend_src/tsconfig.json create mode 100644 frontend_src/vite-env.d.ts create mode 100644 frontend_src/vite.config.ts delete mode 100644 polemarch/templates/auth/login.html delete mode 100644 polemarch/templates/gui/service-worker.js delete mode 100644 webpack.config.js diff --git a/.dockerignore b/.dockerignore index bf10519d..3b6d9db7 100644 --- a/.dockerignore +++ b/.dockerignore @@ -294,7 +294,6 @@ dist !ce/doc !ce/package.json !ce/frontend_src -!ce/webpack.config.js !ce/.dockerignore !ce/requirements.txt !ce/requirements-doc.txt diff --git a/.gitignore b/.gitignore index 59f5eeb8..7ec53636 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,6 @@ env *.pid *nbproject pylint_* -index.html .idea/* /polemarch.egg-info/ /polemarch/projects/ @@ -136,6 +135,7 @@ venv.bak/ # Frontend node_modules polemarch/static/polemarch +polemarch/static/spa yarn-error.log # werf and helm diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3a08f963..bdb76e72 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,6 @@ # set to local images because too long execution default: - image: registry.gitlab.com/vstconsulting/images:ubuntu-v2 + image: registry.gitlab.com/vstconsulting/images:ubuntu-v4 variables: GET_SOURCES_ATTEMPTS: 3 @@ -34,7 +34,7 @@ stages: ########################################### .branch_tests_template: &branch_tests stage: test - image: registry.gitlab.com/vstconsulting/images:ubuntu-v2 + image: registry.gitlab.com/vstconsulting/images:ubuntu-v4 coverage: '/\d+\%\s*$/' variables: TOX_ENVS: "" @@ -60,7 +60,7 @@ stages: .js_tests_template: &branch_js_tests <<: *branch_tests - image: registry.gitlab.com/vstconsulting/images:node18-tests + image: registry.gitlab.com/vstconsulting/images:node20-tests before_script: - yarn install --pure-lockfile --mutex network script: @@ -90,7 +90,7 @@ functional_test: parallel: matrix: - TOX_ENVS: - - py38-install + - py310-install - TOX_ENVS: - py311-coverage @@ -98,7 +98,7 @@ functional_test: ########################################### release: stage: release - image: registry.gitlab.com/vstconsulting/images:ubuntu-v2 + image: registry.gitlab.com/vstconsulting/images:ubuntu-v4 rules: - if: '$CI_COMMIT_BRANCH == "master" && $GIT_ACCESS_USER && $GIT_ACCESS_PASSWORD' when: on_success @@ -162,7 +162,7 @@ publish_docker: publish_release: stage: publish - image: registry.gitlab.com/vstconsulting/images:ubuntu-v2 + image: registry.gitlab.com/vstconsulting/images:ubuntu-v4 allow_failure: true needs: ["release_pypi"] rules: diff --git a/.pylintrc b/.pylintrc index a51b6559..744f8a4d 100644 --- a/.pylintrc +++ b/.pylintrc @@ -34,15 +34,6 @@ unsafe-load-any-extension=no # run arbitrary code extension-pkg-whitelist= -# Allow optimization of some AST trees. This will activate a peephole AST -# optimizer, which will apply various small optimizations. For instance, it can -# be used to obtain the result of joining multiple strings with the addition -# operator. Joining a lot of strings can lead to a maximum recursion error in -# Pylint and this flag can prevent that. It has one side effect, the resulting -# AST will be different than the one from reality. This option is deprecated -# and it will be removed in Pylint 2.0. -optimize-ast=no - [MESSAGES CONTROL] @@ -65,7 +56,7 @@ confidence= # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" -disable=logging-fstring-interpolation,unexpected-keyword-arg,no-name-in-module,useless-super-delegation,len-as-condition,super-init-not-called,keyword-arg-before-vararg,no-else-return,no-self-argument,inconsistent-return-statements,unsubscriptable-object,too-many-branches,deprecated-lambda,old-style-class,no-init,expression-not-assigned,broad-except,logging-format-interpolation,model-no-explicit-unicode,too-many-ancestors,bad-continuation,bad-whitespace,redefined-builtin,missing-docstring,redefined-variable-type,no-self-use,line-too-long,suppressed-message,cmp-method,no-absolute-import,xrange-builtin,using-cmp-argument,basestring-builtin,backtick,unpacking-in-except,old-raise-syntax,getslice-method,long-builtin,print-statement,reduce-builtin,filter-builtin-not-iterating,import-star-module-level,unichr-builtin,dict-iter-method,range-builtin-not-iterating,file-builtin,old-division,standarderror-builtin,coerce-builtin,setslice-method,old-ne-operator,long-suffix,execfile-builtin,oct-method,metaclass-assignment,intern-builtin,apply-builtin,dict-view-method,raw_input-builtin,raising-string,coerce-method,unicode-builtin,next-method-called,hex-method,nonzero-method,round-builtin,cmp-builtin,reload-builtin,buffer-builtin,useless-suppression,zip-builtin-not-iterating,indexing-exception,map-builtin-not-iterating,delslice-method,old-octal-literal,input-builtin,parameter-unpacking,model-has-unicode,bare-except,too-few-public-methods,fixme,dangerous-default-value,attribute-defined-outside-init,pointless-string-statement,too-many-instance-attributes,arguments-differ,binary-op-exception,bad-classmethod-argument,locally-disabled,file-ignored,multiple-statements,superfluous-parens,bad-mcs-classmethod-argument,useless-object-inheritance,duplicate-code,unused-argument +disable=logging-fstring-interpolation,unexpected-keyword-arg,no-name-in-module,useless-super-delegation,len-as-condition,super-init-not-called,keyword-arg-before-vararg,no-else-return,no-self-argument,inconsistent-return-statements,unsubscriptable-object,too-many-branches,,expression-not-assigned,broad-except,logging-format-interpolation,too-many-ancestors,redefined-builtin,missing-docstring,line-too-long,suppressed-message,useless-suppression,bare-except,too-few-public-methods,fixme,dangerous-default-value,attribute-defined-outside-init,pointless-string-statement,too-many-instance-attributes,arguments-differ,binary-op-exception,bad-classmethod-argument,locally-disabled,file-ignored,multiple-statements,superfluous-parens,bad-mcs-classmethod-argument,useless-object-inheritance,duplicate-code,unused-argument,cyclic-import [REPORTS] @@ -75,12 +66,6 @@ disable=logging-fstring-interpolation,unexpected-keyword-arg,no-name-in-module,u # mypackage.mymodule.MyReporterClass. output-format=colorized -# Put messages in a separate file for each module / package specified on the -# command line instead of printing them on stdout. Reports (if any) will be -# written in a file name "pylint_global.[txt|html]". This option is deprecated -# and it will be removed in Pylint 2.0. -files-output=no - # Tells whether to display a full report or only the messages reports=yes @@ -118,63 +103,33 @@ property-classes=abc.abstractproperty # Regular expression matching correct function names function-rgx=[a-z_][a-zA-Z0-9_]{1,30}$ -# Naming hint for function names -function-name-hint=[a-z_][a-zA-Z0-9_]{1,30}$ - # Regular expression matching correct method names method-rgx=[a-z_][a-zA-Z0-9_]{1,30}$ -# Naming hint for method names -method-name-hint=[a-z_][a-zA-Z0-9_]{1,30}$ - # Regular expression matching correct constant names const-rgx=(([a-zA-Z_][a-zA-Z0-9_]*)|(__.*__))$ -# Naming hint for constant names -const-name-hint=(([a-zA-Z_][a-zA-Z0-9_]*)|(__.*__))$ - # Regular expression matching correct variable names variable-rgx=[a-z_][a-zA-Z0-9_]{1,30}$ -# Naming hint for variable names -variable-name-hint=[a-z_][a-zA-Z0-9_]{1,30}$ - # Regular expression matching correct inline iteration names inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ -# Naming hint for inline iteration names -inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ - # Regular expression matching correct attribute names attr-rgx=[a-z_][a-z0-9_]{1,30}$ -# Naming hint for attribute names -attr-name-hint=[a-z_][a-z0-9_]{1,30}$ - # Regular expression matching correct class names class-rgx=[_]{0,2}[A-Z_][a-zA-Z0-9]+$ -# Naming hint for class names -class-name-hint=[A-Z_][a-zA-Z0-9]+$ - # Regular expression matching correct module names module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ -# Naming hint for module names -module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - # Regular expression matching correct class attribute names class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ -# Naming hint for class attribute names -class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - # Regular expression matching correct argument names argument-rgx=[a-z_][a-z0-9_]{1,30}$ -# Naming hint for argument names -argument-name-hint=[a-z_][a-z0-9_]{1,30}$ - # Regular expression which should only match function or class names that do # not require a docstring. no-docstring-rgx=^_ @@ -202,12 +157,6 @@ ignore-long-lines=^\s*(# )??$ # else. single-line-if-stmt=no -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma,dict-separator - # Maximum number of lines in a module max-module-lines=1000 @@ -404,4 +353,4 @@ analyse-fallback-blocks=no # Exceptions that will emit a warning when being caught. Defaults to # "Exception" -overgeneral-exceptions=Exception +overgeneral-exceptions=builtins.Exception diff --git a/Dockerfile b/Dockerfile index d44f31bc..26df1e42 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -FROM registry.gitlab.com/vstconsulting/images:ubuntu-v2 AS build +FROM registry.gitlab.com/vstconsulting/images:ubuntu-v4 AS build RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache WORKDIR /usr/local/polemarch @@ -14,7 +14,7 @@ RUN --mount=type=cache,target=/var/cache/apt \ apt -y install --no-install-recommends \ default-libmysqlclient-dev \ libpcre3-dev \ - python3.8-dev \ + python3.10-dev \ libldap2-dev \ libsasl2-dev \ libffi-dev \ @@ -33,7 +33,7 @@ RUN --mount=type=cache,target=/root/.cache/pip \ ############################################################### -FROM registry.gitlab.com/vstconsulting/images:python as production +FROM registry.gitlab.com/vstconsulting/images:python3.10 AS production RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache @@ -59,7 +59,7 @@ RUN --mount=type=cache,target=/var/cache/apt \ libmysqlclient21 \ libpq5 \ libpcre3 \ - libldap-2.4-2 \ + libldap-2.5-0 \ libsasl2-2 \ libffi7 \ libssl1.1 \ @@ -70,11 +70,10 @@ RUN --mount=type=cache,target=/var/cache/apt \ echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/hashicorp.list && \ apt update && apt install --no-install-recommends terraform -y; \ fi && \ - python3.8 -m pip install cryptography paramiko 'pip~=23.0' && \ - ln -s /usr/bin/python3.8 /usr/bin/python && \ + python3.10 -m pip install cryptography paramiko 'pip~=23.0' && \ mkdir -p /projects /hooks /run/openldap /etc/polemarch/hooks /var/lib/polemarch && \ - python3.8 -m pip install --no-index --find-links /polemarch_env/wheels $PACKAGE_NAME[mysql,postgresql,ansible] && \ - find /usr/lib/python3.8 -regex '.*\(*.pyc\|__pycache__\).*' -delete && \ + python3.10 -m pip install --no-index --find-links /polemarch_env/wheels $PACKAGE_NAME[mysql,postgresql,ansible] && \ + find /usr/lib/python3.10 -regex '.*\(*.pyc\|__pycache__\).*' -delete && \ apt remove gpg wget lsb-release -y && \ apt autoremove -y && \ rm -rf /tmp/* \ @@ -83,7 +82,7 @@ RUN --mount=type=cache,target=/var/cache/apt \ RUN useradd -m -s /bin/bash -U -u 1000 polemarch && \ chown -R polemarch /projects /hooks /run/openldap /etc/polemarch /var/lib/polemarch && \ - ln -s /usr/bin/python3.8 /usr/local/bin/python + ln -s /usr/bin/python3.10 /usr/local/bin/python USER polemarch diff --git a/doc/api_schema.yaml b/doc/api_schema.yaml index a0bbbbf1..180b1b65 100644 --- a/doc/api_schema.yaml +++ b/doc/api_schema.yaml @@ -63,18 +63,16 @@ info: static_path: /static/ enable_gravatar: true time_zone: UTC - logout_url: /account/logout/ - login_url: /account/login/ x-subscriptions-prefix: polemarch.update x-versions: application: 3.0.3 library: 3.0.3 - vstutils: 5.6.4 - django: 4.2.3 - djangorestframework: 3.14.0 - drf_yasg: 1.21.7 - fastapi: 0.100.0 - celery: 5.3.1 + vstutils: 5.11.13 + django: 5.1.3 + djangorestframework: 3.15.2 + drf_yasg: 1.21.8 + fastapi: 0.115.5 + celery: 5.4.0 ansible: 2.9.27 version: v4 host: localhost:8080 @@ -88,10 +86,18 @@ produces: - application/json - application/msgpack securityDefinitions: + oauth2: + type: oauth2 + flow: password + tokenUrl: http://testserver/api/oauth2/token/ + scopes: {} + x-clientId: simple-client-id + session: + type: apiKey + name: sessionid + in: cookie basic: type: basic -security: -- basic: [] paths: /community_template/: get: @@ -109,11 +115,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: name in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Name - name: type in: query description: '' @@ -134,11 +142,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: name__not in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Exclude name - name: ordering in: query description: Which field to use when ordering the results. @@ -193,6 +203,10 @@ paths: $ref: '#/definitions/ProjectCommunityTemplate' tags: - community_template + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.ProjectCommunityTemplate x-list: true @@ -209,6 +223,10 @@ paths: $ref: '#/definitions/OneProjectCommunityTemplate' tags: - community_template + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.ProjectCommunityTemplate x-list: false @@ -235,6 +253,10 @@ paths: $ref: '#/definitions/ProjectCommunityTemplateUseIt' tags: - community_template + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Use it x-multiaction: false parameters: @@ -263,11 +285,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: name in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Name - name: variables in: query description: List of variables to filter. Comma separated "key:value" list. @@ -284,11 +308,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: name__not in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Exclude name - name: ordering in: query description: Which field to use when ordering the results. @@ -345,6 +371,10 @@ paths: $ref: '#/definitions/Group' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Group x-deep-nested-view: groups @@ -365,6 +395,10 @@ paths: $ref: '#/definitions/CreateGroup' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] parameters: [] /group/{id}/: get: @@ -378,6 +412,10 @@ paths: $ref: '#/definitions/OneGroup' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Group x-deep-nested-view: groups @@ -398,6 +436,10 @@ paths: $ref: '#/definitions/OneGroup' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: group_edit description: Update one or more fields on an existing group. @@ -414,6 +456,10 @@ paths: $ref: '#/definitions/OneGroup' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: group_remove description: Remove an existing group. @@ -423,6 +469,10 @@ paths: description: NO CONTENT tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -446,6 +496,10 @@ paths: $ref: '#/definitions/Copy' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -472,11 +526,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: name in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Name - name: variables in: query description: List of variables to filter. Comma separated "key:value" list. @@ -493,11 +549,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: name__not in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Exclude name - name: ordering in: query description: Which field to use when ordering the results. @@ -554,6 +612,10 @@ paths: $ref: '#/definitions/Group' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Group x-list: true @@ -573,6 +635,10 @@ paths: $ref: '#/definitions/CreateGroup' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: true parameters: - name: id @@ -592,6 +658,10 @@ paths: $ref: '#/definitions/OneGroup' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Group x-list: false @@ -611,6 +681,10 @@ paths: $ref: '#/definitions/OneGroup' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: group_groups_edit description: Update one or more fields on an existing group. @@ -627,6 +701,10 @@ paths: $ref: '#/definitions/OneGroup' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: group_groups_remove description: Remove an existing group. @@ -636,6 +714,10 @@ paths: description: NO CONTENT tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -664,6 +746,10 @@ paths: $ref: '#/definitions/Copy' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -691,11 +777,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: name in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Name - name: type in: query description: Instance type. @@ -721,11 +809,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: name__not in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Exclude name - name: ordering in: query description: Which field to use when ordering the results. @@ -782,6 +872,10 @@ paths: $ref: '#/definitions/Host' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Host x-list: true @@ -801,6 +895,10 @@ paths: $ref: '#/definitions/OneHost' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: true parameters: - name: id @@ -825,6 +923,10 @@ paths: $ref: '#/definitions/OneHost' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Host x-list: false @@ -844,6 +946,10 @@ paths: $ref: '#/definitions/OneHost' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: group_groups_hosts_edit description: Update one or more fields on an existing host. @@ -860,6 +966,10 @@ paths: $ref: '#/definitions/OneHost' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: group_groups_hosts_remove description: Remove an existing host. @@ -869,6 +979,10 @@ paths: description: NO CONTENT tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -902,6 +1016,10 @@ paths: $ref: '#/definitions/Copy' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -935,6 +1053,10 @@ paths: $ref: '#/definitions/SetOwner' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Set owner x-multiaction: false parameters: @@ -969,6 +1091,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: key in: query description: A key name string value (or comma separated list) of instance. @@ -990,6 +1113,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: ordering in: query description: Which field to use when ordering the results. @@ -1046,6 +1170,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: true @@ -1065,6 +1193,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: false parameters: - name: id @@ -1094,6 +1226,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: false @@ -1113,6 +1249,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: group_groups_hosts_variables_edit description: Update one or more fields on an existing variable. @@ -1129,6 +1269,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: group_groups_hosts_variables_remove description: Remove an existing variable. @@ -1138,6 +1282,10 @@ paths: description: NO CONTENT tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -1176,6 +1324,10 @@ paths: $ref: '#/definitions/SetOwner' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Set owner x-multiaction: false parameters: @@ -1205,6 +1357,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: key in: query description: A key name string value (or comma separated list) of instance. @@ -1226,6 +1379,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: ordering in: query description: Which field to use when ordering the results. @@ -1282,6 +1436,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: true @@ -1301,6 +1459,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: false parameters: - name: id @@ -1325,6 +1487,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: false @@ -1344,6 +1510,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: group_groups_variables_edit description: Update one or more fields on an existing variable. @@ -1360,6 +1530,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: group_groups_variables_remove description: Remove an existing variable. @@ -1369,6 +1543,10 @@ paths: description: NO CONTENT tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -1419,11 +1597,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: name in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Name - name: type in: query description: Instance type. @@ -1449,11 +1629,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: name__not in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Exclude name - name: ordering in: query description: Which field to use when ordering the results. @@ -1510,6 +1692,10 @@ paths: $ref: '#/definitions/Host' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Host x-list: true @@ -1547,6 +1733,10 @@ paths: $ref: '#/definitions/OneHost' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: true parameters: - name: id @@ -1584,6 +1774,10 @@ paths: $ref: '#/definitions/OneHost' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Host x-list: false @@ -1621,6 +1815,10 @@ paths: $ref: '#/definitions/OneHost' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: group_hosts_edit summary: Manage inventory hosts. @@ -1655,6 +1853,10 @@ paths: $ref: '#/definitions/OneHost' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: group_hosts_remove summary: Manage inventory hosts. @@ -1682,6 +1884,10 @@ paths: description: NO CONTENT tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -1728,6 +1934,10 @@ paths: $ref: '#/definitions/Copy' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -1756,6 +1966,10 @@ paths: $ref: '#/definitions/SetOwner' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Set owner x-multiaction: false parameters: @@ -1803,6 +2017,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: key in: query description: A key name string value (or comma separated list) of instance. @@ -1824,6 +2039,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: ordering in: query description: Which field to use when ordering the results. @@ -1880,6 +2096,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: true @@ -1917,6 +2137,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: false parameters: - name: id @@ -1959,6 +2183,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: false @@ -1996,6 +2224,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: group_hosts_variables_edit summary: Manage inventory hosts. @@ -2030,6 +2262,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: group_hosts_variables_remove summary: Manage inventory hosts. @@ -2057,6 +2293,10 @@ paths: description: NO CONTENT tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -2090,6 +2330,10 @@ paths: $ref: '#/definitions/SetOwner' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Set owner x-multiaction: false parameters: @@ -2132,6 +2376,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: key in: query description: A key name string value (or comma separated list) of instance. @@ -2153,6 +2398,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: ordering in: query description: Which field to use when ordering the results. @@ -2209,6 +2455,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: true @@ -2246,6 +2496,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: false parameters: - name: id @@ -2283,6 +2537,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: false @@ -2320,6 +2578,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: group_variables_edit summary: Inventory hosts variables. @@ -2354,6 +2616,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: group_variables_remove summary: Inventory hosts variables. @@ -2381,6 +2647,10 @@ paths: description: NO CONTENT tags: - group + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -2408,6 +2678,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: status in: query description: Status of execution. @@ -2452,6 +2723,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: ordering in: query description: Which field to use when ordering the results. @@ -2524,6 +2796,10 @@ paths: $ref: '#/definitions/History' tags: - history + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.History x-list: true @@ -2540,6 +2816,10 @@ paths: $ref: '#/definitions/OneHistory' tags: - history + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.History x-list: false @@ -2552,6 +2832,10 @@ paths: description: NO CONTENT tags: - history + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -2575,6 +2859,10 @@ paths: $ref: '#/definitions/Response' tags: - history + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Cancel x-multiaction: false parameters: @@ -2593,6 +2881,10 @@ paths: description: NO CONTENT tags: - history + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Clear x-multiaction: false parameters: @@ -2613,6 +2905,10 @@ paths: $ref: '#/definitions/FactsResponse' tags: - history + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.History x-list: false @@ -2638,11 +2934,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: name in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Name - name: when in: query description: '' @@ -2678,11 +2976,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: name__not in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Exclude name - name: ordering in: query description: Which field to use when ordering the results. @@ -2745,6 +3045,10 @@ paths: $ref: '#/definitions/Hook' tags: - hook + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Hook x-list: true @@ -2764,6 +3068,10 @@ paths: $ref: '#/definitions/OneHook' tags: - hook + security: + - oauth2: [] + - session: [] + - basic: [] parameters: [] /hook/{id}/: get: @@ -2777,6 +3085,10 @@ paths: $ref: '#/definitions/OneHook' tags: - hook + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Hook x-list: false @@ -2796,6 +3108,10 @@ paths: $ref: '#/definitions/OneHook' tags: - hook + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: hook_edit description: Update one or more fields on an existing hook. @@ -2812,6 +3128,10 @@ paths: $ref: '#/definitions/OneHook' tags: - hook + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: hook_remove description: Remove an existing hook. @@ -2821,6 +3141,10 @@ paths: description: NO CONTENT tags: - hook + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -2843,11 +3167,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: name in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Name - name: type in: query description: Instance type. @@ -2873,11 +3199,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: name__not in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Exclude name - name: ordering in: query description: Which field to use when ordering the results. @@ -2934,6 +3262,10 @@ paths: $ref: '#/definitions/Host' tags: - host + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Host x-list: true @@ -2953,6 +3285,10 @@ paths: $ref: '#/definitions/OneHost' tags: - host + security: + - oauth2: [] + - session: [] + - basic: [] parameters: [] /host/{id}/: get: @@ -2966,6 +3302,10 @@ paths: $ref: '#/definitions/OneHost' tags: - host + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Host x-list: false @@ -2985,6 +3325,10 @@ paths: $ref: '#/definitions/OneHost' tags: - host + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: host_edit description: Update one or more fields on an existing host. @@ -3001,6 +3345,10 @@ paths: $ref: '#/definitions/OneHost' tags: - host + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: host_remove description: Remove an existing host. @@ -3010,6 +3358,10 @@ paths: description: NO CONTENT tags: - host + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -3033,6 +3385,10 @@ paths: $ref: '#/definitions/Copy' tags: - host + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -3056,6 +3412,10 @@ paths: $ref: '#/definitions/SetOwner' tags: - host + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Set owner x-multiaction: false parameters: @@ -3098,6 +3458,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: key in: query description: A key name string value (or comma separated list) of instance. @@ -3119,6 +3480,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: ordering in: query description: Which field to use when ordering the results. @@ -3175,6 +3537,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - host + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: true @@ -3212,6 +3578,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - host + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: false parameters: - name: id @@ -3249,6 +3619,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - host + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: false @@ -3286,6 +3660,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - host + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: host_variables_edit summary: Inventory hosts variables. @@ -3320,6 +3698,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - host + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: host_variables_remove summary: Inventory hosts variables. @@ -3347,6 +3729,10 @@ paths: description: NO CONTENT tags: - host + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -3374,11 +3760,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: name in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Name - name: variables in: query description: List of variables to filter. Comma separated "key:value" list. @@ -3405,11 +3793,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: name__not in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Exclude name - name: ordering in: query description: Which field to use when ordering the results. @@ -3466,6 +3856,10 @@ paths: $ref: '#/definitions/Inventory' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Inventory x-list: true @@ -3485,6 +3879,10 @@ paths: $ref: '#/definitions/CreateInventory' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] parameters: [] /inventory/import_inventory/: post: @@ -3503,6 +3901,10 @@ paths: $ref: '#/definitions/ImportInventory' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Import inventory x-multiaction: false parameters: [] @@ -3518,6 +3920,10 @@ paths: $ref: '#/definitions/OneInventory' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Inventory x-list: false @@ -3537,6 +3943,10 @@ paths: $ref: '#/definitions/OneInventory' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: inventory_edit description: Update one or more fields on an existing inventory. @@ -3553,6 +3963,10 @@ paths: $ref: '#/definitions/OneInventory' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: inventory_remove description: Remove an existing inventory. @@ -3562,6 +3976,10 @@ paths: description: NO CONTENT tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -3588,11 +4006,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: name in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Name - name: variables in: query description: List of variables to filter. Comma separated "key:value" list. @@ -3609,11 +4029,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: name__not in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Exclude name - name: ordering in: query description: Which field to use when ordering the results. @@ -3670,6 +4092,10 @@ paths: $ref: '#/definitions/Group' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Group x-deep-nested-view: groups @@ -3692,6 +4118,10 @@ paths: $ref: '#/definitions/OneGroup' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Group x-deep-nested-view: groups @@ -3740,11 +4170,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: name in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Name - name: type in: query description: Instance type. @@ -3770,11 +4202,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: name__not in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Exclude name - name: ordering in: query description: Which field to use when ordering the results. @@ -3831,6 +4265,10 @@ paths: $ref: '#/definitions/Host' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Host x-list: true @@ -3870,6 +4308,10 @@ paths: $ref: '#/definitions/OneHost' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Host x-list: false @@ -3900,6 +4342,10 @@ paths: $ref: '#/definitions/Copy' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -3926,11 +4372,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: name in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Name - name: variables in: query description: List of variables to filter. Comma separated "key:value" list. @@ -3947,11 +4395,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: name__not in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Exclude name - name: ordering in: query description: Which field to use when ordering the results. @@ -4008,6 +4458,10 @@ paths: $ref: '#/definitions/Group' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Group x-deep-nested-view: groups @@ -4028,6 +4482,10 @@ paths: $ref: '#/definitions/CreateGroup' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: true parameters: - name: id @@ -4047,6 +4505,10 @@ paths: $ref: '#/definitions/OneGroup' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Group x-deep-nested-view: groups @@ -4067,6 +4529,10 @@ paths: $ref: '#/definitions/OneGroup' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: inventory_group_edit description: Update one or more fields on an existing group. @@ -4083,6 +4549,10 @@ paths: $ref: '#/definitions/OneGroup' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: inventory_group_remove description: Remove an existing group. @@ -4092,6 +4562,10 @@ paths: description: NO CONTENT tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -4119,6 +4593,10 @@ paths: $ref: '#/definitions/Copy' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -4149,11 +4627,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: name in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Name - name: variables in: query description: List of variables to filter. Comma separated "key:value" list. @@ -4170,11 +4650,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: name__not in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Exclude name - name: ordering in: query description: Which field to use when ordering the results. @@ -4231,6 +4713,10 @@ paths: $ref: '#/definitions/Group' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Group x-list: true @@ -4250,6 +4736,10 @@ paths: $ref: '#/definitions/CreateGroup' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: true parameters: - name: id @@ -4273,6 +4763,10 @@ paths: $ref: '#/definitions/OneGroup' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Group x-list: false @@ -4292,6 +4786,10 @@ paths: $ref: '#/definitions/OneGroup' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: inventory_group_groups_edit description: Update one or more fields on an existing group. @@ -4308,6 +4806,10 @@ paths: $ref: '#/definitions/OneGroup' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: inventory_group_groups_remove description: Remove an existing group. @@ -4317,6 +4819,10 @@ paths: description: NO CONTENT tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -4349,6 +4855,10 @@ paths: $ref: '#/definitions/Copy' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -4380,11 +4890,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: name in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Name - name: type in: query description: Instance type. @@ -4410,11 +4922,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: name__not in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Exclude name - name: ordering in: query description: Which field to use when ordering the results. @@ -4471,6 +4985,10 @@ paths: $ref: '#/definitions/Host' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Host x-list: true @@ -4490,6 +5008,10 @@ paths: $ref: '#/definitions/OneHost' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: true parameters: - name: id @@ -4518,6 +5040,10 @@ paths: $ref: '#/definitions/OneHost' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Host x-list: false @@ -4537,6 +5063,10 @@ paths: $ref: '#/definitions/OneHost' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: inventory_group_groups_hosts_edit description: Update one or more fields on an existing host. @@ -4553,6 +5083,10 @@ paths: $ref: '#/definitions/OneHost' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: inventory_group_groups_hosts_remove description: Remove an existing host. @@ -4562,6 +5096,10 @@ paths: description: NO CONTENT tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -4599,6 +5137,10 @@ paths: $ref: '#/definitions/Copy' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -4636,6 +5178,10 @@ paths: $ref: '#/definitions/SetOwner' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Set owner x-multiaction: false parameters: @@ -4674,6 +5220,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: key in: query description: A key name string value (or comma separated list) of instance. @@ -4695,6 +5242,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: ordering in: query description: Which field to use when ordering the results. @@ -4751,6 +5299,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: true @@ -4770,6 +5322,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: false parameters: - name: id @@ -4803,6 +5359,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: false @@ -4822,6 +5382,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: inventory_group_groups_hosts_variables_edit description: Update one or more fields on an existing variable. @@ -4838,6 +5402,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: inventory_group_groups_hosts_variables_remove description: Remove an existing variable. @@ -4847,6 +5415,10 @@ paths: description: NO CONTENT tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -4889,6 +5461,10 @@ paths: $ref: '#/definitions/SetOwner' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Set owner x-multiaction: false parameters: @@ -4922,6 +5498,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: key in: query description: A key name string value (or comma separated list) of instance. @@ -4943,6 +5520,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: ordering in: query description: Which field to use when ordering the results. @@ -4999,6 +5577,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: true @@ -5018,6 +5600,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: false parameters: - name: id @@ -5046,6 +5632,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: false @@ -5065,6 +5655,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: inventory_group_groups_variables_edit description: Update one or more fields on an existing variable. @@ -5081,6 +5675,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: inventory_group_groups_variables_remove description: Remove an existing variable. @@ -5090,6 +5688,10 @@ paths: description: NO CONTENT tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -5126,11 +5728,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: name in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Name - name: type in: query description: Instance type. @@ -5156,11 +5760,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: name__not in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Exclude name - name: ordering in: query description: Which field to use when ordering the results. @@ -5217,6 +5823,10 @@ paths: $ref: '#/definitions/Host' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Host x-list: true @@ -5236,6 +5846,10 @@ paths: $ref: '#/definitions/OneHost' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: true parameters: - name: id @@ -5259,6 +5873,10 @@ paths: $ref: '#/definitions/OneHost' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Host x-list: false @@ -5278,6 +5896,10 @@ paths: $ref: '#/definitions/OneHost' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: inventory_group_hosts_edit description: Update one or more fields on an existing host. @@ -5294,6 +5916,10 @@ paths: $ref: '#/definitions/OneHost' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: inventory_group_hosts_remove description: Remove an existing host. @@ -5303,6 +5929,10 @@ paths: description: NO CONTENT tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -5335,6 +5965,10 @@ paths: $ref: '#/definitions/Copy' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -5367,6 +6001,10 @@ paths: $ref: '#/definitions/SetOwner' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Set owner x-multiaction: false parameters: @@ -5400,6 +6038,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: key in: query description: A key name string value (or comma separated list) of instance. @@ -5421,6 +6060,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: ordering in: query description: Which field to use when ordering the results. @@ -5477,6 +6117,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: true @@ -5496,6 +6140,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: false parameters: - name: id @@ -5524,6 +6172,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: false @@ -5543,6 +6195,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: inventory_group_hosts_variables_edit description: Update one or more fields on an existing variable. @@ -5559,6 +6215,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: inventory_group_hosts_variables_remove description: Remove an existing variable. @@ -5568,6 +6228,10 @@ paths: description: NO CONTENT tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -5605,6 +6269,10 @@ paths: $ref: '#/definitions/SetOwner' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Set owner x-multiaction: false parameters: @@ -5633,6 +6301,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: key in: query description: A key name string value (or comma separated list) of instance. @@ -5654,6 +6323,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: ordering in: query description: Which field to use when ordering the results. @@ -5710,6 +6380,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: true @@ -5729,6 +6403,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: false parameters: - name: id @@ -5752,6 +6430,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: false @@ -5771,6 +6453,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: inventory_group_variables_edit description: Update one or more fields on an existing variable. @@ -5787,6 +6473,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: inventory_group_variables_remove description: Remove an existing variable. @@ -5796,6 +6486,10 @@ paths: description: NO CONTENT tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -5845,11 +6539,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: name in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Name - name: type in: query description: Instance type. @@ -5875,11 +6571,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: name__not in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Exclude name - name: ordering in: query description: Which field to use when ordering the results. @@ -5936,6 +6634,10 @@ paths: $ref: '#/definitions/Host' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Host x-list: true @@ -5973,6 +6675,10 @@ paths: $ref: '#/definitions/OneHost' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: true parameters: - name: id @@ -6010,6 +6716,10 @@ paths: $ref: '#/definitions/OneHost' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Host x-list: false @@ -6047,6 +6757,10 @@ paths: $ref: '#/definitions/OneHost' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: inventory_hosts_edit summary: Manage inventory hosts. @@ -6081,6 +6795,10 @@ paths: $ref: '#/definitions/OneHost' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: inventory_hosts_remove summary: Manage inventory hosts. @@ -6108,6 +6826,10 @@ paths: description: NO CONTENT tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -6154,6 +6876,10 @@ paths: $ref: '#/definitions/Copy' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -6182,6 +6908,10 @@ paths: $ref: '#/definitions/SetOwner' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Set owner x-multiaction: false parameters: @@ -6229,6 +6959,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: key in: query description: A key name string value (or comma separated list) of instance. @@ -6250,6 +6981,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: ordering in: query description: Which field to use when ordering the results. @@ -6306,6 +7038,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: true @@ -6343,6 +7079,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: false parameters: - name: id @@ -6385,6 +7125,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: false @@ -6422,6 +7166,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: inventory_hosts_variables_edit summary: Manage inventory hosts. @@ -6456,6 +7204,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: inventory_hosts_variables_remove summary: Manage inventory hosts. @@ -6483,6 +7235,10 @@ paths: description: NO CONTENT tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -6516,6 +7272,10 @@ paths: $ref: '#/definitions/SetOwner' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Set owner x-multiaction: false parameters: @@ -6536,6 +7296,10 @@ paths: $ref: '#/definitions/InventoryState' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Inventory x-list: false @@ -6555,6 +7319,10 @@ paths: $ref: '#/definitions/InventoryState' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-multiaction: false parameters: - name: id @@ -6596,6 +7364,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: key in: query description: A key name string value (or comma separated list) of instance. @@ -6617,6 +7386,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: ordering in: query description: Which field to use when ordering the results. @@ -6673,6 +7443,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: true @@ -6710,6 +7484,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: false parameters: - name: id @@ -6747,6 +7525,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: false @@ -6784,6 +7566,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: inventory_variables_edit summary: Inventory hosts variables. @@ -6818,6 +7604,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: inventory_variables_remove summary: Inventory hosts variables. @@ -6845,6 +7635,10 @@ paths: description: NO CONTENT tags: - inventory + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -6872,11 +7666,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: name in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Name - name: status in: query description: Project sync status. @@ -6908,11 +7704,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: name__not in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Exclude name - name: ordering in: query description: Which field to use when ordering the results. @@ -6969,6 +7767,10 @@ paths: $ref: '#/definitions/Project' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Project x-list: true @@ -6988,6 +7790,10 @@ paths: $ref: '#/definitions/CreateProject' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] parameters: [] /project/{id}/: get: @@ -7001,6 +7807,10 @@ paths: $ref: '#/definitions/OneProject' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Project x-list: false @@ -7020,6 +7830,10 @@ paths: $ref: '#/definitions/OneProject' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: project_edit description: Update one or more fields on an existing project. @@ -7036,6 +7850,10 @@ paths: $ref: '#/definitions/OneProject' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: project_remove description: Remove an existing project. @@ -7045,6 +7863,10 @@ paths: description: NO CONTENT tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -7067,6 +7889,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: template in: query description: Search by template's primary key or name @@ -7083,6 +7906,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: ordering in: query description: Which field to use when ordering the results. @@ -7139,6 +7963,10 @@ paths: $ref: '#/definitions/ProjectTemplateOption' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.ExecutionTemplateOption x-list: true @@ -7161,6 +7989,10 @@ paths: $ref: '#/definitions/OneProjectTemplateOption' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.ExecutionTemplateOption x-list: false @@ -7205,6 +8037,7 @@ paths: description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Exclude name - name: ordering in: query description: Which field to use when ordering the results. @@ -7259,6 +8092,10 @@ paths: $ref: '#/definitions/AnsibleModule' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Module x-list: true @@ -7286,6 +8123,10 @@ paths: $ref: '#/definitions/OneAnsibleModule' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Module x-list: false @@ -7383,6 +8224,10 @@ paths: $ref: '#/definitions/AnsiblePlaybook' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.AnsiblePlaybook x-list: true @@ -7410,6 +8255,10 @@ paths: $ref: '#/definitions/OneAnsiblePlaybook' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.AnsiblePlaybook x-list: false @@ -7442,6 +8291,10 @@ paths: $ref: '#/definitions/Project' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -7465,6 +8318,10 @@ paths: $ref: '#/definitions/ProjectExecuteResponse' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Execute ansible module x-multiaction: false parameters: @@ -7490,6 +8347,10 @@ paths: $ref: '#/definitions/ProjectExecuteResponse' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Execute ansible playbook x-multiaction: false parameters: @@ -7532,11 +8393,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: name in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Name - name: plugin in: query description: '' @@ -7557,11 +8420,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: name__not in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Exclude name - name: ordering in: query description: Which field to use when ordering the results. @@ -7618,6 +8483,10 @@ paths: $ref: '#/definitions/ExecutionTemplate' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.ExecutionTemplate x-list: true @@ -7655,6 +8524,10 @@ paths: $ref: '#/definitions/CreateExecutionTemplate' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: false parameters: - name: id @@ -7692,6 +8565,10 @@ paths: $ref: '#/definitions/OneExecutionTemplate' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.ExecutionTemplate x-list: false @@ -7729,6 +8606,10 @@ paths: $ref: '#/definitions/OneExecutionTemplate' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: project_execution_templates_edit summary: Manage execution templates of project. @@ -7763,6 +8644,10 @@ paths: $ref: '#/definitions/OneExecutionTemplate' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: project_execution_templates_remove summary: Manage execution templates of project. @@ -7790,6 +8675,10 @@ paths: description: NO CONTENT tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -7819,6 +8708,10 @@ paths: $ref: '#/definitions/ExecuteTemplateResponse' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Execute x-multiaction: false parameters: @@ -7867,6 +8760,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: status in: query description: Status of execution. @@ -7911,6 +8805,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: ordering in: query description: Which field to use when ordering the results. @@ -7983,6 +8878,10 @@ paths: $ref: '#/definitions/History' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.History x-list: true @@ -8028,6 +8927,10 @@ paths: $ref: '#/definitions/OneHistory' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.History x-list: false @@ -8058,6 +8961,10 @@ paths: description: NO CONTENT tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -8092,6 +8999,10 @@ paths: $ref: '#/definitions/Response' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Cancel x-multiaction: false parameters: @@ -8121,6 +9032,10 @@ paths: description: NO CONTENT tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Clear x-multiaction: false parameters: @@ -8152,6 +9067,10 @@ paths: $ref: '#/definitions/FactsResponse' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.History x-list: false @@ -8206,11 +9125,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: name in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Name - name: id__not in: query description: A unique integer value (or comma separated list) identifying @@ -8222,11 +9143,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: name__not in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Exclude name - name: ordering in: query description: Which field to use when ordering the results. @@ -8281,6 +9204,10 @@ paths: $ref: '#/definitions/ExecutionTemplateOption' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.ExecutionTemplateOption x-list: true @@ -8318,6 +9245,10 @@ paths: $ref: '#/definitions/OneExecutionTemplateOption' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: false parameters: - name: id @@ -8361,6 +9292,10 @@ paths: $ref: '#/definitions/OneExecutionTemplateOption' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.ExecutionTemplateOption x-list: false @@ -8398,6 +9333,10 @@ paths: $ref: '#/definitions/OneExecutionTemplateOption' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: project_execution_templates_options_edit summary: Manage execution templates of project. @@ -8432,6 +9371,10 @@ paths: $ref: '#/definitions/OneExecutionTemplateOption' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: project_execution_templates_options_remove summary: Manage execution templates of project. @@ -8459,6 +9402,10 @@ paths: description: NO CONTENT tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -8492,6 +9439,10 @@ paths: $ref: '#/definitions/ExecuteTemplateResponse' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Execute x-multiaction: false parameters: @@ -8544,11 +9495,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: name in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Name - name: schedule in: query description: '' @@ -8575,11 +9528,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: name__not in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Exclude name - name: ordering in: query description: Which field to use when ordering the results. @@ -8640,6 +9595,10 @@ paths: $ref: '#/definitions/TemplatePeriodicTask' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.TemplatePeriodicTask x-list: true @@ -8677,6 +9636,10 @@ paths: $ref: '#/definitions/OneTemplatePeriodicTask' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: false parameters: - name: id @@ -8724,6 +9687,10 @@ paths: $ref: '#/definitions/OneTemplatePeriodicTask' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.TemplatePeriodicTask x-list: false @@ -8761,6 +9728,10 @@ paths: $ref: '#/definitions/OneTemplatePeriodicTask' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: project_execution_templates_options_periodic_tasks_edit summary: Manage execution templates of project. @@ -8795,6 +9766,10 @@ paths: $ref: '#/definitions/OneTemplatePeriodicTask' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: project_execution_templates_options_periodic_tasks_remove summary: Manage execution templates of project. @@ -8822,6 +9797,10 @@ paths: description: NO CONTENT tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -8867,6 +9846,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: status in: query description: Status of execution. @@ -8911,6 +9891,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: ordering in: query description: Which field to use when ordering the results. @@ -8983,6 +9964,10 @@ paths: $ref: '#/definitions/History' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.History x-list: true @@ -9013,6 +9998,10 @@ paths: $ref: '#/definitions/OneHistory' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.History x-list: false @@ -9034,6 +10023,10 @@ paths: description: NO CONTENT tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -9062,6 +10055,10 @@ paths: $ref: '#/definitions/Response' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Cancel x-multiaction: false parameters: @@ -9085,6 +10082,10 @@ paths: description: NO CONTENT tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Clear x-multiaction: false parameters: @@ -9110,6 +10111,10 @@ paths: $ref: '#/definitions/FactsResponse' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.History x-list: false @@ -9140,11 +10145,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: name in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Name - name: variables in: query description: List of variables to filter. Comma separated "key:value" list. @@ -9171,11 +10178,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: name__not in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Exclude name - name: ordering in: query description: Which field to use when ordering the results. @@ -9232,6 +10241,10 @@ paths: $ref: '#/definitions/Inventory' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Inventory x-list: true @@ -9251,6 +10264,10 @@ paths: $ref: '#/definitions/CreateInventory' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: true parameters: - name: id @@ -9275,6 +10292,10 @@ paths: $ref: '#/definitions/ProjectImportInventory' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Import inventory x-multiaction: false parameters: @@ -9295,6 +10316,10 @@ paths: $ref: '#/definitions/OneInventory' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Inventory x-list: false @@ -9314,6 +10339,10 @@ paths: $ref: '#/definitions/OneInventory' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: project_inventory_edit description: Inventory(id, hidden, notes, owner, name, plugin, _inventory_state) @@ -9330,6 +10359,10 @@ paths: $ref: '#/definitions/OneInventory' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: project_inventory_remove description: Inventory(id, hidden, notes, owner, name, plugin, _inventory_state) @@ -9339,6 +10372,10 @@ paths: description: NO CONTENT tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -9369,11 +10406,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: name in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Name - name: variables in: query description: List of variables to filter. Comma separated "key:value" list. @@ -9390,11 +10429,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: name__not in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Exclude name - name: ordering in: query description: Which field to use when ordering the results. @@ -9451,6 +10492,10 @@ paths: $ref: '#/definitions/Group' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Group x-deep-nested-view: groups @@ -9477,6 +10522,10 @@ paths: $ref: '#/definitions/OneGroup' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Group x-deep-nested-view: groups @@ -9511,11 +10560,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: name in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Name - name: type in: query description: Instance type. @@ -9541,11 +10592,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: name__not in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Exclude name - name: ordering in: query description: Which field to use when ordering the results. @@ -9602,6 +10655,10 @@ paths: $ref: '#/definitions/Host' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Host x-list: true @@ -9627,6 +10684,10 @@ paths: $ref: '#/definitions/OneHost' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Host x-list: false @@ -9661,6 +10722,10 @@ paths: $ref: '#/definitions/Copy' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -9691,11 +10756,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: name in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Name - name: variables in: query description: List of variables to filter. Comma separated "key:value" list. @@ -9712,11 +10779,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: name__not in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Exclude name - name: ordering in: query description: Which field to use when ordering the results. @@ -9773,6 +10842,10 @@ paths: $ref: '#/definitions/Group' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Group x-deep-nested-view: groups @@ -9793,6 +10866,10 @@ paths: $ref: '#/definitions/CreateGroup' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: true parameters: - name: id @@ -9816,6 +10893,10 @@ paths: $ref: '#/definitions/OneGroup' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Group x-deep-nested-view: groups @@ -9836,6 +10917,10 @@ paths: $ref: '#/definitions/OneGroup' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: project_inventory_group_edit description: Update one or more fields on an existing group. @@ -9852,6 +10937,10 @@ paths: $ref: '#/definitions/OneGroup' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: project_inventory_group_remove description: Remove an existing group. @@ -9861,6 +10950,10 @@ paths: description: NO CONTENT tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -9892,6 +10985,10 @@ paths: $ref: '#/definitions/Copy' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -9926,11 +11023,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: name in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Name - name: variables in: query description: List of variables to filter. Comma separated "key:value" list. @@ -9947,11 +11046,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: name__not in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Exclude name - name: ordering in: query description: Which field to use when ordering the results. @@ -10008,6 +11109,10 @@ paths: $ref: '#/definitions/Group' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Group x-list: true @@ -10027,6 +11132,10 @@ paths: $ref: '#/definitions/CreateGroup' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: true parameters: - name: id @@ -10054,6 +11163,10 @@ paths: $ref: '#/definitions/OneGroup' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Group x-list: false @@ -10073,6 +11186,10 @@ paths: $ref: '#/definitions/OneGroup' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: project_inventory_group_groups_edit description: Update one or more fields on an existing group. @@ -10089,6 +11206,10 @@ paths: $ref: '#/definitions/OneGroup' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: project_inventory_group_groups_remove description: Remove an existing group. @@ -10098,6 +11219,10 @@ paths: description: NO CONTENT tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -10134,6 +11259,10 @@ paths: $ref: '#/definitions/Copy' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -10169,11 +11298,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: name in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Name - name: type in: query description: Instance type. @@ -10199,11 +11330,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: name__not in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Exclude name - name: ordering in: query description: Which field to use when ordering the results. @@ -10260,6 +11393,10 @@ paths: $ref: '#/definitions/Host' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Host x-list: true @@ -10279,6 +11416,10 @@ paths: $ref: '#/definitions/OneHost' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: true parameters: - name: id @@ -10311,6 +11452,10 @@ paths: $ref: '#/definitions/OneHost' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Host x-list: false @@ -10330,6 +11475,10 @@ paths: $ref: '#/definitions/OneHost' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: project_inventory_group_groups_hosts_edit description: Update one or more fields on an existing host. @@ -10346,6 +11495,10 @@ paths: $ref: '#/definitions/OneHost' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: project_inventory_group_groups_hosts_remove description: Remove an existing host. @@ -10355,6 +11508,10 @@ paths: description: NO CONTENT tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -10396,6 +11553,10 @@ paths: $ref: '#/definitions/Copy' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -10437,6 +11598,10 @@ paths: $ref: '#/definitions/SetOwner' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Set owner x-multiaction: false parameters: @@ -10479,6 +11644,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: key in: query description: A key name string value (or comma separated list) of instance. @@ -10500,6 +11666,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: ordering in: query description: Which field to use when ordering the results. @@ -10556,6 +11723,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: true @@ -10575,6 +11746,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: false parameters: - name: id @@ -10612,6 +11787,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: false @@ -10631,6 +11810,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: project_inventory_group_groups_hosts_variables_edit description: Update one or more fields on an existing variable. @@ -10647,6 +11830,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: project_inventory_group_groups_hosts_variables_remove description: Remove an existing variable. @@ -10656,6 +11843,10 @@ paths: description: NO CONTENT tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -10702,6 +11893,10 @@ paths: $ref: '#/definitions/SetOwner' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Set owner x-multiaction: false parameters: @@ -10739,6 +11934,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: key in: query description: A key name string value (or comma separated list) of instance. @@ -10760,6 +11956,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: ordering in: query description: Which field to use when ordering the results. @@ -10816,6 +12013,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: true @@ -10835,6 +12036,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: false parameters: - name: id @@ -10867,6 +12072,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: false @@ -10886,6 +12095,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: project_inventory_group_groups_variables_edit description: Update one or more fields on an existing variable. @@ -10902,6 +12115,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: project_inventory_group_groups_variables_remove description: Remove an existing variable. @@ -10911,6 +12128,10 @@ paths: description: NO CONTENT tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -10951,11 +12172,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: name in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Name - name: type in: query description: Instance type. @@ -10981,11 +12204,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: name__not in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Exclude name - name: ordering in: query description: Which field to use when ordering the results. @@ -11042,6 +12267,10 @@ paths: $ref: '#/definitions/Host' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Host x-list: true @@ -11061,6 +12290,10 @@ paths: $ref: '#/definitions/OneHost' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: true parameters: - name: id @@ -11088,6 +12321,10 @@ paths: $ref: '#/definitions/OneHost' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Host x-list: false @@ -11107,6 +12344,10 @@ paths: $ref: '#/definitions/OneHost' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: project_inventory_group_hosts_edit description: Update one or more fields on an existing host. @@ -11123,6 +12364,10 @@ paths: $ref: '#/definitions/OneHost' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: project_inventory_group_hosts_remove description: Remove an existing host. @@ -11132,6 +12377,10 @@ paths: description: NO CONTENT tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -11168,6 +12417,10 @@ paths: $ref: '#/definitions/Copy' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -11204,6 +12457,10 @@ paths: $ref: '#/definitions/SetOwner' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Set owner x-multiaction: false parameters: @@ -11241,6 +12498,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: key in: query description: A key name string value (or comma separated list) of instance. @@ -11262,6 +12520,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: ordering in: query description: Which field to use when ordering the results. @@ -11318,6 +12577,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: true @@ -11337,6 +12600,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: false parameters: - name: id @@ -11369,6 +12636,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: false @@ -11388,6 +12659,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: project_inventory_group_hosts_variables_edit description: Update one or more fields on an existing variable. @@ -11404,6 +12679,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: project_inventory_group_hosts_variables_remove description: Remove an existing variable. @@ -11413,6 +12692,10 @@ paths: description: NO CONTENT tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -11454,6 +12737,10 @@ paths: $ref: '#/definitions/SetOwner' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Set owner x-multiaction: false parameters: @@ -11486,6 +12773,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: key in: query description: A key name string value (or comma separated list) of instance. @@ -11507,6 +12795,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: ordering in: query description: Which field to use when ordering the results. @@ -11563,6 +12852,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: true @@ -11582,6 +12875,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: false parameters: - name: id @@ -11609,6 +12906,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: false @@ -11628,6 +12929,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: project_inventory_group_variables_edit description: Update one or more fields on an existing variable. @@ -11644,6 +12949,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: project_inventory_group_variables_remove description: Remove an existing variable. @@ -11653,6 +12962,10 @@ paths: description: NO CONTENT tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -11688,11 +13001,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: name in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Name - name: type in: query description: Instance type. @@ -11718,11 +13033,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: name__not in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Exclude name - name: ordering in: query description: Which field to use when ordering the results. @@ -11779,6 +13096,10 @@ paths: $ref: '#/definitions/Host' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Host x-list: true @@ -11798,6 +13119,10 @@ paths: $ref: '#/definitions/OneHost' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: true parameters: - name: id @@ -11821,6 +13146,10 @@ paths: $ref: '#/definitions/OneHost' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Host x-list: false @@ -11840,6 +13169,10 @@ paths: $ref: '#/definitions/OneHost' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: project_inventory_hosts_edit description: Update one or more fields on an existing host. @@ -11856,6 +13189,10 @@ paths: $ref: '#/definitions/OneHost' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: project_inventory_hosts_remove description: Remove an existing host. @@ -11865,6 +13202,10 @@ paths: description: NO CONTENT tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -11897,6 +13238,10 @@ paths: $ref: '#/definitions/Copy' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -11929,6 +13274,10 @@ paths: $ref: '#/definitions/SetOwner' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Set owner x-multiaction: false parameters: @@ -11962,6 +13311,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: key in: query description: A key name string value (or comma separated list) of instance. @@ -11983,6 +13333,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: ordering in: query description: Which field to use when ordering the results. @@ -12039,6 +13390,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: true @@ -12058,6 +13413,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: false parameters: - name: id @@ -12086,6 +13445,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: false @@ -12105,6 +13468,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: project_inventory_hosts_variables_edit description: Update one or more fields on an existing variable. @@ -12121,6 +13488,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: project_inventory_hosts_variables_remove description: Remove an existing variable. @@ -12130,6 +13501,10 @@ paths: description: NO CONTENT tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -12167,6 +13542,10 @@ paths: $ref: '#/definitions/SetOwner' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Set owner x-multiaction: false parameters: @@ -12191,6 +13570,10 @@ paths: $ref: '#/definitions/InventoryState' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Inventory x-list: false @@ -12210,6 +13593,10 @@ paths: $ref: '#/definitions/InventoryState' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-multiaction: false parameters: - name: id @@ -12237,6 +13624,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: key in: query description: A key name string value (or comma separated list) of instance. @@ -12258,6 +13646,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: ordering in: query description: Which field to use when ordering the results. @@ -12314,6 +13703,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: true @@ -12333,6 +13726,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: false parameters: - name: id @@ -12356,6 +13753,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: false @@ -12375,6 +13776,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: project_inventory_variables_edit description: Update one or more fields on an existing variable. @@ -12391,6 +13796,10 @@ paths: $ref: '#/definitions/InventoryVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: project_inventory_variables_remove description: Remove an existing variable. @@ -12400,6 +13809,10 @@ paths: description: NO CONTENT tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -12432,6 +13845,10 @@ paths: $ref: '#/definitions/SetOwner' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Set owner x-multiaction: false parameters: @@ -12457,6 +13874,10 @@ paths: $ref: '#/definitions/Response' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-title: Sync x-multiaction: false parameters: @@ -12481,6 +13902,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: key in: query description: A key name string value (or comma separated list) of instance. @@ -12502,6 +13924,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: ordering in: query description: Which field to use when ordering the results. @@ -12558,6 +13981,10 @@ paths: $ref: '#/definitions/ProjectVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: true @@ -12577,6 +14004,10 @@ paths: $ref: '#/definitions/ProjectVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-allow-append: false parameters: - name: id @@ -12596,6 +14027,10 @@ paths: $ref: '#/definitions/ProjectVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - main.Variable x-list: false @@ -12615,6 +14050,10 @@ paths: $ref: '#/definitions/ProjectVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: project_variables_edit description: Variable(id, hidden, content_type, object_id, key, value) @@ -12631,6 +14070,10 @@ paths: $ref: '#/definitions/ProjectVariable' tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: project_variables_remove description: Variable(id, hidden, content_type, object_id, key, value) @@ -12640,6 +14083,10 @@ paths: description: NO CONTENT tags: - project + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -12663,6 +14110,10 @@ paths: $ref: '#/definitions/Stats' tags: - stats + security: + - oauth2: [] + - session: [] + - basic: [] x-list: true parameters: [] /user/: @@ -12681,6 +14132,7 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Primary keys - name: username in: query description: A name string value (or comma separated list) of instance. @@ -12717,11 +14169,13 @@ paths: collectionFormat: csv minItems: 1 uniqueItems: true + x-title: Exclude primary keys - name: username__not in: query description: A name string value (or comma separated list) of instance. required: false type: string + x-title: Username not - name: ordering in: query description: Which field to use when ordering the results. @@ -12780,6 +14234,10 @@ paths: $ref: '#/definitions/User' tags: - user + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - auth.User x-list: true @@ -12799,6 +14257,10 @@ paths: $ref: '#/definitions/CreateUser' tags: - user + security: + - oauth2: [] + - session: [] + - basic: [] parameters: [] /user/{id}/: get: @@ -12812,6 +14274,10 @@ paths: $ref: '#/definitions/OneUser' tags: - user + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - auth.User x-list: false @@ -12831,6 +14297,10 @@ paths: $ref: '#/definitions/OneUser' tags: - user + security: + - oauth2: [] + - session: [] + - basic: [] patch: operationId: user_edit description: Update one or more fields on an existing user. @@ -12847,6 +14317,10 @@ paths: $ref: '#/definitions/OneUser' tags: - user + security: + - oauth2: [] + - session: [] + - basic: [] delete: operationId: user_remove description: Remove an existing user. @@ -12856,6 +14330,10 @@ paths: description: NO CONTENT tags: - user + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -12874,6 +14352,10 @@ paths: $ref: '#/definitions/_UserSettings' tags: - user + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - auth.User x-list: false @@ -12893,6 +14375,10 @@ paths: $ref: '#/definitions/_UserSettings' tags: - user + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -12916,6 +14402,10 @@ paths: $ref: '#/definitions/ChangePassword' tags: - user + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -12939,6 +14429,10 @@ paths: $ref: '#/definitions/User' tags: - user + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -12957,6 +14451,10 @@ paths: $ref: '#/definitions/TwoFA' tags: - user + security: + - oauth2: [] + - session: [] + - basic: [] x-subscribe-labels: - auth.User x-list: false @@ -12976,6 +14474,10 @@ paths: $ref: '#/definitions/TwoFA' tags: - user + security: + - oauth2: [] + - session: [] + - basic: [] parameters: - name: id in: path @@ -12992,6 +14494,8 @@ definitions: id: title: Id type: integer + maximum: 9223372036854775807 + minimum: 0 name: title: Name type: string @@ -13020,6 +14524,8 @@ definitions: id: title: Id type: integer + maximum: 9223372036854775807 + minimum: 0 name: title: Name type: string @@ -13322,6 +14828,7 @@ definitions: - ansible_ruby_interpreter - ansible_perl_interpreter - ansible_shell_executable + minLength: 1 value: title: Value type: string @@ -13333,15 +14840,19 @@ definitions: ansible_password: type: string format: password + minLength: 1 ansible_ssh_pass: type: string format: password + minLength: 1 ansible_become_pass: type: string format: password + minLength: 1 ansible_become_password: type: string format: password + minLength: 1 ansible_become: type: boolean ansible_port: @@ -13350,6 +14861,7 @@ definitions: ansible_ssh_private_key_file: type: string format: secretfile + minLength: 1 x-options: media_types: - '*/*' @@ -13415,6 +14927,8 @@ definitions: initiator: title: Initiator type: integer + maximum: 9223372036854775807 + minimum: -9223372036854775808 initiator_type: title: Initiator type type: string @@ -13429,6 +14943,7 @@ definitions: type: string format: inventory readOnly: true + minLength: 1 x-options: filters: null kind: @@ -13576,6 +15091,7 @@ definitions: title: Playbook type: string format: fk_autocomplete + minLength: 1 x-options: model: $ref: '#/definitions/AnsiblePlaybook' @@ -13658,6 +15174,7 @@ definitions: description: use this file to authenticate the connection type: string format: secretfile + minLength: 1 x-options: media_types: - '*/*' @@ -13714,6 +15231,7 @@ definitions: description: vault password file type: string format: secretfile + minLength: 1 x-options: media_types: - '*/*' @@ -13767,6 +15285,7 @@ definitions: title: Module type: string format: fk_autocomplete + minLength: 1 x-options: model: $ref: '#/definitions/AnsibleModule' @@ -13851,6 +15370,7 @@ definitions: description: use this file to authenticate the connection type: string format: secretfile + minLength: 1 x-options: media_types: - '*/*' @@ -13894,6 +15414,7 @@ definitions: description: vault password file type: string format: secretfile + minLength: 1 x-options: media_types: - '*/*' @@ -13956,6 +15477,8 @@ definitions: initiator: title: Initiator type: integer + maximum: 9223372036854775807 + minimum: -9223372036854775808 initiator_type: title: Initiator type type: string @@ -14248,6 +15771,7 @@ definitions: title: Body type: string format: file + minLength: 1 x-options: media_types: - '*/*' @@ -14366,6 +15890,7 @@ definitions: title: Body type: string format: file + minLength: 1 x-options: media_types: - '*/*' @@ -14382,6 +15907,7 @@ definitions: - ini - json default: yaml + minLength: 1 executable: title: Executable type: boolean @@ -14438,6 +15964,7 @@ definitions: title: Body type: string format: file + minLength: 1 x-options: media_types: - '*/*' @@ -14454,6 +15981,7 @@ definitions: - ini - json default: yaml + minLength: 1 executable: title: Executable type: boolean @@ -14583,12 +16111,14 @@ definitions: KEY: type: string format: secretfile + minLength: 1 x-options: media_types: - '*/*' PASSWORD: type: string format: password + minLength: 1 branch: title: Branch for GIT (branch/tag/SHA) or TAR (subdir) type: string @@ -14695,6 +16225,7 @@ definitions: type: string format: html readOnly: true + minLength: 1 execute_view_data: $ref: '#/definitions/Data' x-properties-groups: @@ -14879,6 +16410,7 @@ definitions: title: Module type: string format: fk_autocomplete + minLength: 1 x-options: model: $ref: '#/definitions/AnsibleModule' @@ -14961,6 +16493,7 @@ definitions: description: use this file to authenticate the connection type: string format: secretfile + minLength: 1 x-options: media_types: - '*/*' @@ -15001,6 +16534,7 @@ definitions: description: vault password file type: string format: secretfile + minLength: 1 x-options: media_types: - '*/*' @@ -15094,6 +16628,7 @@ definitions: title: Playbook type: string format: fk_autocomplete + minLength: 1 x-options: model: $ref: '#/definitions/AnsiblePlaybook' @@ -15174,6 +16709,7 @@ definitions: description: use this file to authenticate the connection type: string format: secretfile + minLength: 1 x-options: media_types: - '*/*' @@ -15226,6 +16762,7 @@ definitions: description: vault password file type: string format: secretfile + minLength: 1 x-options: media_types: - '*/*' @@ -15333,6 +16870,7 @@ definitions: title: Playbook type: string format: fk_autocomplete + minLength: 1 x-options: model: $ref: '#/definitions/AnsiblePlaybook' @@ -15415,6 +16953,7 @@ definitions: description: use this file to authenticate the connection type: string format: secretfile + minLength: 1 x-options: media_types: - '*/*' @@ -15471,6 +17010,7 @@ definitions: description: vault password file type: string format: secretfile + minLength: 1 x-options: media_types: - '*/*' @@ -15524,6 +17064,7 @@ definitions: title: Module type: string format: fk_autocomplete + minLength: 1 x-options: model: $ref: '#/definitions/AnsibleModule' @@ -15608,6 +17149,7 @@ definitions: description: use this file to authenticate the connection type: string format: secretfile + minLength: 1 x-options: media_types: - '*/*' @@ -15651,6 +17193,7 @@ definitions: description: vault password file type: string format: secretfile + minLength: 1 x-options: media_types: - '*/*' @@ -15852,6 +17395,7 @@ definitions: title: Playbook type: string format: fk_autocomplete + minLength: 1 x-options: model: $ref: '#/definitions/AnsiblePlaybook' @@ -15934,6 +17478,7 @@ definitions: description: use this file to authenticate the connection type: string format: secretfile + minLength: 1 x-options: media_types: - '*/*' @@ -15990,6 +17535,7 @@ definitions: description: vault password file type: string format: secretfile + minLength: 1 x-options: media_types: - '*/*' @@ -16043,6 +17589,7 @@ definitions: title: Module type: string format: fk_autocomplete + minLength: 1 x-options: model: $ref: '#/definitions/AnsibleModule' @@ -16127,6 +17674,7 @@ definitions: description: use this file to authenticate the connection type: string format: secretfile + minLength: 1 x-options: media_types: - '*/*' @@ -16170,6 +17718,7 @@ definitions: description: vault password file type: string format: secretfile + minLength: 1 x-options: media_types: - '*/*' @@ -16252,6 +17801,7 @@ definitions: CRONTAB: type: string format: crontab + minLength: 1 enabled: title: Enabled type: boolean @@ -16302,6 +17852,7 @@ definitions: CRONTAB: type: string format: crontab + minLength: 1 enabled: title: Enabled type: boolean @@ -16365,6 +17916,7 @@ definitions: title: Body type: string format: file + minLength: 1 x-options: media_types: - '*/*' @@ -16426,6 +17978,7 @@ definitions: - repo_key - playbook_path - ci_template + minLength: 1 value: title: Value type: string @@ -16440,9 +17993,11 @@ definitions: repo_password: type: string format: password + minLength: 1 repo_key: type: string format: secretfile + minLength: 1 x-options: media_types: - '*/*' @@ -16571,14 +18126,17 @@ definitions: type: object properties: day: + title: Day type: array items: $ref: '#/definitions/DayJob' month: + title: Month type: array items: $ref: '#/definitions/MonthJob' year: + title: Year type: array items: $ref: '#/definitions/YearJob' @@ -16697,10 +18255,12 @@ definitions: title: Password type: string format: password + minLength: 1 password2: title: Repeat password type: string format: password + minLength: 1 x-properties-groups: '': - id @@ -16764,10 +18324,14 @@ definitions: enum: - en - ru + default: en dark_mode: title: Dark mode type: boolean default: false + default: + language: en + dark_mode: false x-properties-groups: '': - language @@ -16780,9 +18344,9 @@ definitions: $ref: '#/definitions/_MainSettings' custom: title: Custom - type: string - format: json + type: object default: {} + additionalProperties: true x-properties-groups: '': - main @@ -16799,14 +18363,17 @@ definitions: title: Old password type: string format: password + minLength: 1 password: title: New password type: string format: password + minLength: 1 password2: title: Confirm new password type: string format: password + minLength: 1 x-properties-groups: '': - old_password diff --git a/docker-compose.yml b/docker-compose.yml index b67842b9..e1ee7e5b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,6 @@ -version: '3' - services: traefik: - image: traefik:2.9 + image: traefik:3.2 restart: unless-stopped volumes: - ./traefik_dynamic.yml:/traefik_dynamic.yml:ro @@ -70,7 +68,7 @@ services: - data-volume:/var/lib/mysql centrifugo: - image: centrifugo/centrifugo:v3 + image: centrifugo/centrifugo:v5 restart: unless-stopped tty: true environment: @@ -83,6 +81,7 @@ services: CENTRIFUGO_HISTORY_TTL: "300s" CENTRIFUGO_HEALTH: "true" CENTRIFUGO_ALLOWED_ORIGINS: "*" + CENTRIFUGO_ALLOW_SUBSCRIBE_FOR_CLIENT: "true" healthcheck: test: [ "CMD", "sh", "-c", "wget -nv -O - http://localhost:8000/health" ] interval: 3s diff --git a/frontend_src/.babelrc.js b/frontend_src/.babelrc.js deleted file mode 100644 index dadefdc8..00000000 --- a/frontend_src/.babelrc.js +++ /dev/null @@ -1,36 +0,0 @@ -if (process.env.APP_ENV === 'prod') { - module.exports = { - presets: [ - [ - '@babel/preset-env', - { - useBuiltIns: 'usage', - corejs: { version: '3.26.1', proposals: true }, - }, - ], - ], - plugins: [ - [ - '@babel/plugin-transform-runtime', - { - 'corejs': false, - 'regenerator': false, - 'version': '^7.19.6' - } - ], - ], - } -} else { - module.exports = { - presets: [ - [ - '@babel/preset-env', - { - useBuiltIns: 'usage', - corejs: { version: '3.26.1', proposals: true }, - targets: 'last 1 Chrome versions', - }, - ], - ], - } -} diff --git a/frontend_src/.editorconfig b/frontend_src/.editorconfig deleted file mode 100644 index f821fcc2..00000000 --- a/frontend_src/.editorconfig +++ /dev/null @@ -1,8 +0,0 @@ -[*.{js,vue}] -indent_style = space -indent_size = 4 -tab_width = 4 -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true diff --git a/frontend_src/.eslintrc.js b/frontend_src/.eslintrc.js deleted file mode 100644 index 570d1b4e..00000000 --- a/frontend_src/.eslintrc.js +++ /dev/null @@ -1,30 +0,0 @@ -module.exports = { - extends: ['eslint:recommended', 'plugin:vue/recommended', 'plugin:prettier/recommended'], - parser: 'vue-eslint-parser', - parserOptions: { - parser: '@babel/eslint-parser', - sourceType: 'module', - }, - env: { - browser: true, - commonjs: true, - es6: true, - node: true, - 'vue/setup-compiler-macros': true, - }, - rules: { - 'no-debugger': 'warn', - 'prettier/prettier': 'error', - 'vue/component-tags-order': 'off', - 'vue/html-indent': ['error', 4], - 'vue/html-self-closing': ['error', { html: { void: 'any' } }], - 'vue/max-attributes-per-line': 'off', - 'vue/multi-word-component-names': 'off', - 'vue/one-component-per-file': 'off', - }, - globals: { - spa: 'readonly', - app: 'readonly', - Vue: 'readonly', - }, -}; diff --git a/frontend_src/.gitignore b/frontend_src/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/frontend_src/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend_src/.prettierrc b/frontend_src/.prettierrc deleted file mode 100644 index 0eb166b5..00000000 --- a/frontend_src/.prettierrc +++ /dev/null @@ -1,18 +0,0 @@ -{ - "arrowParens": "always", - "bracketSpacing": true, - "htmlWhitespaceSensitivity": "ignore", - "insertPragma": false, - "bracketSameLine": false, - "jsxSingleQuote": false, - "printWidth": 110, - "proseWrap": "preserve", - "quoteProps": "as-needed", - "requirePragma": false, - "semi": true, - "singleQuote": true, - "tabWidth": 4, - "trailingComma": "all", - "useTabs": false, - "vueIndentScriptAndStyle": true -} diff --git a/frontend_src/Home.vue b/frontend_src/Home.vue index 4fbb6192..4b9bbcec 100644 --- a/frontend_src/Home.vue +++ b/frontend_src/Home.vue @@ -23,25 +23,25 @@ diff --git a/frontend_src/TasksChart.vue b/frontend_src/TasksChart.vue index d14c5b53..57cd7d66 100644 --- a/frontend_src/TasksChart.vue +++ b/frontend_src/TasksChart.vue @@ -48,218 +48,218 @@ diff --git a/frontend_src/assets/logo/horizontal.svg b/frontend_src/assets/logo/horizontal.svg new file mode 100644 index 00000000..cd7ad4ed --- /dev/null +++ b/frontend_src/assets/logo/horizontal.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend_src/assets/logo/logo.png b/frontend_src/assets/logo/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..09f751a33d184af20c300e0d4b56498094e3eabb GIT binary patch literal 44204 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ℜ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 literal 0 HcmV?d00001 diff --git a/frontend_src/assets/logo/vertical.png b/frontend_src/assets/logo/vertical.png new file mode 100644 index 0000000000000000000000000000000000000000..2b5c7520a3d575a73b6854ef76db90612f1bec86 GIT binary patch literal 67930 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

hcz^=ThtyfjH&yt&wX$4Yi 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 literal 0 HcmV?d00001 diff --git a/frontend_src/auth/AuthPagesLayout.vue b/frontend_src/auth/AuthPagesLayout.vue new file mode 100644 index 00000000..cfe25654 --- /dev/null +++ b/frontend_src/auth/AuthPagesLayout.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/frontend_src/auth/index.ts b/frontend_src/auth/index.ts new file mode 100644 index 00000000..d45a2f7d --- /dev/null +++ b/frontend_src/auth/index.ts @@ -0,0 +1,8 @@ +import { customizeDefaultAuthAppCreator } from '@vstconsulting/vstutils/auth-app'; +import AuthPagesLayout from './AuthPagesLayout.vue'; + +export const createAuthApp = customizeDefaultAuthAppCreator({ + components: { + layout: AuthPagesLayout, + }, +}); diff --git a/frontend_src/biome.jsonc b/frontend_src/biome.jsonc new file mode 100644 index 00000000..49fc0833 --- /dev/null +++ b/frontend_src/biome.jsonc @@ -0,0 +1,13 @@ +{ + "$schema": "../node_modules/@biomejs/biome/configuration_schema.json", + "formatter": { + "lineWidth": 110, + "indentStyle": "space", + "indentWidth": 4 + }, + "javascript": { + "formatter": { + "quoteStyle": "single" + } + } +} diff --git a/frontend_src/index.js b/frontend_src/common.ts similarity index 82% rename from frontend_src/index.js rename to frontend_src/common.ts index 3cdcf769..456f5f92 100644 --- a/frontend_src/index.js +++ b/frontend_src/common.ts @@ -1,19 +1,20 @@ -import { ref, computed } from 'vue'; +import { onAppAfterInit, onAppCreated, onSchemaViewsCreated, spa } from '@vstconsulting/vstutils'; +import { computed, ref } from 'vue'; + import './style.scss'; import './layout'; import './hooks'; import './users'; -import * as inventory from './inventory'; +import './inventory'; import './project'; -import * as history from './history'; +import './history'; import Home from './Home.vue'; import { UserObjectField } from './fields/UserObjectField'; -export { inventory, history }; - -spa.signals.once('allViews.created', ({ views }) => { +onSchemaViewsCreated(({ views }) => { const homeView = new spa.views.View( + // @ts-expect-error vstutils types are not perfect { path: '/', routeName: 'home', title: 'Dashboard', autoupdate: true }, null, [Home], @@ -30,9 +31,9 @@ spa.signals.once('allViews.created', ({ views }) => { views.set('/', homeView); homeView.extendStore((store) => { const app = spa.getApp(); - let statsData = ref(null); - let period = ref(14); - let smallBoxes = ref([ + const statsData = ref(); + const period = ref(14); + const smallBoxes = ref([ { key: 'projects', label: 'Projects', href: '#/project', icon: 'fa fa-cog' }, { key: 'users', label: 'Users', href: '#/user', icon: 'fa fa-cog' }, { key: 'inventories', label: 'Inventories', href: '#/inventory', icon: 'fas fa-cog' }, @@ -41,12 +42,13 @@ spa.signals.once('allViews.created', ({ views }) => { ]); const additionalSections = computed(() => []); - async function updateData(requestPeriod) { + async function updateData(requestPeriod?: number) { const finalPeriod = requestPeriod || period.value; const response = await app.api.makeRequest({ useBulk: true, method: 'get', path: 'stats', + auth: true, query: { last: finalPeriod }, }); if (response.status !== 200) { @@ -70,7 +72,7 @@ spa.signals.once('allViews.created', ({ views }) => { }); }); -spa.signals.once('APP_CREATED', (app) => { +onAppCreated((app) => { const ownerField = { type: 'string', format: UserObjectField.format, @@ -84,6 +86,6 @@ spa.signals.once('APP_CREATED', (app) => { } }); -spa.signals.connect('app.afterInit', ({ app }) => { +onAppAfterInit(({ app }) => { app.views.get('/group/{id}/groups/{groups_id}/hosts/').nestedQueryset = app.views.get('/host/').objects; }); diff --git a/frontend_src/history/ExecutionTimeField.js b/frontend_src/history/ExecutionTimeField.js index aad429f2..6426025b 100644 --- a/frontend_src/history/ExecutionTimeField.js +++ b/frontend_src/history/ExecutionTimeField.js @@ -42,6 +42,7 @@ export class ExecutionTimeField extends spa.fields.datetime.UptimeField { static format = 'execution-time'; static get mixins() { + // biome-ignore lint/complexity/noThisInStatic: its ok return super.mixins.concat(ExecutionTimeFieldMixin); } } diff --git a/frontend_src/history/HistoryLine.vue b/frontend_src/history/HistoryLine.vue index 213aedd9..7bbe8bb1 100644 --- a/frontend_src/history/HistoryLine.vue +++ b/frontend_src/history/HistoryLine.vue @@ -3,14 +3,15 @@ - diff --git a/frontend_src/history/OutputLines.vue b/frontend_src/history/OutputLines.vue index a16ea2ee..49bcf50b 100644 --- a/frontend_src/history/OutputLines.vue +++ b/frontend_src/history/OutputLines.vue @@ -42,146 +42,148 @@