Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add checking available resources #114

Merged
merged 5 commits into from
Jan 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions k8s_handle/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@
from k8s_handle import config
from k8s_handle import settings
from k8s_handle import templating
from k8s_handle.exceptions import DeprecationError, ProvisioningError
from k8s_handle.exceptions import DeprecationError, ProvisioningError, ResourceNotAvailableError
from k8s_handle.filesystem import InvalidYamlError
from k8s_handle.k8s.deprecation_checker import ApiDeprecationChecker
from k8s_handle.k8s.provisioner import Provisioner
from k8s_handle.k8s.availability_checker import ResourceAvailabilityChecker, make_resource_getters_list

COMMAND_DEPLOY = 'deploy'
COMMAND_DESTROY = 'destroy'
Expand Down Expand Up @@ -106,9 +107,11 @@ def _handler_provision(command, resources, priority_evaluator, use_kubeconfig, s

try:
deprecation_checker = ApiDeprecationChecker(client.VersionApi().get_code().git_version[1:])
available_checker = ResourceAvailabilityChecker(make_resource_getters_list())

for resource in resources:
deprecation_checker.run(resource)
available_checker.run(resource)
rvadim marked this conversation as resolved.
Show resolved Hide resolved
except client.api_client.ApiException:
log.warning("Error while getting API version, deprecation check will be skipped.")

Expand Down Expand Up @@ -228,11 +231,13 @@ def main():
log.error('{}'.format(e))
sys.exit(1)
except DeprecationError as e:
log.error('Deprecation warning: {}'.format(e))
sys.exit(1)
log.warning('Deprecation warning: {}'.format(e))
except RuntimeError as e:
log.error('RuntimeError: {}'.format(e))
sys.exit(1)
except ResourceNotAvailableError as e:
log.error('Resource not available: {}'.format(e))
sys.exit(1)
except ProvisioningError:
sys.exit(1)

Expand Down
4 changes: 4 additions & 0 deletions k8s_handle/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ class DeprecationError(Exception):
pass


class ResourceNotAvailableError(Exception):
pass


class InvalidYamlError(Exception):
pass

Expand Down
29 changes: 29 additions & 0 deletions k8s_handle/k8s/api_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,32 @@ def list_api_resource_arbitrary(self, group, version):

log.error('{}'.format(add_indent(e.body)))
raise ProvisioningError(e)


class CoreResourcesAPI(client.CoreApi):

def list_api_resources(self, version):
try:
return self.api_client.call_api(
justcrafter marked this conversation as resolved.
Show resolved Hide resolved
resource_path='/api/{}'.format(version),
method='GET',
header_params={
'Accept': self.api_client.select_header_accept(
['application/json', 'application/yaml', 'application/vnd.kubernetes.protobuf']
),
'Content-Type': self.api_client.select_header_content_type(
['application/json', 'application/yaml', 'application/vnd.kubernetes.protobuf']
)
},
response_type='V1APIResourceList',
auth_settings=['BearerToken'],
_return_http_data_only=True,
_preload_content=True,
)
except ApiException as e:
if e.reason == 'Not Found':
log.error('The resource definition with the specified version was not found')
return None

log.error('{}'.format(add_indent(e.body)))
raise ProvisioningError(e)
4 changes: 4 additions & 0 deletions k8s_handle/k8s/availability_checker/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .checker import ResourceAvailabilityChecker
from .resource_getters import make_resource_getters_list

__all__ = ['ResourceAvailabilityChecker', 'make_resource_getters_list']
30 changes: 30 additions & 0 deletions k8s_handle/k8s/availability_checker/checker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from typing import List

from k8s_handle.templating import get_template_contexts
from k8s_handle.exceptions import ResourceNotAvailableError

from .resource_getters import AbstractResourceGetter


class ResourceAvailabilityChecker(object):

def __init__(self, resources_getters: List[AbstractResourceGetter]):
self.resources = resources_getters
self.versions = {}

def _is_available_kind(self, api_group: str, kind: str) -> bool:
kinds = []
for api in self.resources:
if api.is_processable_version(api_group):
kinds = api.get_resources_by_version(api_group)

return kind in kinds

def run(self, file_path: str):
for template_body in get_template_contexts(file_path):
if not self._is_available_kind(template_body.get('apiVersion'), template_body.get('kind')):
raise ResourceNotAvailableError(
"The resource with kind {} is not supported with version {}. File: {}".format(
template_body.get('kind'), template_body.get('apiVersion'), file_path
)
)
3 changes: 3 additions & 0 deletions k8s_handle/k8s/availability_checker/mocks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class MockResource(object):
def __init__(self, kind):
self.kind = kind
71 changes: 71 additions & 0 deletions k8s_handle/k8s/availability_checker/resource_getters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import re
from typing import List, Set

from k8s_handle.k8s.api_extensions import ResourcesAPI, CoreResourcesAPI

core_pattern = re.compile(r'^([^/]+)$')
regular_pattern = re.compile(r'^([^/]+)/([^/]+)$')


class AbstractResourceGetter(object):
def is_processable_version(self, api_group: str) -> bool:
raise NotImplementedError

def get_resources_by_version(self, api_group: str) -> Set[str]:
raise NotImplementedError


class CoreResourceGetter(AbstractResourceGetter):

def __init__(self, resource_api: CoreResourcesAPI):
self.api = resource_api
self.versions = {}

def is_processable_version(self, api_group: str) -> bool:
return core_pattern.match(api_group) is not None

def get_resources_by_version(self, api_group: str) -> Set[str]:
if api_group in self.versions:
return self.versions[api_group]

resp = self.api.list_api_resources(api_group)

if not resp:
return set()

kinds = {r.kind for r in resp.resources}
self.versions[api_group] = kinds

return kinds


class RegularResourceGetter(AbstractResourceGetter):

def __init__(self, resource_api: ResourcesAPI):
self.api = resource_api
self.versions = {}

def is_processable_version(self, api_group: str) -> bool:
return regular_pattern.match(api_group) is not None

def get_resources_by_version(self, api_group: str) -> Set[str]:
if api_group in self.versions:
return self.versions[api_group]

group, version = regular_pattern.findall(api_group).pop()
resp = self.api.list_api_resource_arbitrary(group, version)

if not resp:
return set()

kinds = {r.kind for r in resp.resources}
self.versions[api_group] = kinds

return kinds


def make_resource_getters_list() -> List[AbstractResourceGetter]:
return [
CoreResourceGetter(CoreResourcesAPI()),
RegularResourceGetter(ResourcesAPI())
]
39 changes: 39 additions & 0 deletions k8s_handle/k8s/availability_checker/test_availability_checker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from unittest import TestCase

from .checker import ResourceAvailabilityChecker
from .resource_getters import CoreResourceGetter, RegularResourceGetter
from .mocks import MockResource

from k8s_handle.k8s.mocks import ResourcesAPIMock
from k8s_handle.exceptions import ResourceNotAvailableError


class TestAvailabilityChecker(TestCase):

def setUp(self):
core_getter = CoreResourceGetter(
ResourcesAPIMock(group_version="v1", resources=[MockResource("Pod"), MockResource("CronJob")])
)

regular_getter = RegularResourceGetter(
ResourcesAPIMock(group_version="app/v1", resources=[MockResource("Deployment"), MockResource("Service")])
)

self.checker = ResourceAvailabilityChecker([core_getter, regular_getter])

def test_is_available_kind(self):
self.assertTrue(self.checker._is_available_kind("v1", "Pod"))
self.assertTrue(self.checker._is_available_kind("app/v1", "Service"))
self.assertFalse(self.checker._is_available_kind("v1", "Deployment"))
self.assertFalse(self.checker._is_available_kind("app/v1", "CronJob"))

def test_run_with_valid_version(self):
self.checker.run('k8s_handle/k8s/fixtures/valid_version.yaml')

def test_run_with_invalid_version(self):
with self.assertRaises(ResourceNotAvailableError):
self.checker.run('k8s_handle/k8s/fixtures/invalid_version.yaml')

def test_run_with_unsupported_version(self):
with self.assertRaises(ResourceNotAvailableError):
self.checker.run('k8s_handle/k8s/fixtures/unsupported_version.yaml')
43 changes: 43 additions & 0 deletions k8s_handle/k8s/availability_checker/test_resource_getters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from unittest import TestCase

from k8s_handle.k8s.mocks import ResourcesAPIMock

from .resource_getters import CoreResourceGetter, RegularResourceGetter
from .mocks import MockResource


class TestCoreResourceGetter(TestCase):

def setUp(self):
self.getter = CoreResourceGetter(
ResourcesAPIMock(group_version="v1", resources=[MockResource("Pod"), MockResource("CronJob")])
)

def test_is_processable_version(self):
self.assertTrue(self.getter.is_processable_version("v1"))
self.assertFalse(self.getter.is_processable_version("app/v1"))
self.assertFalse(self.getter.is_processable_version("/"))
self.assertFalse(self.getter.is_processable_version(""))

def test_get_resources_by_version(self):
self.assertSetEqual({"Pod", "CronJob"}, self.getter.get_resources_by_version("v1"))
self.assertSetEqual(set(), self.getter.get_resources_by_version("v2"))


class TestRegularResourceGetter(TestCase):

def setUp(self):
self.getter = RegularResourceGetter(
ResourcesAPIMock(group_version="app/v1", resources=[MockResource("Pod"), MockResource("CronJob")])
)

def test_is_processable_version(self):
self.assertFalse(self.getter.is_processable_version("v1"))
self.assertTrue(self.getter.is_processable_version("app/betav1"))
self.assertTrue(self.getter.is_processable_version("app/v1"))
self.assertFalse(self.getter.is_processable_version("/"))
self.assertFalse(self.getter.is_processable_version(""))

def test_get_resources_by_version(self):
self.assertSetEqual({"Pod", "CronJob"}, self.getter.get_resources_by_version("app/v1"))
self.assertSetEqual(set(), self.getter.get_resources_by_version("app/betav1"))
14 changes: 14 additions & 0 deletions k8s_handle/k8s/fixtures/invalid_version.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
apiVersion: extra/v1
kind: Service
metadata:
name: my-service
spec:
ports:
- name: HTTP
port: 80
protocol: TCP
targetPort: 80
selector:
app: my-app
type: ClusterIP
5 changes: 5 additions & 0 deletions k8s_handle/k8s/fixtures/unsupported_version.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
apiVersion: v1
kind: Deployment
metadata:
name: my-service
14 changes: 14 additions & 0 deletions k8s_handle/k8s/fixtures/valid_version.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
apiVersion: app/v1
kind: Service
metadata:
name: my-service
spec:
ports:
- name: HTTP
port: 80
protocol: TCP
targetPort: 80
selector:
app: my-app
type: ClusterIP
6 changes: 6 additions & 0 deletions k8s_handle/k8s/mocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,3 +314,9 @@ def list_api_resource_arbitrary(self, group, version):
return None

return V1APIResourceList(self._api_version, self._group_version, self._kind, self._resources)

def list_api_resources(self, version):
if not self._resources or self._group_version != version:
return None

return V1APIResourceList(self._api_version, self._group_version, self._kind, self._resources)