2 # Copyright 2021 Canonical Ltd.
4 # Licensed under the Apache License, Version 2.0 (the "License"); you may
5 # not use this file except in compliance with the License. You may obtain
6 # a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 # License for the specific language governing permissions and limitations
16 # For those usages not covered by the Apache License, Version 2.0 please
17 # contact: legal@canonical.com
19 # To get in touch with the maintainers, please contact:
20 # osm-charmers@lists.launchpad.net
23 # pylint: disable=E0213
26 from datetime
import datetime
27 from ipaddress
import ip_network
30 from typing
import List
, NoReturn
, Optional
, Tuple
31 from urllib
.parse
import urlparse
33 from cryptography
.fernet
import Fernet
34 from ops
.main
import main
35 from opslib
.osm
.charm
import CharmedOsmBase
, RelationsMissing
36 from opslib
.osm
.interfaces
.keystone
import KeystoneServer
37 from opslib
.osm
.interfaces
.mysql
import MysqlClient
38 from opslib
.osm
.pod
import (
41 IngressResourceV3Builder
,
44 from opslib
.osm
.validator
import ModelValidator
, validator
47 logger
= logging
.getLogger(__name__
)
50 REQUIRED_SETTINGS
= ["token_expiration"]
52 # This is hardcoded in the keystone container script
53 DATABASE_NAME
= "keystone"
55 # We expect the keystone container to use the default port
58 # Number of keys need might need to be adjusted in the future
59 NUMBER_FERNET_KEYS
= 2
60 NUMBER_CREDENTIAL_KEYS
= 2
63 CREDENTIAL_KEYS_PATH
= "/etc/keystone/credential-keys"
64 FERNET_KEYS_PATH
= "/etc/keystone/fernet-keys"
67 class ConfigModel(ModelValidator
):
69 keystone_db_password
: str
77 project_domain_name
: str
80 site_url
: Optional
[str]
81 ingress_class
: Optional
[str]
82 ingress_whitelist_source_range
: Optional
[str]
83 tls_secret_name
: Optional
[str]
84 mysql_host
: Optional
[str]
85 mysql_port
: Optional
[int]
86 mysql_root_password
: Optional
[str]
87 image_pull_policy
: str
89 @validator("max_file_size")
90 def validate_max_file_size(cls
, v
):
92 raise ValueError("value must be equal or greater than 0")
95 @validator("site_url")
96 def validate_site_url(cls
, v
):
99 if not parsed
.scheme
.startswith("http"):
100 raise ValueError("value must start with http")
103 @validator("ingress_whitelist_source_range")
104 def validate_ingress_whitelist_source_range(cls
, v
):
109 @validator("mysql_port")
110 def validate_mysql_port(cls
, v
):
111 if v
and (v
<= 0 or v
>= 65535):
112 raise ValueError("Mysql port out of range")
115 @validator("image_pull_policy")
116 def validate_image_pull_policy(cls
, v
):
119 "ifnotpresent": "IfNotPresent",
123 if v
not in values
.keys():
124 raise ValueError("value must be always, ifnotpresent or never")
128 class ConfigLdapModel(ModelValidator
):
130 ldap_authentication_domain_name
: Optional
[str]
131 ldap_url
: Optional
[str]
132 ldap_bind_user
: Optional
[str]
133 ldap_bind_password
: Optional
[str]
134 ldap_chase_referrals
: Optional
[str]
135 ldap_page_size
: Optional
[int]
136 ldap_user_tree_dn
: Optional
[str]
137 ldap_user_objectclass
: Optional
[str]
138 ldap_user_id_attribute
: Optional
[str]
139 ldap_user_name_attribute
: Optional
[str]
140 ldap_user_pass_attribute
: Optional
[str]
141 ldap_user_filter
: Optional
[str]
142 ldap_user_enabled_attribute
: Optional
[str]
143 ldap_user_enabled_mask
: Optional
[int]
144 ldap_user_enabled_default
: Optional
[str]
145 ldap_user_enabled_invert
: Optional
[bool]
146 ldap_group_objectclass
: Optional
[str]
147 ldap_group_tree_dn
: Optional
[str]
148 ldap_use_starttls
: Optional
[bool]
149 ldap_tls_cacert_base64
: Optional
[str]
150 ldap_tls_req_cert
: Optional
[str]
153 def validate_ldap_user_enabled_default(cls
, v
):
155 if v
not in ["true", "false"]:
156 raise ValueError('must be equal to "true" or "false"')
160 class KeystoneCharm(CharmedOsmBase
):
161 def __init__(self
, *args
) -> NoReturn
:
162 super().__init
__(*args
, oci_image
="image")
163 self
.state
.set_default(fernet_keys
=None)
164 self
.state
.set_default(credential_keys
=None)
165 self
.state
.set_default(keys_timestamp
=0)
167 self
.keystone_server
= KeystoneServer(self
, "keystone")
168 self
.mysql_client
= MysqlClient(self
, "db")
169 self
.framework
.observe(self
.on
["db"].relation_changed
, self
.configure_pod
)
170 self
.framework
.observe(self
.on
["db"].relation_broken
, self
.configure_pod
)
172 self
.framework
.observe(
173 self
.on
["keystone"].relation_joined
, self
._publish
_keystone
_info
176 def _publish_keystone_info(self
, event
):
177 if self
.unit
.is_leader():
178 config
= ConfigModel(**dict(self
.config
))
179 self
.keystone_server
.publish_info(
180 host
=f
"http://{self.app.name}:{PORT}/v3",
182 user_domain_name
=config
.user_domain_name
,
183 project_domain_name
=config
.project_domain_name
,
184 username
=config
.service_username
,
185 password
=config
.service_password
,
186 service
=config
.service_project
,
187 keystone_db_password
=config
.keystone_db_password
,
188 region_id
=config
.region_id
,
189 admin_username
=config
.admin_username
,
190 admin_password
=config
.admin_password
,
191 admin_project_name
=config
.admin_project
,
194 def _check_missing_dependencies(self
, config
: ConfigModel
):
195 missing_relations
= []
196 if not config
.mysql_host
and self
.mysql_client
.is_missing_data_in_unit():
197 missing_relations
.append("mysql")
198 if missing_relations
:
199 raise RelationsMissing(missing_relations
)
201 def _validate_mysql_config(self
, config
: ConfigModel
):
203 if not config
.mysql_root_password
:
204 invalid_values
.append("Mysql root password must be provided")
207 raise ValueError("Invalid values: " + ", ".join(invalid_values
))
209 def _generate_keys(self
) -> Tuple
[List
[str], List
[str]]:
210 """Generating new fernet tokens.
213 Tuple[List[str], List[str]]: contains two lists of strings. First
214 list contains strings that represent
215 the keys for fernet and the second
216 list contains strins that represent
217 the keys for credentials.
220 Fernet
.generate_key().decode() for _
in range(NUMBER_FERNET_KEYS
)
223 Fernet
.generate_key().decode() for _
in range(NUMBER_CREDENTIAL_KEYS
)
226 return (fernet_keys
, credential_keys
)
229 keys_timestamp
= self
.state
.keys_timestamp
230 if fernet_keys
:= self
.state
.fernet_keys
:
231 fernet_keys
= json
.loads(fernet_keys
)
233 if credential_keys
:= self
.state
.credential_keys
:
234 credential_keys
= json
.loads(credential_keys
)
236 now
= datetime
.now().timestamp()
237 token_expiration
= self
.config
["token_expiration"]
239 valid_keys
= (now
- keys_timestamp
) < token_expiration
240 if not credential_keys
or not fernet_keys
or not valid_keys
:
241 fernet_keys
, credential_keys
= self
._generate
_keys
()
242 self
.state
.fernet_keys
= json
.dumps(fernet_keys
)
243 self
.state
.credential_keys
= json
.dumps(credential_keys
)
244 self
.state
.keys_timestamp
= now
245 return credential_keys
, fernet_keys
247 def _build_files(self
, config
: ConfigModel
):
248 credentials_files_builder
= FilesV3Builder()
249 fernet_files_builder
= FilesV3Builder()
251 credential_keys
, fernet_keys
= self
._get
_keys
()
253 for (key_id
, value
) in enumerate(credential_keys
):
254 credentials_files_builder
.add_file(str(key_id
), value
)
255 for (key_id
, value
) in enumerate(fernet_keys
):
256 fernet_files_builder
.add_file(str(key_id
), value
)
257 return credentials_files_builder
.build(), fernet_files_builder
.build()
259 def build_pod_spec(self
, image_info
):
261 config
= ConfigModel(**dict(self
.config
))
262 config_ldap
= ConfigLdapModel(**dict(self
.config
))
264 if config
.mysql_host
and not self
.mysql_client
.is_missing_data_in_unit():
265 raise Exception("Mysql data cannot be provided via config and relation")
267 if config
.mysql_host
:
268 self
._validate
_mysql
_config
(config
)
271 self
._check
_missing
_dependencies
(config
)
273 # Create Builder for the PodSpec
274 pod_spec_builder
= PodSpecV3Builder()
277 container_builder
= ContainerV3Builder(
278 self
.app
.name
, image_info
, config
.image_pull_policy
280 container_builder
.add_port(name
=self
.app
.name
, port
=PORT
)
283 credential_files
, fernet_files
= self
._build
_files
(config
)
284 container_builder
.add_volume_config(
285 "credential-keys", CREDENTIAL_KEYS_PATH
, credential_files
287 container_builder
.add_volume_config(
288 "fernet-keys", FERNET_KEYS_PATH
, fernet_files
290 container_builder
.add_envs(
292 "DB_HOST": config
.mysql_host
or self
.mysql_client
.host
,
293 "DB_PORT": config
.mysql_port
or self
.mysql_client
.port
,
294 "ROOT_DB_USER": "root",
295 "ROOT_DB_PASSWORD": config
.mysql_root_password
296 or self
.mysql_client
.root_password
,
297 "KEYSTONE_DB_PASSWORD": config
.keystone_db_password
,
298 "REGION_ID": config
.region_id
,
299 "KEYSTONE_HOST": self
.app
.name
,
300 "ADMIN_USERNAME": config
.admin_username
,
301 "ADMIN_PASSWORD": config
.admin_password
,
302 "ADMIN_PROJECT": config
.admin_project
,
303 "SERVICE_USERNAME": config
.service_username
,
304 "SERVICE_PASSWORD": config
.service_password
,
305 "SERVICE_PROJECT": config
.service_project
,
309 if config_ldap
.ldap_enabled
:
310 container_builder
.add_envs(
312 "LDAP_AUTHENTICATION_DOMAIN_NAME": config_ldap
.ldap_authentication_domain_name
,
313 "LDAP_URL": config_ldap
.ldap_url
,
314 "LDAP_PAGE_SIZE": config_ldap
.ldap_page_size
,
315 "LDAP_USER_OBJECTCLASS": config_ldap
.ldap_user_objectclass
,
316 "LDAP_USER_ID_ATTRIBUTE": config_ldap
.ldap_user_id_attribute
,
317 "LDAP_USER_NAME_ATTRIBUTE": config_ldap
.ldap_user_name_attribute
,
318 "LDAP_USER_PASS_ATTRIBUTE": config_ldap
.ldap_user_pass_attribute
,
319 "LDAP_USER_ENABLED_MASK": config_ldap
.ldap_user_enabled_mask
,
320 "LDAP_USER_ENABLED_DEFAULT": config_ldap
.ldap_user_enabled_default
,
321 "LDAP_USER_ENABLED_INVERT": config_ldap
.ldap_user_enabled_invert
,
322 "LDAP_GROUP_OBJECTCLASS": config_ldap
.ldap_group_objectclass
,
325 if config_ldap
.ldap_bind_user
:
326 container_builder
.add_envs(
327 {"LDAP_BIND_USER": config_ldap
.ldap_bind_user
}
330 if config_ldap
.ldap_bind_password
:
331 container_builder
.add_envs(
332 {"LDAP_BIND_PASSWORD": config_ldap
.ldap_bind_password
}
335 if config_ldap
.ldap_user_tree_dn
:
336 container_builder
.add_envs(
337 {"LDAP_USER_TREE_DN": config_ldap
.ldap_user_tree_dn
}
340 if config_ldap
.ldap_user_filter
:
341 container_builder
.add_envs(
342 {"LDAP_USER_FILTER": config_ldap
.ldap_user_filter
}
345 if config_ldap
.ldap_user_enabled_attribute
:
346 container_builder
.add_envs(
348 "LDAP_USER_ENABLED_ATTRIBUTE": config_ldap
.ldap_user_enabled_attribute
352 if config_ldap
.ldap_chase_referrals
:
353 container_builder
.add_envs(
354 {"LDAP_CHASE_REFERRALS": config_ldap
.ldap_chase_referrals
}
357 if config_ldap
.ldap_group_tree_dn
:
358 container_builder
.add_envs(
359 {"LDAP_GROUP_TREE_DN": config_ldap
.ldap_group_tree_dn
}
362 if config_ldap
.ldap_tls_cacert_base64
:
363 container_builder
.add_envs(
364 {"LDAP_TLS_CACERT_BASE64": config_ldap
.ldap_tls_cacert_base64
}
367 if config_ldap
.ldap_use_starttls
:
368 container_builder
.add_envs(
370 "LDAP_USE_STARTTLS": config_ldap
.ldap_use_starttls
,
371 "LDAP_TLS_CACERT_BASE64": config_ldap
.ldap_tls_cacert_base64
,
372 "LDAP_TLS_REQ_CERT": config_ldap
.ldap_tls_req_cert
,
375 container
= container_builder
.build()
377 # Add container to pod spec
378 pod_spec_builder
.add_container(container
)
380 # Add ingress resources to pod spec if site url exists
382 parsed
= urlparse(config
.site_url
)
384 "nginx.ingress.kubernetes.io/proxy-body-size": "{}".format(
385 str(config
.max_file_size
) + "m"
386 if config
.max_file_size
> 0
387 else config
.max_file_size
390 if config
.ingress_class
:
391 annotations
["kubernetes.io/ingress.class"] = config
.ingress_class
392 ingress_resource_builder
= IngressResourceV3Builder(
393 f
"{self.app.name}-ingress", annotations
396 if config
.ingress_whitelist_source_range
:
398 "nginx.ingress.kubernetes.io/whitelist-source-range"
399 ] = config
.ingress_whitelist_source_range
401 if parsed
.scheme
== "https":
402 ingress_resource_builder
.add_tls(
403 [parsed
.hostname
], config
.tls_secret_name
406 annotations
["nginx.ingress.kubernetes.io/ssl-redirect"] = "false"
408 ingress_resource_builder
.add_rule(parsed
.hostname
, self
.app
.name
, PORT
)
409 ingress_resource
= ingress_resource_builder
.build()
410 pod_spec_builder
.add_ingress_resource(ingress_resource
)
411 return pod_spec_builder
.build()
414 if __name__
== "__main__":
417 # LOGGER = logging.getLogger(__name__)
420 # class ConfigurePodEvent(EventBase):
421 # """Configure Pod event"""
426 # class KeystoneEvents(CharmEvents):
427 # """Keystone Events"""
429 # configure_pod = EventSource(ConfigurePodEvent)
431 # class KeystoneCharm(CharmBase):
432 # """Keystone K8s Charm"""
434 # state = StoredState()
435 # on = KeystoneEvents()
437 # def __init__(self, *args) -> NoReturn:
438 # """Constructor of the Charm object.
439 # Initializes internal state and register events it can handle.
441 # super().__init__(*args)
442 # self.state.set_default(db_host=None)
443 # self.state.set_default(db_port=None)
444 # self.state.set_default(db_user=None)
445 # self.state.set_default(db_password=None)
446 # self.state.set_default(pod_spec=None)
447 # self.state.set_default(fernet_keys=None)
448 # self.state.set_default(credential_keys=None)
449 # self.state.set_default(keys_timestamp=0)
451 # # Register all of the events we want to observe
452 # self.framework.observe(self.on.config_changed, self.configure_pod)
453 # self.framework.observe(self.on.start, self.configure_pod)
454 # self.framework.observe(self.on.upgrade_charm, self.configure_pod)
455 # self.framework.observe(self.on.leader_elected, self.configure_pod)
456 # self.framework.observe(self.on.update_status, self.configure_pod)
458 # # Registering custom internal events
459 # self.framework.observe(self.on.configure_pod, self.configure_pod)
461 # # Register relation events
462 # self.framework.observe(
463 # self.on.db_relation_changed, self._on_db_relation_changed
465 # self.framework.observe(
466 # self.on.db_relation_broken, self._on_db_relation_broken
468 # self.framework.observe(
469 # self.on.keystone_relation_joined, self._publish_keystone_info
472 # def _publish_keystone_info(self, event: EventBase) -> NoReturn:
473 # """Publishes keystone information for NBI usage through the keystone
477 # event (EventBase): Keystone relation event to update NBI.
479 # config = self.model.config
481 # "host": f"http://{self.app.name}:{KEYSTONE_PORT}/v3",
482 # "port": str(KEYSTONE_PORT),
483 # "keystone_db_password": config["keystone_db_password"],
484 # "region_id": config["region_id"],
485 # "user_domain_name": config["user_domain_name"],
486 # "project_domain_name": config["project_domain_name"],
487 # "admin_username": config["admin_username"],
488 # "admin_password": config["admin_password"],
489 # "admin_project_name": config["admin_project"],
490 # "username": config["service_username"],
491 # "password": config["service_password"],
492 # "service": config["service_project"],
494 # for k, v in rel_data.items():
495 # event.relation.data[self.model.unit][k] = v
497 # def _on_db_relation_changed(self, event: EventBase) -> NoReturn:
498 # """Reads information about the DB relation, in order for keystone to
502 # event (EventBase): DB relation event to access database
505 # if not event.unit in event.relation.data:
507 # relation_data = event.relation.data[event.unit]
508 # db_host = relation_data.get("host")
509 # db_port = int(relation_data.get("port", 3306))
511 # db_password = relation_data.get("root_password")
519 # self.state.db_host != db_host
520 # or self.state.db_port != db_port
521 # or self.state.db_user != db_user
522 # or self.state.db_password != db_password
525 # self.state.db_host = db_host
526 # self.state.db_port = db_port
527 # self.state.db_user = db_user
528 # self.state.db_password = db_password
529 # self.on.configure_pod.emit()
532 # def _on_db_relation_broken(self, event: EventBase) -> NoReturn:
533 # """Clears data from db relation.
536 # event (EventBase): DB relation event.
539 # self.state.db_host = None
540 # self.state.db_port = None
541 # self.state.db_user = None
542 # self.state.db_password = None
543 # self.on.configure_pod.emit()
545 # def _check_settings(self) -> str:
546 # """Check if there any settings missing from Keystone configuration.
549 # str: Information about the problems found (if any).
552 # config = self.model.config
554 # for setting in REQUIRED_SETTINGS:
555 # if not config.get(setting):
556 # problem = f"missing config {setting}"
557 # problems.append(problem)
559 # return ";".join(problems)
561 # def _make_pod_image_details(self) -> Dict[str, str]:
562 # """Generate the pod image details.
565 # Dict[str, str]: pod image details.
567 # config = self.model.config
569 # "imagePath": config["image"],
571 # if config["image_username"]:
572 # image_details.update(
574 # "username": config["image_username"],
575 # "password": config["image_password"],
578 # return image_details
580 # def _make_pod_ports(self) -> List[Dict[str, Any]]:
581 # """Generate the pod ports details.
584 # List[Dict[str, Any]]: pod ports details.
587 # {"name": "keystone", "containerPort": KEYSTONE_PORT, "protocol": "TCP"},
590 # def _make_pod_envconfig(self) -> Dict[str, Any]:
591 # """Generate pod environment configuraiton.
594 # Dict[str, Any]: pod environment configuration.
596 # config = self.model.config
599 # "DB_HOST": self.state.db_host,
600 # "DB_PORT": self.state.db_port,
601 # "ROOT_DB_USER": self.state.db_user,
602 # "ROOT_DB_PASSWORD": self.state.db_password,
603 # "KEYSTONE_DB_PASSWORD": config["keystone_db_password"],
604 # "REGION_ID": config["region_id"],
605 # "KEYSTONE_HOST": self.app.name,
606 # "ADMIN_USERNAME": config["admin_username"],
607 # "ADMIN_PASSWORD": config["admin_password"],
608 # "ADMIN_PROJECT": config["admin_project"],
609 # "SERVICE_USERNAME": config["service_username"],
610 # "SERVICE_PASSWORD": config["service_password"],
611 # "SERVICE_PROJECT": config["service_project"],
614 # if config.get("ldap_enabled"):
615 # envconfig["LDAP_AUTHENTICATION_DOMAIN_NAME"] = config[
616 # "ldap_authentication_domain_name"
618 # envconfig["LDAP_URL"] = config["ldap_url"]
619 # envconfig["LDAP_PAGE_SIZE"] = config["ldap_page_size"]
620 # envconfig["LDAP_USER_OBJECTCLASS"] = config["ldap_user_objectclass"]
621 # envconfig["LDAP_USER_ID_ATTRIBUTE"] = config["ldap_user_id_attribute"]
622 # envconfig["LDAP_USER_NAME_ATTRIBUTE"] = config["ldap_user_name_attribute"]
623 # envconfig["LDAP_USER_PASS_ATTRIBUTE"] = config["ldap_user_pass_attribute"]
624 # envconfig["LDAP_USER_ENABLED_MASK"] = config["ldap_user_enabled_mask"]
625 # envconfig["LDAP_USER_ENABLED_DEFAULT"] = config["ldap_user_enabled_default"]
626 # envconfig["LDAP_USER_ENABLED_INVERT"] = config["ldap_user_enabled_invert"]
627 # envconfig["LDAP_GROUP_OBJECTCLASS"] = config["ldap_group_objectclass"]
629 # if config["ldap_bind_user"]:
630 # envconfig["LDAP_BIND_USER"] = config["ldap_bind_user"]
632 # if config["ldap_bind_password"]:
633 # envconfig["LDAP_BIND_PASSWORD"] = config["ldap_bind_password"]
635 # if config["ldap_user_tree_dn"]:
636 # envconfig["LDAP_USER_TREE_DN"] = config["ldap_user_tree_dn"]
638 # if config["ldap_user_filter"]:
639 # envconfig["LDAP_USER_FILTER"] = config["ldap_user_filter"]
641 # if config["ldap_user_enabled_attribute"]:
642 # envconfig["LDAP_USER_ENABLED_ATTRIBUTE"] = config[
643 # "ldap_user_enabled_attribute"
646 # if config["ldap_chase_referrals"]:
647 # envconfig["LDAP_CHASE_REFERRALS"] = config["ldap_chase_referrals"]
649 # if config["ldap_group_tree_dn"]:
650 # envconfig["LDAP_GROUP_TREE_DN"] = config["ldap_group_tree_dn"]
652 # if config["ldap_use_starttls"]:
653 # envconfig["LDAP_USE_STARTTLS"] = config["ldap_use_starttls"]
654 # envconfig["LDAP_TLS_CACERT_BASE64"] = config["ldap_tls_cacert_base64"]
655 # envconfig["LDAP_TLS_REQ_CERT"] = config["ldap_tls_req_cert"]
659 # def _make_pod_ingress_resources(self) -> List[Dict[str, Any]]:
660 # """Generate pod ingress resources.
663 # List[Dict[str, Any]]: pod ingress resources.
665 # site_url = self.model.config["site_url"]
670 # parsed = urlparse(site_url)
672 # if not parsed.scheme.startswith("http"):
675 # max_file_size = self.model.config["max_file_size"]
676 # ingress_whitelist_source_range = self.model.config[
677 # "ingress_whitelist_source_range"
681 # "nginx.ingress.kubernetes.io/proxy-body-size": "{}m".format(max_file_size)
684 # if ingress_whitelist_source_range:
686 # "nginx.ingress.kubernetes.io/whitelist-source-range"
687 # ] = ingress_whitelist_source_range
689 # ingress_spec_tls = None
691 # if parsed.scheme == "https":
692 # ingress_spec_tls = [{"hosts": [parsed.hostname]}]
693 # tls_secret_name = self.model.config["tls_secret_name"]
694 # if tls_secret_name:
695 # ingress_spec_tls[0]["secretName"] = tls_secret_name
697 # annotations["nginx.ingress.kubernetes.io/ssl-redirect"] = "false"
700 # "name": "{}-ingress".format(self.app.name),
701 # "annotations": annotations,
705 # "host": parsed.hostname,
711 # "serviceName": self.app.name,
712 # "servicePort": KEYSTONE_PORT,
721 # if ingress_spec_tls:
722 # ingress["spec"]["tls"] = ingress_spec_tls
726 # def _generate_keys(self) -> Tuple[List[str], List[str]]:
727 # """Generating new fernet tokens.
730 # Tuple[List[str], List[str]]: contains two lists of strings. First
731 # list contains strings that represent
732 # the keys for fernet and the second
733 # list contains strins that represent
734 # the keys for credentials.
737 # Fernet.generate_key().decode() for _ in range(NUMBER_FERNET_KEYS)
739 # credential_keys = [
740 # Fernet.generate_key().decode() for _ in range(NUMBER_CREDENTIAL_KEYS)
743 # return (fernet_keys, credential_keys)
745 # def configure_pod(self, event: EventBase) -> NoReturn:
746 # """Assemble the pod spec and apply it, if possible.
749 # event (EventBase): Hook or Relation event that started the
752 # if not self.state.db_host:
753 # self.unit.status = WaitingStatus("Waiting for database relation")
757 # if not self.unit.is_leader():
758 # self.unit.status = ActiveStatus("ready")
761 # if fernet_keys := self.state.fernet_keys:
762 # fernet_keys = json.loads(fernet_keys)
764 # if credential_keys := self.state.credential_keys:
765 # credential_keys = json.loads(credential_keys)
767 # now = datetime.now().timestamp()
768 # keys_timestamp = self.state.keys_timestamp
769 # token_expiration = self.model.config["token_expiration"]
771 # valid_keys = (now - keys_timestamp) < token_expiration
772 # if not credential_keys or not fernet_keys or not valid_keys:
773 # fernet_keys, credential_keys = self._generate_keys()
774 # self.state.fernet_keys = json.dumps(fernet_keys)
775 # self.state.credential_keys = json.dumps(credential_keys)
776 # self.state.keys_timestamp = now
778 # # Check problems in the settings
779 # problems = self._check_settings()
781 # self.unit.status = BlockedStatus(problems)
784 # self.unit.status = BlockedStatus("Assembling pod spec")
785 # image_details = self._make_pod_image_details()
786 # ports = self._make_pod_ports()
787 # env_config = self._make_pod_envconfig()
788 # ingress_resources = self._make_pod_ingress_resources()
789 # files = self._make_pod_files(fernet_keys, credential_keys)
795 # "name": self.framework.model.app.name,
796 # "imageDetails": image_details,
798 # "envConfig": env_config,
799 # "volumeConfig": files,
802 # "kubernetesResources": {"ingressResources": ingress_resources or []},
805 # if self.state.pod_spec != (
806 # pod_spec_json := json.dumps(pod_spec, sort_keys=True)
808 # self.state.pod_spec = pod_spec_json
809 # self.model.pod.set_spec(pod_spec)
811 # self.unit.status = ActiveStatus("ready")
814 # if __name__ == "__main__":
815 # main(KeystoneCharm)