示例#1
0
def login_via_pykube(
        *args: Any,
        logger: Union[logging.Logger, logging.LoggerAdapter],
        **kwargs: Any,
) -> Optional[credentials.ConnectionInfo]:

    try:
        import pykube
    except ImportError:
        return None

    # Read the pykube config either way for later interpretation.
    # DEPRECATED: Previously, in some cases, get_pykube_cfg() was monkey-patched
    # to inject custom authentication methods. Support these hacks if possible.
    config: pykube.KubeConfig
    try:
        with warnings.catch_warnings():
            warnings.simplefilter('ignore', DeprecationWarning)
            config = auth.get_pykube_cfg()
        logger.debug("Pykube is configured via monkey-patched get_pykube_cfg().")
    except NotImplementedError:
        try:
            config = pykube.KubeConfig.from_service_account()
            logger.debug("Pykube is configured in cluster with service account.")
        except FileNotFoundError:
            try:
                config = pykube.KubeConfig.from_file()
                logger.debug("Pykube is configured via kubeconfig file.")
            except (pykube.PyKubeError, FileNotFoundError):
                raise credentials.LoginError(f"Cannot authenticate pykube "
                                             f"neither in-cluster, nor via kubeconfig.")

    # We don't know how this token will be retrieved, we just get it afterwards.
    provider_token = None
    if config.user.get('auth-provider'):
        api = pykube.HTTPClient(config)
        api.get(version='', base='/')  # ignore the response status
        provider_token = config.user.get('auth-provider', {}).get('config', {}).get('access-token')

    # Interpret the config object for our own minimalistic credentials.
    ca: Optional[pykube.config.BytesOrFile] = config.cluster.get('certificate-authority')
    cert: Optional[pykube.config.BytesOrFile] = config.user.get('client-certificate')
    pkey: Optional[pykube.config.BytesOrFile] = config.user.get('client-key')
    return credentials.ConnectionInfo(
        server=config.cluster.get('server'),
        ca_path=ca.filename() if ca else None,  # can be a temporary file
        insecure=config.cluster.get('insecure-skip-tls-verify'),
        username=config.user.get('username'),
        password=config.user.get('password'),
        token=config.user.get('token') or provider_token,
        certificate_path=cert.filename() if cert else None,  # can be a temporary file
        private_key_path=pkey.filename() if pkey else None,  # can be a temporary file
        default_namespace=config.namespace,
        priority=PRIORITY_OF_PYKUBE,
    )
示例#2
0
async def post_event(*, obj=None, ref=None, type, reason, message=''):
    """
    Issue an event for the object.

    This is where they can also be accumulated, aggregated, grouped,
    and where the rate-limits should be maintained. It can (and should)
    be done by the client library, as it is done in the Go client.
    """

    # Object reference - similar to the owner reference, but different.
    if obj is not None and ref is not None:
        raise TypeError(
            "Only one of obj= and ref= is allowed for a posted event. Got both."
        )
    if obj is None and ref is None:
        raise TypeError(
            "One of obj= and ref= is required for a posted event. Got none.")
    if ref is None:
        ref = hierarchies.build_object_reference(obj)

    now = datetime.datetime.utcnow()

    # See #164. For cluster-scoped objects, use the current namespace from the current context.
    # It could be "default", but in some systems, we are limited to one specific namespace only.
    namespace = ref.get('namespace') or auth.get_pykube_cfg().namespace
    if not ref.get('namespace'):
        ref = dict(ref, namespace=namespace)

    # Prevent a common case of event posting errors but shortening the message.
    if len(message) > MAX_MESSAGE_LENGTH:
        infix = CUT_MESSAGE_INFIX
        prefix = message[:MAX_MESSAGE_LENGTH // 2 - (len(infix) // 2)]
        suffix = message[-MAX_MESSAGE_LENGTH // 2 +
                         (len(infix) - len(infix) // 2):]
        message = f'{prefix}{infix}{suffix}'

    body = {
        'metadata': {
            'namespace': namespace,
            'generateName': 'kopf-event-',
        },
        'action': 'Action?',
        'type': type,
        'reason': reason,
        'message': message,
        'reportingComponent': 'kopf',
        'reportingInstance': 'dev',
        'source': {
            'component': 'kopf'
        },  # used in the "From" column in `kubectl describe`.
        'involvedObject': ref,
        'firstTimestamp': now.isoformat() +
        'Z',  # '2019-01-28T18:25:03.000000Z' -- seen in `kubectl describe ...`
        'lastTimestamp': now.isoformat() +
        'Z',  # '2019-01-28T18:25:03.000000Z' - seen in `kubectl get events`
        'eventTime': now.isoformat() + 'Z',  # '2019-01-28T18:25:03.000000Z'
    }

    try:
        api = auth.get_pykube_api()
        obj = pykube.Event(api, body)

        loop = asyncio.get_running_loop()
        await loop.run_in_executor(config.WorkersConfig.get_syn_executor(),
                                   obj.create)

    except (requests.exceptions.HTTPError, pykube.exceptions.HTTPError) as e:
        # Events are helpful but auxiliary, they should not fail the handling cycle.
        # Yet we want to notice that something went wrong (in logs).
        logger.warning(
            "Failed to post an event. Ignoring and continuing. "
            f"Error: {e!r}. "
            f"Event: type={type!r}, reason={reason!r}, message={message!r}.")