Move config provider to _init to decouple from app

remove app references from validators
This commit is contained in:
Sam Chow 2018-05-24 14:58:38 -04:00
parent 86929c16d3
commit d45b925155
26 changed files with 54 additions and 51 deletions

View file

@ -2,6 +2,8 @@ import os
import re import re
import subprocess import subprocess
from util.config.provider import get_config_provider
ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
CONF_DIR = os.getenv("QUAYCONF", os.path.join(ROOT_DIR, "conf/")) CONF_DIR = os.getenv("QUAYCONF", os.path.join(ROOT_DIR, "conf/"))
@ -10,6 +12,14 @@ STATIC_LDN_DIR = os.path.join(STATIC_DIR, 'ldn/')
STATIC_FONTS_DIR = os.path.join(STATIC_DIR, 'fonts/') STATIC_FONTS_DIR = os.path.join(STATIC_DIR, 'fonts/')
TEMPLATE_DIR = os.path.join(ROOT_DIR, 'templates/') TEMPLATE_DIR = os.path.join(ROOT_DIR, 'templates/')
IS_TESTING = 'TEST' in os.environ
IS_KUBERNETES = 'KUBERNETES_SERVICE_HOST' in os.environ
OVERRIDE_CONFIG_DIRECTORY = os.path.join(CONF_DIR, 'stack/')
config_provider = get_config_provider(OVERRIDE_CONFIG_DIRECTORY, 'config.yaml', 'config.py',
testing=IS_TESTING, kubernetes=IS_KUBERNETES)
def _get_version_number_changelog(): def _get_version_number_changelog():
try: try:

12
app.py
View file

@ -13,7 +13,9 @@ from flask_principal import Principal
from jwkest.jwk import RSAKey from jwkest.jwk import RSAKey
import features import features
from _init import CONF_DIR
from _init import config_provider, CONF_DIR, IS_KUBERNETES, IS_TESTING, OVERRIDE_CONFIG_DIRECTORY
from auth.auth_context import get_authenticated_user from auth.auth_context import get_authenticated_user
from avatars.avatars import Avatar from avatars.avatars import Avatar
from buildman.manager.buildcanceller import BuildCanceller from buildman.manager.buildcanceller import BuildCanceller
@ -41,7 +43,6 @@ from util.saas.useranalytics import UserAnalytics
from util.saas.exceptionlog import Sentry from util.saas.exceptionlog import Sentry
from util.names import urn_generator from util.names import urn_generator
from util.config.configutil import generate_secret_key from util.config.configutil import generate_secret_key
from util.config.provider import get_config_provider
from util.config.superusermanager import SuperUserManager from util.config.superusermanager import SuperUserManager
from util.label_validator import LabelValidator from util.label_validator import LabelValidator
from util.metrics.metricqueue import MetricQueue from util.metrics.metricqueue import MetricQueue
@ -53,7 +54,6 @@ from util.security.instancekeys import InstanceKeys
from util.security.signing import Signer from util.security.signing import Signer
OVERRIDE_CONFIG_DIRECTORY = os.path.join(CONF_DIR, 'stack/')
OVERRIDE_CONFIG_YAML_FILENAME = os.path.join(CONF_DIR, 'stack/config.yaml') OVERRIDE_CONFIG_YAML_FILENAME = os.path.join(CONF_DIR, 'stack/config.yaml')
OVERRIDE_CONFIG_PY_FILENAME = os.path.join(CONF_DIR, 'stack/config.py') OVERRIDE_CONFIG_PY_FILENAME = os.path.join(CONF_DIR, 'stack/config.py')
@ -65,10 +65,8 @@ app = Flask(__name__)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Instantiate the configuration. # Instantiate the configuration.
is_testing = 'TEST' in os.environ is_testing = IS_TESTING
is_kubernetes = 'KUBERNETES_SERVICE_HOST' in os.environ is_kubernetes = IS_KUBERNETES
config_provider = get_config_provider(OVERRIDE_CONFIG_DIRECTORY, 'config.yaml', 'config.py',
testing=is_testing, kubernetes=is_kubernetes)
if is_testing: if is_testing:
from test.testconfig import TestConfig from test.testconfig import TestConfig

View file

@ -405,6 +405,6 @@ class SuperUserConfigValidate(ApiResource):
# 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.
if not config_provider.config_exists() or SuperUserPermission().can(): if not config_provider.config_exists() or SuperUserPermission().can():
config = request.get_json()['config'] config = request.get_json()['config']
return validate_service_for_config(service, config, request.get_json().get('password', '')) return validate_service_for_config(service, config, request.get_json().get('password', ''), app)
abort(403) abort(403)

