class DynamicClient(object):
    def __init__(self):
        config.load_kube_config()
        self.client = ApiClient()
        self._groups = self.get_api_groups()
        self._resources = flatten([
            self.get_resources_for_group(*group_parts)
            for group_parts in self._groups
        ])

    def default_groups(self):
        groups = [('api', '', 'v1', True)]

        try:
            self.request('get', '/version/openshift')
            is_openshift = True
        except ApiException:
            is_openshift = False

        if is_openshift:
            groups.append(('oapi', '', 'v1', True))
        return groups

    def get_api_groups(self):
        """ Returns a list of API groups in the format:
            (api_prefix, group_name, version, preferred)

            api_prefix is the url prefix to access the group (usually /apis, but
            /api for core kubernetes resources and /oapi for core openshift resources)

            group_name and version are the name and version of the group

            preferred is a boolean indicating whether this version of the group is preferred
        """
        prefix = 'apis'
        groups_response = self.request('GET', '/{}'.format(prefix))['groups']

        groups = self.default_groups()

        for group in groups_response:
            for version in group['versions']:
                groups.append([
                    prefix, group['name'], version['version'],
                    version == group['preferredVersion']
                ])

        return groups

    def get_resources_for_group(self,
                                prefix,
                                group,
                                apiversion,
                                preferred=False):
        """ returns the list of resources associated with provided groupVersion"""

        path = '/'.join(filter(None, [prefix, group, apiversion]))
        resources_response = self.request('GET', path)['resources']

        # Filter out subresources
        resources_raw = filter(lambda resource: '/' not in resource['name'],
                               resources_response)

        resources = []
        for resource in resources_raw:
            resources.append(
                Resource.make_resource(prefix,
                                       group,
                                       apiversion,
                                       resource,
                                       client=self,
                                       preferred=preferred))
        return resources

    def search_resources(self, conditional):
        """ Takes a conditional test and returns a list of resources that satisfy the test
            The test should take an object of the Resource type as an argument,
            and return a boolean

        Ex:
            def is_namespaces(resource):
                return resource.name == 'namespaces'

            client.search_resources(is_namespaces)

        will return all resources with the name 'namespaces'
        """
        return list(filter(conditional, self._resources))

    def list(self, resource, namespace=None):
        path_params = {}
        if resource.namespaced and namespace:
            resource_path = resource.urls['namespaced_base']
            path_params['namespace'] = namespace
        else:
            resource_path = resource.urls['base']
        return ResourceInstance(
            resource,
            self.request('get', resource_path, path_params=path_params))

    def get(self, resource, name=None, namespace=None):
        if name is None:
            return self.list(resource, namespace=namespace)
        path_params = {'name': name}
        if resource.namespaced and namespace:
            resource_path = resource.urls['namespaced_full']
            path_params['namespace'] = namespace
        else:
            resource_path = resource.urls['full']
        return ResourceInstance(
            resource,
            self.request('get', resource_path, path_params=path_params))

    def create(self, resource, body, namespace=None):
        path_params = {}
        if resource.namespaced and namespace:
            resource_path = resource.urls['namespaced_base']
            path_params['namespace'] = namespace
        elif resource.namespaced and not namespace:
            if body.get('metadata') and body['metadata'].get('namespace'):
                resource_path = resource.urls['namespaced_base']
                path_params['namespace'] = body['metadata']['namespace']
        else:
            resource_path = resource.urls['base']
        return ResourceInstance(
            resource,
            self.request('post',
                         resource_path,
                         path_params=path_params,
                         body=body))

    def delete(self, resource, name, namespace=None):
        path_params = {'name': name}
        if resource.namespaced and namespace:
            resource_path = resource.urls['namespaced_full']
            path_params['namespace'] = namespace
        else:
            resource_path = resource.urls['full']
        return ResourceInstance(
            resource,
            self.request('delete', resource_path, path_params=path_params))

    def replace(self, resource, body, name=None, namespace=None):
        if name is None:
            name = body['metadata']['name']
        path_params = {'name': name}
        if resource.namespaced and namespace:
            resource_path = resource.urls['namespaced_full']
            path_params['namespace'] = namespace
        elif resource.namespaced and not namespace:
            if body.get('metadata') and body['metadata'].get('namespace'):
                resource_path = resource.urls['namespaced_full']
                path_params['namespace'] = body['metadata']['namespace']
        else:
            resource_path = resource.urls['full']

        return ResourceInstance(
            resource,
            self.request('put',
                         resource_path,
                         path_params=path_params,
                         body=body))

    def update(self, resource, body, name=None, namespace=None):
        if name is None:
            name = body['metadata']['name']
        path_params = {'name': name}
        if resource.namespaced and namespace:
            resource_path = resource.urls['namespaced_full']
            path_params['namespace'] = namespace
        elif resource.namespaced and not namespace:
            if body.get('metadata') and body['metadata'].get('namespace'):
                resource_path = resource.urls['namespaced_full']
                path_params['namespace'] = body['metadata']['namespace']
        else:
            resource_path = resource.urls['full']
        content_type = self.client.\
            select_header_content_type(['application/json-patch+json', 'application/merge-patch+json', 'application/strategic-merge-patch+json'])

        return ResourceInstance(
            resource,
            self.request('patch',
                         resource_path,
                         path_params=path_params,
                         body=body,
                         content_type=content_type))

    def request(self, method, path, body=None, **params):

        if not path.startswith('/'):
            path = '/' + path

        path_params = params.get('path_params', {})
        query_params = []
        if 'pretty' in params:
            query_params.append(('pretty', params['pretty']))
        header_params = {}
        form_params = []
        local_var_files = {}
        # HTTP header `Accept`
        header_params['Accept'] = self.client.select_header_accept([
            'application/json', 'application/yaml',
            'application/vnd.kubernetes.protobuf'
        ])

        # HTTP header `Content-Type`
        if params.get('content_type'):
            header_params['Content-Type'] = params['content_type']
        else:
            header_params[
                'Content-Type'] = self.client.select_header_content_type(
                    ['*/*'])

        # Authentication setting
        auth_settings = ['BearerToken']

        return json.loads(
            self.client.call_api(path,
                                 method.upper(),
                                 path_params,
                                 query_params,
                                 header_params,
                                 body=body,
                                 post_params=form_params,
                                 files=local_var_files,
                                 auth_settings=auth_settings,
                                 _preload_content=False)[0].data)
