Merge pull request #3208 from quay/project/2-spaces
Fix formatting for the config app
This commit is contained in:
commit
8eb7d73f22
28 changed files with 936 additions and 913 deletions
|
@ -2,7 +2,6 @@ import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
# Note: this currently points to the directory above, since we're in the quay config_app dir
|
# Note: this currently points to the directory above, since we're in the quay config_app dir
|
||||||
# TODO(config_extract): revert to root directory rather than the one above
|
# TODO(config_extract): revert to root directory rather than the one above
|
||||||
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
@ -15,7 +14,6 @@ TEMPLATE_DIR = os.path.join(ROOT_DIR, 'templates/')
|
||||||
IS_KUBERNETES = 'KUBERNETES_SERVICE_HOST' in os.environ
|
IS_KUBERNETES = 'KUBERNETES_SERVICE_HOST' in os.environ
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _get_version_number_changelog():
|
def _get_version_number_changelog():
|
||||||
try:
|
try:
|
||||||
with open(os.path.join(ROOT_DIR, 'CHANGELOG.md')) as f:
|
with open(os.path.join(ROOT_DIR, 'CHANGELOG.md')) as f:
|
||||||
|
|
|
@ -28,10 +28,12 @@ config_provider = get_config_provider(OVERRIDE_CONFIG_DIRECTORY, 'config.yaml',
|
||||||
|
|
||||||
if is_testing:
|
if is_testing:
|
||||||
from test.testconfig import TestConfig
|
from test.testconfig import TestConfig
|
||||||
|
|
||||||
logger.debug('Loading test config.')
|
logger.debug('Loading test config.')
|
||||||
app.config.from_object(TestConfig())
|
app.config.from_object(TestConfig())
|
||||||
else:
|
else:
|
||||||
from config import DefaultConfig
|
from config import DefaultConfig
|
||||||
|
|
||||||
logger.debug('Loading default config.')
|
logger.debug('Loading default config.')
|
||||||
app.config.from_object(DefaultConfig())
|
app.config.from_object(DefaultConfig())
|
||||||
app.teardown_request(database.close_db_filter)
|
app.teardown_request(database.close_db_filter)
|
||||||
|
|
|
@ -3,7 +3,6 @@ from config_app.c_app import app as application
|
||||||
# Bind all of the blueprints
|
# Bind all of the blueprints
|
||||||
import config_web
|
import config_web
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
logging.config.fileConfig(logfile_path(debug=True), disable_existing_loggers=False)
|
logging.config.fileConfig(logfile_path(debug=True), disable_existing_loggers=False)
|
||||||
application.run(port=5000, debug=True, threaded=True, host='0.0.0.0')
|
application.run(port=5000, debug=True, threaded=True, host='0.0.0.0')
|
||||||
|
|
|
@ -7,7 +7,6 @@ from config_app.c_app import app
|
||||||
from config_app.config_endpoints.api import method_metadata
|
from config_app.config_endpoints.api import method_metadata
|
||||||
from config_app.config_endpoints.common import fully_qualified_name, PARAM_REGEX, TYPE_CONVERTER
|
from config_app.config_endpoints.common import fully_qualified_name, PARAM_REGEX, TYPE_CONVERTER
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -82,7 +81,8 @@ def generate_route_data():
|
||||||
paths[swagger_path] = path_swagger
|
paths[swagger_path] = path_swagger
|
||||||
|
|
||||||
# Add any global path parameters.
|
# Add any global path parameters.
|
||||||
param_data_map = view_class.__api_path_params if '__api_path_params' in dir(view_class) else {}
|
param_data_map = view_class.__api_path_params if '__api_path_params' in dir(
|
||||||
|
view_class) else {}
|
||||||
if param_data_map:
|
if param_data_map:
|
||||||
path_parameters_swagger = []
|
path_parameters_swagger = []
|
||||||
for path_parameter in param_data_map:
|
for path_parameter in param_data_map:
|
||||||
|
@ -128,7 +128,8 @@ def generate_route_data():
|
||||||
if rule.arguments:
|
if rule.arguments:
|
||||||
for path_parameter in rule.arguments:
|
for path_parameter in rule.arguments:
|
||||||
description = param_data_map.get(path_parameter, {}).get('description')
|
description = param_data_map.get(path_parameter, {}).get('description')
|
||||||
operation_swagger['parameters'].append(swagger_parameter(path_parameter, description))
|
operation_swagger['parameters'].append(
|
||||||
|
swagger_parameter(path_parameter, description))
|
||||||
|
|
||||||
# Add the query parameters.
|
# Add the query parameters.
|
||||||
if '__api_query_params' in dir(method):
|
if '__api_query_params' in dir(method):
|
||||||
|
|
|
@ -3,7 +3,8 @@ import logging
|
||||||
from flask import abort, request
|
from flask import abort, request
|
||||||
|
|
||||||
from config_app.config_endpoints.api.suconfig_models_pre_oci import pre_oci_model as model
|
from config_app.config_endpoints.api.suconfig_models_pre_oci import pre_oci_model as model
|
||||||
from config_app.config_endpoints.api import resource, ApiResource, nickname, validate_json_request, kubernetes_only
|
from config_app.config_endpoints.api import resource, ApiResource, nickname, validate_json_request, \
|
||||||
|
kubernetes_only
|
||||||
from config_app.c_app import (app, config_provider, superusers, ip_resolver,
|
from config_app.c_app import (app, config_provider, superusers, ip_resolver,
|
||||||
instance_keys, INIT_SCRIPTS_LOCATION)
|
instance_keys, INIT_SCRIPTS_LOCATION)
|
||||||
from config_app.config_util.k8saccessor import KubernetesAccessorSingleton
|
from config_app.config_util.k8saccessor import KubernetesAccessorSingleton
|
||||||
|
@ -11,7 +12,8 @@ from config_app.config_util.k8saccessor import KubernetesAccessorSingleton
|
||||||
from data.database import configure
|
from data.database import configure
|
||||||
from data.runmigration import run_alembic_migration
|
from data.runmigration import run_alembic_migration
|
||||||
from util.config.configutil import add_enterprise_config_defaults
|
from util.config.configutil import add_enterprise_config_defaults
|
||||||
from util.config.validator import validate_service_for_config, ValidatorContext, is_valid_config_upload_filename
|
from util.config.validator import validate_service_for_config, ValidatorContext, \
|
||||||
|
is_valid_config_upload_filename
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -85,6 +87,7 @@ class SuperUserRegistryStatus(ApiResource):
|
||||||
""" Resource for determining the status of the registry, such as if config exists,
|
""" Resource for determining the status of the registry, such as if config exists,
|
||||||
if a database is configured, and if it has any defined users.
|
if a database is configured, and if it has any defined users.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@nickname('scRegistryStatus')
|
@nickname('scRegistryStatus')
|
||||||
def get(self):
|
def get(self):
|
||||||
""" Returns the status of the registry. """
|
""" Returns the status of the registry. """
|
||||||
|
@ -121,6 +124,7 @@ class _AlembicLogHandler(logging.Handler):
|
||||||
@resource('/v1/superuser/setupdb')
|
@resource('/v1/superuser/setupdb')
|
||||||
class SuperUserSetupDatabase(ApiResource):
|
class SuperUserSetupDatabase(ApiResource):
|
||||||
""" Resource for invoking alembic to setup the database. """
|
""" Resource for invoking alembic to setup the database. """
|
||||||
|
|
||||||
@nickname('scSetupDatabase')
|
@nickname('scSetupDatabase')
|
||||||
def get(self):
|
def get(self):
|
||||||
""" Invokes the alembic upgrade process. """
|
""" Invokes the alembic upgrade process. """
|
||||||
|
@ -251,7 +255,8 @@ class SuperUserConfigValidate(ApiResource):
|
||||||
# so we also allow it to be called if there is no valid registry configuration setup. Note that
|
# so we also allow it to be called if there is no valid registry configuration setup. Note that
|
||||||
# this is also safe since this method does not access any information not given in the request.
|
# this is also safe since this method does not access any information not given in the request.
|
||||||
config = request.get_json()['config']
|
config = request.get_json()['config']
|
||||||
validator_context = ValidatorContext.from_app(app, config, request.get_json().get('password', ''),
|
validator_context = ValidatorContext.from_app(app, config,
|
||||||
|
request.get_json().get('password', ''),
|
||||||
instance_keys=instance_keys,
|
instance_keys=instance_keys,
|
||||||
ip_resolver=ip_resolver,
|
ip_resolver=ip_resolver,
|
||||||
config_provider=config_provider,
|
config_provider=config_provider,
|
||||||
|
@ -294,6 +299,7 @@ class SuperUserKubernetesDeployment(ApiResource):
|
||||||
@resource('/v1/superuser/config/kubernetes')
|
@resource('/v1/superuser/config/kubernetes')
|
||||||
class SuperUserKubernetesConfiguration(ApiResource):
|
class SuperUserKubernetesConfiguration(ApiResource):
|
||||||
""" Resource for saving the config files to kubernetes secrets. """
|
""" Resource for saving the config files to kubernetes secrets. """
|
||||||
|
|
||||||
@kubernetes_only
|
@kubernetes_only
|
||||||
@nickname('scDeployConfiguration')
|
@nickname('scDeployConfiguration')
|
||||||
def post(self):
|
def post(self):
|
||||||
|
@ -303,6 +309,7 @@ class SuperUserKubernetesConfiguration(ApiResource):
|
||||||
@resource('/v1/superuser/config/file/<filename>')
|
@resource('/v1/superuser/config/file/<filename>')
|
||||||
class SuperUserConfigFile(ApiResource):
|
class SuperUserConfigFile(ApiResource):
|
||||||
""" Resource for fetching the status of config files and overriding them. """
|
""" Resource for fetching the status of config files and overriding them. """
|
||||||
|
|
||||||
@nickname('scConfigFileExists')
|
@nickname('scConfigFileExists')
|
||||||
def get(self, filename):
|
def get(self, filename):
|
||||||
""" Returns whether the configuration file with the given name exists. """
|
""" Returns whether the configuration file with the given name exists. """
|
||||||
|
@ -313,7 +320,6 @@ class SuperUserConfigFile(ApiResource):
|
||||||
'exists': config_provider.volume_file_exists(filename)
|
'exists': config_provider.volume_file_exists(filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@nickname('scUpdateConfigFile')
|
@nickname('scUpdateConfigFile')
|
||||||
def post(self, filename):
|
def post(self, filename):
|
||||||
""" Updates the configuration file with the given name. """
|
""" Updates the configuration file with the given name. """
|
||||||
|
|
|
@ -14,9 +14,12 @@ def user_view(user):
|
||||||
|
|
||||||
|
|
||||||
class RepositoryBuild(namedtuple('RepositoryBuild',
|
class RepositoryBuild(namedtuple('RepositoryBuild',
|
||||||
['uuid', 'logs_archived', 'repository_namespace_user_username', 'repository_name',
|
['uuid', 'logs_archived', 'repository_namespace_user_username',
|
||||||
'can_write', 'can_read', 'pull_robot', 'resource_key', 'trigger', 'display_name',
|
'repository_name',
|
||||||
'started', 'job_config', 'phase', 'status', 'error', 'archive_url'])):
|
'can_write', 'can_read', 'pull_robot', 'resource_key', 'trigger',
|
||||||
|
'display_name',
|
||||||
|
'started', 'job_config', 'phase', 'status', 'error',
|
||||||
|
'archive_url'])):
|
||||||
"""
|
"""
|
||||||
RepositoryBuild represents a build associated with a repostiory
|
RepositoryBuild represents a build associated with a repostiory
|
||||||
:type uuid: string
|
:type uuid: string
|
||||||
|
@ -89,7 +92,8 @@ class Approval(namedtuple('Approval', ['approver', 'approval_type', 'approved_da
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ServiceKey(namedtuple('ServiceKey', ['name', 'kid', 'service', 'jwk', 'metadata', 'created_date',
|
class ServiceKey(
|
||||||
|
namedtuple('ServiceKey', ['name', 'kid', 'service', 'jwk', 'metadata', 'created_date',
|
||||||
'expiration_date', 'rotation_duration', 'approval'])):
|
'expiration_date', 'rotation_duration', 'approval'])):
|
||||||
"""
|
"""
|
||||||
ServiceKey is an apostille signing key
|
ServiceKey is an apostille signing key
|
||||||
|
@ -156,13 +160,12 @@ class Organization(namedtuple('Organization', ['username', 'email'])):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@add_metaclass(ABCMeta)
|
@add_metaclass(ABCMeta)
|
||||||
class SuperuserDataInterface(object):
|
class SuperuserDataInterface(object):
|
||||||
"""
|
"""
|
||||||
Interface that represents all data store interactions required by a superuser api.
|
Interface that represents all data store interactions required by a superuser api.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def list_all_service_keys(self):
|
def list_all_service_keys(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
from data import model
|
from data import model
|
||||||
|
|
||||||
from config_app.config_endpoints.api.superuser_models_interface import SuperuserDataInterface, User, ServiceKey, Approval
|
from config_app.config_endpoints.api.superuser_models_interface import (SuperuserDataInterface, User, ServiceKey,
|
||||||
|
Approval)
|
||||||
|
|
||||||
|
|
||||||
def _create_user(user):
|
def _create_user(user):
|
||||||
if user is None:
|
if user is None:
|
||||||
|
@ -11,12 +13,15 @@ def _create_user(user):
|
||||||
def _create_key(key):
|
def _create_key(key):
|
||||||
approval = None
|
approval = None
|
||||||
if key.approval is not None:
|
if key.approval is not None:
|
||||||
approval = Approval(_create_user(key.approval.approver), key.approval.approval_type, key.approval.approved_date,
|
approval = Approval(_create_user(key.approval.approver), key.approval.approval_type,
|
||||||
|
key.approval.approved_date,
|
||||||
key.approval.notes)
|
key.approval.notes)
|
||||||
|
|
||||||
return ServiceKey(key.name, key.kid, key.service, key.jwk, key.metadata, key.created_date, key.expiration_date,
|
return ServiceKey(key.name, key.kid, key.service, key.jwk, key.metadata, key.created_date,
|
||||||
|
key.expiration_date,
|
||||||
key.rotation_duration, approval)
|
key.rotation_duration, approval)
|
||||||
|
|
||||||
|
|
||||||
class ServiceKeyDoesNotExist(Exception):
|
class ServiceKeyDoesNotExist(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -30,6 +35,7 @@ class PreOCIModel(SuperuserDataInterface):
|
||||||
PreOCIModel implements the data model for the SuperUser using a database schema
|
PreOCIModel implements the data model for the SuperUser using a database schema
|
||||||
before it was changed to support the OCI specification.
|
before it was changed to support the OCI specification.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def list_all_service_keys(self):
|
def list_all_service_keys(self):
|
||||||
keys = model.service_keys.list_all_keys()
|
keys = model.service_keys.list_all_keys()
|
||||||
return [_create_key(key) for key in keys]
|
return [_create_key(key) for key in keys]
|
||||||
|
@ -43,8 +49,10 @@ class PreOCIModel(SuperuserDataInterface):
|
||||||
except model.ServiceKeyAlreadyApproved:
|
except model.ServiceKeyAlreadyApproved:
|
||||||
raise ServiceKeyAlreadyApproved
|
raise ServiceKeyAlreadyApproved
|
||||||
|
|
||||||
def generate_service_key(self, service, expiration_date, kid=None, name='', metadata=None, rotation_duration=None):
|
def generate_service_key(self, service, expiration_date, kid=None, name='', metadata=None,
|
||||||
(private_key, key) = model.service_keys.generate_service_key(service, expiration_date, metadata=metadata, name=name)
|
rotation_duration=None):
|
||||||
|
(private_key, key) = model.service_keys.generate_service_key(service, expiration_date,
|
||||||
|
metadata=metadata, name=name)
|
||||||
|
|
||||||
return private_key, key.kid
|
return private_key, key.kid
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ from config_app.c_app import app, config_provider
|
||||||
from config_app.config_endpoints.api import resource, ApiResource, nickname
|
from config_app.config_endpoints.api import resource, ApiResource, nickname
|
||||||
from config_app.config_util.tar import tarinfo_filter_partial, strip_absolute_path_and_add_trailing_dir
|
from config_app.config_util.tar import tarinfo_filter_partial, strip_absolute_path_and_add_trailing_dir
|
||||||
|
|
||||||
|
|
||||||
@resource('/v1/configapp/initialization')
|
@resource('/v1/configapp/initialization')
|
||||||
class ConfigInitialization(ApiResource):
|
class ConfigInitialization(ApiResource):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -16,4 +16,3 @@ class User(ApiResource):
|
||||||
# raise InvalidToken("Requires authentication", payload={'session_required': False})
|
# raise InvalidToken("Requires authentication", payload={'session_required': False})
|
||||||
|
|
||||||
return user_view(user)
|
return user_view(user)
|
||||||
|
|
||||||
|
|
|
@ -60,5 +60,3 @@ def render_page_template(name, route_data=None, js_bundle_name=DEFAULT_JS_BUNDLE
|
||||||
|
|
||||||
def fully_qualified_name(method_view_class):
|
def fully_qualified_name(method_view_class):
|
||||||
return '%s.%s' % (method_view_class.__module__, method_view_class.__name__)
|
return '%s.%s' % (method_view_class.__module__, method_view_class.__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,6 @@ class ApiException(HTTPException):
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidRequest(ApiException):
|
class InvalidRequest(ApiException):
|
||||||
def __init__(self, error_description, payload=None):
|
def __init__(self, error_description, payload=None):
|
||||||
ApiException.__init__(self, ApiErrorType.invalid_request, 400, error_description, payload)
|
ApiException.__init__(self, ApiErrorType.invalid_request, 400, error_description, payload)
|
||||||
|
|
|
@ -5,7 +5,6 @@ from config_app.config_endpoints.common import render_page_template
|
||||||
from config_app.config_endpoints.api.discovery import generate_route_data
|
from config_app.config_endpoints.api.discovery import generate_route_data
|
||||||
from config_app.config_endpoints.api import no_cache
|
from config_app.config_endpoints.api import no_cache
|
||||||
|
|
||||||
|
|
||||||
setup_web = Blueprint('setup_web', __name__, template_folder='templates')
|
setup_web = Blueprint('setup_web', __name__, template_folder='templates')
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,5 +21,3 @@ def render_page_template_with_routedata(name, *args, **kwargs):
|
||||||
@setup_web.route('/', methods=['GET'], defaults={'path': ''})
|
@setup_web.route('/', methods=['GET'], defaults={'path': ''})
|
||||||
def index(path, **kwargs):
|
def index(path, **kwargs):
|
||||||
return render_page_template_with_routedata('index.html', js_bundle_name='configapp', **kwargs)
|
return render_page_template_with_routedata('index.html', js_bundle_name='configapp', **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ class TransientDirectoryProvider(FileConfigProvider):
|
||||||
from/to the file system, only using temporary directories,
|
from/to the file system, only using temporary directories,
|
||||||
deleting old dirs and creating new ones as requested.
|
deleting old dirs and creating new ones as requested.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, config_volume, yaml_filename, py_filename):
|
def __init__(self, config_volume, yaml_filename, py_filename):
|
||||||
# Create a temp directory that will be cleaned up when we change the config path
|
# Create a temp directory that will be cleaned up when we change the config path
|
||||||
# This should ensure we have no "pollution" of different configs:
|
# This should ensure we have no "pollution" of different configs:
|
||||||
|
|
|
@ -9,6 +9,7 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class BaseFileProvider(BaseProvider):
|
class BaseFileProvider(BaseProvider):
|
||||||
""" Base implementation of the config provider that reads the data from the file system. """
|
""" Base implementation of the config provider that reads the data from the file system. """
|
||||||
|
|
||||||
def __init__(self, config_volume, yaml_filename, py_filename):
|
def __init__(self, config_volume, yaml_filename, py_filename):
|
||||||
self.config_volume = config_volume
|
self.config_volume = config_volume
|
||||||
self.yaml_filename = yaml_filename
|
self.yaml_filename = yaml_filename
|
||||||
|
|
|
@ -4,7 +4,6 @@ import logging
|
||||||
from config_app.config_util.config.baseprovider import export_yaml, CannotWriteConfigException
|
from config_app.config_util.config.baseprovider import export_yaml, CannotWriteConfigException
|
||||||
from config_app.config_util.config.basefileprovider import BaseFileProvider
|
from config_app.config_util.config.basefileprovider import BaseFileProvider
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,6 +20,7 @@ def _ensure_parent_dir(filepath):
|
||||||
class FileConfigProvider(BaseFileProvider):
|
class FileConfigProvider(BaseFileProvider):
|
||||||
""" Implementation of the config provider that reads and writes the data
|
""" Implementation of the config provider that reads and writes the data
|
||||||
from/to the file system. """
|
from/to the file system. """
|
||||||
|
|
||||||
def __init__(self, config_volume, yaml_filename, py_filename):
|
def __init__(self, config_volume, yaml_filename, py_filename):
|
||||||
super(FileConfigProvider, self).__init__(config_volume, yaml_filename, py_filename)
|
super(FileConfigProvider, self).__init__(config_volume, yaml_filename, py_filename)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import json
|
import json
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
from datetime import datetime, timedelta
|
|
||||||
|
|
||||||
from config_app.config_util.config.baseprovider import BaseProvider
|
from config_app.config_util.config.baseprovider import BaseProvider
|
||||||
|
|
||||||
|
@ -11,6 +10,7 @@ REAL_FILES = ['test/data/signing-private.gpg', 'test/data/signing-public.gpg', '
|
||||||
class TestConfigProvider(BaseProvider):
|
class TestConfigProvider(BaseProvider):
|
||||||
""" Implementation of the config provider for testing. Everything is kept in-memory instead on
|
""" Implementation of the config provider for testing. Everything is kept in-memory instead on
|
||||||
the real file system. """
|
the real file system. """
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.clear()
|
self.clear()
|
||||||
|
|
||||||
|
@ -78,4 +78,3 @@ class TestConfigProvider(BaseProvider):
|
||||||
|
|
||||||
def get_volume_path(self, directory, filename):
|
def get_volume_path(self, directory, filename):
|
||||||
return os.path.join(directory, filename)
|
return os.path.join(directory, filename)
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
QE_DEPLOYMENT_LABEL = 'quay-enterprise-component'
|
QE_DEPLOYMENT_LABEL = 'quay-enterprise-component'
|
||||||
|
|
||||||
|
|
||||||
class KubernetesAccessorSingleton(object):
|
class KubernetesAccessorSingleton(object):
|
||||||
""" Singleton allowing access to kubernetes operations """
|
""" Singleton allowing access to kubernetes operations """
|
||||||
_instance = None
|
_instance = None
|
||||||
|
@ -79,7 +80,6 @@ class KubernetesAccessorSingleton(object):
|
||||||
}
|
}
|
||||||
}, api_prefix='apis/extensions/v1beta1', content_type='application/strategic-merge-patch+json'))
|
}, api_prefix='apis/extensions/v1beta1', content_type='application/strategic-merge-patch+json'))
|
||||||
|
|
||||||
|
|
||||||
def _assert_success(self, response):
|
def _assert_success(self, response):
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
logger.error('Kubernetes API call failed with response: %s => %s', response.status_code,
|
logger.error('Kubernetes API call failed with response: %s => %s', response.status_code,
|
||||||
|
|
|
@ -8,9 +8,11 @@ DEFAULT_QE_CONFIG_SECRET = 'quay-enterprise-config-secret'
|
||||||
# The name of the quay enterprise deployment (not config app) that is used to query & rollout
|
# The name of the quay enterprise deployment (not config app) that is used to query & rollout
|
||||||
DEFAULT_QE_DEPLOYMENT_SELECTOR = 'app'
|
DEFAULT_QE_DEPLOYMENT_SELECTOR = 'app'
|
||||||
|
|
||||||
|
|
||||||
def get_k8s_namespace():
|
def get_k8s_namespace():
|
||||||
return os.environ.get('QE_K8S_NAMESPACE', DEFAULT_QE_NAMESPACE)
|
return os.environ.get('QE_K8S_NAMESPACE', DEFAULT_QE_NAMESPACE)
|
||||||
|
|
||||||
|
|
||||||
class KubernetesConfig(object):
|
class KubernetesConfig(object):
|
||||||
def __init__(self, api_host='', service_account_token=SERVICE_ACCOUNT_TOKEN_PATH,
|
def __init__(self, api_host='', service_account_token=SERVICE_ACCOUNT_TOKEN_PATH,
|
||||||
qe_namespace=DEFAULT_QE_NAMESPACE,
|
qe_namespace=DEFAULT_QE_NAMESPACE,
|
||||||
|
@ -42,6 +44,3 @@ class KubernetesConfig(object):
|
||||||
|
|
||||||
return cls(api_host=api_host, service_account_token=service_token, qe_namespace=qe_namespace,
|
return cls(api_host=api_host, service_account_token=service_token, qe_namespace=qe_namespace,
|
||||||
qe_config_secret=qe_config_secret, qe_deployment_selector=qe_deployment_selector)
|
qe_config_secret=qe_config_secret, qe_deployment_selector=qe_deployment_selector)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,12 @@ from fnmatch import fnmatch
|
||||||
|
|
||||||
import OpenSSL
|
import OpenSSL
|
||||||
|
|
||||||
|
|
||||||
class CertInvalidException(Exception):
|
class CertInvalidException(Exception):
|
||||||
""" Exception raised when a certificate could not be parsed/loaded. """
|
""" Exception raised when a certificate could not be parsed/loaded. """
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class KeyInvalidException(Exception):
|
class KeyInvalidException(Exception):
|
||||||
""" Exception raised when a key could not be parsed/loaded or successfully applied to a cert. """
|
""" Exception raised when a key could not be parsed/loaded or successfully applied to a cert. """
|
||||||
pass
|
pass
|
||||||
|
@ -24,8 +26,10 @@ def load_certificate(cert_contents):
|
||||||
|
|
||||||
_SUBJECT_ALT_NAME = 'subjectAltName'
|
_SUBJECT_ALT_NAME = 'subjectAltName'
|
||||||
|
|
||||||
|
|
||||||
class SSLCertificate(object):
|
class SSLCertificate(object):
|
||||||
""" Helper class for easier working with SSL certificates. """
|
""" Helper class for easier working with SSL certificates. """
|
||||||
|
|
||||||
def __init__(self, openssl_cert):
|
def __init__(self, openssl_cert):
|
||||||
self.openssl_cert = openssl_cert
|
self.openssl_cert = openssl_cert
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
from util.config.validator import EXTRA_CA_DIRECTORY
|
from util.config.validator import EXTRA_CA_DIRECTORY
|
||||||
|
|
||||||
|
|
||||||
def strip_absolute_path_and_add_trailing_dir(path):
|
def strip_absolute_path_and_add_trailing_dir(path):
|
||||||
"""
|
"""
|
||||||
Removes the initial trailing / from the prefix path, and add the last dir one
|
Removes the initial trailing / from the prefix path, and add the last dir one
|
||||||
"""
|
"""
|
||||||
return path[1:] + '/'
|
return path[1:] + '/'
|
||||||
|
|
||||||
|
|
||||||
def tarinfo_filter_partial(prefix):
|
def tarinfo_filter_partial(prefix):
|
||||||
def tarinfo_filter(tarinfo):
|
def tarinfo_filter(tarinfo):
|
||||||
# remove leading directory info
|
# remove leading directory info
|
||||||
|
|
|
@ -1,23 +1,25 @@
|
||||||
import pytest
|
import pytest
|
||||||
import re
|
|
||||||
|
|
||||||
from httmock import urlmatch, HTTMock, response
|
from httmock import urlmatch, HTTMock, response
|
||||||
|
|
||||||
from config_app.config_util.k8saccessor import KubernetesAccessorSingleton
|
from config_app.config_util.k8saccessor import KubernetesAccessorSingleton
|
||||||
from config_app.config_util.k8sconfig import KubernetesConfig
|
from config_app.config_util.k8sconfig import KubernetesConfig
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('kube_config, expected_api, expected_query', [
|
@pytest.mark.parametrize('kube_config, expected_api, expected_query', [
|
||||||
({'api_host': 'www.customhost.com'},
|
({'api_host': 'www.customhost.com'},
|
||||||
'/apis/extensions/v1beta1/namespaces/quay-enterprise/deployments', 'labelSelector=quay-enterprise-component%3Dapp'),
|
'/apis/extensions/v1beta1/namespaces/quay-enterprise/deployments', 'labelSelector=quay-enterprise-component%3Dapp'),
|
||||||
|
|
||||||
({'api_host': 'www.customhost.com', 'qe_deployment_selector': 'custom-selector'},
|
({'api_host': 'www.customhost.com', 'qe_deployment_selector': 'custom-selector'},
|
||||||
'/apis/extensions/v1beta1/namespaces/quay-enterprise/deployments', 'labelSelector=quay-enterprise-component%3Dcustom-selector'),
|
'/apis/extensions/v1beta1/namespaces/quay-enterprise/deployments',
|
||||||
|
'labelSelector=quay-enterprise-component%3Dcustom-selector'),
|
||||||
|
|
||||||
({'api_host': 'www.customhost.com', 'qe_namespace': 'custom-namespace'},
|
({'api_host': 'www.customhost.com', 'qe_namespace': 'custom-namespace'},
|
||||||
'/apis/extensions/v1beta1/namespaces/custom-namespace/deployments', 'labelSelector=quay-enterprise-component%3Dapp'),
|
'/apis/extensions/v1beta1/namespaces/custom-namespace/deployments', 'labelSelector=quay-enterprise-component%3Dapp'),
|
||||||
|
|
||||||
({'api_host': 'www.customhost.com', 'qe_namespace': 'custom-namespace', 'qe_deployment_selector': 'custom-selector'},
|
({'api_host': 'www.customhost.com', 'qe_namespace': 'custom-namespace', 'qe_deployment_selector': 'custom-selector'},
|
||||||
'/apis/extensions/v1beta1/namespaces/custom-namespace/deployments', 'labelSelector=quay-enterprise-component%3Dcustom-selector'),
|
'/apis/extensions/v1beta1/namespaces/custom-namespace/deployments',
|
||||||
|
'labelSelector=quay-enterprise-component%3Dcustom-selector'),
|
||||||
])
|
])
|
||||||
def test_get_qe_deployments(kube_config, expected_api, expected_query):
|
def test_get_qe_deployments(kube_config, expected_api, expected_query):
|
||||||
config = KubernetesConfig(**kube_config)
|
config = KubernetesConfig(**kube_config)
|
||||||
|
@ -36,12 +38,15 @@ def test_get_qe_deployments(kube_config, expected_api, expected_query):
|
||||||
|
|
||||||
assert url_hit[0]
|
assert url_hit[0]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('kube_config, deployment_names, expected_api_hits', [
|
@pytest.mark.parametrize('kube_config, deployment_names, expected_api_hits', [
|
||||||
({'api_host': 'www.customhost.com'}, [], []),
|
({'api_host': 'www.customhost.com'}, [], []),
|
||||||
({'api_host':'www.customhost.com'}, ['myDeployment'], ['/apis/extensions/v1beta1/namespaces/quay-enterprise/deployments/myDeployment']),
|
({'api_host': 'www.customhost.com'}, ['myDeployment'],
|
||||||
|
['/apis/extensions/v1beta1/namespaces/quay-enterprise/deployments/myDeployment']),
|
||||||
({'api_host': 'www.customhost.com', 'qe_namespace': 'custom-namespace'},
|
({'api_host': 'www.customhost.com', 'qe_namespace': 'custom-namespace'},
|
||||||
['myDeployment', 'otherDeployment'],
|
['myDeployment', 'otherDeployment'],
|
||||||
['/apis/extensions/v1beta1/namespaces/custom-namespace/deployments/myDeployment', '/apis/extensions/v1beta1/namespaces/custom-namespace/deployments/otherDeployment']),
|
['/apis/extensions/v1beta1/namespaces/custom-namespace/deployments/myDeployment',
|
||||||
|
'/apis/extensions/v1beta1/namespaces/custom-namespace/deployments/otherDeployment']),
|
||||||
])
|
])
|
||||||
def test_cycle_qe_deployments(kube_config, deployment_names, expected_api_hits):
|
def test_cycle_qe_deployments(kube_config, deployment_names, expected_api_hits):
|
||||||
KubernetesAccessorSingleton._instance = None
|
KubernetesAccessorSingleton._instance = None
|
||||||
|
|
|
@ -6,6 +6,7 @@ from util.config.validator import EXTRA_CA_DIRECTORY
|
||||||
|
|
||||||
from test.fixtures import *
|
from test.fixtures import *
|
||||||
|
|
||||||
|
|
||||||
class MockTarInfo:
|
class MockTarInfo:
|
||||||
def __init__(self, name, isdir):
|
def __init__(self, name, isdir):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
@ -14,13 +15,15 @@ class MockTarInfo:
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return other is not None and self.name == other.name
|
return other is not None and self.name == other.name
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('prefix,tarinfo,expected', [
|
@pytest.mark.parametrize('prefix,tarinfo,expected', [
|
||||||
# It should handle simple files
|
# It should handle simple files
|
||||||
('Users/sam/', MockTarInfo('Users/sam/config.yaml', False), MockTarInfo('config.yaml', False)),
|
('Users/sam/', MockTarInfo('Users/sam/config.yaml', False), MockTarInfo('config.yaml', False)),
|
||||||
# It should allow the extra CA dir
|
# It should allow the extra CA dir
|
||||||
('Users/sam/', MockTarInfo('Users/sam/%s' % EXTRA_CA_DIRECTORY, True), MockTarInfo('%s' % EXTRA_CA_DIRECTORY, True)),
|
('Users/sam/', MockTarInfo('Users/sam/%s' % EXTRA_CA_DIRECTORY, True), MockTarInfo('%s' % EXTRA_CA_DIRECTORY, True)),
|
||||||
# it should allow a file in that extra dir
|
# it should allow a file in that extra dir
|
||||||
('Users/sam/', MockTarInfo('Users/sam/%s/cert.crt' % EXTRA_CA_DIRECTORY, False), MockTarInfo('%s/cert.crt' % EXTRA_CA_DIRECTORY, False)),
|
('Users/sam/', MockTarInfo('Users/sam/%s/cert.crt' % EXTRA_CA_DIRECTORY, False),
|
||||||
|
MockTarInfo('%s/cert.crt' % EXTRA_CA_DIRECTORY, False)),
|
||||||
# it should not allow a directory that isn't the CA dir
|
# it should not allow a directory that isn't the CA dir
|
||||||
('Users/sam/', MockTarInfo('Users/sam/dirignore', True), None),
|
('Users/sam/', MockTarInfo('Users/sam/dirignore', True), None),
|
||||||
])
|
])
|
||||||
|
|
|
@ -2,7 +2,5 @@ from config_app.c_app import app as application
|
||||||
from config_app.config_endpoints.api import api_bp
|
from config_app.config_endpoints.api import api_bp
|
||||||
from config_app.config_endpoints.setup_web import setup_web
|
from config_app.config_endpoints.setup_web import setup_web
|
||||||
|
|
||||||
|
|
||||||
application.register_blueprint(setup_web)
|
application.register_blueprint(setup_web)
|
||||||
application.register_blueprint(api_bp, url_prefix='/api')
|
application.register_blueprint(api_bp, url_prefix='/api')
|
||||||
|
|
||||||
|
|
Reference in a new issue