def get_control_plane(self):
     crd_api = CustomObjectsApi(self.management_kube_api_client)
     return crd_api.get_namespaced_custom_object(
         group=HyperShift.HYPERSHIFT_API_GROUP,
         version=HyperShift.HYPERSHIFT_API_VERSION,
         plural=HyperShift.HOSTED_CONTROL_PLANE_PLOURAL,
         name=self.name,
         namespace=self.namespace,
     )
示例#2
0
    def get_pod_stats(self, tenant, vnf_uuid):

        cust = CustomObjectsApi()
        data = ['metrics.k8s.io', 'v1beta1', tenant, 'pods']
        try:
            if vnf_uuid:
                data.append(self.vnfs[vnf_uuid].name)
                return cust.get_namespaced_custom_object(*data)
            else:
                return cust.list_namespaced_custom_object(*data)
        except ApiException as ex:
            raise KeyError(ex)
示例#3
0
def read_policy(custom_objects: CustomObjectsApi, namespace, name) -> object:
    """
    Get policy information (kubectl describe output)

    :param custom_objects: CustomObjectsApi
    :param namespace: The policy's namespace	
    :param name: policy's name
    :return: object
    """
    print(f"Getting info for policy {name} in namespace {namespace}")
    try:
        response = custom_objects.get_namespaced_custom_object(
            "k8s.nginx.org", "v1", namespace, "policies", name)
        pprint(response)
        return response

    except ApiException:
        logging.exception(f"Exception occurred while reading Policy")
        raise
def read_ap_crd(custom_objects: CustomObjectsApi, namespace, plural, name) -> object:
    """
    Get AppProtect CRD information (kubectl describe output)
    :param custom_objects: CustomObjectsApi
    :param namespace: The custom resource's namespace	
    :param plural: the custom resource's plural name
    :param name: the custom object's name
    :return: object
    """
    print(f"Getting info for {name} in namespace {namespace}")
    try:
        response = custom_objects.get_namespaced_custom_object(
            "appprotect.f5.com", "v1beta1", namespace, plural, name
        )
        return response

    except ApiException:
        logging.exception(f"Exception occurred while reading CRD")
        raise
示例#5
0
def read_custom_resource_v1alpha1(custom_objects: CustomObjectsApi, namespace,
                                  plural, name) -> object:
    """
    Get CRD information (kubectl describe output)

    :param custom_objects: CustomObjectsApi
    :param namespace: The custom resource's namespace
    :param plural: the custom resource's plural name
    :param name: the custom object's name
    :return: object
    """
    print(f"Getting info for v1alpha1 crd {name} in namespace {namespace}")
    try:
        response = custom_objects.get_namespaced_custom_object(
            "k8s.nginx.org", "v1alpha1", namespace, plural, name)
        pprint(response)
        return response

    except ApiException:
        logging.exception(f"Exception occurred while reading CRD")
        raise
示例#6
0
class TraefikMiddlewareReconciler(KubernetesObjectReconciler[TraefikMiddleware]
                                  ):
    """Kubernetes Traefik Middleware Reconciler"""
    def __init__(self, controller: "KubernetesController") -> None:
        super().__init__(controller)
        self.api_ex = ApiextensionsV1Api(controller.client)
        self.api = CustomObjectsApi(controller.client)

    def _crd_exists(self) -> bool:
        """Check if the traefik middleware exists"""
        return bool(
            len(
                self.api_ex.list_custom_resource_definition(
                    field_selector=f"metadata.name={CRD_NAME}").items))

    def reconcile(self, current: TraefikMiddleware,
                  reference: TraefikMiddleware):
        super().reconcile(current, reference)
        if current.spec.forwardAuth.address != reference.spec.forwardAuth.address:
            raise NeedsUpdate()

    def get_reference_object(self) -> TraefikMiddleware:
        """Get deployment object for outpost"""
        if not ProxyProvider.objects.filter(
                outpost__in=[self.controller.outpost],
                forward_auth_mode=True,
        ).exists():
            self.logger.debug("No providers with forward auth enabled.")
            raise Disabled()
        if not self._crd_exists():
            self.logger.debug("CRD doesn't exist")
            raise Disabled()
        return TraefikMiddleware(
            apiVersion=f"{CRD_GROUP}/{CRD_VERSION}",
            kind="Middleware",
            metadata=TraefikMiddlewareMetadata(
                name=self.name,
                namespace=self.namespace,
                labels=self.get_object_meta().labels,
            ),
            spec=
            TraefikMiddlewareSpec(forwardAuth=TraefikMiddlewareSpecForwardAuth(
                address=
                f"http://{self.name}.{self.namespace}:4180/akprox/auth?traefik",
                authResponseHeaders=[
                    "Set-Cookie",
                    "X-Auth-Username",
                    "X-Forwarded-Email",
                    "X-Forwarded-Preferred-Username",
                    "X-Forwarded-User",
                ],
                trustForwardHeader=True,
            )),
        )

    def create(self, reference: TraefikMiddleware):
        return self.api.create_namespaced_custom_object(
            group=CRD_GROUP,
            version=CRD_VERSION,
            plural=CRD_PLURAL,
            namespace=self.namespace,
            body=asdict(reference),
            field_manager=FIELD_MANAGER,
        )

    def delete(self, reference: TraefikMiddleware):
        return self.api.delete_namespaced_custom_object(
            group=CRD_GROUP,
            version=CRD_VERSION,
            namespace=self.namespace,
            plural=CRD_PLURAL,
            name=self.name,
        )

    def retrieve(self) -> TraefikMiddleware:
        return from_dict(
            TraefikMiddleware,
            self.api.get_namespaced_custom_object(
                group=CRD_GROUP,
                version=CRD_VERSION,
                namespace=self.namespace,
                plural=CRD_PLURAL,
                name=self.name,
            ),
        )

    def update(self, current: TraefikMiddleware, reference: TraefikMiddleware):
        return self.api.patch_namespaced_custom_object(
            group=CRD_GROUP,
            version=CRD_VERSION,
            namespace=self.namespace,
            plural=CRD_PLURAL,
            name=self.name,
            body=asdict(reference),
            field_manager=FIELD_MANAGER,
        )
示例#7
0
class Agent(BaseCustomResource):
    """
    A CRD that represents host's agent in assisted-service.
    When host is registered to the cluster the service will create an Agent
    resource and assign it to the relevant cluster.
    In oder to start the installation, all assigned agents must be approved.
    """
    _api_group = 'adi.io.my.domain'
    _version = 'v1alpha1'
    _plural = 'agents'

    def __init__(self,
                 kube_api_client: ApiClient,
                 name: str,
                 namespace: str = env_variables['namespace']):
        super().__init__(name, namespace)
        self.crd_api = CustomObjectsApi(kube_api_client)

    @classmethod
    def list(
        cls,
        crd_api: CustomObjectsApi,
        cluster_deployment: 'ClusterDeployment',
    ) -> List['Agent']:
        resources = crd_api.list_namespaced_custom_object(
            group=cls._api_group,
            version=cls._version,
            plural=cls._plural,
            namespace=cluster_deployment.ref.namespace,
        )
        assigned_agents = []
        for item in resources.get('items', []):
            assigned_cluster_ref = ObjectReference(
                name=item['spec']['clusterDeploymentName']['name'],
                namespace=item['spec']['clusterDeploymentName']['namespace'])
            if assigned_cluster_ref == cluster_deployment.ref:
                assigned_agents.append(
                    cls(kube_api_client=cluster_deployment.crd_api.api_client,
                        name=item['metadata']['name'],
                        namespace=item['metadata']['namespace']))

        return assigned_agents

    def create(self):
        raise RuntimeError(
            'agent resource must be created by the assisted-installer operator'
        )

    def get(self) -> dict:
        return self.crd_api.get_namespaced_custom_object(
            group=self._api_group,
            version=self._version,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace)

    def patch(self, **kwargs) -> None:
        body = {'spec': kwargs}

        self.crd_api.patch_namespaced_custom_object(
            group=self._api_group,
            version=self._version,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
            body=body)

        logger.info('patching agent %s: %s', self.ref, pformat(body))

    def delete(self) -> None:
        self.crd_api.delete_namespaced_custom_object(
            group=self._api_group,
            version=self._version,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace)

        logger.info('deleted agent %s', self.ref)

    def status(self) -> dict:
        return self.get()['status']

    def approve(self) -> None:
        self.patch(approved=True)

        logger.info('approved agent %s', self.ref)