View file

@ -64,7 +64,7 @@ VALIDATORS = {
AppTokenAuthValidator.name: AppTokenAuthValidator.validate, AppTokenAuthValidator.name: AppTokenAuthValidator.validate,
} }
def validate_service_for_config(service, config, password=None): def validate_service_for_config(service, config, password=None, app=None):
""" Attempts to validate the configuration for the given service. """ """ Attempts to validate the configuration for the given service. """
if not service in VALIDATORS: if not service in VALIDATORS:
return { return {
@ -72,7 +72,7 @@ def validate_service_for_config(service, config, password=None):
} }
try: try:
VALIDATORS[service](config, get_authenticated_user(), password) VALIDATORS[service](config, get_authenticated_user(), password, app)
return { return {
'status': True 'status': True
} }

View file

@ -15,6 +15,6 @@ class BaseValidator(object):
@classmethod @classmethod
@abstractmethod @abstractmethod
def validate(cls, config, user, user_password): def validate(cls, config, user, user_password, app):
""" Raises Exception if failure to validate. """ """ Raises Exception if failure to validate. """
pass pass

View file

@ -14,7 +14,7 @@ from test.fixtures import *
({'AUTHENTICATION_TYPE': 'Database'}), ({'AUTHENTICATION_TYPE': 'Database'}),
]) ])
def test_validate_noop(unvalidated_config, app): def test_validate_noop(unvalidated_config, app):
JWTAuthValidator.validate(unvalidated_config, None, None) JWTAuthValidator.validate(unvalidated_config, None, None, app)
@pytest.mark.parametrize('unvalidated_config', [ @pytest.mark.parametrize('unvalidated_config', [
@ -24,7 +24,7 @@ def test_validate_noop(unvalidated_config, app):
]) ])
def test_invalid_config(unvalidated_config, app): def test_invalid_config(unvalidated_config, app):
with pytest.raises(ConfigValidationException): with pytest.raises(ConfigValidationException):
JWTAuthValidator.validate(unvalidated_config, None, None) JWTAuthValidator.validate(unvalidated_config, None, None, app)
@pytest.mark.parametrize('username, password, expected_exception', [ @pytest.mark.parametrize('username, password, expected_exception', [
@ -44,8 +44,8 @@ def test_validated_jwt(username, password, expected_exception, app):
if expected_exception is not None: if expected_exception is not None:
with pytest.raises(ConfigValidationException): with pytest.raises(ConfigValidationException):
JWTAuthValidator.validate(config, AttrDict(dict(username=username)), password, JWTAuthValidator.validate(config, AttrDict(dict(username=username)), password, app,
public_key_path=jwt_auth.public_key_path) public_key_path=jwt_auth.public_key_path)
else: else:
JWTAuthValidator.validate(config, AttrDict(dict(username=username)), password, JWTAuthValidator.validate(config, AttrDict(dict(username=username)), password, app,
public_key_path=jwt_auth.public_key_path) public_key_path=jwt_auth.public_key_path)

View file

@ -1,4 +1,3 @@
from app import app
from util.config.validators import BaseValidator, ConfigValidationException from util.config.validators import BaseValidator, ConfigValidationException
from oauth.loginmanager import OAuthLoginManager from oauth.loginmanager import OAuthLoginManager
from oauth.oidc import OIDCLoginService from oauth.oidc import OIDCLoginService
@ -7,7 +6,7 @@ class AccessSettingsValidator(BaseValidator):
name = "access" name = "access"
@classmethod @classmethod
def validate(cls, config, user, user_password): def validate(cls, config, user, user_password, app):
if not config.get('FEATURE_DIRECT_LOGIN', True): if not config.get('FEATURE_DIRECT_LOGIN', True):
# Make sure we have at least one OIDC enabled. # Make sure we have at least one OIDC enabled.
github_login = config.get('FEATURE_GITHUB_LOGIN', False) github_login = config.get('FEATURE_GITHUB_LOGIN', False)

View file

@ -4,7 +4,7 @@ class ActionLogArchivingValidator(BaseValidator):
name = "actionlogarchiving" name = "actionlogarchiving"
@classmethod @classmethod
def validate(cls, config, user, user_password): def validate(cls, config, user, user_password, app):
""" Validates the action log archiving configuration. """ """ Validates the action log archiving configuration. """
if not config.get('FEATURE_ACTION_LOG_ROTATION', False): if not config.get('FEATURE_ACTION_LOG_ROTATION', False):
return return

View file

@ -4,7 +4,7 @@ class AppTokenAuthValidator(BaseValidator):
name = "apptoken-auth" name = "apptoken-auth"
@classmethod @classmethod
def validate(cls, config, user, user_password): def validate(cls, config, user, user_password, app):
if config.get('AUTHENTICATION_TYPE', 'Database') != 'AppToken': if config.get('AUTHENTICATION_TYPE', 'Database') != 'AppToken':
return return

View file

@ -1,13 +1,13 @@
from bitbucket import BitBucket from bitbucket import BitBucket
from app import get_app_url from util import get_app_url
from util.config.validators import BaseValidator, ConfigValidationException from util.config.validators import BaseValidator, ConfigValidationException
class BitbucketTriggerValidator(BaseValidator): class BitbucketTriggerValidator(BaseValidator):
name = "bitbucket-trigger" name = "bitbucket-trigger"
@classmethod @classmethod
def validate(cls, config, user, user_password): def validate(cls, config, user, user_password, app):
""" Validates the config for BitBucket. """ """ Validates the config for BitBucket. """
trigger_config = config.get('BITBUCKET_TRIGGER_CONFIG') trigger_config = config.get('BITBUCKET_TRIGGER_CONFIG')
if not trigger_config: if not trigger_config:
@ -21,7 +21,7 @@ class BitbucketTriggerValidator(BaseValidator):
key = trigger_config['CONSUMER_KEY'] key = trigger_config['CONSUMER_KEY']
secret = trigger_config['CONSUMER_SECRET'] secret = trigger_config['CONSUMER_SECRET']
callback_url = '%s/oauth1/bitbucket/callback/trigger/' % (get_app_url()) callback_url = '%s/oauth1/bitbucket/callback/trigger/' % (get_app_url(app.config))
bitbucket_client = BitBucket(key, secret, callback_url) bitbucket_client = BitBucket(key, secret, callback_url)
(result, _, _) = bitbucket_client.get_authorization_url() (result, _, _) = bitbucket_client.get_authorization_url()

View file

@ -7,7 +7,7 @@ class DatabaseValidator(BaseValidator):
name = "database" name = "database"
@classmethod @classmethod
def validate(cls, config, user, user_password): def validate(cls, config, user, user_password, app):
""" Validates connecting to the database. """ """ Validates connecting to the database. """
try: try:
validate_database_url(config['DB_URI'], config.get('DB_CONNECTION_ARGS', {})) validate_database_url(config['DB_URI'], config.get('DB_CONNECTION_ARGS', {}))

View file

@ -1,14 +1,13 @@
from flask import Flask from flask import Flask
from flask_mail import Mail, Message from flask_mail import Mail, Message
from app import app
from util.config.validators import BaseValidator from util.config.validators import BaseValidator
class EmailValidator(BaseValidator): class EmailValidator(BaseValidator):
name = "mail" name = "mail"
@classmethod @classmethod
def validate(cls, config, user, user_password): def validate(cls, config, user, user_password, app):
""" Validates sending email. """ """ Validates sending email. """
with app.app_context(): with app.app_context():
test_app = Flask("mail-test-app") test_app = Flask("mail-test-app")

View file

@ -1,4 +1,3 @@
from app import app
from oauth.services.github import GithubOAuthService from oauth.services.github import GithubOAuthService
from util.config.validators import BaseValidator, ConfigValidationException from util.config.validators import BaseValidator, ConfigValidationException
@ -7,7 +6,7 @@ class BaseGitHubValidator(BaseValidator):
config_key = None config_key = None
@classmethod @classmethod
def validate(cls, config, user, user_password): def validate(cls, config, user, user_password, app):
""" Validates the OAuth credentials and API endpoint for a Github service. """ """ Validates the OAuth credentials and API endpoint for a Github service. """
github_config = config.get(cls.config_key) github_config = config.get(cls.config_key)
if not github_config: if not github_config:

View file

@ -1,4 +1,3 @@
from app import app
from oauth.services.gitlab import GitLabOAuthService from oauth.services.gitlab import GitLabOAuthService
from util.config.validators import BaseValidator, ConfigValidationException from util.config.validators import BaseValidator, ConfigValidationException
@ -6,7 +5,7 @@ class GitLabTriggerValidator(BaseValidator):
name = "gitlab-trigger" name = "gitlab-trigger"
@classmethod @classmethod
def validate(cls, config, user, user_password): def validate(cls, config, user, user_password, app):
""" Validates the OAuth credentials and API endpoint for a GitLab service. """ """ Validates the OAuth credentials and API endpoint for a GitLab service. """
github_config = config.get('GITLAB_TRIGGER_CONFIG') github_config = config.get('GITLAB_TRIGGER_CONFIG')
if not github_config: if not github_config:

View file

@ -1,4 +1,3 @@
from app import app
from oauth.services.google import GoogleOAuthService from oauth.services.google import GoogleOAuthService
from util.config.validators import BaseValidator, ConfigValidationException from util.config.validators import BaseValidator, ConfigValidationException
@ -6,7 +5,7 @@ class GoogleLoginValidator(BaseValidator):
name = "google-login" name = "google-login"
@classmethod @classmethod
def validate(cls, config, user, user_password): def validate(cls, config, user, user_password, app):
""" Validates the Google Login client ID and secret. """ """ Validates the Google Login client ID and secret. """
google_login_config = config.get('GOOGLE_LOGIN_CONFIG') google_login_config = config.get('GOOGLE_LOGIN_CONFIG')
if not google_login_config: if not google_login_config:

View file

@ -1,4 +1,4 @@
from app import app, OVERRIDE_CONFIG_DIRECTORY from _init import OVERRIDE_CONFIG_DIRECTORY
from data.users.externaljwt import ExternalJWTAuthN from data.users.externaljwt import ExternalJWTAuthN
from util.config.validators import BaseValidator, ConfigValidationException from util.config.validators import BaseValidator, ConfigValidationException
@ -6,7 +6,7 @@ class JWTAuthValidator(BaseValidator):
name = "jwt" name = "jwt"
@classmethod @classmethod
def validate(cls, config, user, user_password, public_key_path=None): def validate(cls, config, user, user_password, app, public_key_path=None):
""" Validates the JWT authentication system. """ """ Validates the JWT authentication system. """
if config.get('AUTHENTICATION_TYPE', 'Database') != 'JWT': if config.get('AUTHENTICATION_TYPE', 'Database') != 'JWT':
return return

View file

@ -5,7 +5,7 @@ class KeystoneValidator(BaseValidator):
name = "keystone" name = "keystone"
@classmethod @classmethod
def validate(cls, config, user, user_password): def validate(cls, config, user, user_password, app):
""" Validates the Keystone authentication system. """ """ Validates the Keystone authentication system. """
if config.get('AUTHENTICATION_TYPE', 'Database') != 'Keystone': if config.get('AUTHENTICATION_TYPE', 'Database') != 'Keystone':
return return

View file

@ -2,17 +2,16 @@ import os
import ldap import ldap
import subprocess import subprocess
from app import app, config_provider
from data.users import LDAP_CERT_FILENAME from data.users import LDAP_CERT_FILENAME
from data.users.externalldap import LDAPConnection, LDAPUsers from data.users.externalldap import LDAPConnection, LDAPUsers
from util.config.validators import BaseValidator, ConfigValidationException from util.config.validators import BaseValidator, ConfigValidationException
from _init import CONF_DIR from _init import CONF_DIR, config_provider
class LDAPValidator(BaseValidator): class LDAPValidator(BaseValidator):
name = "ldap" name = "ldap"
@classmethod @classmethod
def validate(cls, config, user, user_password): def validate(cls, config, user, user_password, app):
""" Validates the LDAP connection. """ """ Validates the LDAP connection. """
if config.get('AUTHENTICATION_TYPE', 'Database') != 'LDAP': if config.get('AUTHENTICATION_TYPE', 'Database') != 'LDAP':
return return

View file

@ -1,4 +1,3 @@
from app import app
from oauth.loginmanager import OAuthLoginManager from oauth.loginmanager import OAuthLoginManager
from oauth.oidc import OIDCLoginService, DiscoveryFailureException from oauth.oidc import OIDCLoginService, DiscoveryFailureException
from util.config.validators import BaseValidator, ConfigValidationException from util.config.validators import BaseValidator, ConfigValidationException
@ -7,7 +6,7 @@ class OIDCLoginValidator(BaseValidator):
name = "oidc-login" name = "oidc-login"
@classmethod @classmethod
def validate(cls, config, user, user_password): def validate(cls, config, user, user_password, app):
client = app.config['HTTPCLIENT'] client = app.config['HTTPCLIENT']
login_manager = OAuthLoginManager(config, client=client) login_manager = OAuthLoginManager(config, client=client)
for service in login_manager.services: for service in login_manager.services:

View file

@ -6,7 +6,7 @@ class RedisValidator(BaseValidator):
name = "redis" name = "redis"
@classmethod @classmethod
def validate(cls, config, user, user_password): def validate(cls, config, user, user_password, app):
""" Validates connecting to redis. """ """ Validates connecting to redis. """
redis_config = config.get('BUILDLOGS_REDIS', {}) redis_config = config.get('BUILDLOGS_REDIS', {})
if not 'host' in redis_config: if not 'host' in redis_config:

View file

@ -1,6 +1,5 @@
import time import time
from app import app
from boot import setup_jwt_proxy from boot import setup_jwt_proxy
from util.secscan.api import SecurityScannerAPI from util.secscan.api import SecurityScannerAPI
from util.config.validators import BaseValidator, ConfigValidationException from util.config.validators import BaseValidator, ConfigValidationException
@ -9,7 +8,7 @@ class SecurityScannerValidator(BaseValidator):
name = "security-scanner" name = "security-scanner"
@classmethod @classmethod
def validate(cls, config, user, user_password): def validate(cls, config, user, user_password, app):
""" Validates the configuration for talking to a Quay Security Scanner. """ """ Validates the configuration for talking to a Quay Security Scanner. """
if not config.get('FEATURE_SECURITY_SCANNER', False): if not config.get('FEATURE_SECURITY_SCANNER', False):
return return

View file

@ -1,6 +1,6 @@
from StringIO import StringIO from StringIO import StringIO
from app import config_provider from _init import config_provider
from util.config.validators import BaseValidator, ConfigValidationException from util.config.validators import BaseValidator, ConfigValidationException
from util.security.signing import SIGNING_ENGINES from util.security.signing import SIGNING_ENGINES
@ -8,7 +8,7 @@ class SignerValidator(BaseValidator):
name = "signer" name = "signer"
@classmethod @classmethod
def validate(cls, config, user, user_password): def validate(cls, config, user, user_password, app):
""" Validates the GPG public+private key pair used for signing converted ACIs. """ """ Validates the GPG public+private key pair used for signing converted ACIs. """
if config.get('SIGNING_ENGINE') is None: if config.get('SIGNING_ENGINE') is None:
return return

View file

@ -1,4 +1,4 @@
from app import config_provider from _init import config_provider
from util.config.validators import BaseValidator, ConfigValidationException from util.config.validators import BaseValidator, ConfigValidationException
from util.security.ssl import load_certificate, CertInvalidException, KeyInvalidException from util.security.ssl import load_certificate, CertInvalidException, KeyInvalidException
@ -8,7 +8,7 @@ class SSLValidator(BaseValidator):
name = "ssl" name = "ssl"
@classmethod @classmethod
def validate(cls, config, user, user_password): def validate(cls, config, user, user_password, app):
""" Validates the SSL configuration (if enabled). """ """ Validates the SSL configuration (if enabled). """
# Skip if non-SSL. # Skip if non-SSL.

View file

@ -1,12 +1,16 @@
from app import app, ip_resolver, config_provider from _init import config_provider
from storage import get_storage_driver from storage import get_storage_driver
from util.config.validators import BaseValidator, ConfigValidationException from util.config.validators import BaseValidator, ConfigValidationException
from util.ipresolver import NoopIPResolver
ip_resolver = NoopIPResolver()
class StorageValidator(BaseValidator): class StorageValidator(BaseValidator):
name = "registry-storage" name = "registry-storage"
@classmethod @classmethod
def validate(cls, config, user, user_password): def validate(cls, config, user, user_password, app):
""" Validates registry storage. """ """ Validates registry storage. """
replication_enabled = config.get('FEATURE_STORAGE_REPLICATION', False) replication_enabled = config.get('FEATURE_STORAGE_REPLICATION', False)

View file

@ -9,7 +9,7 @@ class TimeMachineValidator(BaseValidator):
name = "time-machine" name = "time-machine"
@classmethod @classmethod
def validate(cls, config, user, user_password): def validate(cls, config, user, user_password, app):
if not 'DEFAULT_TAG_EXPIRATION' in config: if not 'DEFAULT_TAG_EXPIRATION' in config:
# Old style config # Old style config
return return

View file

@ -2,7 +2,6 @@ import logging
from hashlib import sha1 from hashlib import sha1
from app import app
from util.config.validators import BaseValidator, ConfigValidationException from util.config.validators import BaseValidator, ConfigValidationException
from util.registry.torrent import jwt_from_infohash from util.registry.torrent import jwt_from_infohash
@ -12,7 +11,7 @@ class BittorrentValidator(BaseValidator):
name = "bittorrent" name = "bittorrent"
@classmethod @classmethod
def validate(cls, config, user, user_password): def validate(cls, config, user, user_password, app):
""" Validates the configuration for using BitTorrent for downloads. """ """ Validates the configuration for using BitTorrent for downloads. """
announce_url = config.get('BITTORRENT_ANNOUNCE_URL') announce_url = config.get('BITTORRENT_ANNOUNCE_URL')
if not announce_url: if not announce_url: