Beispiel #1
0
def test_load_all_yaml_template_env():
    import jinja2
    env = jinja2.Environment()
    env.globals['test'] = 'global'

    objs = list(codecs.load_all_yaml(
        data_dir.joinpath('example-def.tmpl').read_text(),
        context={},
        template_env=env)
    )
    kinds = [o.kind for o in objs]

    assert kinds == ['Secret', 'Mydb', 'Service', 'Deployment']
    assert objs[1].metadata.name == 'bla-global'

    with data_dir.joinpath('example-def.tmpl').open() as f:
        objs = list(codecs.load_all_yaml(f, context={}, template_env=env))
    kinds = [o.kind for o in objs]

    assert kinds == ['Secret', 'Mydb', 'Service', 'Deployment']
    assert objs[1].metadata.name == 'bla-global'

    # template_env is not an environment
    with pytest.raises(LoadResourceError, match='.*valid jinja2 template'):
        codecs.load_all_yaml(
            data_dir.joinpath('example-def.tmpl').read_text(),
            context={},
            template_env={}
        )
Beispiel #2
0
def test_load_all_yaml_static():
    objs = list(codecs.load_all_yaml(data_dir.joinpath('example-def.yaml').read_text()))
    kinds = [o.kind for o in objs]

    assert kinds == ['Secret', 'Mydb', 'Service', 'Deployment']

    with data_dir.joinpath('example-def.yaml').open() as f:
        objs = list(codecs.load_all_yaml(f))
    kinds = [o.kind for o in objs]

    assert kinds == ['Secret', 'Mydb', 'Service', 'Deployment']
Beispiel #3
0
def test_load_all_yaml_template():
    objs = list(codecs.load_all_yaml(
        data_dir.joinpath('example-def.tmpl').read_text(),
        context={'test': 'xyz'})
    )
    kinds = [o.kind for o in objs]

    assert kinds == ['Secret', 'Mydb', 'Service', 'Deployment']
    assert objs[1].metadata.name == 'bla-xyz'

    with data_dir.joinpath('example-def.tmpl').open() as f:
        objs = list(codecs.load_all_yaml(f, context={'test': 'xyz'}))
    kinds = [o.kind for o in objs]

    assert kinds == ['Secret', 'Mydb', 'Service', 'Deployment']
    assert objs[1].metadata.name == 'bla-xyz'
Beispiel #4
0
    def reconcile_desired_resources(
        self,
        resource,
        desired_resources: Union[str, TextIO, None],
        namespace: str = None,
    ) -> None:
        """Reconciles the desired list of resources of any kind.

        Args:
            resource: resource kind (e.g. Service, Pod)
            desired_resources: all desired resources in manifest form as str
            namespace: namespace of the resource
        """
        existing_resources = self.lightkube_client.list(
            resource,
            labels={
                "app.juju.is/created-by": f"{self.app_name}",
                f"app.{self.app_name}.io/is-workload-entity": "true",
            },
            namespace=namespace,
        )

        if desired_resources is not None:
            desired_resources_list = codecs.load_all_yaml(desired_resources)
            diff_obj = in_left_not_right(left=existing_resources,
                                         right=desired_resources_list)
            for obj in diff_obj:
                self.delete_resource(obj)
            self.apply_manifest(desired_resources, namespace=namespace)
Beispiel #5
0
    def remove(self, event):
        """Remove charm."""

        env = Environment(loader=FileSystemLoader('src'))
        template = env.get_template('manifest.yaml')
        rendered = template.render(
            kind=self.model.config['kind'],
            namespace=self.model.name,
            pilot_host='foo',
            pilot_port='foo',
        )

        try:
            for obj in codecs.load_all_yaml(rendered):
                self.lightkube_client.delete(type(obj),
                                             obj.metadata.name,
                                             namespace=obj.metadata.namespace)
        except ApiError as err:
            self.log.exception(
                "ApiError encountered while attempting to delete resource.")
            if err.status.message is not None:
                if "(Unauthorized)" in err.status.message:
                    # Ignore error from https://bugs.launchpad.net/juju/+bug/1941655
                    self.log.error(
                        f"Ignoring unauthorized error during cleanup:"
                        f"\n{err.status.message}")
                else:
                    # But surface any other errors
                    self.log.error(err.status.message)
                    raise
            else:
                raise
Beispiel #6
0
 def delete_manifest(self,
                     manifest,
                     namespace=None,
                     ignore_not_found=False,
                     ignore_unauthorized=False):
     for obj in codecs.load_all_yaml(manifest):
         self.delete_resource(
             obj,
             namespace=namespace,
             ignore_not_found=ignore_not_found,
             ignore_unauthorized=ignore_unauthorized,
         )
Beispiel #7
0
def profile(lightkube_client):
    """Creates a Profile object in cluster, cleaning it up after tests."""
    profile_file = "./tests/integration/profile.yaml"
    yaml_text = _safe_load_file_to_text(profile_file)
    yaml_rendered = yaml.safe_load(yaml_text)
    profilename = yaml_rendered["metadata"]["name"]

    for obj in codecs.load_all_yaml(yaml_text):
        try:
            lightkube_client.apply(obj)
        except lightkube.core.exceptions.ApiError as e:
            raise e

    yield profilename

    delete_all_from_yaml(yaml_text, lightkube_client)
