+++ /dev/null
-# Copyright 2020 Canonical Ltd.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-options:
- max_file_size:
- type: int
- description: |
- The maximum file size, in megabytes.
-
- If there is a reverse proxy in front of Keystone, it may
- need to be configured to handle the requested size.
- default: 5
- ingress_class:
- type: string
- description: |
- Ingress class name. This is useful for selecting the ingress to be used
- in case there are multiple ingresses in the underlying k8s clusters.
- ingress_whitelist_source_range:
- type: string
- description: |
- A comma-separated list of CIDRs to store in the
- ingress.kubernetes.io/whitelist-source-range annotation.
-
- This can be used to lock down access to
- Keystone based on source IP address.
- default: ""
- tls_secret_name:
- type: string
- description: TLS Secret name
- default: ""
- site_url:
- type: string
- description: Ingress URL
- default: ""
- image_pull_policy:
- type: string
- description: |
- ImagePullPolicy configuration for the pod.
- Possible values: always, ifnotpresent, never
- default: always
- security_context:
- description: Enables the security context of the pods
- type: boolean
- default: false
- region_id:
- type: string
- description: Region ID to be created when starting the service
- default: RegionOne
- keystone_db_password:
- type: string
- description: Keystone DB Password
- default: admin
- mysql_uri:
- type: string
- description: |
- Mysql uri with the following format:
- mysql://<user>:<pass>@<host>:<port>/<database>
- admin_username:
- type: string
- description: Admin username to be created when starting the service
- default: admin
- admin_password:
- type: string
- description: Admin password to be created when starting the service
- default: admin
- admin_project:
- type: string
- description: Admin project to be created when starting the service
- default: admin
- service_username:
- type: string
- description: Service Username to be created when starting the service
- default: nbi
- service_password:
- type: string
- description: Service Password to be created when starting the service
- default: nbi
- service_project:
- type: string
- description: Service Project to be created when starting the service
- default: service
- user_domain_name:
- type: string
- description: User domain name (Hardcoded in the container start.sh script)
- default: default
- project_domain_name:
- type: string
- description: |
- Project domain name (Hardcoded in the container start.sh script)
- default: default
- token_expiration:
- type: int
- description: Token keys expiration in seconds
- default: 172800
- ldap_enabled:
- type: boolean
- description: Boolean to enable/disable LDAP authentication
- default: false
- ldap_authentication_domain_name:
- type: string
- description: Name of the domain which use LDAP authentication
- default: ""
- ldap_url:
- type: string
- description: URL of the LDAP server
- default: "ldap://localhost"
- ldap_bind_user:
- type: string
- description: User to bind and search for users
- default: ""
- ldap_bind_password:
- type: string
- description: Password to bind and search for users
- default: ""
- ldap_chase_referrals:
- type: string
- description: |
- Sets keystone’s referral chasing behavior across directory partitions.
- If left unset, the system’s default behavior will be used.
- default: ""
- ldap_page_size:
- type: int
- description: |
- Defines the maximum number of results per page that keystone should
- request from the LDAP server when listing objects. A value of zero (0)
- disables paging.
- default: 0
- ldap_user_tree_dn:
- type: string
- description: |
- Root of the tree in LDAP server in which Keystone will search for users
- default: ""
- ldap_user_objectclass:
- type: string
- description: |
- LDAP object class that Keystone will filter on within user_tree_dn to
- find user objects. Any objects of other classes will be ignored.
- default: inetOrgPerson
- ldap_user_id_attribute:
- type: string
- description: |
- This set of options define the mapping to LDAP attributes for the three
- key user attributes supported by Keystone. The LDAP attribute chosen for
- user_id must be something that is immutable for a user and no more than
- 64 characters in length. Notice that Distinguished Name (DN) may be
- longer than 64 characters and thus is not suitable. An uid, or mail may
- be appropriate.
- default: cn
- ldap_user_name_attribute:
- type: string
- description: |
- This set of options define the mapping to LDAP attributes for the three
- key user attributes supported by Keystone. The LDAP attribute chosen for
- user_id must be something that is immutable for a user and no more than
- 64 characters in length. Notice that Distinguished Name (DN) may be
- longer than 64 characters and thus is not suitable. An uid, or mail may
- be appropriate.
- default: sn
- ldap_user_pass_attribute:
- type: string
- description: |
- This set of options define the mapping to LDAP attributes for the three
- key user attributes supported by Keystone. The LDAP attribute chosen for
- user_id must be something that is immutable for a user and no more than
- 64 characters in length. Notice that Distinguished Name (DN) may be
- longer than 64 characters and thus is not suitable. An uid, or mail may
- be appropriate.
- default: userPassword
- ldap_user_filter:
- type: string
- description: |
- This filter option allow additional filter (over and above
- user_objectclass) to be included into the search of user. One common use
- of this is to provide more efficient searching, where the recommended
- search for user objects is (&(objectCategory=person)(objectClass=user)).
- By specifying user_objectclass as user and user_filter as
- objectCategory=person in the Keystone configuration file, this can be
- achieved.
- default: ""
- ldap_user_enabled_attribute:
- type: string
- description: |
- In Keystone, a user entity can be either enabled or disabled. Setting
- the above option will give a mapping to an equivalent attribute in LDAP,
- allowing your LDAP management tools to disable a user.
- default: enabled
- ldap_user_enabled_mask:
- type: int
- description: |
- Some LDAP schemas, rather than having a dedicated attribute for user
- enablement, use a bit within a general control attribute (such as
- userAccountControl) to indicate this. Setting user_enabled_mask will
- cause Keystone to look at only the status of this bit in the attribute
- specified by user_enabled_attribute, with the bit set indicating the
- user is enabled.
- default: 0
- ldap_user_enabled_default:
- type: string
- description: |
- Most LDAP servers use a boolean or bit in a control field to indicate
- enablement. However, some schemas might use an integer value in an
- attribute. In this situation, set user_enabled_default to the integer
- value that represents a user being enabled.
- default: "true"
- ldap_user_enabled_invert:
- type: boolean
- description: |
- Some LDAP schemas have an “account locked” attribute, which is the
- equivalent to account being “disabled.” In order to map this to the
- Keystone enabled attribute, you can utilize the user_enabled_invert
- setting in conjunction with user_enabled_attribute to map the lock
- status to disabled in Keystone.
- default: false
- ldap_group_objectclass:
- type: string
- description: The LDAP object class to use for groups.
- default: groupOfNames
- ldap_group_tree_dn:
- type: string
- description: The search base to use for groups.
- default: ""
- ldap_use_starttls:
- type: boolean
- description: |
- Enable Transport Layer Security (TLS) for providing a secure connection
- from Keystone to LDAP (StartTLS, not LDAPS).
- default: false
- ldap_tls_cacert_base64:
- type: string
- description: |
- CA certificate in Base64 format (if you have the PEM file, text inside
- "-----BEGIN CERTIFICATE-----"/"-----END CERTIFICATE-----" tags).
- default: ""
- ldap_tls_req_cert:
- type: string
- description: |
- Defines how the certificates are checked for validity in the client
- (i.e., Keystone end) of the secure connection (this doesn’t affect what
- level of checking the server is doing on the certificates it receives
- from Keystone). Possible values are "demand", "never", and "allow". The
- default of demand means the client always checks the certificate and
- will drop the connection if it is not provided or invalid. never is the
- opposite—it never checks it, nor requires it to be provided. allow means
- that if it is not provided then the connection is allowed to continue,
- but if it is provided it will be checked—and if invalid, the connection
- will be dropped.
- default: demand
+++ /dev/null
-#!/usr/bin/env python3
-# Copyright 2021 Canonical Ltd.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-# pylint: disable=E0213
-
-
-from datetime import datetime
-from ipaddress import ip_network
-import json
-import logging
-from typing import List, NoReturn, Optional, Tuple
-from urllib.parse import urlparse
-
-from cryptography.fernet import Fernet
-from ops.main import main
-from opslib.osm.charm import CharmedOsmBase, RelationsMissing
-from opslib.osm.interfaces.keystone import KeystoneServer
-from opslib.osm.interfaces.mysql import MysqlClient
-from opslib.osm.pod import (
- ContainerV3Builder,
- FilesV3Builder,
- IngressResourceV3Builder,
- PodRestartPolicy,
- PodSpecV3Builder,
-)
-from opslib.osm.validator import ModelValidator, validator
-
-
-logger = logging.getLogger(__name__)
-
-
-REQUIRED_SETTINGS = ["token_expiration"]
-
-# This is hardcoded in the keystone container script
-DATABASE_NAME = "keystone"
-
-# We expect the keystone container to use the default port
-PORT = 5000
-
-# Number of keys need might need to be adjusted in the future
-NUMBER_FERNET_KEYS = 2
-NUMBER_CREDENTIAL_KEYS = 2
-
-# Path for keys
-CREDENTIAL_KEYS_PATH = "/etc/keystone/credential-keys"
-FERNET_KEYS_PATH = "/etc/keystone/fernet-keys"
-
-
-class ConfigModel(ModelValidator):
- region_id: str
- keystone_db_password: str
- admin_username: str
- admin_password: str
- admin_project: str
- service_username: str
- service_password: str
- service_project: str
- user_domain_name: str
- project_domain_name: str
- 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):
- if v < 0:
- raise ValueError("value must be equal or greater than 0")
- return v
-
- @validator("site_url")
- def validate_site_url(cls, v):
- if v:
- parsed = urlparse(v)
- if not parsed.scheme.startswith("http"):
- raise ValueError("value must start with http")
- return v
-
- @validator("ingress_whitelist_source_range")
- def validate_ingress_whitelist_source_range(cls, v):
- if 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_authentication_domain_name: Optional[str]
- ldap_url: Optional[str]
- ldap_bind_user: Optional[str]
- ldap_bind_password: Optional[str]
- ldap_chase_referrals: Optional[str]
- ldap_page_size: Optional[int]
- ldap_user_tree_dn: Optional[str]
- ldap_user_objectclass: Optional[str]
- ldap_user_id_attribute: Optional[str]
- ldap_user_name_attribute: Optional[str]
- ldap_user_pass_attribute: Optional[str]
- ldap_user_filter: Optional[str]
- ldap_user_enabled_attribute: Optional[str]
- ldap_user_enabled_mask: Optional[int]
- ldap_user_enabled_default: Optional[str]
- ldap_user_enabled_invert: Optional[bool]
- ldap_group_objectclass: Optional[str]
- ldap_group_tree_dn: Optional[str]
- ldap_use_starttls: Optional[bool]
- 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",
- 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.keystone_server = KeystoneServer(self, "keystone")
- 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
- )
-
- def _publish_keystone_info(self, event):
- if self.unit.is_leader():
- config = ConfigModel(**dict(self.config))
- self.keystone_server.publish_info(
- host=f"http://{self.app.name}:{PORT}/v3",
- port=PORT,
- user_domain_name=config.user_domain_name,
- project_domain_name=config.project_domain_name,
- username=config.service_username,
- password=config.service_password,
- service=config.service_project,
- keystone_db_password=config.keystone_db_password,
- region_id=config.region_id,
- admin_username=config.admin_username,
- admin_password=config.admin_password,
- admin_project_name=config.admin_project,
- )
-
- def _check_missing_dependencies(self, config: ConfigModel, external_db: bool):
- missing_relations = []
- if not external_db and self.mysql_client.is_missing_data_in_unit():
- missing_relations.append("mysql")
- if missing_relations:
- raise RelationsMissing(missing_relations)
-
- def _generate_keys(self) -> Tuple[List[str], List[str]]:
- """Generating new fernet tokens.
-
- Returns:
- Tuple[List[str], List[str]]: contains two lists of strings. First
- list contains strings that represent
- the keys for fernet and the second
- list contains strins that represent
- the keys for credentials.
- """
- fernet_keys = [
- Fernet.generate_key().decode() for _ in range(NUMBER_FERNET_KEYS)
- ]
- credential_keys = [
- Fernet.generate_key().decode() for _ in range(NUMBER_CREDENTIAL_KEYS)
- ]
-
- return (fernet_keys, credential_keys)
-
- def _get_keys(self):
- keys_timestamp = self.state.keys_timestamp
- if fernet_keys := self.state.fernet_keys:
- fernet_keys = json.loads(fernet_keys)
-
- if credential_keys := self.state.credential_keys:
- credential_keys = json.loads(credential_keys)
-
- now = datetime.now().timestamp()
- token_expiration = self.config["token_expiration"]
-
- valid_keys = (now - keys_timestamp) < token_expiration
- if not credential_keys or not fernet_keys or not valid_keys:
- fernet_keys, credential_keys = self._generate_keys()
- self.state.fernet_keys = json.dumps(fernet_keys)
- self.state.credential_keys = json.dumps(credential_keys)
- self.state.keys_timestamp = now
- return credential_keys, fernet_keys
-
- def _build_files(
- self, config: ConfigModel, credential_keys: List, fernet_keys: List
- ):
- credentials_files_builder = FilesV3Builder()
- fernet_files_builder = FilesV3Builder()
- 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, **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
- 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(
- 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_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,
- secret_name=credential_keys_secret_name,
- )
- container_builder.add_volume_config(
- "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(
- {
- "REGION_ID": config.region_id,
- "KEYSTONE_HOST": self.app.name,
- }
- )
- 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:
- # 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:
- ldap_secrets["bind_user"] = config_ldap.ldap_bind_user
- ldap_envs["LDAP_BIND_USER"] = "bind_user"
-
- if 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:
- 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:
- ldap_secrets["user_filter"] = config_ldap.ldap_user_filter
- ldap_envs["LDAP_USER_FILTER"] = "user_filter"
-
- if 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:
- ldap_secrets["chase_referrals"] = config_ldap.ldap_chase_referrals
- ldap_envs["LDAP_CHASE_REFERRALS"] = "chase_referrals"
-
- if 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:
- 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)
- annotations = {
- "nginx.ingress.kubernetes.io/proxy-body-size": "{}".format(
- 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
- )
-
- if config.ingress_whitelist_source_range:
- annotations[
- "nginx.ingress.kubernetes.io/whitelist-source-range"
- ] = config.ingress_whitelist_source_range
-
- if parsed.scheme == "https":
- ingress_resource_builder.add_tls(
- [parsed.hostname], config.tls_secret_name
- )
- else:
- annotations["nginx.ingress.kubernetes.io/ssl-redirect"] = "false"
-
- ingress_resource_builder.add_rule(parsed.hostname, self.app.name, PORT)
- ingress_resource = ingress_resource_builder.build()
- pod_spec_builder.add_ingress_resource(ingress_resource)
- return pod_spec_builder.build()
-
-
-if __name__ == "__main__":
- main(KeystoneCharm)
-
-# LOGGER = logging.getLogger(__name__)
-
-
-# class ConfigurePodEvent(EventBase):
-# """Configure Pod event"""
-
-# pass
-
-
-# class KeystoneEvents(CharmEvents):
-# """Keystone Events"""
-
-# configure_pod = EventSource(ConfigurePodEvent)
-
-# class KeystoneCharm(CharmBase):
-# """Keystone K8s Charm"""
-
-# state = StoredState()
-# on = KeystoneEvents()
-
-# def __init__(self, *args) -> NoReturn:
-# """Constructor of the Charm object.
-# Initializes internal state and register events it can handle.
-# """
-# super().__init__(*args)
-# self.state.set_default(db_host=None)
-# self.state.set_default(db_port=None)
-# self.state.set_default(db_user=None)
-# self.state.set_default(db_password=None)
-# self.state.set_default(pod_spec=None)
-# self.state.set_default(fernet_keys=None)
-# self.state.set_default(credential_keys=None)
-# self.state.set_default(keys_timestamp=0)
-
-# # Register all of the events we want to observe
-# self.framework.observe(self.on.config_changed, self.configure_pod)
-# self.framework.observe(self.on.start, self.configure_pod)
-# self.framework.observe(self.on.upgrade_charm, self.configure_pod)
-# self.framework.observe(self.on.leader_elected, self.configure_pod)
-# self.framework.observe(self.on.update_status, self.configure_pod)
-
-# # Registering custom internal events
-# self.framework.observe(self.on.configure_pod, self.configure_pod)
-
-# # Register relation events
-# self.framework.observe(
-# self.on.db_relation_changed, self._on_db_relation_changed
-# )
-# self.framework.observe(
-# self.on.db_relation_broken, self._on_db_relation_broken
-# )
-# self.framework.observe(
-# self.on.keystone_relation_joined, self._publish_keystone_info
-# )
-
-# def _publish_keystone_info(self, event: EventBase) -> NoReturn:
-# """Publishes keystone information for NBI usage through the keystone
-# relation.
-
-# Args:
-# event (EventBase): Keystone relation event to update NBI.
-# """
-# config = self.model.config
-# rel_data = {
-# "host": f"http://{self.app.name}:{KEYSTONE_PORT}/v3",
-# "port": str(KEYSTONE_PORT),
-# "keystone_db_password": config["keystone_db_password"],
-# "region_id": config["region_id"],
-# "user_domain_name": config["user_domain_name"],
-# "project_domain_name": config["project_domain_name"],
-# "admin_username": config["admin_username"],
-# "admin_password": config["admin_password"],
-# "admin_project_name": config["admin_project"],
-# "username": config["service_username"],
-# "password": config["service_password"],
-# "service": config["service_project"],
-# }
-# for k, v in rel_data.items():
-# event.relation.data[self.model.unit][k] = v
-
-# def _on_db_relation_changed(self, event: EventBase) -> NoReturn:
-# """Reads information about the DB relation, in order for keystone to
-# access it.
-
-# Args:
-# event (EventBase): DB relation event to access database
-# information.
-# """
-# if not event.unit in event.relation.data:
-# return
-# relation_data = event.relation.data[event.unit]
-# db_host = relation_data.get("host")
-# db_port = int(relation_data.get("port", 3306))
-# db_user = "root"
-# db_password = relation_data.get("root_password")
-
-# if (
-# db_host
-# and db_port
-# and db_user
-# and db_password
-# and (
-# self.state.db_host != db_host
-# or self.state.db_port != db_port
-# or self.state.db_user != db_user
-# or self.state.db_password != db_password
-# )
-# ):
-# self.state.db_host = db_host
-# self.state.db_port = db_port
-# self.state.db_user = db_user
-# self.state.db_password = db_password
-# self.on.configure_pod.emit()
-
-
-# def _on_db_relation_broken(self, event: EventBase) -> NoReturn:
-# """Clears data from db relation.
-
-# Args:
-# event (EventBase): DB relation event.
-
-# """
-# self.state.db_host = None
-# self.state.db_port = None
-# self.state.db_user = None
-# self.state.db_password = None
-# self.on.configure_pod.emit()
-
-# def _check_settings(self) -> str:
-# """Check if there any settings missing from Keystone configuration.
-
-# Returns:
-# str: Information about the problems found (if any).
-# """
-# problems = []
-# config = self.model.config
-
-# for setting in REQUIRED_SETTINGS:
-# if not config.get(setting):
-# problem = f"missing config {setting}"
-# problems.append(problem)
-
-# return ";".join(problems)
-
-# def _make_pod_image_details(self) -> Dict[str, str]:
-# """Generate the pod image details.
-
-# Returns:
-# Dict[str, str]: pod image details.
-# """
-# config = self.model.config
-# image_details = {
-# "imagePath": config["image"],
-# }
-# if config["image_username"]:
-# image_details.update(
-# {
-# "username": config["image_username"],
-# "password": config["image_password"],
-# }
-# )
-# return image_details
-
-# def _make_pod_ports(self) -> List[Dict[str, Any]]:
-# """Generate the pod ports details.
-
-# Returns:
-# List[Dict[str, Any]]: pod ports details.
-# """
-# return [
-# {"name": "keystone", "containerPort": KEYSTONE_PORT, "protocol": "TCP"},
-# ]
-
-# def _make_pod_envconfig(self) -> Dict[str, Any]:
-# """Generate pod environment configuraiton.
-
-# Returns:
-# Dict[str, Any]: pod environment configuration.
-# """
-# config = self.model.config
-
-# envconfig = {
-# "DB_HOST": self.state.db_host,
-# "DB_PORT": self.state.db_port,
-# "ROOT_DB_USER": self.state.db_user,
-# "ROOT_DB_PASSWORD": self.state.db_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"],
-# }
-
-# if config.get("ldap_enabled"):
-# envconfig["LDAP_AUTHENTICATION_DOMAIN_NAME"] = config[
-# "ldap_authentication_domain_name"
-# ]
-# envconfig["LDAP_URL"] = config["ldap_url"]
-# envconfig["LDAP_PAGE_SIZE"] = config["ldap_page_size"]
-# envconfig["LDAP_USER_OBJECTCLASS"] = config["ldap_user_objectclass"]
-# envconfig["LDAP_USER_ID_ATTRIBUTE"] = config["ldap_user_id_attribute"]
-# envconfig["LDAP_USER_NAME_ATTRIBUTE"] = config["ldap_user_name_attribute"]
-# envconfig["LDAP_USER_PASS_ATTRIBUTE"] = config["ldap_user_pass_attribute"]
-# envconfig["LDAP_USER_ENABLED_MASK"] = config["ldap_user_enabled_mask"]
-# envconfig["LDAP_USER_ENABLED_DEFAULT"] = config["ldap_user_enabled_default"]
-# envconfig["LDAP_USER_ENABLED_INVERT"] = config["ldap_user_enabled_invert"]
-# envconfig["LDAP_GROUP_OBJECTCLASS"] = config["ldap_group_objectclass"]
-
-# if config["ldap_bind_user"]:
-# envconfig["LDAP_BIND_USER"] = config["ldap_bind_user"]
-
-# if config["ldap_bind_password"]:
-# envconfig["LDAP_BIND_PASSWORD"] = config["ldap_bind_password"]
-
-# if config["ldap_user_tree_dn"]:
-# envconfig["LDAP_USER_TREE_DN"] = config["ldap_user_tree_dn"]
-
-# if config["ldap_user_filter"]:
-# envconfig["LDAP_USER_FILTER"] = config["ldap_user_filter"]
-
-# if config["ldap_user_enabled_attribute"]:
-# envconfig["LDAP_USER_ENABLED_ATTRIBUTE"] = config[
-# "ldap_user_enabled_attribute"
-# ]
-
-# if config["ldap_chase_referrals"]:
-# envconfig["LDAP_CHASE_REFERRALS"] = config["ldap_chase_referrals"]
-
-# if config["ldap_group_tree_dn"]:
-# envconfig["LDAP_GROUP_TREE_DN"] = config["ldap_group_tree_dn"]
-
-# if config["ldap_use_starttls"]:
-# envconfig["LDAP_USE_STARTTLS"] = config["ldap_use_starttls"]
-# envconfig["LDAP_TLS_CACERT_BASE64"] = config["ldap_tls_cacert_base64"]
-# envconfig["LDAP_TLS_REQ_CERT"] = config["ldap_tls_req_cert"]
-
-# return envconfig
-
-# def _make_pod_ingress_resources(self) -> List[Dict[str, Any]]:
-# """Generate pod ingress resources.
-
-# Returns:
-# List[Dict[str, Any]]: pod ingress resources.
-# """
-# site_url = self.model.config["site_url"]
-
-# if not site_url:
-# return
-
-# parsed = urlparse(site_url)
-
-# if not parsed.scheme.startswith("http"):
-# return
-
-# max_file_size = self.model.config["max_file_size"]
-# ingress_whitelist_source_range = self.model.config[
-# "ingress_whitelist_source_range"
-# ]
-
-# annotations = {
-# "nginx.ingress.kubernetes.io/proxy-body-size": "{}m".format(max_file_size)
-# }
-
-# if ingress_whitelist_source_range:
-# annotations[
-# "nginx.ingress.kubernetes.io/whitelist-source-range"
-# ] = ingress_whitelist_source_range
-
-# ingress_spec_tls = None
-
-# if parsed.scheme == "https":
-# ingress_spec_tls = [{"hosts": [parsed.hostname]}]
-# tls_secret_name = self.model.config["tls_secret_name"]
-# if tls_secret_name:
-# ingress_spec_tls[0]["secretName"] = tls_secret_name
-# else:
-# annotations["nginx.ingress.kubernetes.io/ssl-redirect"] = "false"
-
-# ingress = {
-# "name": "{}-ingress".format(self.app.name),
-# "annotations": annotations,
-# "spec": {
-# "rules": [
-# {
-# "host": parsed.hostname,
-# "http": {
-# "paths": [
-# {
-# "path": "/",
-# "backend": {
-# "serviceName": self.app.name,
-# "servicePort": KEYSTONE_PORT,
-# },
-# }
-# ]
-# },
-# }
-# ],
-# },
-# }
-# if ingress_spec_tls:
-# ingress["spec"]["tls"] = ingress_spec_tls
-
-# return [ingress]
-
-# def _generate_keys(self) -> Tuple[List[str], List[str]]:
-# """Generating new fernet tokens.
-
-# Returns:
-# Tuple[List[str], List[str]]: contains two lists of strings. First
-# list contains strings that represent
-# the keys for fernet and the second
-# list contains strins that represent
-# the keys for credentials.
-# """
-# fernet_keys = [
-# Fernet.generate_key().decode() for _ in range(NUMBER_FERNET_KEYS)
-# ]
-# credential_keys = [
-# Fernet.generate_key().decode() for _ in range(NUMBER_CREDENTIAL_KEYS)
-# ]
-
-# return (fernet_keys, credential_keys)
-
-# def configure_pod(self, event: EventBase) -> NoReturn:
-# """Assemble the pod spec and apply it, if possible.
-
-# Args:
-# event (EventBase): Hook or Relation event that started the
-# function.
-# """
-# if not self.state.db_host:
-# self.unit.status = WaitingStatus("Waiting for database relation")
-# event.defer()
-# return
-
-# if not self.unit.is_leader():
-# self.unit.status = ActiveStatus("ready")
-# return
-
-# if fernet_keys := self.state.fernet_keys:
-# fernet_keys = json.loads(fernet_keys)
-
-# if credential_keys := self.state.credential_keys:
-# credential_keys = json.loads(credential_keys)
-
-# now = datetime.now().timestamp()
-# keys_timestamp = self.state.keys_timestamp
-# token_expiration = self.model.config["token_expiration"]
-
-# valid_keys = (now - keys_timestamp) < token_expiration
-# if not credential_keys or not fernet_keys or not valid_keys:
-# fernet_keys, credential_keys = self._generate_keys()
-# self.state.fernet_keys = json.dumps(fernet_keys)
-# self.state.credential_keys = json.dumps(credential_keys)
-# self.state.keys_timestamp = now
-
-# # Check problems in the settings
-# problems = self._check_settings()
-# if problems:
-# self.unit.status = BlockedStatus(problems)
-# return
-
-# self.unit.status = BlockedStatus("Assembling pod spec")
-# image_details = self._make_pod_image_details()
-# ports = self._make_pod_ports()
-# env_config = self._make_pod_envconfig()
-# ingress_resources = self._make_pod_ingress_resources()
-# files = self._make_pod_files(fernet_keys, credential_keys)
-
-# pod_spec = {
-# "version": 3,
-# "containers": [
-# {
-# "name": self.framework.model.app.name,
-# "imageDetails": image_details,
-# "ports": ports,
-# "envConfig": env_config,
-# "volumeConfig": files,
-# }
-# ],
-# "kubernetesResources": {"ingressResources": ingress_resources or []},
-# }
-
-# if self.state.pod_spec != (
-# pod_spec_json := json.dumps(pod_spec, sort_keys=True)
-# ):
-# self.state.pod_spec = pod_spec_json
-# self.model.pod.set_spec(pod_spec)
-
-# self.unit.status = ActiveStatus("ready")
-
-
-# if __name__ == "__main__":
-# main(KeystoneCharm)
+++ /dev/null
-#!/usr/bin/env python3
-# Copyright 2021 Canonical Ltd.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-#
-# For those usages not covered by the Apache License, Version 2.0 please
-# contact: legal@canonical.com
-#
-# To get in touch with the maintainers, please contact:
-# osm-charmers@lists.launchpad.net
-##
-
-import sys
-from typing import NoReturn
-import unittest
-
-
-from charm import KeystoneCharm
-from ops.model import ActiveStatus, BlockedStatus
-from ops.testing import Harness
-
-
-class TestCharm(unittest.TestCase):
- """Keystone Charm unit tests."""
-
- def setUp(self) -> NoReturn:
- """Test setup"""
- self.image_info = sys.modules["oci_image"].OCIImageResource().fetch()
- self.harness = Harness(KeystoneCharm)
- self.harness.set_leader(is_leader=True)
- self.harness.begin()
- self.config = {
- "region_id": "str",
- "keystone_db_password": "str",
- "mysql_host": "",
- "mysql_port": 3306,
- "mysql_root_password": "manopw",
- "admin_username": "str",
- "admin_password": "str",
- "admin_project": "str",
- "service_username": "str",
- "service_password": "str",
- "service_project": "str",
- "user_domain_name": "str",
- "project_domain_name": "str",
- "token_expiration": 10,
- "max_file_size": 1,
- "site_url": "http://keystone.com",
- "ldap_enabled": False,
- }
- self.harness.update_config(self.config)
-
- def test_config_changed_no_relations(
- self,
- ) -> NoReturn:
- """Test ingress resources without HTTP."""
-
- self.harness.charm.on.config_changed.emit()
-
- # Assertions
- self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
- self.assertTrue(
- all(
- relation in self.harness.charm.unit.status.message
- for relation in ["mysql"]
- )
- )
-
- def test_config_changed_non_leader(
- self,
- ) -> NoReturn:
- """Test ingress resources without HTTP."""
- self.harness.set_leader(is_leader=False)
- self.harness.charm.on.config_changed.emit()
-
- # Assertions
- self.assertIsInstance(self.harness.charm.unit.status, ActiveStatus)
-
- def test_with_config(self) -> NoReturn:
- "Test with mysql config"
- self.initialize_mysql_config()
- # Verifying status
- self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
- def test_with_relations(
- self,
- ) -> NoReturn:
- "Test with relations"
- self.initialize_mysql_relation()
- # Verifying status
- self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
- def test_exception_mysql_relation_and_config(
- self,
- ) -> NoReturn:
- "Test with relations and config. Must throw exception"
- self.initialize_mysql_config()
- self.initialize_mysql_relation()
- # Verifying status
- self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
- def initialize_mysql_config(self):
- self.harness.update_config({"mysql_uri": "mysql://root:manopw@mysql:3306/"})
-
- def initialize_mysql_relation(self):
- relation_id = self.harness.add_relation("db", "mysql")
- self.harness.add_relation_unit(relation_id, "mysql/0")
- self.harness.update_relation_data(
- relation_id,
- "mysql/0",
- {
- "host": "mysql",
- "port": 3306,
- "user": "mano",
- "password": "manopw",
- "root_password": "rootmanopw",
- },
- )
-
-
-if __name__ == "__main__":
- unittest.main()
-
-
-# class TestCharm(unittest.TestCase):
-# """Prometheus Charm unit tests."""
-
-# def setUp(self) -> NoReturn:
-# """Test setup"""
-# self.image_info = sys.modules["oci_image"].OCIImageResource().fetch()
-# self.harness = Harness(KeystoneCharm)
-# self.harness.set_leader(is_leader=True)
-# self.harness.begin()
-# self.config = {
-# "enable_ng_ro": True,
-# "database_commonkey": "commonkey",
-# "log_level": "INFO",
-# "vim_database": "db_name",
-# "ro_database": "ro_db_name",
-# "openmano_tenant": "mano",
-# }
-
-# def test_config_changed_no_relations(
-# self,
-# ) -> NoReturn:
-# """Test ingress resources without HTTP."""
-
-# self.harness.charm.on.config_changed.emit()
-
-# # Assertions
-# self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
-# self.assertTrue(
-# all(
-# relation in self.harness.charm.unit.status.message
-# for relation in ["mongodb", "kafka"]
-# )
-# )
-
-# # Disable ng-ro
-# self.harness.update_config({"enable_ng_ro": False})
-# self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus)
-# self.assertTrue(
-# all(
-# relation in self.harness.charm.unit.status.message
-# for relation in ["mysql"]
-# )
-# )
-
-# def test_config_changed_non_leader(
-# self,
-# ) -> NoReturn:
-# """Test ingress resources without HTTP."""
-# self.harness.set_leader(is_leader=False)
-# self.harness.charm.on.config_changed.emit()
-
-# # Assertions
-# self.assertIsInstance(self.harness.charm.unit.status, ActiveStatus)
-
-# def test_with_relations_ng(
-# self,
-# ) -> NoReturn:
-# "Test with relations (ng-ro)"
-
-# # Initializing the kafka relation
-# kafka_relation_id = self.harness.add_relation("kafka", "kafka")
-# self.harness.add_relation_unit(kafka_relation_id, "kafka/0")
-# self.harness.update_relation_data(
-# kafka_relation_id, "kafka/0", {"host": "kafka", "port": 9092}
-# )
-
-# # Initializing the mongo relation
-# mongodb_relation_id = self.harness.add_relation("mongodb", "mongodb")
-# self.harness.add_relation_unit(mongodb_relation_id, "mongodb/0")
-# self.harness.update_relation_data(
-# mongodb_relation_id,
-# "mongodb/0",
-# {"connection_string": "mongodb://mongo:27017"},
-# )
-
-# self.harness.charm.on.config_changed.emit()
-
-# # Verifying status
-# self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus)
-
-
-# if __name__ == "__main__":
-# unittest.main()