Further fixes to the Kubernetes config provider, and a new set of proper unit tests

This commit is contained in:
Joseph Schorr 2018-05-10 16:44:18 +03:00
parent babb7bb803
commit 2ae69dc651
8 changed files with 181 additions and 107 deletions

View file

@ -1,60 +1,138 @@
import base64
import os
import json
import uuid
import pytest
from mock import Mock
from contextlib import contextmanager
from collections import namedtuple
from httmock import urlmatch, HTTMock
from util.config.provider import KubernetesConfigProvider
from test.fixtures import *
def normalize_path(path):
return path.replace('/', '_')
@contextmanager
def fake_kubernetes_api(tmpdir_factory, files=None):
hostname = 'kubapi'
service_account_token_path = str(tmpdir_factory.mktemp("k8s").join("serviceaccount"))
auth_header = str(uuid.uuid4())
with open(service_account_token_path, 'w') as f:
f.write(auth_header)
global secret
secret = {
'data': {}
}
def write_file(config_dir, filepath, value):
normalized_path = normalize_path(filepath)
absolute_path = str(config_dir.join(normalized_path))
try:
os.makedirs(os.path.dirname(absolute_path))
except OSError:
pass
with open(absolute_path, 'w') as f:
f.write(value)
config_dir = tmpdir_factory.mktemp("config")
if files:
for filepath, value in files.iteritems():
normalized_path = normalize_path(filepath)
write_file(config_dir, filepath, value)
secret['data'][normalized_path] = base64.b64encode(value)
@urlmatch(netloc=hostname,
path='/api/v1/namespaces/quay-enterprise/secrets/quay-enterprise-config-secret$',
method='get')
def get_secret(_, __):
return {'status_code': 200, 'content': json.dumps(secret)}
@urlmatch(netloc=hostname,
path='/api/v1/namespaces/quay-enterprise/secrets/quay-enterprise-config-secret$',
method='put')
def put_secret(_, request):
updated_secret = json.loads(request.body)
for filepath, value in updated_secret['data'].iteritems():
if filepath not in secret['data']:
# Add
write_file(config_dir, filepath, base64.b64decode(value))
for filepath in secret['data']:
if filepath not in updated_secret['data']:
# Remove.
normalized_path = normalize_path(filepath)
os.remove(str(config_dir.join(normalized_path)))
secret['data'] = updated_secret['data']
return {'status_code': 200, 'content': json.dumps(secret)}
@urlmatch(netloc=hostname, path='/api/v1/namespaces/quay-enterprise$')
def get_namespace(_, __):
return {'status_code': 200, 'content': json.dumps({})}
@urlmatch(netloc=hostname)
def catch_all(url, _):
print url
return {'status_code': 404, 'content': '{}'}
with HTTMock(get_secret, put_secret, get_namespace, catch_all):
provider = KubernetesConfigProvider(str(config_dir), 'config.yaml', 'config.py',
api_host=hostname,
service_account_token_path=service_account_token_path)
# Validate all the files.
for filepath, value in files.iteritems():
normalized_path = normalize_path(filepath)
assert provider.volume_file_exists(normalized_path)
with provider.get_volume_file(normalized_path) as f:
assert f.read() == value
yield provider
class TestKubernetesConfigProvider(KubernetesConfigProvider):
def __init__(self):
self.config_volume = ''
self.yaml_filename = 'yaml_filename'
self.py_filename = None
def test_basic_config(tmpdir_factory):
basic_files = {
'config.yaml': 'FOO: bar',
}
self.yaml_path = os.path.join(self.config_volume, self.yaml_filename)
self._service_token = 'service_token'
self._execute_k8s_api = Mock()
with fake_kubernetes_api(tmpdir_factory, files=basic_files) as provider:
assert provider.config_exists()
assert provider.get_config() is not None
assert provider.get_config()['FOO'] == 'bar'
@pytest.mark.parametrize('directory,filename,expected', [
("directory", "file", "directory_file"),
("directory/dir", "file", "directory/dir_file"),
("directory/dir/", "file", "directory/dir_file"),
("directory", "file/test", "directory_file/test"),
@pytest.mark.parametrize('filepath', [
'foo',
'foo/meh',
'foo/bar/baz',
])
def test_get_volume_path(directory, filename, expected):
provider = TestKubernetesConfigProvider()
assert expected == provider.get_volume_path(directory, filename)
def test_remove_file(filepath, tmpdir_factory):
basic_files = {
filepath: 'foo',
}
with fake_kubernetes_api(tmpdir_factory, files=basic_files) as provider:
normalized_path = normalize_path(filepath)
assert provider.volume_file_exists(normalized_path)
provider.remove_volume_file(normalized_path)
assert not provider.volume_file_exists(normalized_path)
@pytest.mark.parametrize('response,expected', [
(Mock(text="{\"data\": {\"license\":\"test\"}}", status_code=200), {"data": {"license":"test"}}),
(Mock(text="{\"data\": {\"license\":\"test\"}}", status_code=404), None),
])
def test_lookup_secret(response, expected):
provider = TestKubernetesConfigProvider()
provider._execute_k8s_api.return_value = response
assert expected == provider._lookup_secret()
@pytest.mark.parametrize('response,key,expected', [
(Mock(text="{\"data\": {\"license\":\"test\"}}", status_code=200), "license", True),
(Mock(text="{\"data\": {\"license\":\"test\"}}", status_code=200), "config.yaml", False),
(Mock(text="", status_code=404), "license", False),
])
def test_volume_file_exists(response, key, expected):
provider = TestKubernetesConfigProvider()
provider._execute_k8s_api.return_value = response
assert expected == provider.volume_file_exists(key)
class TestFlaskFile(object):
def save(self, buf):
buf.write('hello world!')
@pytest.mark.parametrize('response,expected', [
(Mock(text="{\"data\": {\"extra_license\":\"test\"}}", status_code=200), ["license"]),
(Mock(text="", status_code=404), []),
])
def test_list_volume_directory(response, expected):
provider = TestKubernetesConfigProvider()
provider._execute_k8s_api.return_value = response
assert expected == provider.list_volume_directory("extra")
def test_save_file(tmpdir_factory):
basic_files = {}
with fake_kubernetes_api(tmpdir_factory, files=basic_files) as provider:
assert not provider.volume_file_exists('testfile')
flask_file = TestFlaskFile()
provider.save_volume_file(flask_file, 'testfile')
assert provider.volume_file_exists('testfile')