ContainerV3Builder,
FilesV3Builder,
IngressResourceV3Builder,
+ PodRestartPolicy,
PodSpecV3Builder,
)
from opslib.osm.validator import ModelValidator, validator
token_expiration: int
max_file_size: int
site_url: Optional[str]
+ ingress_class: Optional[str]
ingress_whitelist_source_range: Optional[str]
tls_secret_name: Optional[str]
+ mysql_host: Optional[str]
+ mysql_port: Optional[int]
+ mysql_root_password: Optional[str]
+ image_pull_policy: str
+ security_context: bool
@validator("max_file_size")
def validate_max_file_size(cls, v):
ip_network(v)
return v
+ @validator("mysql_port")
+ def validate_mysql_port(cls, v):
+ if v and (v <= 0 or v >= 65535):
+ raise ValueError("Mysql port out of range")
+ return v
+
+ @validator("image_pull_policy")
+ def validate_image_pull_policy(cls, v):
+ values = {
+ "always": "Always",
+ "ifnotpresent": "IfNotPresent",
+ "never": "Never",
+ }
+ v = v.lower()
+ if v not in values.keys():
+ raise ValueError("value must be always, ifnotpresent or never")
+ return values[v]
+
class ConfigLdapModel(ModelValidator):
ldap_enabled: bool
ldap_user_filter: Optional[str]
ldap_user_enabled_attribute: Optional[str]
ldap_user_enabled_mask: Optional[int]
- ldap_user_enabled_default: Optional[bool]
+ ldap_user_enabled_default: Optional[str]
ldap_user_enabled_invert: Optional[bool]
ldap_group_objectclass: Optional[str]
ldap_group_tree_dn: Optional[str]
ldap_tls_cacert_base64: Optional[str]
ldap_tls_req_cert: Optional[str]
+ @validator
+ def validate_ldap_user_enabled_default(cls, v):
+ if v:
+ if v not in ["true", "false"]:
+ raise ValueError('must be equal to "true" or "false"')
+ return v
+
class KeystoneCharm(CharmedOsmBase):
def __init__(self, *args) -> NoReturn:
- super().__init__(*args, oci_image="image")
+ super().__init__(
+ *args,
+ oci_image="image",
+ mysql_uri=True,
+ )
self.state.set_default(fernet_keys=None)
self.state.set_default(credential_keys=None)
self.state.set_default(keys_timestamp=0)
self.mysql_client = MysqlClient(self, "db")
self.framework.observe(self.on["db"].relation_changed, self.configure_pod)
self.framework.observe(self.on["db"].relation_broken, self.configure_pod)
+ self.framework.observe(self.on.update_status, self.configure_pod)
self.framework.observe(
self.on["keystone"].relation_joined, self._publish_keystone_info
admin_project_name=config.admin_project,
)
- def _check_missing_dependencies(self, config: ConfigModel):
+ def _check_missing_dependencies(self, config: ConfigModel, external_db: bool):
missing_relations = []
- if self.mysql_client.is_missing_data_in_unit():
+ if not external_db and self.mysql_client.is_missing_data_in_unit():
missing_relations.append("mysql")
if missing_relations:
raise RelationsMissing(missing_relations)
self.state.keys_timestamp = now
return credential_keys, fernet_keys
- def _build_files(self, config: ConfigModel):
+ def _build_files(
+ self, config: ConfigModel, credential_keys: List, fernet_keys: List
+ ):
credentials_files_builder = FilesV3Builder()
fernet_files_builder = FilesV3Builder()
-
- credential_keys, fernet_keys = self._get_keys()
-
- for (key_id, value) in enumerate(credential_keys):
- credentials_files_builder.add_file(str(key_id), value)
- for (key_id, value) in enumerate(fernet_keys):
- fernet_files_builder.add_file(str(key_id), value)
+ for (key_id, _) in enumerate(credential_keys):
+ credentials_files_builder.add_file(str(key_id), str(key_id), secret=True)
+ for (key_id, _) in enumerate(fernet_keys):
+ fernet_files_builder.add_file(str(key_id), str(key_id), secret=True)
return credentials_files_builder.build(), fernet_files_builder.build()
- def build_pod_spec(self, image_info):
+ def build_pod_spec(self, image_info, **kwargs):
# Validate config
config = ConfigModel(**dict(self.config))
+ mysql_config = kwargs["mysql_config"]
config_ldap = ConfigLdapModel(**dict(self.config))
+
+ if mysql_config.mysql_uri and not self.mysql_client.is_missing_data_in_unit():
+ raise Exception("Mysql data cannot be provided via config and relation")
# Check relations
- self._check_missing_dependencies(config)
+ external_db = True if mysql_config.mysql_uri else False
+ self._check_missing_dependencies(config, external_db)
+
# Create Builder for the PodSpec
- pod_spec_builder = PodSpecV3Builder()
- # Build Container
- container_builder = ContainerV3Builder(self.app.name, image_info)
- container_builder.add_port(name=self.app.name, port=PORT)
+ pod_spec_builder = PodSpecV3Builder(
+ enable_security_context=config.security_context
+ )
+ container_builder = ContainerV3Builder(
+ self.app.name,
+ image_info,
+ config.image_pull_policy,
+ run_as_non_root=config.security_context,
+ )
+
# Build files
- credential_files, fernet_files = self._build_files(config)
+ credential_keys, fernet_keys = self._get_keys()
+ credential_files, fernet_files = self._build_files(
+ config, credential_keys, fernet_keys
+ )
+
+ # Add pod secrets
+ fernet_keys_secret_name = f"{self.app.name}-fernet-keys-secret"
+ pod_spec_builder.add_secret(
+ fernet_keys_secret_name,
+ {str(key_id): value for (key_id, value) in enumerate(fernet_keys)},
+ )
+ credential_keys_secret_name = f"{self.app.name}-credential-keys-secret"
+ pod_spec_builder.add_secret(
+ credential_keys_secret_name,
+ {str(key_id): value for (key_id, value) in enumerate(credential_keys)},
+ )
+ mysql_secret_name = f"{self.app.name}-mysql-secret"
+
+ pod_spec_builder.add_secret(
+ mysql_secret_name,
+ {
+ "host": mysql_config.host,
+ "port": str(mysql_config.port),
+ "user": mysql_config.username,
+ "password": mysql_config.password,
+ }
+ if mysql_config.mysql_uri
+ else {
+ "host": self.mysql_client.host,
+ "port": str(self.mysql_client.port),
+ "user": "root",
+ "password": self.mysql_client.root_password,
+ },
+ )
+ keystone_secret_name = f"{self.app.name}-keystone-secret"
+ pod_spec_builder.add_secret(
+ keystone_secret_name,
+ {
+ "db_password": config.keystone_db_password,
+ "admin_username": config.admin_username,
+ "admin_password": config.admin_password,
+ "admin_project": config.admin_project,
+ "service_username": config.service_username,
+ "service_password": config.service_password,
+ "service_project": config.service_project,
+ },
+ )
+ # Build Container
container_builder.add_volume_config(
- "credential-keys", CREDENTIAL_KEYS_PATH, credential_files
+ "credential-keys",
+ CREDENTIAL_KEYS_PATH,
+ credential_files,
+ secret_name=credential_keys_secret_name,
)
container_builder.add_volume_config(
- "fernet-keys", FERNET_KEYS_PATH, fernet_files
+ "fernet-keys",
+ FERNET_KEYS_PATH,
+ fernet_files,
+ secret_name=fernet_keys_secret_name,
)
+ container_builder.add_port(name=self.app.name, port=PORT)
container_builder.add_envs(
{
- "DB_HOST": self.mysql_client.host,
- "DB_PORT": self.mysql_client.port,
- "ROOT_DB_USER": "root",
- "ROOT_DB_PASSWORD": self.mysql_client.root_password,
- "KEYSTONE_DB_PASSWORD": config.keystone_db_password,
"REGION_ID": config.region_id,
"KEYSTONE_HOST": self.app.name,
- "ADMIN_USERNAME": config.admin_username,
- "ADMIN_PASSWORD": config.admin_password,
- "ADMIN_PROJECT": config.admin_project,
- "SERVICE_USERNAME": config.service_username,
- "SERVICE_PASSWORD": config.service_password,
- "SERVICE_PROJECT": config.service_project,
}
)
-
+ container_builder.add_secret_envs(
+ secret_name=mysql_secret_name,
+ envs={
+ "DB_HOST": "host",
+ "DB_PORT": "port",
+ "ROOT_DB_USER": "user",
+ "ROOT_DB_PASSWORD": "password",
+ },
+ )
+ container_builder.add_secret_envs(
+ secret_name=keystone_secret_name,
+ envs={
+ "KEYSTONE_DB_PASSWORD": "db_password",
+ "ADMIN_USERNAME": "admin_username",
+ "ADMIN_PASSWORD": "admin_password",
+ "ADMIN_PROJECT": "admin_project",
+ "SERVICE_USERNAME": "service_username",
+ "SERVICE_PASSWORD": "service_password",
+ "SERVICE_PROJECT": "service_project",
+ },
+ )
+ ldap_secret_name = f"{self.app.name}-ldap-secret"
if config_ldap.ldap_enabled:
-
- container_builder.add_envs(
- {
- "LDAP_AUTHENTICATION_DOMAIN_NAME": config_ldap.ldap_authentication_domain_name,
- "LDAP_URL": config_ldap.ldap_url,
- "LDAP_PAGE_SIZE": config_ldap.ldap_page_size,
- "LDAP_USER_OBJECTCLASS": config_ldap.ldap_user_objectclass,
- "LDAP_USER_ID_ATTRIBUTE": config_ldap.ldap_user_id_attribute,
- "LDAP_USER_NAME_ATTRIBUTE": config_ldap.ldap_user_name_attribute,
- "LDAP_USER_PASS_ATTRIBUTE": config_ldap.ldap_user_pass_attribute,
- "LDAP_USER_ENABLED_MASK": config_ldap.ldap_user_enabled_mask,
- "LDAP_USER_ENABLED_DEFAULT": config_ldap.ldap_user_enabled_default,
- "LDAP_USER_ENABLED_INVERT": config_ldap.ldap_user_enabled_invert,
- "LDAP_GROUP_OBJECTCLASS": config_ldap.ldap_group_objectclass,
- }
- )
+ # Add ldap secrets and envs
+ ldap_secrets = {
+ "authentication_domain_name": config_ldap.ldap_authentication_domain_name,
+ "url": config_ldap.ldap_url,
+ "page_size": str(config_ldap.ldap_page_size),
+ "user_objectclass": config_ldap.ldap_user_objectclass,
+ "user_id_attribute": config_ldap.ldap_user_id_attribute,
+ "user_name_attribute": config_ldap.ldap_user_name_attribute,
+ "user_pass_attribute": config_ldap.ldap_user_pass_attribute,
+ "user_enabled_mask": str(config_ldap.ldap_user_enabled_mask),
+ "user_enabled_default": config_ldap.ldap_user_enabled_default,
+ "user_enabled_invert": str(config_ldap.ldap_user_enabled_invert),
+ "group_objectclass": config_ldap.ldap_group_objectclass,
+ }
+ ldap_envs = {
+ "LDAP_AUTHENTICATION_DOMAIN_NAME": "authentication_domain_name",
+ "LDAP_URL": "url",
+ "LDAP_PAGE_SIZE": "page_size",
+ "LDAP_USER_OBJECTCLASS": "user_objectclass",
+ "LDAP_USER_ID_ATTRIBUTE": "user_id_attribute",
+ "LDAP_USER_NAME_ATTRIBUTE": "user_name_attribute",
+ "LDAP_USER_PASS_ATTRIBUTE": "user_pass_attribute",
+ "LDAP_USER_ENABLED_MASK": "user_enabled_mask",
+ "LDAP_USER_ENABLED_DEFAULT": "user_enabled_default",
+ "LDAP_USER_ENABLED_INVERT": "user_enabled_invert",
+ "LDAP_GROUP_OBJECTCLASS": "group_objectclass",
+ }
if config_ldap.ldap_bind_user:
- container_builder.add_envs(
- {"LDAP_BIND_USER": config_ldap.ldap_bind_user}
- )
+ ldap_secrets["bind_user"] = config_ldap.ldap_bind_user
+ ldap_envs["LDAP_BIND_USER"] = "bind_user"
if config_ldap.ldap_bind_password:
- container_builder.add_envs(
- {"LDAP_BIND_PASSWORD": config_ldap.ldap_bind_password}
- )
+ ldap_secrets["bind_password"] = config_ldap.ldap_bind_password
+ ldap_envs["LDAP_BIND_PASSWORD"] = "bind_password"
if config_ldap.ldap_user_tree_dn:
- container_builder.add_envs(
- {"LDAP_USER_TREE_DN": config_ldap.ldap_user_tree_dn}
- )
+ ldap_secrets["user_tree_dn"] = config_ldap.ldap_user_tree_dn
+ ldap_envs["LDAP_USER_TREE_DN"] = "user_tree_dn"
if config_ldap.ldap_user_filter:
- container_builder.add_envs(
- {"LDAP_USER_FILTER": config_ldap.ldap_user_filter}
- )
+ ldap_secrets["user_filter"] = config_ldap.ldap_user_filter
+ ldap_envs["LDAP_USER_FILTER"] = "user_filter"
if config_ldap.ldap_user_enabled_attribute:
- container_builder.add_envs(
- {
- "LDAP_USER_ENABLED_ATTRIBUTE": config_ldap.ldap_user_enabled_attribute
- }
- )
-
+ ldap_secrets[
+ "user_enabled_attribute"
+ ] = config_ldap.ldap_user_enabled_attribute
+ ldap_envs["LDAP_USER_ENABLED_ATTRIBUTE"] = "user_enabled_attribute"
if config_ldap.ldap_chase_referrals:
- container_builder.add_envs(
- {"LDAP_CHASE_REFERRALS": config_ldap.ldap_chase_referrals}
- )
+ ldap_secrets["chase_referrals"] = config_ldap.ldap_chase_referrals
+ ldap_envs["LDAP_CHASE_REFERRALS"] = "chase_referrals"
if config_ldap.ldap_group_tree_dn:
- container_builder.add_envs(
- {"LDAP_GROUP_TREE_DN": config_ldap.ldap_group_tree_dn}
- )
+ ldap_secrets["group_tree_dn"] = config_ldap.ldap_group_tree_dn
+ ldap_envs["LDAP_GROUP_TREE_DN"] = "group_tree_dn"
+
+ if config_ldap.ldap_tls_cacert_base64:
+ ldap_secrets["tls_cacert_base64"] = config_ldap.ldap_tls_cacert_base64
+ ldap_envs["LDAP_TLS_CACERT_BASE64"] = "tls_cacert_base64"
if config_ldap.ldap_use_starttls:
- container_builder.add_envs(
- {
- "LDAP_USE_STARTTLS": config_ldap.ldap_use_starttls,
- "LDAP_TLS_CACERT_BASE64": config_ldap.ldap_tls_cacert_base64,
- "LDAP_TLS_REQ_CERT": config_ldap.ldap_tls_req_cert,
- }
- )
+ ldap_secrets["use_starttls"] = str(config_ldap.ldap_use_starttls)
+ ldap_secrets["tls_cacert_base64"] = config_ldap.ldap_tls_cacert_base64
+ ldap_secrets["tls_req_cert"] = config_ldap.ldap_tls_req_cert
+ ldap_envs["LDAP_USE_STARTTLS"] = "use_starttls"
+ ldap_envs["LDAP_TLS_CACERT_BASE64"] = "tls_cacert_base64"
+ ldap_envs["LDAP_TLS_REQ_CERT"] = "tls_req_cert"
+
+ pod_spec_builder.add_secret(
+ ldap_secret_name,
+ ldap_secrets,
+ )
+ container_builder.add_secret_envs(
+ secret_name=ldap_secret_name,
+ envs=ldap_envs,
+ )
+
container = container_builder.build()
+
# Add container to pod spec
pod_spec_builder.add_container(container)
+
+ # Add Pod Restart Policy
+ restart_policy = PodRestartPolicy()
+ restart_policy.add_secrets(
+ secret_names=(mysql_secret_name, keystone_secret_name, ldap_secret_name)
+ )
+ pod_spec_builder.set_restart_policy(restart_policy)
+
# Add ingress resources to pod spec if site url exists
if config.site_url:
parsed = urlparse(config.site_url)
str(config.max_file_size) + "m"
if config.max_file_size > 0
else config.max_file_size
- ),
+ )
}
+ if config.ingress_class:
+ annotations["kubernetes.io/ingress.class"] = config.ingress_class
ingress_resource_builder = IngressResourceV3Builder(
f"{self.app.name}-ingress", annotations
)