示例#8
0
class Agent(BaseCustomResource):
    """
    A CRD that represents host's agent in assisted-service.
    When host is registered to the cluster the service will create an Agent
    resource and assign it to the relevant cluster.
    In oder to start the installation, all assigned agents must be approved.
    """

    _plural = "agents"

    def __init__(
        self,
        kube_api_client: ApiClient,
        name: str,
        namespace: str = consts.DEFAULT_NAMESPACE,
    ):
        super().__init__(name, namespace)
        self.crd_api = CustomObjectsApi(kube_api_client)

    @classmethod
    def list(
        cls,
        crd_api: CustomObjectsApi,
        cluster_deployment: "ClusterDeployment",
    ) -> List["Agent"]:
        resources = crd_api.list_namespaced_custom_object(
            group=CRD_API_GROUP,
            version=CRD_API_VERSION,
            plural=cls._plural,
            namespace=cluster_deployment.ref.namespace,
        )
        assigned_agents = []
        for item in resources.get("items", []):
            assigned_cluster_ref = ObjectReference(
                name=item["spec"]["clusterDeploymentName"]["name"],
                namespace=item["spec"]["clusterDeploymentName"]["namespace"],
            )
            if assigned_cluster_ref == cluster_deployment.ref:
                assigned_agents.append(
                    cls(
                        kube_api_client=cluster_deployment.crd_api.api_client,
                        name=item["metadata"]["name"],
                        namespace=item["metadata"]["namespace"],
                    ))

        return assigned_agents

    def create(self):
        raise RuntimeError(
            "agent resource must be created by the assisted-installer operator"
        )

    def get(self) -> dict:
        return self.crd_api.get_namespaced_custom_object(
            group=CRD_API_GROUP,
            version=CRD_API_VERSION,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
        )

    def patch(self, **kwargs) -> None:
        body = {"spec": kwargs}

        self.crd_api.patch_namespaced_custom_object(
            group=CRD_API_GROUP,
            version=CRD_API_VERSION,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
            body=body,
        )

        logger.info("patching agent %s: %s", self.ref, pformat(body))

    def delete(self) -> None:
        self.crd_api.delete_namespaced_custom_object(
            group=CRD_API_GROUP,
            version=CRD_API_VERSION,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
        )

        logger.info("deleted agent %s", self.ref)

    def status(
        self,
        timeout: Union[int,
                       float] = DEFAULT_WAIT_FOR_CRD_STATUS_TIMEOUT) -> dict:
        def _attempt_to_get_status() -> dict:
            return self.get()["status"]

        return waiting.wait(
            _attempt_to_get_status,
            sleep_seconds=0.5,
            timeout_seconds=timeout,
            waiting_for=f"agent {self.ref} status",
            expected_exceptions=KeyError,
        )

    def approve(self) -> None:
        self.patch(approved=True)
        logger.info("approved agent %s", self.ref)
示例#9
0
class ClusterDeployment(BaseCustomResource):
    """
    A CRD that represents cluster in assisted-service.
    On creation the cluster will be registered to the service.
    On deletion it will be unregistered from the service.
    When has sufficient data installation will start automatically.
    """

    _plural = "clusterdeployments"
    _platform_field = {"platform": {"agentBareMetal": {"agentSelector": {}}}}

    def __init__(
        self,
        kube_api_client: ApiClient,
        name: str,
        namespace: str = env_variables["namespace"],
    ):
        super().__init__(name, namespace)
        self.crd_api = CustomObjectsApi(kube_api_client)

    def create_from_yaml(self, yaml_data: dict) -> None:
        self.crd_api.create_namespaced_custom_object(
            group=HIVE_API_GROUP,
            version=HIVE_API_VERSION,
            plural=self._plural,
            body=yaml_data,
            namespace=self.ref.namespace,
        )

        logger.info("created cluster deployment %s: %s", self.ref, pformat(yaml_data))

    def create(
            self,
            secret: Secret,
            base_domain: str = env_variables["base_domain"],
            agent_cluster_install_ref: Optional[ObjectReference] = None,
            **kwargs,
    ):
        body = {
            "apiVersion": f"{HIVE_API_GROUP}/{HIVE_API_VERSION}",
            "kind": "ClusterDeployment",
            "metadata": self.ref.as_dict(),
            "spec": {
                "clusterName": self.ref.name,
                "baseDomain": base_domain,
                "pullSecretRef": secret.ref.as_dict(),
            }
        }
        body["spec"].update(self._platform_field)

        if agent_cluster_install_ref:
            body["spec"]["clusterInstallRef"] = agent_cluster_install_ref.as_dict()

        body["spec"].update(kwargs)
        self.crd_api.create_namespaced_custom_object(
            group=HIVE_API_GROUP,
            version=HIVE_API_VERSION,
            plural=self._plural,
            body=body,
            namespace=self.ref.namespace,
        )

        logger.info("created cluster deployment %s: %s", self.ref, pformat(body))

    def patch(
        self,
        secret: Optional[Secret] = None,
        **kwargs,
    ) -> None:
        body = {"spec": kwargs}
        body["spec"]["platform"] = {"agentBareMetal": {}}

        spec = body["spec"]
        body["spec"].update(self._platform_field)

        if secret:
            spec["pullSecretRef"] = secret.ref.as_dict()

        if "agent_cluster_install_ref" in kwargs:
            spec["clusterInstallRef"] = kwargs["agent_cluster_install_ref"].as_dict()

        self.crd_api.patch_namespaced_custom_object(
            group=HIVE_API_GROUP,
            version=HIVE_API_VERSION,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
            body=body,
        )

        logger.info("patching cluster deployment %s: %s", self.ref, pformat(body))

    def annotate_install_config(self, install_config: str) -> None:
        body = {"metadata": {"annotations": {f"{CRD_API_GROUP}/install-config-overrides": install_config}}}

        self.crd_api.patch_namespaced_custom_object(
            group=HIVE_API_GROUP,
            version=HIVE_API_VERSION,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
            body=body,
        )

        logger.info("patching cluster install config %s: %s", self.ref, pformat(body))

    def get(self) -> dict:
        return self.crd_api.get_namespaced_custom_object(
            group=HIVE_API_GROUP,
            version=HIVE_API_VERSION,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
        )

    def delete(self) -> None:
        self.crd_api.delete_namespaced_custom_object(
            group=HIVE_API_GROUP,
            version=HIVE_API_VERSION,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
        )

        logger.info("deleted cluster deployment %s", self.ref)

    def status(
        self,
        timeout: Union[int, float] = DEFAULT_WAIT_FOR_CRD_STATUS_TIMEOUT,
    ) -> dict:
        """
        Status is a section in the CRD that is created after registration to
        assisted service and it defines the observed state of ClusterDeployment.
        Since the status key is created only after resource is processed by the
        controller in the service, it might take a few seconds before appears.
        """

        def _attempt_to_get_status() -> dict:
            return self.get()["status"]

        return waiting.wait(
            _attempt_to_get_status,
            sleep_seconds=0.5,
            timeout_seconds=timeout,
            waiting_for=f"cluster {self.ref} status",
            expected_exceptions=KeyError,
        )

    def condition(
            self,
            cond_type,
            timeout: Union[int, float] = DEFAULT_WAIT_FOR_CRD_STATE_TIMEOUT,
    ) -> Tuple[Optional[str], Optional[str]]:
        for condition in self.status(timeout).get("conditions", []):
            if cond_type == condition.get("type"):
                return condition.get("status"), condition.get("reason")
        return None, None

    def wait_for_condition(
        self,
        cond_type: str,
        required_status: str,
        required_reason: Optional[str] = None,
        timeout: Union[int, float] = DEFAULT_WAIT_FOR_CRD_STATE_TIMEOUT,
    ) -> None:
        def _has_required_condition() -> Optional[bool]:
            status, reason = self.condition(cond_type=cond_type, timeout=0.5)
            if status == required_status:
                if required_reason:
                    return required_reason == reason
                return True
            return False

        logger.info(
            "Waiting till cluster will be in condition %s with status: %s "
            "reason: %s", cond_type, required_status, required_reason
        )

        waiting.wait(
            _has_required_condition,
            timeout_seconds=timeout,
            waiting_for=f"cluster {self.ref} condition {cond_type} to be in {required_status}",
            expected_exceptions=waiting.exceptions.TimeoutExpired,
        )

    def list_agents(self) -> List[Agent]:
        return Agent.list(self.crd_api, self)

    def wait_for_agents(
        self,
        num_agents: int = 1,
        timeout: Union[int, float] = DEFAULT_WAIT_FOR_AGENTS_TIMEOUT,
    ) -> List[Agent]:
        def _wait_for_sufficient_agents_number() -> List[Agent]:
            agents = self.list_agents()
            return agents if len(agents) == num_agents else []

        return waiting.wait(
            _wait_for_sufficient_agents_number,
            sleep_seconds=0.5,
            timeout_seconds=timeout,
            waiting_for=f"cluster {self.ref} to have {num_agents} agents",
        )
