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_whitelist_source_range
: Optional
[str]
82 tls_secret_name
: Optional
[str]
84 @validator("max_file_size")
85 def validate_max_file_size(cls
, v
):
87 raise ValueError("value must be equal or greater than 0")
90 @validator("site_url")
91 def validate_site_url(cls
, v
):
94 if not parsed
.scheme
.startswith("http"):
95 raise ValueError("value must start with http")
98 @validator("ingress_whitelist_source_range")
99 def validate_ingress_whitelist_source_range(cls
, v
):
105 class ConfigLdapModel(ModelValidator
):
107 ldap_authentication_domain_name
: Optional
[str]
108 ldap_url
: Optional
[str]
109 ldap_bind_user
: Optional
[str]
110 ldap_bind_password
: Optional
[str]
111 ldap_chase_referrals
: Optional
[str]
112 ldap_page_size
: Optional
[int]
113 ldap_user_tree_dn
: Optional
[str]
114 ldap_user_objectclass
: Optional
[str]
115 ldap_user_id_attribute
: Optional
[str]
116 ldap_user_name_attribute
: Optional
[str]
117 ldap_user_pass_attribute
: Optional
[str]
118 ldap_user_filter
: Optional
[str]
119 ldap_user_enabled_attribute
: Optional
[str]
120 ldap_user_enabled_mask
: Optional
[int]
121 ldap_user_enabled_default
: Optional
[bool]
122 ldap_user_enabled_invert
: Optional
[bool]
123 ldap_group_objectclass
: Optional
[str]
124 ldap_group_tree_dn
: Optional
[str]
125 ldap_use_starttls
: Optional
[bool]
126 ldap_tls_cacert_base64
: Optional
[str]
127 ldap_tls_req_cert
: Optional
[str]
130 class KeystoneCharm(CharmedOsmBase
):
131 def __init__(self
, *args
) -> NoReturn
:
132 super().__init
__(*args
, oci_image
="image")
133 self
.state
.set_default(fernet_keys
=None)
134 self
.state
.set_default(credential_keys
=None)
135 self
.state
.set_default(keys_timestamp
=0)
137 self
.keystone_server
= KeystoneServer(self
, "keystone")
138 self
.mysql_client
= MysqlClient(self
, "db")
139 self
.framework
.observe(self
.on
["db"].relation_changed
, self
.configure_pod
)
140 self
.framework
.observe(self
.on
["db"].relation_broken
, self
.configure_pod
)
142 self
.framework
.observe(
143 self
.on
["keystone"].relation_joined
, self
._publish
_keystone
_info
146 def _publish_keystone_info(self
, event
):
147 if self
.unit
.is_leader():
148 config
= ConfigModel(**dict(self
.config
))
149 self
.keystone_server
.publish_info(
150 host
=f
"http://{self.app.name}:{PORT}/v3",
152 user_domain_name
=config
.user_domain_name
,
153 project_domain_name
=config
.project_domain_name
,
154 username
=config
.service_username
,
155 password
=config
.service_password
,
156 service
=config
.service_project
,
157 keystone_db_password
=config
.keystone_db_password
,
158 region_id
=config
.region_id
,
159 admin_username
=config
.admin_username
,
160 admin_password
=config
.admin_password
,
161 admin_project_name
=config
.admin_project
,
164 def _check_missing_dependencies(self
, config
: ConfigModel
):
165 missing_relations
= []
166 if self
.mysql_client
.is_missing_data_in_unit():
167 missing_relations
.append("mysql")
168 if missing_relations
:
169 raise RelationsMissing(missing_relations
)
171 def _generate_keys(self
) -> Tuple
[List
[str], List
[str]]:
172 """Generating new fernet tokens.
175 Tuple[List[str], List[str]]: contains two lists of strings. First
176 list contains strings that represent
177 the keys for fernet and the second
178 list contains strins that represent
179 the keys for credentials.
182 Fernet
.generate_key().decode() for _
in range(NUMBER_FERNET_KEYS
)
185 Fernet
.generate_key().decode() for _
in range(NUMBER_CREDENTIAL_KEYS
)
188 return (fernet_keys
, credential_keys
)
191 keys_timestamp
= self
.state
.keys_timestamp
192 if fernet_keys
:= self
.state
.fernet_keys
:
193 fernet_keys
= json
.loads(fernet_keys
)
195 if credential_keys
:= self
.state
.credential_keys
:
196 credential_keys
= json
.loads(credential_keys
)
198 now
= datetime
.now().timestamp()
199 token_expiration
= self
.config
["token_expiration"]
201 valid_keys
= (now
- keys_timestamp
) < token_expiration
202 if not credential_keys
or not fernet_keys
or not valid_keys
:
203 fernet_keys
, credential_keys
= self
._generate
_keys
()
204 self
.state
.fernet_keys
= json
.dumps(fernet_keys
)
205 self
.state
.credential_keys
= json
.dumps(credential_keys
)
206 self
.state
.keys_timestamp
= now
207 return credential_keys
, fernet_keys
209 def _build_files(self
, config
: ConfigModel
):
210 credentials_files_builder
= FilesV3Builder()
211 fernet_files_builder
= FilesV3Builder()
213 credential_keys
, fernet_keys
= self
._get
_keys
()
215 for (key_id
, value
) in enumerate(credential_keys
):
216 credentials_files_builder
.add_file(str(key_id
), value
)
217 for (key_id
, value
) in enumerate(fernet_keys
):
218 fernet_files_builder
.add_file(str(key_id
), value
)
219 return credentials_files_builder
.build(), fernet_files_builder
.build()
221 def build_pod_spec(self
, image_info
):
223 config
= ConfigModel(**dict(self
.config
))
224 config_ldap
= ConfigLdapModel(**dict(self
.config
))
226 self
._check
_missing
_dependencies
(config
)
227 # Create Builder for the PodSpec
228 pod_spec_builder
= PodSpecV3Builder()
230 container_builder
= ContainerV3Builder(self
.app
.name
, image_info
)
231 container_builder
.add_port(name
=self
.app
.name
, port
=PORT
)
233 credential_files
, fernet_files
= self
._build
_files
(config
)
234 container_builder
.add_volume_config(
235 "credential-keys", CREDENTIAL_KEYS_PATH
, credential_files
237 container_builder
.add_volume_config(
238 "fernet-keys", FERNET_KEYS_PATH
, fernet_files
240 container_builder
.add_envs(
242 "DB_HOST": self
.mysql_client
.host
,
243 "DB_PORT": self
.mysql_client
.port
,
244 "ROOT_DB_USER": "root",
245 "ROOT_DB_PASSWORD": self
.mysql_client
.root_password
,
246 "KEYSTONE_DB_PASSWORD": config
.keystone_db_password
,
247 "REGION_ID": config
.region_id
,
248 "KEYSTONE_HOST": self
.app
.name
,
249 "ADMIN_USERNAME": config
.admin_username
,
250 "ADMIN_PASSWORD": config
.admin_password
,
251 "ADMIN_PROJECT": config
.admin_project
,
252 "SERVICE_USERNAME": config
.service_username
,
253 "SERVICE_PASSWORD": config
.service_password
,
254 "SERVICE_PROJECT": config
.service_project
,
258 if config_ldap
.ldap_enabled
:
260 container_builder
.add_envs(
262 "LDAP_AUTHENTICATION_DOMAIN_NAME": config_ldap
.ldap_authentication_domain_name
,
263 "LDAP_URL": config_ldap
.ldap_url
,
264 "LDAP_PAGE_SIZE": config_ldap
.ldap_page_size
,
265 "LDAP_USER_OBJECTCLASS": config_ldap
.ldap_user_objectclass
,
266 "LDAP_USER_ID_ATTRIBUTE": config_ldap
.ldap_user_id_attribute
,
267 "LDAP_USER_NAME_ATTRIBUTE": config_ldap
.ldap_user_name_attribute
,
268 "LDAP_USER_PASS_ATTRIBUTE": config_ldap
.ldap_user_pass_attribute
,
269 "LDAP_USER_ENABLED_MASK": config_ldap
.ldap_user_enabled_mask
,
270 "LDAP_USER_ENABLED_DEFAULT": config_ldap
.ldap_user_enabled_default
,
271 "LDAP_USER_ENABLED_INVERT": config_ldap
.ldap_user_enabled_invert
,
272 "LDAP_GROUP_OBJECTCLASS": config_ldap
.ldap_group_objectclass
,
275 if config_ldap
.ldap_bind_user
:
276 container_builder
.add_envs(
277 {"LDAP_BIND_USER": config_ldap
.ldap_bind_user
}
280 if config_ldap
.ldap_bind_password
:
281 container_builder
.add_envs(
282 {"LDAP_BIND_PASSWORD": config_ldap
.ldap_bind_password
}
285 if config_ldap
.ldap_user_tree_dn
:
286 container_builder
.add_envs(
287 {"LDAP_USER_TREE_DN": config_ldap
.ldap_user_tree_dn
}
290 if config_ldap
.ldap_user_filter
:
291 container_builder
.add_envs(
292 {"LDAP_USER_FILTER": config_ldap
.ldap_user_filter
}
295 if config_ldap
.ldap_user_enabled_attribute
:
296 container_builder
.add_envs(
298 "LDAP_USER_ENABLED_ATTRIBUTE": config_ldap
.ldap_user_enabled_attribute
302 if config_ldap
.ldap_chase_referrals
:
303 container_builder
.add_envs(
304 {"LDAP_CHASE_REFERRALS": config_ldap
.ldap_chase_referrals
}
307 if config_ldap
.ldap_group_tree_dn
:
308 container_builder
.add_envs(
309 {"LDAP_GROUP_TREE_DN": config_ldap
.ldap_group_tree_dn
}
312 if config_ldap
.ldap_use_starttls
:
313 container_builder
.add_envs(
315 "LDAP_USE_STARTTLS": config_ldap
.ldap_use_starttls
,
316 "LDAP_TLS_CACERT_BASE64": config_ldap
.ldap_tls_cacert_base64
,
317 "LDAP_TLS_REQ_CERT": config_ldap
.ldap_tls_req_cert
,
320 container
= container_builder
.build()
321 # Add container to pod spec
322 pod_spec_builder
.add_container(container
)
323 # Add ingress resources to pod spec if site url exists
325 parsed
= urlparse(config
.site_url
)
327 "nginx.ingress.kubernetes.io/proxy-body-size": "{}".format(
328 str(config
.max_file_size
) + "m"
329 if config
.max_file_size
> 0
330 else config
.max_file_size
333 ingress_resource_builder
= IngressResourceV3Builder(
334 f
"{self.app.name}-ingress", annotations
337 if config
.ingress_whitelist_source_range
:
339 "nginx.ingress.kubernetes.io/whitelist-source-range"
340 ] = config
.ingress_whitelist_source_range
342 if parsed
.scheme
== "https":
343 ingress_resource_builder
.add_tls(
344 [parsed
.hostname
], config
.tls_secret_name
347 annotations
["nginx.ingress.kubernetes.io/ssl-redirect"] = "false"
349 ingress_resource_builder
.add_rule(parsed
.hostname
, self
.app
.name
, PORT
)
350 ingress_resource
= ingress_resource_builder
.build()
351 pod_spec_builder
.add_ingress_resource(ingress_resource
)
352 return pod_spec_builder
.build()
355 if __name__
== "__main__":
358 # LOGGER = logging.getLogger(__name__)
361 # class ConfigurePodEvent(EventBase):
362 # """Configure Pod event"""
367 # class KeystoneEvents(CharmEvents):
368 # """Keystone Events"""
370 # configure_pod = EventSource(ConfigurePodEvent)
372 # class KeystoneCharm(CharmBase):
373 # """Keystone K8s Charm"""
375 # state = StoredState()
376 # on = KeystoneEvents()
378 # def __init__(self, *args) -> NoReturn:
379 # """Constructor of the Charm object.
380 # Initializes internal state and register events it can handle.
382 # super().__init__(*args)
383 # self.state.set_default(db_host=None)
384 # self.state.set_default(db_port=None)
385 # self.state.set_default(db_user=None)
386 # self.state.set_default(db_password=None)
387 # self.state.set_default(pod_spec=None)
388 # self.state.set_default(fernet_keys=None)
389 # self.state.set_default(credential_keys=None)
390 # self.state.set_default(keys_timestamp=0)
392 # # Register all of the events we want to observe
393 # self.framework.observe(self.on.config_changed, self.configure_pod)
394 # self.framework.observe(self.on.start, self.configure_pod)
395 # self.framework.observe(self.on.upgrade_charm, self.configure_pod)
396 # self.framework.observe(self.on.leader_elected, self.configure_pod)
397 # self.framework.observe(self.on.update_status, self.configure_pod)
399 # # Registering custom internal events
400 # self.framework.observe(self.on.configure_pod, self.configure_pod)
402 # # Register relation events
403 # self.framework.observe(
404 # self.on.db_relation_changed, self._on_db_relation_changed
406 # self.framework.observe(
407 # self.on.db_relation_broken, self._on_db_relation_broken
409 # self.framework.observe(
410 # self.on.keystone_relation_joined, self._publish_keystone_info
413 # def _publish_keystone_info(self, event: EventBase) -> NoReturn:
414 # """Publishes keystone information for NBI usage through the keystone
418 # event (EventBase): Keystone relation event to update NBI.
420 # config = self.model.config
422 # "host": f"http://{self.app.name}:{KEYSTONE_PORT}/v3",
423 # "port": str(KEYSTONE_PORT),
424 # "keystone_db_password": config["keystone_db_password"],
425 # "region_id": config["region_id"],
426 # "user_domain_name": config["user_domain_name"],
427 # "project_domain_name": config["project_domain_name"],
428 # "admin_username": config["admin_username"],
429 # "admin_password": config["admin_password"],
430 # "admin_project_name": config["admin_project"],
431 # "username": config["service_username"],
432 # "password": config["service_password"],
433 # "service": config["service_project"],
435 # for k, v in rel_data.items():
436 # event.relation.data[self.model.unit][k] = v
438 # def _on_db_relation_changed(self, event: EventBase) -> NoReturn:
439 # """Reads information about the DB relation, in order for keystone to
443 # event (EventBase): DB relation event to access database
446 # if not event.unit in event.relation.data:
448 # relation_data = event.relation.data[event.unit]
449 # db_host = relation_data.get("host")
450 # db_port = int(relation_data.get("port", 3306))
452 # db_password = relation_data.get("root_password")
460 # self.state.db_host != db_host
461 # or self.state.db_port != db_port
462 # or self.state.db_user != db_user
463 # or self.state.db_password != db_password
466 # self.state.db_host = db_host
467 # self.state.db_port = db_port
468 # self.state.db_user = db_user
469 # self.state.db_password = db_password
470 # self.on.configure_pod.emit()
473 # def _on_db_relation_broken(self, event: EventBase) -> NoReturn:
474 # """Clears data from db relation.
477 # event (EventBase): DB relation event.
480 # self.state.db_host = None
481 # self.state.db_port = None
482 # self.state.db_user = None
483 # self.state.db_password = None
484 # self.on.configure_pod.emit()
486 # def _check_settings(self) -> str:
487 # """Check if there any settings missing from Keystone configuration.
490 # str: Information about the problems found (if any).
493 # config = self.model.config
495 # for setting in REQUIRED_SETTINGS:
496 # if not config.get(setting):
497 # problem = f"missing config {setting}"
498 # problems.append(problem)
500 # return ";".join(problems)
502 # def _make_pod_image_details(self) -> Dict[str, str]:
503 # """Generate the pod image details.
506 # Dict[str, str]: pod image details.
508 # config = self.model.config
510 # "imagePath": config["image"],
512 # if config["image_username"]:
513 # image_details.update(
515 # "username": config["image_username"],
516 # "password": config["image_password"],
519 # return image_details
521 # def _make_pod_ports(self) -> List[Dict[str, Any]]:
522 # """Generate the pod ports details.
525 # List[Dict[str, Any]]: pod ports details.
528 # {"name": "keystone", "containerPort": KEYSTONE_PORT, "protocol": "TCP"},
531 # def _make_pod_envconfig(self) -> Dict[str, Any]:
532 # """Generate pod environment configuraiton.
535 # Dict[str, Any]: pod environment configuration.
537 # config = self.model.config
540 # "DB_HOST": self.state.db_host,
541 # "DB_PORT": self.state.db_port,
542 # "ROOT_DB_USER": self.state.db_user,
543 # "ROOT_DB_PASSWORD": self.state.db_password,
544 # "KEYSTONE_DB_PASSWORD": config["keystone_db_password"],
545 # "REGION_ID": config["region_id"],
546 # "KEYSTONE_HOST": self.app.name,
547 # "ADMIN_USERNAME": config["admin_username"],
548 # "ADMIN_PASSWORD": config["admin_password"],
549 # "ADMIN_PROJECT": config["admin_project"],
550 # "SERVICE_USERNAME": config["service_username"],
551 # "SERVICE_PASSWORD": config["service_password"],
552 # "SERVICE_PROJECT": config["service_project"],
555 # if config.get("ldap_enabled"):
556 # envconfig["LDAP_AUTHENTICATION_DOMAIN_NAME"] = config[
557 # "ldap_authentication_domain_name"
559 # envconfig["LDAP_URL"] = config["ldap_url"]
560 # envconfig["LDAP_PAGE_SIZE"] = config["ldap_page_size"]
561 # envconfig["LDAP_USER_OBJECTCLASS"] = config["ldap_user_objectclass"]
562 # envconfig["LDAP_USER_ID_ATTRIBUTE"] = config["ldap_user_id_attribute"]
563 # envconfig["LDAP_USER_NAME_ATTRIBUTE"] = config["ldap_user_name_attribute"]
564 # envconfig["LDAP_USER_PASS_ATTRIBUTE"] = config["ldap_user_pass_attribute"]
565 # envconfig["LDAP_USER_ENABLED_MASK"] = config["ldap_user_enabled_mask"]
566 # envconfig["LDAP_USER_ENABLED_DEFAULT"] = config["ldap_user_enabled_default"]
567 # envconfig["LDAP_USER_ENABLED_INVERT"] = config["ldap_user_enabled_invert"]
568 # envconfig["LDAP_GROUP_OBJECTCLASS"] = config["ldap_group_objectclass"]
570 # if config["ldap_bind_user"]:
571 # envconfig["LDAP_BIND_USER"] = config["ldap_bind_user"]
573 # if config["ldap_bind_password"]:
574 # envconfig["LDAP_BIND_PASSWORD"] = config["ldap_bind_password"]
576 # if config["ldap_user_tree_dn"]:
577 # envconfig["LDAP_USER_TREE_DN"] = config["ldap_user_tree_dn"]
579 # if config["ldap_user_filter"]:
580 # envconfig["LDAP_USER_FILTER"] = config["ldap_user_filter"]
582 # if config["ldap_user_enabled_attribute"]:
583 # envconfig["LDAP_USER_ENABLED_ATTRIBUTE"] = config[
584 # "ldap_user_enabled_attribute"
587 # if config["ldap_chase_referrals"]:
588 # envconfig["LDAP_CHASE_REFERRALS"] = config["ldap_chase_referrals"]
590 # if config["ldap_group_tree_dn"]:
591 # envconfig["LDAP_GROUP_TREE_DN"] = config["ldap_group_tree_dn"]
593 # if config["ldap_use_starttls"]:
594 # envconfig["LDAP_USE_STARTTLS"] = config["ldap_use_starttls"]
595 # envconfig["LDAP_TLS_CACERT_BASE64"] = config["ldap_tls_cacert_base64"]
596 # envconfig["LDAP_TLS_REQ_CERT"] = config["ldap_tls_req_cert"]
600 # def _make_pod_ingress_resources(self) -> List[Dict[str, Any]]:
601 # """Generate pod ingress resources.
604 # List[Dict[str, Any]]: pod ingress resources.
606 # site_url = self.model.config["site_url"]
611 # parsed = urlparse(site_url)
613 # if not parsed.scheme.startswith("http"):
616 # max_file_size = self.model.config["max_file_size"]
617 # ingress_whitelist_source_range = self.model.config[
618 # "ingress_whitelist_source_range"
622 # "nginx.ingress.kubernetes.io/proxy-body-size": "{}m".format(max_file_size)
625 # if ingress_whitelist_source_range:
627 # "nginx.ingress.kubernetes.io/whitelist-source-range"
628 # ] = ingress_whitelist_source_range
630 # ingress_spec_tls = None
632 # if parsed.scheme == "https":
633 # ingress_spec_tls = [{"hosts": [parsed.hostname]}]
634 # tls_secret_name = self.model.config["tls_secret_name"]
635 # if tls_secret_name:
636 # ingress_spec_tls[0]["secretName"] = tls_secret_name
638 # annotations["nginx.ingress.kubernetes.io/ssl-redirect"] = "false"
641 # "name": "{}-ingress".format(self.app.name),
642 # "annotations": annotations,
646 # "host": parsed.hostname,
652 # "serviceName": self.app.name,
653 # "servicePort": KEYSTONE_PORT,
662 # if ingress_spec_tls:
663 # ingress["spec"]["tls"] = ingress_spec_tls
667 # def _generate_keys(self) -> Tuple[List[str], List[str]]:
668 # """Generating new fernet tokens.
671 # Tuple[List[str], List[str]]: contains two lists of strings. First
672 # list contains strings that represent
673 # the keys for fernet and the second
674 # list contains strins that represent
675 # the keys for credentials.
678 # Fernet.generate_key().decode() for _ in range(NUMBER_FERNET_KEYS)
680 # credential_keys = [
681 # Fernet.generate_key().decode() for _ in range(NUMBER_CREDENTIAL_KEYS)
684 # return (fernet_keys, credential_keys)
686 # def configure_pod(self, event: EventBase) -> NoReturn:
687 # """Assemble the pod spec and apply it, if possible.
690 # event (EventBase): Hook or Relation event that started the
693 # if not self.state.db_host:
694 # self.unit.status = WaitingStatus("Waiting for database relation")
698 # if not self.unit.is_leader():
699 # self.unit.status = ActiveStatus("ready")
702 # if fernet_keys := self.state.fernet_keys:
703 # fernet_keys = json.loads(fernet_keys)
705 # if credential_keys := self.state.credential_keys:
706 # credential_keys = json.loads(credential_keys)
708 # now = datetime.now().timestamp()
709 # keys_timestamp = self.state.keys_timestamp
710 # token_expiration = self.model.config["token_expiration"]
712 # valid_keys = (now - keys_timestamp) < token_expiration
713 # if not credential_keys or not fernet_keys or not valid_keys:
714 # fernet_keys, credential_keys = self._generate_keys()
715 # self.state.fernet_keys = json.dumps(fernet_keys)
716 # self.state.credential_keys = json.dumps(credential_keys)
717 # self.state.keys_timestamp = now
719 # # Check problems in the settings
720 # problems = self._check_settings()
722 # self.unit.status = BlockedStatus(problems)
725 # self.unit.status = BlockedStatus("Assembling pod spec")
726 # image_details = self._make_pod_image_details()
727 # ports = self._make_pod_ports()
728 # env_config = self._make_pod_envconfig()
729 # ingress_resources = self._make_pod_ingress_resources()
730 # files = self._make_pod_files(fernet_keys, credential_keys)
736 # "name": self.framework.model.app.name,
737 # "imageDetails": image_details,
739 # "envConfig": env_config,
740 # "volumeConfig": files,
743 # "kubernetesResources": {"ingressResources": ingress_resources or []},
746 # if self.state.pod_spec != (
747 # pod_spec_json := json.dumps(pod_spec, sort_keys=True)
749 # self.state.pod_spec = pod_spec_json
750 # self.model.pod.set_spec(pod_spec)
752 # self.unit.status = ActiveStatus("ready")
755 # if __name__ == "__main__":
756 # main(KeystoneCharm)