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
28 from typing
import NoReturn
, Optional
30 from charms
.kafka_k8s
.v0
.kafka
import KafkaEvents
, KafkaRequires
31 from ops
.main
import main
32 from opslib
.osm
.charm
import CharmedOsmBase
, RelationsMissing
33 from opslib
.osm
.interfaces
.mongo
import MongoClient
34 from opslib
.osm
.interfaces
.mysql
import MysqlClient
35 from opslib
.osm
.pod
import (
40 from opslib
.osm
.validator
import ModelValidator
, validator
43 logger
= logging
.getLogger(__name__
)
46 DEFAULT_MYSQL_DATABASE
= "pol"
49 class ConfigModel(ModelValidator
):
51 mongodb_uri
: Optional
[str]
52 mysql_uri
: Optional
[str]
53 image_pull_policy
: str
55 security_context
: bool
57 @validator("log_level")
58 def validate_log_level(cls
, v
):
59 if v
not in {"INFO", "DEBUG"}:
60 raise ValueError("value must be INFO or DEBUG")
63 @validator("mongoddb_uri")
64 def validate_mongodb_uri(cls
, v
):
65 if v
and not v
.startswith("mongodb://"):
66 raise ValueError("mongodb_uri is not properly formed")
69 @validator("mysql_uri")
70 def validate_mysql_uri(cls
, v
):
71 pattern
= re
.compile("^mysql:\/\/.*:.*@.*:\d+\/.*$") # noqa: W605
72 if v
and not pattern
.search(v
):
73 raise ValueError("mysql_uri is not properly formed")
76 @validator("image_pull_policy")
77 def validate_image_pull_policy(cls
, v
):
80 "ifnotpresent": "IfNotPresent",
84 if v
not in values
.keys():
85 raise ValueError("value must be always, ifnotpresent or never")
89 class PolCharm(CharmedOsmBase
):
92 def __init__(self
, *args
) -> NoReturn
:
96 vscode_workspace
=VSCODE_WORKSPACE
,
98 if self
.config
.get("debug_mode"):
99 self
.enable_debug_mode(
100 pubkey
=self
.config
.get("debug_pubkey"),
103 "hostpath": self
.config
.get("debug_pol_local_path"),
104 "container-path": "/usr/lib/python3/dist-packages/osm_policy_module",
107 "hostpath": self
.config
.get("debug_common_local_path"),
108 "container-path": "/usr/lib/python3/dist-packages/osm_common",
112 self
.kafka
= KafkaRequires(self
)
113 self
.framework
.observe(self
.on
.kafka_available
, self
.configure_pod
)
114 self
.framework
.observe(self
.on
.kafka_broken
, self
.configure_pod
)
116 self
.mongodb_client
= MongoClient(self
, "mongodb")
117 self
.framework
.observe(self
.on
["mongodb"].relation_changed
, self
.configure_pod
)
118 self
.framework
.observe(self
.on
["mongodb"].relation_broken
, self
.configure_pod
)
120 self
.mysql_client
= MysqlClient(self
, "mysql")
121 self
.framework
.observe(self
.on
["mysql"].relation_changed
, self
.configure_pod
)
122 self
.framework
.observe(self
.on
["mysql"].relation_broken
, self
.configure_pod
)
124 def _check_missing_dependencies(self
, config
: ConfigModel
):
125 missing_relations
= []
127 if not self
.kafka
.host
or not self
.kafka
.port
:
128 missing_relations
.append("kafka")
129 if not config
.mongodb_uri
and self
.mongodb_client
.is_missing_data_in_unit():
130 missing_relations
.append("mongodb")
131 if not config
.mysql_uri
and self
.mysql_client
.is_missing_data_in_unit():
132 missing_relations
.append("mysql")
133 if missing_relations
:
134 raise RelationsMissing(missing_relations
)
136 def build_pod_spec(self
, image_info
):
138 config
= ConfigModel(**dict(self
.config
))
140 if config
.mongodb_uri
and not self
.mongodb_client
.is_missing_data_in_unit():
141 raise Exception("Mongodb data cannot be provided via config and relation")
142 if config
.mysql_uri
and not self
.mysql_client
.is_missing_data_in_unit():
143 raise Exception("Mysql data cannot be provided via config and relation")
146 self
._check
_missing
_dependencies
(config
)
148 security_context_enabled
= (
149 config
.security_context
if not config
.debug_mode
else False
152 # Create Builder for the PodSpec
153 pod_spec_builder
= PodSpecV3Builder(
154 enable_security_context
=security_context_enabled
157 # Add secrets to the pod
158 mongodb_secret_name
= f
"{self.app.name}-mongodb-secret"
159 pod_spec_builder
.add_secret(
161 {"uri": config
.mongodb_uri
or self
.mongodb_client
.connection_string
},
163 mysql_secret_name
= f
"{self.app.name}-mysql-secret"
164 pod_spec_builder
.add_secret(
167 "uri": config
.mysql_uri
168 or self
.mysql_client
.get_root_uri(DEFAULT_MYSQL_DATABASE
)
173 container_builder
= ContainerV3Builder(
176 config
.image_pull_policy
,
177 run_as_non_root
=security_context_enabled
,
179 container_builder
.add_port(name
=self
.app
.name
, port
=PORT
)
180 container_builder
.add_envs(
182 # General configuration
183 "ALLOW_ANONYMOUS_LOGIN": "yes",
184 "OSMPOL_GLOBAL_LOGLEVEL": config
.log_level
,
185 # Kafka configuration
186 "OSMPOL_MESSAGE_DRIVER": "kafka",
187 "OSMPOL_MESSAGE_HOST": self
.kafka
.host
,
188 "OSMPOL_MESSAGE_PORT": self
.kafka
.port
,
189 # Database configuration
190 "OSMPOL_DATABASE_DRIVER": "mongo",
193 container_builder
.add_secret_envs(
194 mongodb_secret_name
, {"OSMPOL_DATABASE_URI": "uri"}
196 container_builder
.add_secret_envs(
197 mysql_secret_name
, {"OSMPOL_SQL_DATABASE_URI": "uri"}
199 container
= container_builder
.build()
201 # Add Pod restart policy
202 restart_policy
= PodRestartPolicy()
203 restart_policy
.add_secrets(
204 secret_names
=(mongodb_secret_name
, mysql_secret_name
)
206 pod_spec_builder
.set_restart_policy(restart_policy
)
208 # Add container to pod spec
209 pod_spec_builder
.add_container(container
)
211 return pod_spec_builder
.build()
216 {"path": "/usr/lib/python3/dist-packages/osm_policy_module"},
217 {"path": "/usr/lib/python3/dist-packages/osm_common"},
227 "module": "osm_policy_module.cmd.policy_module_agent",
235 if __name__
== "__main__":