Add config ability to rollback changes on kube
This commit is contained in:
parent
2b59432414
commit
9695c98e5f
15 changed files with 237 additions and 63 deletions
|
@ -1,13 +1,11 @@
|
|||
import os
|
||||
import base64
|
||||
|
||||
from shutil import copytree
|
||||
from backports.tempfile import TemporaryDirectory
|
||||
|
||||
from config_app.config_util.config.fileprovider import FileConfigProvider
|
||||
from config_app.config_util.k8saccessor import KubernetesAccessorSingleton
|
||||
from util.config.validator import EXTRA_CA_DIRECTORY, EXTRA_CA_DIRECTORY_PREFIX
|
||||
|
||||
|
||||
OLD_CONFIG_SUBDIR = 'old/'
|
||||
|
||||
class TransientDirectoryProvider(FileConfigProvider):
|
||||
""" Implementation of the config provider that reads and writes the data
|
||||
|
@ -21,6 +19,7 @@ class TransientDirectoryProvider(FileConfigProvider):
|
|||
# no uploaded config should ever affect subsequent config modifications/creations
|
||||
temp_dir = TemporaryDirectory()
|
||||
self.temp_dir = temp_dir
|
||||
self.old_config_dir = None
|
||||
super(TransientDirectoryProvider, self).__init__(temp_dir.name, yaml_filename, py_filename)
|
||||
|
||||
@property
|
||||
|
@ -38,29 +37,26 @@ class TransientDirectoryProvider(FileConfigProvider):
|
|||
self.temp_dir = temp_dir
|
||||
self.yaml_path = os.path.join(temp_dir.name, self.yaml_filename)
|
||||
|
||||
def create_copy_of_config_dir(self):
|
||||
"""
|
||||
Create a directory to store loaded/populated configuration (for rollback if necessary)
|
||||
"""
|
||||
if self.old_config_dir is not None:
|
||||
self.old_config_dir.cleanup()
|
||||
|
||||
temp_dir = TemporaryDirectory()
|
||||
self.old_config_dir = temp_dir
|
||||
|
||||
# Python 2.7's shutil.copy() doesn't allow for copying to existing directories,
|
||||
# so when copying/reading to the old saved config, we have to talk to a subdirectory,
|
||||
# and use the shutil.copytree() function
|
||||
copytree(self.config_volume, os.path.join(temp_dir.name, OLD_CONFIG_SUBDIR))
|
||||
|
||||
def get_config_dir_path(self):
|
||||
return self.config_volume
|
||||
|
||||
def save_configuration_to_kubernetes(self):
|
||||
data = {}
|
||||
def get_old_config_dir(self):
|
||||
if self.old_config_dir is None:
|
||||
raise Exception('Cannot return a configuration that was no old configuration')
|
||||
|
||||
# Kubernetes secrets don't have sub-directories, so for the extra_ca_certs dir
|
||||
# we have to put the extra certs in with a prefix, and then one of our init scripts
|
||||
# (02_get_kube_certs.sh) will expand the prefixed certs into the equivalent directory
|
||||
# so that they'll be installed correctly on startup by the certs_install script
|
||||
certs_dir = os.path.join(self.config_volume, EXTRA_CA_DIRECTORY)
|
||||
if os.path.exists(certs_dir):
|
||||
for extra_cert in os.listdir(certs_dir):
|
||||
with open(os.path.join(certs_dir, extra_cert)) as f:
|
||||
data[EXTRA_CA_DIRECTORY_PREFIX + extra_cert] = base64.b64encode(f.read())
|
||||
|
||||
|
||||
for name in os.listdir(self.config_volume):
|
||||
file_path = os.path.join(self.config_volume, name)
|
||||
if not os.path.isdir(file_path):
|
||||
with open(file_path) as f:
|
||||
data[name] = base64.b64encode(f.read())
|
||||
|
||||
KubernetesAccessorSingleton.get_instance().replace_qe_secret(data)
|
||||
|
||||
return 200
|
||||
return os.path.join(self.old_config_dir.name, OLD_CONFIG_SUBDIR)
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import base64
|
||||
import os
|
||||
|
||||
from config_app.config_util.config.fileprovider import FileConfigProvider
|
||||
from config_app.config_util.config.testprovider import TestConfigProvider
|
||||
from config_app.config_util.config.TransientDirectoryProvider import TransientDirectoryProvider
|
||||
from util.config.validator import EXTRA_CA_DIRECTORY, EXTRA_CA_DIRECTORY_PREFIX
|
||||
|
||||
|
||||
def get_config_provider(config_volume, yaml_filename, py_filename, testing=False):
|
||||
|
@ -10,3 +14,26 @@ def get_config_provider(config_volume, yaml_filename, py_filename, testing=False
|
|||
return TestConfigProvider()
|
||||
|
||||
return TransientDirectoryProvider(config_volume, yaml_filename, py_filename)
|
||||
|
||||
|
||||
def get_config_as_kube_secret(config_path):
|
||||
data = {}
|
||||
|
||||
# Kubernetes secrets don't have sub-directories, so for the extra_ca_certs dir
|
||||
# we have to put the extra certs in with a prefix, and then one of our init scripts
|
||||
# (02_get_kube_certs.sh) will expand the prefixed certs into the equivalent directory
|
||||
# so that they'll be installed correctly on startup by the certs_install script
|
||||
certs_dir = os.path.join(config_path, EXTRA_CA_DIRECTORY)
|
||||
if os.path.exists(certs_dir):
|
||||
for extra_cert in os.listdir(certs_dir):
|
||||
with open(os.path.join(certs_dir, extra_cert)) as f:
|
||||
data[EXTRA_CA_DIRECTORY_PREFIX + extra_cert] = base64.b64encode(f.read())
|
||||
|
||||
|
||||
for name in os.listdir(config_path):
|
||||
file_path = os.path.join(config_path, name)
|
||||
if not os.path.isdir(file_path):
|
||||
with open(file_path) as f:
|
||||
data[name] = base64.b64encode(f.read())
|
||||
|
||||
return data
|
||||
|
|
|
@ -21,6 +21,9 @@ QE_CONTAINER_NAME = 'quay-enterprise-app'
|
|||
# message is any string describing the state.
|
||||
DeploymentRolloutStatus = namedtuple('DeploymentRolloutStatus', ['status', 'message'])
|
||||
|
||||
class K8sApiException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def _deployment_rollout_status_message(deployment, deployment_name):
|
||||
"""
|
||||
|
@ -225,11 +228,24 @@ class KubernetesAccessorSingleton(object):
|
|||
}
|
||||
}, api_prefix='apis/extensions/v1beta1', content_type='application/strategic-merge-patch+json'))
|
||||
|
||||
def _assert_success(self, response):
|
||||
if response.status_code != 200:
|
||||
def rollback_deployment(self, deployment_name):
|
||||
deployment_rollback_url = 'namespaces/%s/deployments/%s/rollback' % (
|
||||
self.kube_config.qe_namespace, deployment_name
|
||||
)
|
||||
|
||||
self._assert_success(self._execute_k8s_api('POST', deployment_rollback_url, {
|
||||
'name': deployment_name,
|
||||
'rollbackTo': {
|
||||
# revision=0 makes the deployment rollout to the previous revision
|
||||
'revision': 0
|
||||
}
|
||||
}, api_prefix='apis/extensions/v1beta1'), 201)
|
||||
|
||||
def _assert_success(self, response, expected_code=200):
|
||||
if response.status_code != expected_code:
|
||||
logger.error('Kubernetes API call failed with response: %s => %s', response.status_code,
|
||||
response.text)
|
||||
raise Exception('Kubernetes API call failed: %s' % response.text)
|
||||
raise K8sApiException('Kubernetes API call failed: %s' % response.text)
|
||||
|
||||
def _update_secret_file(self, relative_file_path, value=None):
|
||||
if '/' in relative_file_path:
|
||||
|
|
Reference in a new issue