196 lines
7.1 KiB
Python
196 lines
7.1 KiB
Python
# Copyright 2017 AT&T Intellectual Property. All other rights reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
"""Module for resolving design references."""
|
|
|
|
import urllib.parse
|
|
import re
|
|
|
|
from oslo_log import log as logging
|
|
import requests
|
|
import yaml
|
|
from kubernetes.client.rest import ApiException
|
|
|
|
from armada.exceptions.source_exceptions import InvalidPathException
|
|
from armada.utils import keystone as ks_utils
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class ReferenceResolver(object):
|
|
"""Class for handling different data references to resolve the data."""
|
|
|
|
@classmethod
|
|
def resolve_reference(cls, design_ref, k8s=None):
|
|
"""Resolve a reference to a design document.
|
|
|
|
Locate a schema handler based on the URI scheme of the data reference
|
|
and use that handler to get the data referenced.
|
|
|
|
:param design_ref: A list of URI-formatted reference to a data entity
|
|
|
|
:returns: A list of byte arrays
|
|
"""
|
|
data = []
|
|
if isinstance(design_ref, str):
|
|
design_ref = [design_ref]
|
|
|
|
for link in design_ref:
|
|
try:
|
|
LOG.debug("Resolving reference %s." % link)
|
|
design_uri = urllib.parse.urlparse(link)
|
|
|
|
# when scheme is a empty string assume it is a local
|
|
# file path
|
|
scheme = design_uri.scheme or 'file'
|
|
handler = cls.scheme_handlers.get(scheme, None)
|
|
handler = handler.__get__(None, cls)
|
|
if design_uri.scheme == 'kube':
|
|
handler_2 = handler
|
|
|
|
def handler_1(design_uri):
|
|
return handler_2(design_uri, k8s)
|
|
|
|
handler = handler_1
|
|
|
|
if handler is None:
|
|
raise InvalidPathException(
|
|
"Invalid reference scheme %s: no handler."
|
|
% design_uri.scheme)
|
|
else:
|
|
# Have to do a little magic to call the classmethod
|
|
# as a pointer
|
|
data.append(handler.__get__(None, cls)(design_uri))
|
|
except ValueError:
|
|
raise InvalidPathException(
|
|
"Cannot resolve design reference %s: unable "
|
|
"to parse as valid URI." % link)
|
|
|
|
return data
|
|
|
|
@classmethod
|
|
def resolve_reference_kube(cls, design_uri, k8s):
|
|
"""Retrieve design documents from kubernetes crd.
|
|
|
|
Return the result of converting the CRD to the armada/Chart/v2 schema.
|
|
|
|
:param design_uri: Tuple as returned by urllib.parse
|
|
for the design reference
|
|
"""
|
|
if design_uri.path != '':
|
|
parts = design_uri.path.split('/')
|
|
if len(parts) != 3:
|
|
raise InvalidPathException(
|
|
"Invalid kubernetes custom resource path segment count {} "
|
|
"for '{}', expected <kind>/<namespace>/<name>".format(
|
|
len(parts), design_uri.path))
|
|
plural, namespace, name = parts
|
|
if plural != 'armadacharts':
|
|
raise InvalidPathException(
|
|
"Invalid kubernetes custom resource kind '{}' for '{}', "
|
|
"only 'armadacharts' are supported".format(
|
|
plural, design_uri.path))
|
|
|
|
try:
|
|
cr = k8s.read_custom_resource(
|
|
group='armada.airshipit.org',
|
|
version='v1alpha1',
|
|
namespace=namespace,
|
|
plural=plural,
|
|
name=name)
|
|
|
|
except ApiException as err:
|
|
if err.status == 404:
|
|
raise InvalidPathException(
|
|
"Kubernetes custom resource not found: plural='{}', "
|
|
"namespace='{}', name='{}', api exception=\n{}".format(
|
|
plural, namespace, name, err.message))
|
|
raise
|
|
|
|
cr['schema'] = 'armada/Chart/v2'
|
|
spec = cr.pop('spec')
|
|
cr['data'] = spec
|
|
return yaml.safe_dump(cr, encoding='utf-8')
|
|
|
|
@classmethod
|
|
def resolve_reference_http(cls, design_uri):
|
|
"""Retrieve design documents from http/https endpoints.
|
|
|
|
Return a byte array of the response content. Support
|
|
unsecured or basic auth
|
|
|
|
:param design_uri: Tuple as returned by urllib.parse
|
|
for the design reference
|
|
"""
|
|
if design_uri.username is not None and design_uri.password is not None:
|
|
response = requests.get(
|
|
design_uri.geturl(),
|
|
auth=(design_uri.username, design_uri.password),
|
|
timeout=30)
|
|
else:
|
|
response = requests.get(design_uri.geturl(), timeout=30)
|
|
if response.status_code >= 400:
|
|
raise InvalidPathException(
|
|
"Error received for HTTP reference: %d"
|
|
% response.status_code)
|
|
|
|
return response.content
|
|
|
|
@classmethod
|
|
def resolve_reference_file(cls, design_uri):
|
|
"""Retrieve design documents from local file endpoints.
|
|
|
|
Return a byte array of the file contents
|
|
|
|
:param design_uri: Tuple as returned by urllib.parse for the design
|
|
reference
|
|
"""
|
|
if design_uri.path != '':
|
|
with open(design_uri.path, 'rb') as f:
|
|
doc = f.read()
|
|
return doc
|
|
|
|
@classmethod
|
|
def resolve_reference_ucp(cls, design_uri):
|
|
"""Retrieve artifacts from a Airship service endpoint.
|
|
|
|
Return a byte array of the response content. Assumes Keystone
|
|
authentication required.
|
|
|
|
:param design_uri: Tuple as returned by urllib.parse for the design
|
|
reference
|
|
"""
|
|
ks_sess = ks_utils.get_keystone_session()
|
|
(new_scheme, foo) = re.subn(r'^[^+]+\+', '', design_uri.scheme)
|
|
url = urllib.parse.urlunparse(
|
|
(
|
|
new_scheme, design_uri.netloc, design_uri.path,
|
|
design_uri.params, design_uri.query, design_uri.fragment))
|
|
LOG.debug("Calling Keystone session for url %s" % str(url))
|
|
resp = ks_sess.get(url)
|
|
if resp.status_code >= 400:
|
|
raise InvalidPathException(
|
|
"Received error code for reference %s: %s - %s" %
|
|
(url, str(resp.status_code), resp.text))
|
|
return resp.content
|
|
|
|
scheme_handlers = {
|
|
'kube': resolve_reference_kube,
|
|
'http': resolve_reference_http,
|
|
'file': resolve_reference_file,
|
|
'https': resolve_reference_http,
|
|
'deckhand+http': resolve_reference_ucp,
|
|
'promenade+http': resolve_reference_ucp,
|
|
}
|