示例#10
0
class NMStateConfig(BaseCustomResource):
    """Configure nmstate (static IP) related settings for agents."""
    _plural = 'nmstateconfigs'

    def __init__(
        self,
        kube_api_client: ApiClient,
        name: str,
        namespace: str = env_variables['namespace'],
    ):
        super().__init__(name, namespace)
        self.crd_api = CustomObjectsApi(kube_api_client)

    def create_from_yaml(self, yaml_data: dict) -> None:
        self.crd_api.create_namespaced_custom_object(
            group=CRD_API_GROUP,
            version=CRD_API_VERSION,
            plural=self._plural,
            body=yaml_data,
            namespace=self.ref.namespace,
        )

        logger.info('created nmstate config %s: %s', self.ref,
                    pformat(yaml_data))

    def create(
        self,
        config: dict,
        interfaces: list,
        label: Optional[str] = None,
        **kwargs,
    ) -> None:
        body = {
            'apiVersion': f'{CRD_API_GROUP}/{CRD_API_VERSION}',
            'kind': 'NMStateConfig',
            'metadata': {
                'labels': {
                    f'{CRD_API_GROUP}/selector-nmstate-config-name': label,
                },
                **self.ref.as_dict()
            },
            'spec': {
                'config': config,
                'interfaces': interfaces,
            }
        }
        body['spec'].update(kwargs)
        self.crd_api.create_namespaced_custom_object(
            group=CRD_API_GROUP,
            version=CRD_API_VERSION,
            plural=self._plural,
            body=body,
            namespace=self.ref.namespace,
        )

        logger.info('created nmstate config %s: %s', self.ref, pformat(body))

    def patch(
        self,
        config: dict,
        interfaces: list,
        **kwargs,
    ) -> None:
        body = {'spec': kwargs}

        spec = body['spec']
        if config:
            spec['config'] = config

        if interfaces:
            spec['interfaces'] = interfaces

        self.crd_api.patch_namespaced_custom_object(
            group=CRD_API_GROUP,
            version=CRD_API_VERSION,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
            body=body,
        )

        logger.info('patching nmstate config %s: %s', self.ref, pformat(body))

    def get(self) -> dict:
        return self.crd_api.get_namespaced_custom_object(
            group=CRD_API_GROUP,
            version=CRD_API_VERSION,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
        )

    def delete(self) -> None:
        self.crd_api.delete_namespaced_custom_object(
            group=CRD_API_GROUP,
            version=CRD_API_VERSION,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
        )

        logger.info('deleted nmstate config %s', self.ref)

    def status(
        self,
        timeout: Union[int,
                       float] = DEFAULT_WAIT_FOR_CRD_STATUS_TIMEOUT) -> dict:
        """
        Status is a section in the CRD that is created after registration to
        assisted service and it defines the observed state of NMStateConfig.
        Since the status key is created only after resource is processed by the
        controller in the service, it might take a few seconds before appears.
        """
        def _attempt_to_get_status() -> dict:
            return self.get()['status']

        return waiting.wait(_attempt_to_get_status,
                            sleep_seconds=0.5,
                            timeout_seconds=timeout,
                            waiting_for=f'nmstate config {self.ref} status',
                            expected_exceptions=KeyError)
示例#11
0
class ClusterDeployment(BaseCustomResource):
    """
    A CRD that represents cluster in assisted-service.
    On creation the cluster will be registered to the service.
    On deletion it will be unregistered from the service.
    When has sufficient data installation will start automatically.
    """
    _plural = 'clusterdeployments'

    def __init__(
        self,
        kube_api_client: ApiClient,
        name: str,
        namespace: str = env_variables['namespace'],
    ):
        super().__init__(name, namespace)
        self.crd_api = CustomObjectsApi(kube_api_client)

    def create_from_yaml(self, yaml_data: dict) -> None:
        self.crd_api.create_namespaced_custom_object(
            group=HIVE_API_GROUP,
            version=HIVE_API_VERSION,
            plural=self._plural,
            body=yaml_data,
            namespace=self.ref.namespace,
        )

        logger.info('created cluster deployment %s: %s', self.ref,
                    pformat(yaml_data))

    def create(
        self,
        platform: Platform,
        install_strategy: InstallStrategy,
        secret: Secret,
        base_domain: str = env_variables['base_domain'],
        **kwargs,
    ):
        body = {
            'apiVersion': f'{HIVE_API_GROUP}/{HIVE_API_VERSION}',
            'kind': 'ClusterDeployment',
            'metadata': self.ref.as_dict(),
            'spec': {
                'clusterName': self.ref.name,
                'baseDomain': base_domain,
                'platform': platform.as_dict(),
                'provisioning': {
                    'installStrategy': install_strategy.as_dict()
                },
                'pullSecretRef': secret.ref.as_dict(),
            }
        }
        body['spec'].update(kwargs)
        self.crd_api.create_namespaced_custom_object(
            group=HIVE_API_GROUP,
            version=HIVE_API_VERSION,
            plural=self._plural,
            body=body,
            namespace=self.ref.namespace,
        )

        logger.info('created cluster deployment %s: %s', self.ref,
                    pformat(body))

    def patch(
        self,
        platform: Optional[Platform] = None,
        install_strategy: Optional[InstallStrategy] = None,
        secret: Optional[Secret] = None,
        **kwargs,
    ) -> None:
        body = {'spec': kwargs}

        spec = body['spec']
        if platform:
            spec['platform'] = platform.as_dict()

        if install_strategy:
            spec['provisioning'] = {
                'installStrategy': install_strategy.as_dict()
            }

        if secret:
            spec['pullSecretRef'] = secret.ref.as_dict()

        self.crd_api.patch_namespaced_custom_object(
            group=HIVE_API_GROUP,
            version=HIVE_API_VERSION,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
            body=body,
        )

        logger.info('patching cluster deployment %s: %s', self.ref,
                    pformat(body))

    def annotate_install_config(self, install_config: str) -> None:
        body = {
            'metadata': {
                'annotations': {
                    f'{CRD_API_GROUP}/install-config-overrides': install_config
                }
            }
        }

        self.crd_api.patch_namespaced_custom_object(
            group=HIVE_API_GROUP,
            version=HIVE_API_VERSION,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
            body=body,
        )

        logger.info('patching cluster install config %s: %s', self.ref,
                    pformat(body))

    def get(self) -> dict:
        return self.crd_api.get_namespaced_custom_object(
            group=HIVE_API_GROUP,
            version=HIVE_API_VERSION,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
        )

    def delete(self) -> None:
        self.crd_api.delete_namespaced_custom_object(
            group=HIVE_API_GROUP,
            version=HIVE_API_VERSION,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
        )

        logger.info('deleted cluster deployment %s', self.ref)

    def status(
        self,
        timeout: Union[int, float] = DEFAULT_WAIT_FOR_CRD_STATUS_TIMEOUT,
    ) -> dict:
        """
        Status is a section in the CRD that is created after registration to
        assisted service and it defines the observed state of ClusterDeployment.
        Since the status key is created only after resource is processed by the
        controller in the service, it might take a few seconds before appears.
        """
        def _attempt_to_get_status() -> dict:
            return self.get()['status']

        return waiting.wait(
            _attempt_to_get_status,
            sleep_seconds=0.5,
            timeout_seconds=timeout,
            waiting_for=f'cluster {self.ref} status',
            expected_exceptions=KeyError,
        )

    def state(
        self,
        timeout: Union[int, float] = DEFAULT_WAIT_FOR_CRD_STATE_TIMEOUT,
    ) -> Tuple[str, str]:
        state, state_info = None, None
        for condition in self.status(timeout).get('conditions', []):
            reason = condition.get('reason')

            if reason == 'AgentPlatformState':
                state = condition.get('message')
            elif reason == 'AgentPlatformStateInfo':
                state_info = condition.get('message')

            if state and state_info:
                break

        return state, state_info

    def wait_for_state(
        self,
        required_state: str,
        timeout: Union[int, float] = DEFAULT_WAIT_FOR_CRD_STATE_TIMEOUT,
        *,
        raise_on_states: Iterable[str] = FAILURE_STATES,
    ) -> None:
        required_state = required_state.lower()
        raise_on_states = [x.lower() for x in raise_on_states]

        def _has_required_state() -> Optional[bool]:
            state, state_info = self.state(timeout=0.5)
            state = state.lower() if state else state
            if state == required_state:
                return True
            elif state in raise_on_states:
                raise UnexpectedStateError(
                    f'while waiting for state `{required_state}`, cluster '
                    f'{self.ref} state changed unexpectedly to `{state}`: '
                    f'{state_info}')

        logger.info("Waiting till cluster will be in %s state", required_state)
        waiting.wait(
            _has_required_state,
            timeout_seconds=timeout,
            waiting_for=f'cluster {self.ref} state to be {required_state}',
            expected_exceptions=waiting.exceptions.TimeoutExpired,
        )

    def list_agents(self) -> List[Agent]:
        return Agent.list(self.crd_api, self)

    def wait_for_agents(
        self,
        num_agents: int = 1,
        timeout: Union[int, float] = DEFAULT_WAIT_FOR_AGENTS_TIMEOUT,
    ) -> List[Agent]:
        def _wait_for_sufficient_agents_number() -> List[Agent]:
            agents = self.list_agents()
            return agents if len(agents) == num_agents else []

        return waiting.wait(
            _wait_for_sufficient_agents_number,
            sleep_seconds=0.5,
            timeout_seconds=timeout,
            waiting_for=f'cluster {self.ref} to have {num_agents} agents',
        )

    def wait_to_be_installed(
        self,
        timeout: Union[int,
                       float] = DEFAULT_WAIT_FOR_INSTALLATION_COMPLETE_TIMEOUT,
    ) -> None:

        waiting.wait(
            lambda: self.get()['spec'].get('installed') is True,
            timeout_seconds=timeout,
            waiting_for=f'cluster {self.ref} state installed',
            expected_exceptions=waiting.exceptions.TimeoutExpired,
        )

    def download_kubeconfig(self, kubeconfig_path):
        def _get_kubeconfig_secret() -> dict:
            return self.get(
            )['spec']['clusterMetadata']['adminKubeconfigSecretRef']

        secret_ref = waiting.wait(
            _get_kubeconfig_secret,
            sleep_seconds=1,
            timeout_seconds=240,
            expected_exceptions=KeyError,
            waiting_for=f'kubeconfig secret creation for cluster {self.ref}',
        )

        kubeconfig_data = Secret(
            kube_api_client=self.crd_api.api_client,
            **secret_ref,
        ).get().data['kubeconfig']

        with open(kubeconfig_path, 'wt') as kubeconfig_file:
            kubeconfig_file.write(b64decode(kubeconfig_data).decode())
