Add ability to populate config from kube cluster
This commit is contained in:
parent
8a83bf7c81
commit
df25ec0061
7 changed files with 126 additions and 51 deletions
|
@ -152,8 +152,9 @@ nickname = partial(add_method_metadata, 'nickname')
|
||||||
|
|
||||||
import config_endpoints.api
|
import config_endpoints.api
|
||||||
import config_endpoints.api.discovery
|
import config_endpoints.api.discovery
|
||||||
|
import config_endpoints.api.kubeconfig
|
||||||
import config_endpoints.api.suconfig
|
import config_endpoints.api.suconfig
|
||||||
import config_endpoints.api.superuser
|
import config_endpoints.api.superuser
|
||||||
import config_endpoints.api.user
|
|
||||||
import config_endpoints.api.tar_config_loader
|
import config_endpoints.api.tar_config_loader
|
||||||
|
import config_endpoints.api.user
|
||||||
|
|
||||||
|
|
69
config_app/config_endpoints/api/kubeconfig.py
Normal file
69
config_app/config_endpoints/api/kubeconfig.py
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
from flask import request
|
||||||
|
|
||||||
|
from data.database import configure
|
||||||
|
|
||||||
|
from config_app.c_app import app, config_provider
|
||||||
|
from config_app.config_endpoints.api import resource, ApiResource, nickname, kubernetes_only
|
||||||
|
from config_app.config_util.k8saccessor import KubernetesAccessorSingleton
|
||||||
|
|
||||||
|
|
||||||
|
@resource('/v1/kubernetes/deployments/')
|
||||||
|
class SuperUserKubernetesDeployment(ApiResource):
|
||||||
|
""" Resource for the getting the status of Quay Enterprise deployments and cycling them """
|
||||||
|
schemas = {
|
||||||
|
'ValidateDeploymentNames': {
|
||||||
|
'type': 'object',
|
||||||
|
'description': 'Validates deployment names for cycling',
|
||||||
|
'required': [
|
||||||
|
'deploymentNames'
|
||||||
|
],
|
||||||
|
'properties': {
|
||||||
|
'deploymentNames': {
|
||||||
|
'type': 'array',
|
||||||
|
'description': 'The names of the deployments to cycle'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@kubernetes_only
|
||||||
|
@nickname('scGetNumDeployments')
|
||||||
|
def get(self):
|
||||||
|
return KubernetesAccessorSingleton.get_instance().get_qe_deployments()
|
||||||
|
|
||||||
|
@kubernetes_only
|
||||||
|
@nickname('scCycleQEDeployments')
|
||||||
|
def put(self):
|
||||||
|
deployment_names = request.get_json()['deploymentNames']
|
||||||
|
return KubernetesAccessorSingleton.get_instance().cycle_qe_deployments(deployment_names)
|
||||||
|
|
||||||
|
|
||||||
|
@resource('/v1/superuser/config/kubernetes')
|
||||||
|
class SuperUserKubernetesConfiguration(ApiResource):
|
||||||
|
""" Resource for saving the config files to kubernetes secrets. """
|
||||||
|
|
||||||
|
@kubernetes_only
|
||||||
|
@nickname('scDeployConfiguration')
|
||||||
|
def post(self):
|
||||||
|
return config_provider.save_configuration_to_kubernetes()
|
||||||
|
|
||||||
|
|
||||||
|
@resource('/v1/kubernetes/config/populate')
|
||||||
|
class KubernetesConfigurationPopulator(ApiResource):
|
||||||
|
""" Resource for populating the local configuration from the cluster's kubernetes secrets. """
|
||||||
|
|
||||||
|
@kubernetes_only
|
||||||
|
@nickname('scKubePopulateConfig')
|
||||||
|
def post(self):
|
||||||
|
# Get a clean transient directory to write the config into
|
||||||
|
config_provider.new_config_dir()
|
||||||
|
KubernetesAccessorSingleton.get_instance().save_secret_to_directory(config_provider.get_config_dir_path())
|
||||||
|
|
||||||
|
# We update the db configuration to connect to their specified one
|
||||||
|
# (Note, even if this DB isn't valid, it won't affect much in the config app, since we'll report an error,
|
||||||
|
# and all of the options create a new clean dir, so we'll never pollute configs)
|
||||||
|
combined = dict(**app.config)
|
||||||
|
combined.update(config_provider.get_config())
|
||||||
|
configure(combined)
|
||||||
|
|
||||||
|
return 200
|
|
@ -3,11 +3,9 @@ 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, \
|
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 data.database import configure
|
from data.database import configure
|
||||||
from data.runmigration import run_alembic_migration
|
from data.runmigration import run_alembic_migration
|
||||||
|
@ -265,47 +263,6 @@ class SuperUserConfigValidate(ApiResource):
|
||||||
return validate_service_for_config(service, validator_context)
|
return validate_service_for_config(service, validator_context)
|
||||||
|
|
||||||
|
|
||||||
@resource('/v1/kubernetes/deployments/')
|
|
||||||
class SuperUserKubernetesDeployment(ApiResource):
|
|
||||||
""" Resource for the getting the status of Quay Enterprise deployments and cycling them """
|
|
||||||
schemas = {
|
|
||||||
'ValidateDeploymentNames': {
|
|
||||||
'type': 'object',
|
|
||||||
'description': 'Validates deployment names for cycling',
|
|
||||||
'required': [
|
|
||||||
'deploymentNames'
|
|
||||||
],
|
|
||||||
'properties': {
|
|
||||||
'deploymentNames': {
|
|
||||||
'type': 'array',
|
|
||||||
'description': 'The names of the deployments to cycle'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@kubernetes_only
|
|
||||||
@nickname('scGetNumDeployments')
|
|
||||||
def get(self):
|
|
||||||
return KubernetesAccessorSingleton.get_instance().get_qe_deployments()
|
|
||||||
|
|
||||||
@kubernetes_only
|
|
||||||
@nickname('scCycleQEDeployments')
|
|
||||||
def put(self):
|
|
||||||
deployment_names = request.get_json()['deploymentNames']
|
|
||||||
return KubernetesAccessorSingleton.get_instance().cycle_qe_deployments(deployment_names)
|
|
||||||
|
|
||||||
|
|
||||||
@resource('/v1/superuser/config/kubernetes')
|
|
||||||
class SuperUserKubernetesConfiguration(ApiResource):
|
|
||||||
""" Resource for saving the config files to kubernetes secrets. """
|
|
||||||
|
|
||||||
@kubernetes_only
|
|
||||||
@nickname('scDeployConfiguration')
|
|
||||||
def post(self):
|
|
||||||
return config_provider.save_configuration_to_kubernetes()
|
|
||||||
|
|
||||||
|
|
||||||
@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. """
|
||||||
|
|
|
@ -2,8 +2,10 @@ import logging
|
||||||
import json
|
import json
|
||||||
import base64
|
import base64
|
||||||
import datetime
|
import datetime
|
||||||
|
import os
|
||||||
|
|
||||||
from requests import Request, Session
|
from requests import Request, Session
|
||||||
|
from util.config.validator import EXTRA_CA_DIRECTORY, EXTRA_CA_DIRECTORY_PREFIX
|
||||||
|
|
||||||
from config_app.config_util.k8sconfig import KubernetesConfig
|
from config_app.config_util.k8sconfig import KubernetesConfig
|
||||||
|
|
||||||
|
@ -35,6 +37,30 @@ class KubernetesAccessorSingleton(object):
|
||||||
|
|
||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
|
def save_secret_to_directory(self, dir_path):
|
||||||
|
"""
|
||||||
|
Saves all files in the kubernetes secret to a local directory.
|
||||||
|
Assumes the directory is empty.
|
||||||
|
"""
|
||||||
|
secret = self._lookup_secret()
|
||||||
|
|
||||||
|
secret_data = secret.get('data', {})
|
||||||
|
|
||||||
|
# Make the `extra_ca_certs` dir to ensure we can populate extra certs
|
||||||
|
extra_ca_dir_path = os.path.join(dir_path, EXTRA_CA_DIRECTORY)
|
||||||
|
os.mkdir(extra_ca_dir_path)
|
||||||
|
|
||||||
|
for secret_filename, data in secret_data.iteritems():
|
||||||
|
write_path = os.path.join(dir_path, secret_filename)
|
||||||
|
|
||||||
|
if EXTRA_CA_DIRECTORY_PREFIX in secret_filename:
|
||||||
|
write_path = os.path.join(extra_ca_dir_path, secret_filename.replace(EXTRA_CA_DIRECTORY_PREFIX, ''))
|
||||||
|
|
||||||
|
with open(write_path, 'w') as f:
|
||||||
|
f.write(base64.b64decode(data))
|
||||||
|
|
||||||
|
return 200
|
||||||
|
|
||||||
def save_file_as_secret(self, name, file_pointer):
|
def save_file_as_secret(self, name, file_pointer):
|
||||||
value = file_pointer.read()
|
value = file_pointer.read()
|
||||||
self._update_secret_file(name, value)
|
self._update_secret_file(name, value)
|
||||||
|
|
|
@ -15,7 +15,6 @@ export class ConfigSetupAppComponent {
|
||||||
: 'choice'
|
: 'choice'
|
||||||
| 'setup'
|
| 'setup'
|
||||||
| 'load'
|
| 'load'
|
||||||
| 'populate'
|
|
||||||
| 'download'
|
| 'download'
|
||||||
| 'deploy';
|
| 'deploy';
|
||||||
|
|
||||||
|
@ -45,7 +44,15 @@ export class ConfigSetupAppComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
private choosePopulate(): void {
|
private choosePopulate(): void {
|
||||||
this.state = 'populate';
|
this.apiService.scKubePopulateConfig()
|
||||||
|
.then(() => {
|
||||||
|
this.state = 'setup';
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
this.apiService.errorDisplay(
|
||||||
|
`Could not populate the configuration from your cluster. Please report this error: ${JSON.stringify(err)}`
|
||||||
|
)()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private configLoaded(): void {
|
private configLoaded(): void {
|
||||||
|
|
|
@ -209,19 +209,19 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Footer: SUPERUSER_ERROR -->
|
<!-- Footer: SUPERUSER_ERROR -->
|
||||||
<div class="modal-footer alert alert-warning"
|
<div class="modal-footer alert alert-danger"
|
||||||
ng-show="isStep(currentStep, States.SUPERUSER_ERROR)">
|
ng-show="isStep(currentStep, States.SUPERUSER_ERROR)">
|
||||||
{{ errors.SuperuserCreationError }}
|
{{ errors.SuperuserCreationError }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Footer: DB_SETUP_ERROR -->
|
<!-- Footer: DB_SETUP_ERROR -->
|
||||||
<div class="modal-footer alert alert-warning"
|
<div class="modal-footer alert alert-danger"
|
||||||
ng-show="isStep(currentStep, States.DB_SETUP_ERROR)">
|
ng-show="isStep(currentStep, States.DB_SETUP_ERROR)">
|
||||||
Database Setup Failed. Please report this to support: {{ errors.DatabaseSetupError }}
|
Database Setup Failed: {{ errors.DatabaseSetupError }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Footer: DB_ERROR -->
|
<!-- Footer: DB_ERROR -->
|
||||||
<div class="modal-footer alert alert-warning" ng-show="isStep(currentStep, States.DB_ERROR)">
|
<div class="modal-footer alert alert-danger" ng-show="isStep(currentStep, States.DB_ERROR)">
|
||||||
Database Validation Issue: {{ errors.DatabaseValidationError }}
|
Database Validation Issue: {{ errors.DatabaseValidationError }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,21 @@
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.quay-config-app .alert-danger {
|
||||||
|
padding: 25px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quay-config-app .alert-danger:before {
|
||||||
|
content: "\f071";
|
||||||
|
font-family: Font Awesome\ 5 Free;
|
||||||
|
font-weight: 900;
|
||||||
|
font-size: 30px;
|
||||||
|
padding-right: 15px;
|
||||||
|
color: #c53c3f;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
/* Overrides for fixing old quay styles*/
|
/* Overrides for fixing old quay styles*/
|
||||||
|
|
||||||
|
|
||||||
|
|
Reference in a new issue