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={} )
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']
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'
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)
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
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, )
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)
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)
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) ]
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'} )
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()
def apply_manifest(self, manifest, namespace=None): for obj in codecs.load_all_yaml(manifest): self.lightkube_client.apply(obj, namespace=namespace)