示例#12
0
class TraefikMiddlewareReconciler(KubernetesObjectReconciler[TraefikMiddleware]
                                  ):
    """Kubernetes Traefik Middleware Reconciler"""
    def __init__(self, controller: "KubernetesController") -> None:
        super().__init__(controller)
        self.api_ex = ApiextensionsV1Api(controller.client)
        self.api = CustomObjectsApi(controller.client)

    @property
    def noop(self) -> bool:
        if not ProxyProvider.objects.filter(
                outpost__in=[self.controller.outpost],
                mode__in=[ProxyMode.FORWARD_SINGLE, ProxyMode.FORWARD_DOMAIN],
        ).exists():
            self.logger.debug("No providers with forward auth enabled.")
            return True
        if not self._crd_exists():
            self.logger.debug("CRD doesn't exist")
            return True
        return False

    def _crd_exists(self) -> bool:
        """Check if the traefik middleware exists"""
        return bool(
            len(
                self.api_ex.list_custom_resource_definition(
                    field_selector=f"metadata.name={CRD_NAME}").items))

    def reconcile(self, current: TraefikMiddleware,
                  reference: TraefikMiddleware):
        super().reconcile(current, reference)
        if current.spec.forwardAuth.address != reference.spec.forwardAuth.address:
            raise NeedsUpdate()
        if (current.spec.forwardAuth.authResponseHeadersRegex !=
                reference.spec.forwardAuth.authResponseHeadersRegex):
            raise NeedsUpdate()
        # Ensure all of our headers are set, others can be added by the user.
        if not set(current.spec.forwardAuth.authResponseHeaders).issubset(
                reference.spec.forwardAuth.authResponseHeaders):
            raise NeedsUpdate()

    def get_reference_object(self) -> TraefikMiddleware:
        """Get deployment object for outpost"""
        return TraefikMiddleware(
            apiVersion=f"{CRD_GROUP}/{CRD_VERSION}",
            kind="Middleware",
            metadata=TraefikMiddlewareMetadata(
                name=self.name,
                namespace=self.namespace,
                labels=self.get_object_meta().labels,
            ),
            spec=
            TraefikMiddlewareSpec(forwardAuth=TraefikMiddlewareSpecForwardAuth(
                address=
                f"http://{self.name}.{self.namespace}:9000/akprox/auth/traefik",
                authResponseHeaders=[
                    "X-authentik-username",
                    "X-authentik-groups",
                    "X-authentik-email",
                    "X-authentik-name",
                    "X-authentik-uid",
                    "X-authentik-jwt",
                    "X-authentik-meta-jwks",
                    "X-authentik-meta-outpost",
                    "X-authentik-meta-provider",
                    "X-authentik-meta-app",
                    "X-authentik-meta-version",
                ],
                authResponseHeadersRegex="",
                trustForwardHeader=True,
            )),
        )

    def create(self, reference: TraefikMiddleware):
        return self.api.create_namespaced_custom_object(
            group=CRD_GROUP,
            version=CRD_VERSION,
            plural=CRD_PLURAL,
            namespace=self.namespace,
            body=asdict(reference),
            field_manager=FIELD_MANAGER,
        )

    def delete(self, reference: TraefikMiddleware):
        return self.api.delete_namespaced_custom_object(
            group=CRD_GROUP,
            version=CRD_VERSION,
            namespace=self.namespace,
            plural=CRD_PLURAL,
            name=self.name,
        )

    def retrieve(self) -> TraefikMiddleware:
        return from_dict(
            TraefikMiddleware,
            self.api.get_namespaced_custom_object(
                group=CRD_GROUP,
                version=CRD_VERSION,
                namespace=self.namespace,
                plural=CRD_PLURAL,
                name=self.name,
            ),
        )

    def update(self, current: TraefikMiddleware, reference: TraefikMiddleware):
        return self.api.patch_namespaced_custom_object(
            group=CRD_GROUP,
            version=CRD_VERSION,
            namespace=self.namespace,
            plural=CRD_PLURAL,
            name=self.name,
            body=asdict(reference),
            field_manager=FIELD_MANAGER,
        )
示例#13
0
def get_podsetting_spec(podsetting_name, team_name):
    load_kube_config()
    co = CustomObjectsApi()
    return co.get_namespaced_custom_object("orbit.aws", "v1", team_name,
                                           "podsettings", podsetting_name)
示例#14
0
class PrometheusServiceMonitorReconciler(
        KubernetesObjectReconciler[PrometheusServiceMonitor]):
    """Kubernetes Prometheus ServiceMonitor Reconciler"""
    def __init__(self, controller: "KubernetesController") -> None:
        super().__init__(controller)
        self.api_ex = ApiextensionsV1Api(controller.client)
        self.api = CustomObjectsApi(controller.client)

    @property
    def noop(self) -> bool:
        return (not self._crd_exists()) or (self.is_embedded)

    def _crd_exists(self) -> bool:
        """Check if the Prometheus ServiceMonitor exists"""
        return bool(
            len(
                self.api_ex.list_custom_resource_definition(
                    field_selector=f"metadata.name={CRD_NAME}").items))

    def get_reference_object(self) -> PrometheusServiceMonitor:
        """Get service monitor object for outpost"""
        return PrometheusServiceMonitor(
            apiVersion=f"{CRD_GROUP}/{CRD_VERSION}",
            kind="ServiceMonitor",
            metadata=PrometheusServiceMonitorMetadata(
                name=self.name,
                namespace=self.namespace,
                labels=self.get_object_meta().labels,
            ),
            spec=PrometheusServiceMonitorSpec(
                endpoints=[
                    PrometheusServiceMonitorSpecEndpoint(port="http-metrics", )
                ],
                selector=PrometheusServiceMonitorSpecSelector(
                    matchLabels=self.get_object_meta(name=self.name).labels, ),
            ),
        )

    def create(self, reference: PrometheusServiceMonitor):
        return self.api.create_namespaced_custom_object(
            group=CRD_GROUP,
            version=CRD_VERSION,
            plural=CRD_PLURAL,
            namespace=self.namespace,
            body=asdict(reference),
            field_manager=FIELD_MANAGER,
        )

    def delete(self, reference: PrometheusServiceMonitor):
        return self.api.delete_namespaced_custom_object(
            group=CRD_GROUP,
            version=CRD_VERSION,
            namespace=self.namespace,
            plural=CRD_PLURAL,
            name=self.name,
        )

    def retrieve(self) -> PrometheusServiceMonitor:
        return from_dict(
            PrometheusServiceMonitor,
            self.api.get_namespaced_custom_object(
                group=CRD_GROUP,
                version=CRD_VERSION,
                namespace=self.namespace,
                plural=CRD_PLURAL,
                name=self.name,
            ),
        )

    def update(self, current: PrometheusServiceMonitor,
               reference: PrometheusServiceMonitor):
        return self.api.patch_namespaced_custom_object(
            group=CRD_GROUP,
            version=CRD_VERSION,
            namespace=self.namespace,
            plural=CRD_PLURAL,
            name=self.name,
            body=asdict(reference),
            field_manager=FIELD_MANAGER,
        )