Beispiel #8
0
def delete_all_from_yaml(yaml_file: str,
                         lightkube_client: lightkube.Client = None):
    """Deletes all k8s resources listed in a YAML file via lightkube.

    Args:
        yaml_file (str or Path): Either a string filename or a string of valid YAML.  Will attempt
                                 to open a filename at this path, failing back to interpreting the
                                 string directly as YAML.
        lightkube_client: Instantiated lightkube client or None
    """
    yaml_text = _safe_load_file_to_text(yaml_file)

    if lightkube_client is None:
        lightkube_client = lightkube.Client()

    for obj in codecs.load_all_yaml(yaml_text):
        lightkube_client.delete(type(obj), obj.metadata.name)
Beispiel #9
0
    def get_manifest(self):
        # Handle ingress
        ingress = self._get_interface("ingress")

        if ingress:
            for app_name, version in ingress.versions.items():
                data = {
                    "prefix": "/dex",
                    "rewrite": "/dex",
                    "service": self.model.app.name,
                    "port": self.model.config["port"],
                }

                ingress.send_data(data, app_name)

        # Get OIDC client info
        oidc = self._get_interface("oidc-client")

        if oidc:
            oidc_client_info = list(oidc.get_data().values())
        else:
            oidc_client_info = []

        # Load config values as convenient variables
        connectors = yaml.safe_load(self.model.config["connectors"])
        port = self.model.config["port"]
        public_url = self.model.config["public-url"].lower()
        if not public_url.startswith(("http://", "https://")):
            public_url = f"http://{public_url}"
        static_username = self.model.config[
            "static-username"] or self.state.username
        static_password = self.model.config[
            "static-password"] or self.state.password
        static_password = static_password.encode("utf-8")
        hashed = bcrypt.hashpw(static_password,
                               self.state.salt).decode("utf-8")

        static_config = {
            "enablePasswordDB":
            True,
            "staticPasswords": [{
                "email": static_username,
                "hash": hashed,
                "username": static_username,
                "userID": self.state.user_id,
            }],
        }

        config = json.dumps({
            "issuer": f"{public_url}/dex",
            "storage": {
                "type": "kubernetes",
                "config": {
                    "inCluster": True
                }
            },
            "web": {
                "http": f"0.0.0.0:{port}"
            },
            "logger": {
                "level": "debug",
                "format": "text"
            },
            "oauth2": {
                "skipApprovalScreen": True
            },
            "staticClients": oidc_client_info,
            "connectors": connectors,
            **static_config,
        })

        # Kubernetes won't automatically restart the pod when the configmap changes
        # unless we manually add the hash somewhere into the Deployment spec, so that
        # it changes whenever the configmap changes.
        config_hash = sha256()
        config_hash.update(config.encode("utf-8"))

        context = {
            "name": self.model.app.name.replace("-operator", ""),
            "namespace": self.model.name,
            "port": self.model.config["port"],
            "config_yaml": config,
            "config_hash": config_hash.hexdigest(),
        }

        return [
            obj for path in glob("src/manifests/*.yaml")
            for obj in codecs.load_all_yaml(Path(path).read_text(),
                                            context=context)
        ]
Beispiel #10
0
def test_load_all_yaml_missing_dependency():
    with pytest.raises(ImportError, match='.*requires jinja2.*'):
        codecs.load_all_yaml(
            data_dir.joinpath('example-def.tmpl').read_text(),
            context={'test': 'xyz'}
        )
Beispiel #11
0
class Operator(CharmBase):
    def __init__(self, *args):
        super().__init__(*args)

        if not self.unit.is_leader():
            # We can't do anything useful when not the leader, so do nothing.
            self.model.unit.status = WaitingStatus("Waiting for leadership")
            return

        try:
            self.interfaces = get_interfaces(self)
        except NoVersionsListed as err:
            self.model.unit.status = WaitingStatus(str(err))
            return
        except NoCompatibleVersions as err:
            self.model.unit.status = BlockedStatus(str(err))
            return
        else:
            self.model.unit.status = ActiveStatus()

        self.log = logging.getLogger(__name__)

        # Every lightkube API call will use the model name as the namespace by default
        self.lightkube_client = Client(namespace=self.model.name,
                                       field_manager="lightkube")

        self.framework.observe(self.on.start, self.start)
        self.framework.observe(self.on["istio-pilot"].relation_changed,
                               self.start)
        self.framework.observe(self.on.config_changed, self.start)
        self.framework.observe(self.on.remove, self.remove)

    def start(self, event):
        """Event handler for StartEevnt."""

        if self.model.config['kind'] not in ('ingress', 'egress'):
            self.model.unit.status = BlockedStatus(
                'Config item `kind` must be set')
            return

        if not self.model.relations['istio-pilot']:
            self.model.unit.status = BlockedStatus(
                "Waiting for istio-pilot relation")
            return

        if not ((pilot := self.interfaces["istio-pilot"])
                and pilot.get_data()):
            self.model.unit.status = WaitingStatus(
                "Waiting for istio-pilot relation data")
            return

        pilot = list(pilot.get_data().values())[0]

        env = Environment(loader=FileSystemLoader('src'))
        template = env.get_template('manifest.yaml')
        rendered = template.render(
            kind=self.model.config['kind'],
            namespace=self.model.name,
            pilot_host=pilot['service-name'],
            pilot_port=pilot['service-port'],
        )

        for obj in codecs.load_all_yaml(rendered):
            self.log.debug(f"Deploying {obj.metadata.name} of kind {obj.kind}")
            self.lightkube_client.apply(obj, namespace=obj.metadata.namespace)

        self.unit.status = ActiveStatus()
Beispiel #12
0
 def apply_manifest(self, manifest, namespace=None):
     for obj in codecs.load_all_yaml(manifest):
         self.lightkube_client.apply(obj, namespace=namespace)