Esempio n. 2
0
class Kubernetes:
    """Get some additional the information about the kubernetes contest."""
    def __init__(self) -> None:
        if os.path.exists(SERVICE_TOKEN_FILENAME):
            load_incluster_config()
        else:
            load_kube_config()
        self.api = ApiClient()
        version_api = VersionApi(self.api)
        self._is_openshift = "eks" not in version_api.get_code().git_version

    def get_pod_infos(self) -> Dict[Any, Dict[str, Any]]:
        if NAMESPACE is None:
            results = {}
            namespaces = self.get_namespaces()
            for namespace in namespaces:
                results.update(self._get_pod_infos_ns(namespace))
            return results
        else:
            return self._get_pod_infos_ns(NAMESPACE)

    def _get_pod_infos_ns(self, namespace: str) -> Dict[Any, Dict[str, Any]]:
        v1 = CoreV1Api(self.api)
        results = {}
        pods: V1PodList = v1.list_namespaced_pod(namespace)
        for pod in pods.items:
            md = pod.metadata
            status = pod.status
            containers = {}
            for statuses in (status.container_statuses,
                             status.init_container_statuses):
                if statuses is None:
                    continue
                for container_status in statuses:
                    for location in (
                            container_status,
                            container_status.last_state.terminated,
                            container_status.state.terminated,
                    ):
                        if location is not None and location.container_id is not None:
                            containers[location.container_id.replace(
                                "docker://", "")] = container_status.name
            results[md.uid] = {
                "namespace":
                md.namespace,
                "release":
                md.labels.get("release",
                              md.labels.get("app.kubernetes.io/instance")),
                "service":
                md.labels.get("service",
                              md.labels.get("app.kubernetes.io/name")),
                "pod_name":
                md.name,
                "containers":
                containers,
            }
        return results

    def get_namespaces(self) -> List[Any]:
        if self._is_openshift:
            data, status, _headers = self.api.call_api(
                "/apis/project.openshift.io/v1/projects",
                "GET",
                auth_settings=["BearerToken"],
                response_type=object,
            )
            assert status == 200  # nosec
            assert data["kind"] == "ProjectList"  # nosec
            return [ns["metadata"]["name"] for ns in data["items"]]
        else:
            v1 = CoreV1Api(self.api)
            namespaces = v1.list_namespace()
            return [ns.metadata.name for ns in namespaces.items]