示例#15
0
class Agent(BaseCustomResource):
    """
    A CRD that represents host's agent in assisted-service.
    When host is registered to the cluster the service will create an Agent
    resource and assign it to the relevant cluster.
    In oder to start the installation, all assigned agents must be approved.
    """

    _plural = "agents"

    def __init__(
        self,
        kube_api_client: ApiClient,
        name: str,
        namespace: str = consts.DEFAULT_NAMESPACE,
    ):
        super().__init__(name, namespace)
        self.crd_api = CustomObjectsApi(kube_api_client)

    @classmethod
    def list(
        cls,
        crd_api: CustomObjectsApi,
        cluster_deployment,
        agents_namespace=None,
    ) -> List["Agent"]:
        agents_namespace = agents_namespace or cluster_deployment.ref.namespace
        resources = crd_api.list_namespaced_custom_object(
            group=consts.CRD_API_GROUP,
            version=consts.CRD_API_VERSION,
            plural=cls._plural,
            namespace=agents_namespace,
        )
        assigned_agents = []
        for item in resources.get("items", []):
            if item["spec"].get("clusterDeploymentName") is None:
                # Unbound late-binding agent, not part of the given cluster_deployment
                continue

            assigned_cluster_ref = ObjectReference(
                name=item["spec"]["clusterDeploymentName"]["name"],
                namespace=item["spec"]["clusterDeploymentName"]["namespace"],
            )
            if assigned_cluster_ref == cluster_deployment.ref:
                assigned_agents.append(
                    cls(
                        kube_api_client=cluster_deployment.crd_api.api_client,
                        name=item["metadata"]["name"],
                        namespace=item["metadata"]["namespace"],
                    )
                )

        return assigned_agents

    def create(self):
        raise RuntimeError("agent resource must be created by the assisted-installer operator")

    def get(self) -> dict:
        return self.crd_api.get_namespaced_custom_object(
            group=consts.CRD_API_GROUP,
            version=consts.CRD_API_VERSION,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
        )

    def patch(self, **kwargs) -> None:
        body = {"spec": kwargs}

        self.crd_api.patch_namespaced_custom_object(
            group=consts.CRD_API_GROUP,
            version=consts.CRD_API_VERSION,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
            body=body,
        )

        log.info("patching agent %s: %s", self.ref, pformat(body))

    def delete(self) -> None:
        self.crd_api.delete_namespaced_custom_object(
            group=consts.CRD_API_GROUP,
            version=consts.CRD_API_VERSION,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
        )

        log.info("deleted agent %s", self.ref)

    def status(self, timeout: Union[int, float] = consts.DEFAULT_WAIT_FOR_CRD_STATUS_TIMEOUT) -> dict:
        def _attempt_to_get_status() -> dict:
            return self.get()["status"]

        return waiting.wait(
            _attempt_to_get_status,
            sleep_seconds=0.5,
            timeout_seconds=timeout,
            waiting_for=f"agent {self.ref} status",
            expected_exceptions=KeyError,
        )

    @property
    def role(self) -> Optional[str]:
        return self.get()["spec"].get("role")

    def set_role(self, role: str) -> None:
        self.patch(role=role)
        log.info(f"set agent {self.ref} role to {role}")

    def approve(self) -> None:
        self.patch(approved=True)
        log.info("approved agent %s", self.ref)

    def bind(self, cluster_deployment) -> None:
        """
        Bind an unbound agent to a cluster deployment
        """
        self.patch(
            clusterDeploymentName={
                "name": cluster_deployment.ref.name,
                "namespace": cluster_deployment.ref.namespace,
            }
        )
        log.info(f"Bound agent {self.ref} to cluster_deployment {cluster_deployment.ref}")

    @classmethod
    def wait_for_agents_to_be_bound(
        cls, agents: List["Agent"], timeout: Union[int, float] = consts.CLUSTER_READY_FOR_INSTALL_TIMEOUT
    ) -> None:
        cls.wait_till_all_agents_are_in_status(
            agents=agents,
            status_type=consts.AgentStatus.BOUND,
            timeout=timeout,
        )

    @classmethod
    def wait_for_agents_to_be_ready_for_install(
        cls, agents: List["Agent"], timeout: Union[int, float] = consts.CLUSTER_READY_FOR_INSTALL_TIMEOUT
    ) -> None:
        for status_type in (
            consts.AgentStatus.SPEC_SYNCED,
            consts.AgentStatus.CONNECTED,
            consts.AgentStatus.REQUIREMENTS_MET,
            consts.AgentStatus.VALIDATED,
        ):
            cls.wait_till_all_agents_are_in_status(
                agents=agents,
                status_type=status_type,
                timeout=timeout,
            )

    @classmethod
    def wait_for_agents_to_unbound(
        cls, agents: List["Agent"], timeout: Union[int, float] = consts.DEFAULT_WAIT_FOR_CRD_STATUS_TIMEOUT
    ) -> None:
        cls.wait_for_agents_to_be_ready_for_install(agents=agents, timeout=timeout)
        cls.wait_till_all_agents_are_in_status(
            agents=agents,
            status_type=consts.AgentStatus.BOUND,
            status="False",
            timeout=timeout,
        )

    @classmethod
    def wait_for_agents_to_install(
        cls, agents: List["Agent"], timeout: Union[int, float] = consts.CLUSTER_INSTALLATION_TIMEOUT
    ) -> None:
        cls.wait_for_agents_to_be_ready_for_install(agents=agents, timeout=timeout)
        cls.wait_till_all_agents_are_in_status(
            agents=agents,
            status_type=consts.AgentStatus.INSTALLED,
            timeout=timeout,
        )

    @staticmethod
    def are_agents_in_status(
        agents: List["Agent"],
        status_type: str,
        status: str,
    ) -> bool:
        agents_conditions = {
            agent.ref.name: {condition["type"]: condition["status"] for condition in agent.status()["conditions"]}
            for agent in agents
        }

        log.info(
            f"Waiting for agents to have the condition '{status_type}' ="
            f" '{status}' and currently agent conditions are {agents_conditions}"
        )

        return all(agent_conditions.get(status_type, None) == status for agent_conditions in agents_conditions.values())

    @staticmethod
    def wait_till_all_agents_are_in_status(
        agents: List["Agent"],
        status_type: str,
        timeout,
        status="True",
        interval=10,
    ) -> None:
        log.info(f"Now Wait till agents have status as {status_type}")

        waiting.wait(
            lambda: Agent.are_agents_in_status(
                agents,
                status_type,
                status=status,
            ),
            timeout_seconds=timeout,
            sleep_seconds=interval,
            waiting_for=f"Agents to have {status_type} status",
        )
示例#16
0
class InfraEnv(BaseCustomResource):
    """
    InfraEnv is used to generate cluster iso.
    Image is automatically generated on CRD deployment, after InfraEnv is
    reconciled. Image download url will be exposed in the status.
    """
    _plural = 'infraenvs'

    def __init__(
        self,
        kube_api_client: ApiClient,
        name: str,
        namespace: str = env_variables['namespace'],
    ):
        super().__init__(name, namespace)
        self.crd_api = CustomObjectsApi(kube_api_client)

    def create_from_yaml(self, yaml_data: dict) -> None:
        self.crd_api.create_namespaced_custom_object(
            group=CRD_API_GROUP,
            version=CRD_API_VERSION,
            plural=self._plural,
            body=yaml_data,
            namespace=self.ref.namespace,
        )

        logger.info('created infraEnv %s: %s', self.ref, pformat(yaml_data))

    def create(
        self,
        cluster_deployment: ClusterDeployment,
        secret: Secret,
        proxy: Optional[Proxy] = None,
        label_selector: Optional[Dict[str, str]] = None,
        ignition_config_override: Optional[str] = None,
        nmstate_label: Optional[str] = None,
        **kwargs,
    ) -> None:
        body = {
            'apiVersion': f'{CRD_API_GROUP}/{CRD_API_VERSION}',
            'kind': 'InfraEnv',
            'metadata': self.ref.as_dict(),
            'spec': {
                'clusterRef': cluster_deployment.ref.as_dict(),
                'pullSecretRef': secret.ref.as_dict(),
                'nmStateConfigLabelSelector': {
                    'matchLabels': {
                        f'{CRD_API_GROUP}/selector-nmstate-config-name':
                        nmstate_label or ''
                    }
                },
                'agentLabelSelector': {
                    'matchLabels': label_selector or {}
                },
                'ignitionConfigOverride': ignition_config_override or '',
            }
        }
        spec = body['spec']
        if proxy:
            spec['proxy'] = proxy.as_dict()
        spec.update(kwargs)
        self.crd_api.create_namespaced_custom_object(
            group=CRD_API_GROUP,
            version=CRD_API_VERSION,
            plural=self._plural,
            body=body,
            namespace=self.ref.namespace,
        )

        logger.info('created infraEnv %s: %s', self.ref, pformat(body))

    def patch(
        self,
        cluster_deployment: Optional[ClusterDeployment],
        secret: Optional[Secret],
        proxy: Optional[Proxy] = None,
        label_selector: Optional[Dict[str, str]] = None,
        ignition_config_override: Optional[str] = None,
        nmstate_label: Optional[str] = None,
        **kwargs,
    ) -> None:
        body = {'spec': kwargs}

        spec = body['spec']
        if cluster_deployment:
            spec['clusterRef'] = cluster_deployment.ref.as_dict()

        if secret:
            spec['pullSecretRef'] = secret.ref.as_dict()

        if proxy:
            spec['proxy'] = proxy.as_dict()

        if label_selector:
            spec['agentLabelSelector'] = {'matchLabels': label_selector}

        if nmstate_label:
            spec['nmStateConfigLabelSelector'] = {
                'matchLabels': {
                    f'{CRD_API_GROUP}/selector-nmstate-config-name':
                    nmstate_label,
                }
            }

        if ignition_config_override:
            spec['ignitionConfigOverride'] = ignition_config_override

        self.crd_api.patch_namespaced_custom_object(
            group=CRD_API_GROUP,
            version=CRD_API_VERSION,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
            body=body,
        )

        logger.info('patching infraEnv %s: %s', self.ref, pformat(body))

    def get(self) -> dict:
        return self.crd_api.get_namespaced_custom_object(
            group=CRD_API_GROUP,
            version=CRD_API_VERSION,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
        )

    def delete(self) -> None:
        self.crd_api.delete_namespaced_custom_object(
            group=CRD_API_GROUP,
            version=CRD_API_VERSION,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
        )

        logger.info('deleted infraEnv %s', self.ref)

    def status(
        self,
        timeout: Union[int, float] = DEFAULT_WAIT_FOR_CRD_STATUS_TIMEOUT,
    ) -> dict:
        """
        Status is a section in the CRD that is created after registration to
        assisted service and it defines the observed state of InfraEnv.
        Since the status key is created only after resource is processed by the
        controller in the service, it might take a few seconds before appears.
        """
        def _attempt_to_get_status() -> dict:
            return self.get()['status']

        return waiting.wait(
            _attempt_to_get_status,
            sleep_seconds=0.5,
            timeout_seconds=timeout,
            waiting_for=f'infraEnv {self.ref} status',
            expected_exceptions=KeyError,
        )

    def get_iso_download_url(
        self,
        timeout: Union[int, float] = DEFAULT_WAIT_FOR_ISO_URL_TIMEOUT,
    ):
        def _attempt_to_get_image_url() -> str:
            return self.get()['status']['isoDownloadURL']

        return waiting.wait(
            _attempt_to_get_image_url,
            sleep_seconds=3,
            timeout_seconds=timeout,
            waiting_for='image to be created',
            expected_exceptions=KeyError,
        )

    def get_cluster_id(self):
        iso_download_url = self.get_iso_download_url()
        return ISO_URL_PATTERN.match(iso_download_url).group('cluster_id')
示例#17
0
class InfraEnv(BaseCustomResource):
    """
    InfraEnv is used to generate cluster iso.
    Image is automatically generated on CRD deployment, after InfraEnv is
    reconciled. Image download url will be exposed in the status.
    """

    _plural = "infraenvs"

    def __init__(
        self,
        kube_api_client: ApiClient,
        name: str,
        namespace: str = env_variables["namespace"],
    ):
        super().__init__(name, namespace)
        self.crd_api = CustomObjectsApi(kube_api_client)

    def create_from_yaml(self, yaml_data: dict) -> None:
        self.crd_api.create_namespaced_custom_object(
            group=CRD_API_GROUP,
            version=CRD_API_VERSION,
            plural=self._plural,
            body=yaml_data,
            namespace=self.ref.namespace,
        )

        logger.info("created infraEnv %s: %s", self.ref, pformat(yaml_data))

    def create(
        self,
        cluster_deployment: ClusterDeployment,
        secret: Secret,
        proxy: Optional[Proxy] = None,
        label_selector: Optional[Dict[str, str]] = None,
        ignition_config_override: Optional[str] = None,
        nmstate_label: Optional[str] = None,
        ssh_pub_key: Optional[str] = None,
        **kwargs,
    ) -> None:
        body = {
            "apiVersion": f"{CRD_API_GROUP}/{CRD_API_VERSION}",
            "kind": "InfraEnv",
            "metadata": self.ref.as_dict(),
            "spec": {
                "clusterRef": cluster_deployment.ref.as_dict(),
                "pullSecretRef": secret.ref.as_dict(),
                "nmStateConfigLabelSelector": {
                    "matchLabels": {
                        f"{CRD_API_GROUP}/selector-nmstate-config-name":
                        nmstate_label or ""
                    }
                },
                "agentLabelSelector": {
                    "matchLabels": label_selector or {}
                },
                "ignitionConfigOverride": ignition_config_override or "",
            },
        }
        spec = body["spec"]
        if proxy:
            spec["proxy"] = proxy.as_dict()
        if ssh_pub_key:
            spec["sshAuthorizedKey"] = ssh_pub_key

        spec.update(kwargs)
        self.crd_api.create_namespaced_custom_object(
            group=CRD_API_GROUP,
            version=CRD_API_VERSION,
            plural=self._plural,
            body=body,
            namespace=self.ref.namespace,
        )

        logger.info("created infraEnv %s: %s", self.ref, pformat(body))

    def patch(
        self,
        cluster_deployment: Optional[ClusterDeployment],
        secret: Optional[Secret],
        proxy: Optional[Proxy] = None,
        label_selector: Optional[Dict[str, str]] = None,
        ignition_config_override: Optional[str] = None,
        nmstate_label: Optional[str] = None,
        ssh_pub_key: Optional[str] = None,
        **kwargs,
    ) -> None:
        body = {"spec": kwargs}

        spec = body["spec"]
        if cluster_deployment:
            spec["clusterRef"] = cluster_deployment.ref.as_dict()

        if secret:
            spec["pullSecretRef"] = secret.ref.as_dict()

        if proxy:
            spec["proxy"] = proxy.as_dict()

        if label_selector:
            spec["agentLabelSelector"] = {"matchLabels": label_selector}

        if nmstate_label:
            spec["nmStateConfigLabelSelector"] = {
                "matchLabels": {
                    f"{CRD_API_GROUP}/selector-nmstate-config-name":
                    nmstate_label,
                }
            }

        if ignition_config_override:
            spec["ignitionConfigOverride"] = ignition_config_override

        if ssh_pub_key:
            spec["sshAuthorizedKey"] = ssh_pub_key

        self.crd_api.patch_namespaced_custom_object(
            group=CRD_API_GROUP,
            version=CRD_API_VERSION,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
            body=body,
        )

        logger.info("patching infraEnv %s: %s", self.ref, pformat(body))

    def get(self) -> dict:
        return self.crd_api.get_namespaced_custom_object(
            group=CRD_API_GROUP,
            version=CRD_API_VERSION,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
        )

    def delete(self) -> None:
        self.crd_api.delete_namespaced_custom_object(
            group=CRD_API_GROUP,
            version=CRD_API_VERSION,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
        )

        logger.info("deleted infraEnv %s", self.ref)

    def status(
        self,
        timeout: Union[int, float] = DEFAULT_WAIT_FOR_CRD_STATUS_TIMEOUT,
    ) -> dict:
        """
        Status is a section in the CRD that is created after registration to
        assisted service and it defines the observed state of InfraEnv.
        Since the status key is created only after resource is processed by the
        controller in the service, it might take a few seconds before appears.
        """
        def _attempt_to_get_status() -> dict:
            return self.get()["status"]

        return waiting.wait(
            _attempt_to_get_status,
            sleep_seconds=0.5,
            timeout_seconds=timeout,
            waiting_for=f"infraEnv {self.ref} status",
            expected_exceptions=KeyError,
        )

    def get_iso_download_url(
        self,
        timeout: Union[int, float] = DEFAULT_WAIT_FOR_ISO_URL_TIMEOUT,
    ):
        def _attempt_to_get_image_url() -> str:
            return self.get()["status"]["isoDownloadURL"]

        return waiting.wait(
            _attempt_to_get_image_url,
            sleep_seconds=3,
            timeout_seconds=timeout,
            waiting_for="image to be created",
            expected_exceptions=KeyError,
        )

    def get_cluster_id(self):
        iso_download_url = self.get_iso_download_url()
        return ISO_URL_PATTERN.match(iso_download_url).group("cluster_id")

    @classmethod
    def deploy_default_infraenv(
        cls,
        kube_api_client: ApiClient,
        name: str,
        ignore_conflict: bool = True,
        cluster_deployment: Optional[ClusterDeployment] = None,
        secret: Optional[Secret] = None,
        proxy: Optional[Proxy] = None,
        label_selector: Optional[Dict[str, str]] = None,
        ignition_config_override: Optional[str] = None,
        **kwargs,
    ) -> "InfraEnv":

        infra_env = InfraEnv(kube_api_client, name)
        try:
            if "filepath" in kwargs:
                infra_env._create_infraenv_from_yaml_file(
                    filepath=kwargs["filepath"], )
            else:
                infra_env._create_infraenv_from_attrs(
                    kube_api_client=kube_api_client,
                    name=name,
                    ignore_conflict=ignore_conflict,
                    cluster_deployment=cluster_deployment,
                    secret=secret,
                    proxy=proxy,
                    label_selector=label_selector,
                    ignition_config_override=ignition_config_override,
                    **kwargs,
                )
        except ApiException as e:
            if not (e.reason == "Conflict" and ignore_conflict):
                raise

        # wait until install-env will have status (i.e until resource will be
        # processed in assisted-service).
        infra_env.status()

        return infra_env

    def _create_infraenv_from_yaml_file(
        self,
        filepath: str,
    ) -> None:
        with open(filepath) as fp:
            yaml_data = yaml.safe_load(fp)

        self.create_from_yaml(yaml_data)

    def _create_infraenv_from_attrs(
        self,
        kube_api_client: ApiClient,
        cluster_deployment: ClusterDeployment,
        secret: Optional[Secret] = None,
        proxy: Optional[Proxy] = None,
        label_selector: Optional[Dict[str, str]] = None,
        ignition_config_override: Optional[str] = None,
        **kwargs,
    ) -> None:
        if not secret:
            secret = deploy_default_secret(
                kube_api_client=kube_api_client,
                name=cluster_deployment.ref.name,
            )
        self.create(
            cluster_deployment=cluster_deployment,
            secret=secret,
            proxy=proxy,
            label_selector=label_selector,
            ignition_config_override=ignition_config_override,
            **kwargs,
        )
class NMStateConfig(BaseCustomResource):
    """Configure nmstate (static IP) related settings for agents."""

    _plural = "nmstateconfigs"

    def __init__(
        self,
        kube_api_client: ApiClient,
        name: str,
        namespace: str = consts.DEFAULT_NAMESPACE,
    ):
        super().__init__(name, namespace)
        self.crd_api = CustomObjectsApi(kube_api_client)

    def create_from_yaml(self, yaml_data: dict) -> None:
        self.crd_api.create_namespaced_custom_object(
            group=CRD_API_GROUP,
            version=CRD_API_VERSION,
            plural=self._plural,
            body=yaml_data,
            namespace=self.ref.namespace,
        )

        logger.info("created nmstate config %s: %s", self.ref,
                    pformat(yaml_data))

    def create(
        self,
        config: dict,
        interfaces: list,
        label: Optional[str] = None,
        **kwargs,
    ) -> None:
        body = {
            "apiVersion": f"{CRD_API_GROUP}/{CRD_API_VERSION}",
            "kind": "NMStateConfig",
            "metadata": {
                "labels": {
                    f"{CRD_API_GROUP}/selector-nmstate-config-name": label,
                },
                **self.ref.as_dict(),
            },
            "spec": {
                "config": config,
                "interfaces": interfaces,
            },
        }
        body["spec"].update(kwargs)
        self.crd_api.create_namespaced_custom_object(
            group=CRD_API_GROUP,
            version=CRD_API_VERSION,
            plural=self._plural,
            body=body,
            namespace=self.ref.namespace,
        )

        logger.info("created nmstate config %s: %s", self.ref, pformat(body))

    def patch(
        self,
        config: dict,
        interfaces: list,
        **kwargs,
    ) -> None:
        body = {"spec": kwargs}

        spec = body["spec"]
        if config:
            spec["config"] = config

        if interfaces:
            spec["interfaces"] = interfaces

        self.crd_api.patch_namespaced_custom_object(
            group=CRD_API_GROUP,
            version=CRD_API_VERSION,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
            body=body,
        )

        logger.info("patching nmstate config %s: %s", self.ref, pformat(body))

    def get(self) -> dict:
        return self.crd_api.get_namespaced_custom_object(
            group=CRD_API_GROUP,
            version=CRD_API_VERSION,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
        )

    def delete(self) -> None:
        self.crd_api.delete_namespaced_custom_object(
            group=CRD_API_GROUP,
            version=CRD_API_VERSION,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
        )

        logger.info("deleted nmstate config %s", self.ref)

    def status(
        self,
        timeout: Union[int,
                       float] = DEFAULT_WAIT_FOR_CRD_STATUS_TIMEOUT) -> dict:
        """
        Status is a section in the CRD that is created after registration to
        assisted service and it defines the observed state of NMStateConfig.
        Since the status key is created only after resource is processed by the
        controller in the service, it might take a few seconds before appears.
        """
        def _attempt_to_get_status() -> dict:
            return self.get()["status"]

        return waiting.wait(
            _attempt_to_get_status,
            sleep_seconds=0.5,
            timeout_seconds=timeout,
            waiting_for=f"nmstate config {self.ref} status",
            expected_exceptions=KeyError,
        )
class ClusterDeployment(BaseCustomResource):
    """
    A CRD that represents cluster in assisted-service.
    On creation the cluster will be registered to the service.
    On deletion it will be unregistered from the service.
    When has sufficient data installation will start automatically.
    """

    _hive_api_group = 'hive.openshift.io'
    _plural = 'clusterdeployments'

    def __init__(self,
                 kube_api_client: ApiClient,
                 name: str,
                 namespace: str = env_variables['namespace']):
        BaseCustomResource.__init__(self, name, namespace)
        self.crd_api = CustomObjectsApi(kube_api_client)
        self._assigned_secret = None

    @property
    def secret(self) -> Secret:
        return self._assigned_secret

    def create_from_yaml(self, yaml_data: dict) -> None:
        self.crd_api.create_namespaced_custom_object(
            group=self._hive_api_group,
            version='v1',
            plural=self._plural,
            body=yaml_data,
            namespace=self.ref.namespace)
        secret_ref = yaml_data['spec']['pullSecretRef']
        self._assigned_secret = Secret(
            kube_api_client=self.crd_api.api_client,
            name=secret_ref['name'],
        )

        logger.info('created cluster deployment %s: %s', self.ref,
                    pformat(yaml_data))

    def create(self,
               platform: Platform,
               install_strategy: InstallStrategy,
               secret: Secret,
               base_domain: str = env_variables['base_domain'],
               **kwargs) -> None:
        body = {
            'apiVersion': f'{self._hive_api_group}/v1',
            'kind': 'ClusterDeployment',
            'metadata': self.ref.as_dict(),
            'spec': {
                'clusterName': self.ref.name,
                'baseDomain': base_domain,
                'platform': platform.as_dict(),
                'provisioning': {
                    'installStrategy': install_strategy.as_dict()
                },
                'pullSecretRef': secret.ref.as_dict(),
            }
        }
        body['spec'].update(kwargs)
        self.crd_api.create_namespaced_custom_object(
            group=self._hive_api_group,
            version='v1',
            plural=self._plural,
            body=body,
            namespace=self.ref.namespace)
        self._assigned_secret = secret

        logger.info('created cluster deployment %s: %s', self.ref,
                    pformat(body))

    def patch(self,
              platform: Optional[Platform] = None,
              install_strategy: Optional[InstallStrategy] = None,
              secret: Optional[Secret] = None,
              **kwargs) -> None:
        body = {'spec': kwargs}

        spec = body['spec']
        if platform:
            spec['platform'] = platform.as_dict()

        if install_strategy:
            spec['provisioning'] = {
                'installStrategy': install_strategy.as_dict()
            }

        if secret:
            spec['pullSecretRef'] = secret.ref.as_dict()

        self.crd_api.patch_namespaced_custom_object(
            group=self._hive_api_group,
            version='v1',
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
            body=body)

        logger.info('patching cluster deployment %s: %s', self.ref,
                    pformat(body))

    def get(self) -> dict:
        return self.crd_api.get_namespaced_custom_object(
            group=self._hive_api_group,
            version='v1',
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace)

    def delete(self) -> None:
        self.crd_api.delete_namespaced_custom_object(
            group=self._hive_api_group,
            version='v1',
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace)

        logger.info('deleted cluster deployment %s', self.ref)

    def status(
        self,
        timeout: Union[int,
                       float] = DEFAULT_WAIT_FOR_CRD_STATUS_TIMEOUT) -> dict:
        """
        Status is a section in the CRD that is created after registration to
        assisted service and it defines the observed state of ClusterDeployment.
        Since the status key is created only after resource is processed by the
        controller in the service, it might take a few seconds before appears.
        """
        def _attempt_to_get_status() -> dict:
            return self.get()['status']

        return waiting.wait(_attempt_to_get_status,
                            sleep_seconds=0.5,
                            timeout_seconds=timeout,
                            waiting_for=f'cluster {self.ref} status',
                            expected_exceptions=KeyError)

    def state(
        self,
        timeout: Union[int, float] = DEFAULT_WAIT_FOR_CRD_STATE_TIMEOUT
    ) -> Tuple[str, str]:
        state, state_info = None, None
        for condition in self.status(timeout).get('conditions', []):
            reason = condition.get('reason')

            if reason == 'AgentPlatformState':
                state = condition.get('message')
            elif reason == 'AgentPlatformStateInfo':
                state_info = condition.get('message')

            if state and state_info:
                break

        return state, state_info

    def wait_for_state(
        self,
        required_state: str,
        timeout: Union[int,
                       float] = DEFAULT_WAIT_FOR_CRD_STATE_TIMEOUT) -> None:
        required_state = required_state.lower()

        def _has_required_state() -> bool:
            state, _ = self.state(timeout=0.5)
            return state.lower() == required_state

        waiting.wait(
            _has_required_state,
            timeout_seconds=timeout,
            waiting_for=f'cluster {self.ref} state to be {required_state}',
            expected_exceptions=waiting.exceptions.TimeoutExpired)
class AgentClusterInstall(BaseCustomResource):
    """ This CRD represents a request to provision an agent based cluster.
        In the AgentClusterInstall, the user can specify requirements like
        networking, number of control plane and workers nodes and more.
        The installation will start automatically if the required number of
        hosts is available, the hosts are ready to be installed and the Agents
        are approved.
        The AgentClusterInstall reflects the ClusterDeployment/Installation
        status through Conditions."""

    _api_group = "extensions.hive.openshift.io"
    _api_version = "v1beta1"
    _plural = "agentclusterinstalls"
    _kind = "AgentClusterInstall"
    _requirements_met_condition_name = "RequirementsMet"
    _completed_condition_name = "Completed"

    def __init__(
        self,
        kube_api_client: ApiClient,
        name: str,
        namespace: str = consts.DEFAULT_NAMESPACE,
    ):
        super().__init__(name, namespace)
        self.crd_api = CustomObjectsApi(kube_api_client)
        self.ref.kind = self._kind
        self.ref.group = self._api_group
        self.ref.version = self._api_version

    def create(
        self,
        cluster_deployment_ref: ObjectReference,
        cluster_cidr: str,
        host_prefix: int,
        service_network: str,
        control_plane_agents: int,
        **kwargs,
    ) -> None:
        body = {
            "apiVersion":
            f"{self._api_group}/{self._api_version}",
            "kind":
            self._kind,
            "metadata":
            self.ref.as_dict(),
            "spec":
            self._get_spec_dict(
                cluster_deployment_ref=cluster_deployment_ref,
                cluster_cidr=cluster_cidr,
                host_prefix=host_prefix,
                service_network=service_network,
                control_plane_agents=control_plane_agents,
                **kwargs,
            ),
        }

        self.crd_api.create_namespaced_custom_object(
            group=self._api_group,
            version=self._api_version,
            plural=self._plural,
            body=body,
            namespace=self.ref.namespace,
        )

        logger.info("created agentclusterinstall %s: %s", self.ref,
                    pformat(body))

    def patch(
        self,
        cluster_deployment_ref: ObjectReference,
        cluster_cidr: str,
        host_prefix: int,
        service_network: str,
        control_plane_agents: int,
        **kwargs,
    ) -> None:
        body = {
            "spec":
            self._get_spec_dict(
                cluster_deployment_ref=cluster_deployment_ref,
                cluster_cidr=cluster_cidr,
                host_prefix=host_prefix,
                service_network=service_network,
                control_plane_agents=control_plane_agents,
                **kwargs,
            )
        }

        self.crd_api.patch_namespaced_custom_object(
            group=self._api_group,
            version=self._api_version,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
            body=body,
        )

        logger.info("patching agentclusterinstall %s: %s", self.ref,
                    pformat(body))

    @staticmethod
    def _get_spec_dict(
        cluster_deployment_ref: ObjectReference,
        cluster_cidr: str,
        host_prefix: int,
        service_network: str,
        control_plane_agents: int,
        **kwargs,
    ) -> dict:
        spec = {
            "clusterDeploymentRef":
            cluster_deployment_ref.as_dict(),
            "imageSetRef":
            kwargs.pop("image_set_ref", ClusterImageSetReference()).as_dict(),
            "networking": {
                "clusterNetwork": [{
                    "cidr": cluster_cidr,
                    "hostPrefix": host_prefix,
                }],
                "serviceNetwork": [service_network],
            },
            "provisionRequirements": {
                "controlPlaneAgents": control_plane_agents,
                "workerAgents": kwargs.pop("worker_agents", 0),
            }
        }

        if "api_vip" in kwargs:
            spec["apiVIP"] = kwargs.pop("api_vip")

        if "ingress_vip" in kwargs:
            spec["ingressVIP"] = kwargs.pop("ingress_vip")

        if "ssh_pub_key" in kwargs:
            spec["sshPublicKey"] = kwargs.pop("ssh_pub_key")

        if "machine_cidr" in kwargs:
            spec["networking"]["machineNetwork"] = [{
                "cidr":
                kwargs.pop("machine_cidr")
            }]

        spec.update(kwargs)
        return spec

    def get(self) -> dict:
        return self.crd_api.get_namespaced_custom_object(
            group=self._api_group,
            version=self._api_version,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
        )

    def delete(self) -> None:
        self.crd_api.delete_namespaced_custom_object(
            group=self._api_group,
            version=self._api_version,
            plural=self._plural,
            name=self.ref.name,
            namespace=self.ref.namespace,
        )

        logger.info("deleted agentclusterinstall %s", self.ref)

    def status(
            self,
            timeout: Union[int] = DEFAULT_WAIT_FOR_CRD_STATE_TIMEOUT) -> dict:
        def _attempt_to_get_status() -> dict:
            return self.get()["status"]

        return waiting.wait(
            _attempt_to_get_status,
            sleep_seconds=0.5,
            timeout_seconds=timeout,
            waiting_for=f"cluster {self.ref} status",
            expected_exceptions=KeyError,
        )

    def wait_to_be_ready(
        self,
        ready: bool,
        timeout: Union[int, float] = DEFAULT_WAIT_FOR_CRD_STATE_TIMEOUT,
    ) -> None:
        return self.wait_for_condition(
            cond_type=self._requirements_met_condition_name,
            required_status=str(ready),
            timeout=timeout,
        )

    def wait_to_be_installing(
        self,
        timeout: Union[int, float] = DEFAULT_WAIT_FOR_CRD_STATE_TIMEOUT,
    ) -> None:
        return self.wait_for_condition(
            cond_type=self._requirements_met_condition_name,
            required_status="True",
            required_reason="ClusterAlreadyInstalling",
            timeout=timeout,
        )

    def wait_to_be_installed(
        self,
        timeout: Union[int,
                       float] = DEFAULT_WAIT_FOR_INSTALLATION_COMPLETE_TIMEOUT,
    ) -> None:
        return self.wait_for_condition(
            cond_type=self._completed_condition_name,
            required_status="True",
            required_reason="InstallationCompleted",
            timeout=timeout,
        )

    def wait_for_condition(
        self,
        cond_type: str,
        required_status: str,
        required_reason: Optional[str] = None,
        timeout: Union[int, float] = DEFAULT_WAIT_FOR_CRD_STATE_TIMEOUT,
    ) -> None:

        logger.info(
            "waiting for agentclusterinstall %s condition %s to be in status "
            "%s", self.ref, cond_type, required_status)

        def _has_required_condition() -> Optional[bool]:
            status, reason = self.condition(cond_type=cond_type, timeout=0.5)
            logger.info(
                f"waiting for condition <{cond_type}> to be in status <{required_status}>. actual status is: {status} {reason}"
            )
            if status == required_status:
                if required_reason:
                    return required_reason == reason
                return True

        waiting.wait(
            _has_required_condition,
            timeout_seconds=timeout,
            waiting_for=f"agentclusterinstall {self.ref} condition "
            f"{cond_type} to be {required_status}",
            sleep_seconds=10,
            expected_exceptions=waiting.exceptions.TimeoutExpired,
        )

    def condition(
        self,
        cond_type,
        timeout: Union[int, float] = DEFAULT_WAIT_FOR_CRD_STATE_TIMEOUT,
    ) -> Tuple[Optional[str], Optional[str]]:
        for condition in self.status(timeout).get("conditions", []):
            if cond_type == condition.get("type"):
                return condition.get("status"), condition.get("reason")

        return None, None

    def download_kubeconfig(self, kubeconfig_path):
        def _get_kubeconfig_secret() -> dict:
            return self.get(
            )["spec"]["clusterMetadata"]["adminKubeconfigSecretRef"]

        secret_ref = waiting.wait(
            _get_kubeconfig_secret,
            sleep_seconds=1,
            timeout_seconds=DEFAULT_WAIT_FOR_KUBECONFIG_TIMEOUT,
            expected_exceptions=KeyError,
            waiting_for=f"kubeconfig secret creation for cluster {self.ref}",
        )

        kubeconfig_data = (Secret(
            kube_api_client=self.crd_api.api_client,
            namespace=self._reference.namespace,
            **secret_ref,
        ).get().data["kubeconfig"])

        with open(kubeconfig_path, "wt") as kubeconfig_file:
            kubeconfig_file.write(b64decode(kubeconfig_data).decode())