Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/env python3 

2# Copyright 2021 Canonical Ltd. 

3# 

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 

7# 

8# http://www.apache.org/licenses/LICENSE-2.0 

9# 

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 

14# under the License. 

15# 

16# For those usages not covered by the Apache License, Version 2.0 please 

17# contact: legal@canonical.com 

18# 

19# To get in touch with the maintainers, please contact: 

20# osm-charmers@lists.launchpad.net 

21## 

22 

23# pylint: disable=E0213 

24 

25 

26from datetime import datetime 

27from ipaddress import ip_network 

28import json 

29import logging 

30from typing import List, NoReturn, Optional, Tuple 

31from urllib.parse import urlparse 

32 

33from cryptography.fernet import Fernet 

34from ops.main import main 

35from opslib.osm.charm import CharmedOsmBase, RelationsMissing 

36from opslib.osm.interfaces.keystone import KeystoneServer 

37from opslib.osm.interfaces.mysql import MysqlClient 

38from opslib.osm.pod import ( 

39 ContainerV3Builder, 

40 FilesV3Builder, 

41 IngressResourceV3Builder, 

42 PodSpecV3Builder, 

43) 

44from opslib.osm.validator import ModelValidator, validator 

45 

46 

47logger = logging.getLogger(__name__) 

48 

49 

50REQUIRED_SETTINGS = ["token_expiration"] 

51 

52# This is hardcoded in the keystone container script 

53DATABASE_NAME = "keystone" 

54 

55# We expect the keystone container to use the default port 

56PORT = 5000 

57 

58# Number of keys need might need to be adjusted in the future 

59NUMBER_FERNET_KEYS = 2 

60NUMBER_CREDENTIAL_KEYS = 2 

61 

62# Path for keys 

63CREDENTIAL_KEYS_PATH = "/etc/keystone/credential-keys" 

64FERNET_KEYS_PATH = "/etc/keystone/fernet-keys" 

65 

66 

67class ConfigModel(ModelValidator): 

68 region_id: str 

69 keystone_db_password: str 

70 admin_username: str 

71 admin_password: str 

72 admin_project: str 

73 service_username: str 

74 service_password: str 

75 service_project: str 

76 user_domain_name: str 

77 project_domain_name: str 

78 token_expiration: int 

79 max_file_size: int 

80 site_url: Optional[str] 

81 ingress_whitelist_source_range: Optional[str] 

82 tls_secret_name: Optional[str] 

83 

84 @validator("max_file_size") 

85 def validate_max_file_size(cls, v): 

86 if v < 0: 

87 raise ValueError("value must be equal or greater than 0") 

88 return v 

89 

90 @validator("site_url") 

91 def validate_site_url(cls, v): 

92 if v: 

93 parsed = urlparse(v) 

94 if not parsed.scheme.startswith("http"): 

95 raise ValueError("value must start with http") 

96 return v 

97 

98 @validator("ingress_whitelist_source_range") 

99 def validate_ingress_whitelist_source_range(cls, v): 

100 if v: 

101 ip_network(v) 

102 return v 

103 

104 

105class ConfigLdapModel(ModelValidator): 

106 ldap_enabled: bool 

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] 

128 

129 

130class 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) 

136 

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) 

141 

142 self.framework.observe( 

143 self.on["keystone"].relation_joined, self._publish_keystone_info 

144 ) 

145 

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", 

151 port=PORT, 

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, 

162 ) 

163 

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) 

170 

171 def _generate_keys(self) -> Tuple[List[str], List[str]]: 

172 """Generating new fernet tokens. 

173 

174 Returns: 

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. 

180 """ 

181 fernet_keys = [ 

182 Fernet.generate_key().decode() for _ in range(NUMBER_FERNET_KEYS) 

183 ] 

184 credential_keys = [ 

185 Fernet.generate_key().decode() for _ in range(NUMBER_CREDENTIAL_KEYS) 

186 ] 

187 

188 return (fernet_keys, credential_keys) 

189 

190 def _get_keys(self): 

191 keys_timestamp = self.state.keys_timestamp 

192 if fernet_keys := self.state.fernet_keys: 

193 fernet_keys = json.loads(fernet_keys) 

194 

195 if credential_keys := self.state.credential_keys: 

196 credential_keys = json.loads(credential_keys) 

197 

198 now = datetime.now().timestamp() 

199 token_expiration = self.config["token_expiration"] 

200 

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 

208 

209 def _build_files(self, config: ConfigModel): 

210 credentials_files_builder = FilesV3Builder() 

211 fernet_files_builder = FilesV3Builder() 

212 

213 credential_keys, fernet_keys = self._get_keys() 

214 

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() 

220 

221 def build_pod_spec(self, image_info): 

222 # Validate config 

223 config = ConfigModel(**dict(self.config)) 

224 config_ldap = ConfigLdapModel(**dict(self.config)) 

225 # Check relations 

226 self._check_missing_dependencies(config) 

227 # Create Builder for the PodSpec 

228 pod_spec_builder = PodSpecV3Builder() 

229 # Build Container 

230 container_builder = ContainerV3Builder(self.app.name, image_info) 

231 container_builder.add_port(name=self.app.name, port=PORT) 

232 # Build files 

233 credential_files, fernet_files = self._build_files(config) 

234 container_builder.add_volume_config( 

235 "credential-keys", CREDENTIAL_KEYS_PATH, credential_files 

236 ) 

237 container_builder.add_volume_config( 

238 "fernet-keys", FERNET_KEYS_PATH, fernet_files 

239 ) 

240 container_builder.add_envs( 

241 { 

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, 

255 } 

256 ) 

257 

258 if config_ldap.ldap_enabled: 

259 

260 container_builder.add_envs( 

261 { 

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, 

273 } 

274 ) 

275 if config_ldap.ldap_bind_user: 

276 container_builder.add_envs( 

277 {"LDAP_BIND_USER": config_ldap.ldap_bind_user} 

278 ) 

279 

280 if config_ldap.ldap_bind_password: 

281 container_builder.add_envs( 

282 {"LDAP_BIND_PASSWORD": config_ldap.ldap_bind_password} 

283 ) 

284 

285 if config_ldap.ldap_user_tree_dn: 

286 container_builder.add_envs( 

287 {"LDAP_USER_TREE_DN": config_ldap.ldap_user_tree_dn} 

288 ) 

289 

290 if config_ldap.ldap_user_filter: 

291 container_builder.add_envs( 

292 {"LDAP_USER_FILTER": config_ldap.ldap_user_filter} 

293 ) 

294 

295 if config_ldap.ldap_user_enabled_attribute: 

296 container_builder.add_envs( 

297 { 

298 "LDAP_USER_ENABLED_ATTRIBUTE": config_ldap.ldap_user_enabled_attribute 

299 } 

300 ) 

301 

302 if config_ldap.ldap_chase_referrals: 

303 container_builder.add_envs( 

304 {"LDAP_CHASE_REFERRALS": config_ldap.ldap_chase_referrals} 

305 ) 

306 

307 if config_ldap.ldap_group_tree_dn: 

308 container_builder.add_envs( 

309 {"LDAP_GROUP_TREE_DN": config_ldap.ldap_group_tree_dn} 

310 ) 

311 

312 if config_ldap.ldap_use_starttls: 

313 container_builder.add_envs( 

314 { 

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, 

318 } 

319 ) 

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 

324 if config.site_url: 

325 parsed = urlparse(config.site_url) 

326 annotations = { 

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 

331 ), 

332 } 

333 ingress_resource_builder = IngressResourceV3Builder( 

334 f"{self.app.name}-ingress", annotations 

335 ) 

336 

337 if config.ingress_whitelist_source_range: 

338 annotations[ 

339 "nginx.ingress.kubernetes.io/whitelist-source-range" 

340 ] = config.ingress_whitelist_source_range 

341 

342 if parsed.scheme == "https": 

343 ingress_resource_builder.add_tls( 

344 [parsed.hostname], config.tls_secret_name 

345 ) 

346 else: 

347 annotations["nginx.ingress.kubernetes.io/ssl-redirect"] = "false" 

348 

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() 

353 

354 

355if __name__ == "__main__": 

356 main(KeystoneCharm) 

357 

358# LOGGER = logging.getLogger(__name__) 

359 

360 

361# class ConfigurePodEvent(EventBase): 

362# """Configure Pod event""" 

363 

364# pass 

365 

366 

367# class KeystoneEvents(CharmEvents): 

368# """Keystone Events""" 

369 

370# configure_pod = EventSource(ConfigurePodEvent) 

371 

372# class KeystoneCharm(CharmBase): 

373# """Keystone K8s Charm""" 

374 

375# state = StoredState() 

376# on = KeystoneEvents() 

377 

378# def __init__(self, *args) -> NoReturn: 

379# """Constructor of the Charm object. 

380# Initializes internal state and register events it can handle. 

381# """ 

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) 

391 

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) 

398 

399# # Registering custom internal events 

400# self.framework.observe(self.on.configure_pod, self.configure_pod) 

401 

402# # Register relation events 

403# self.framework.observe( 

404# self.on.db_relation_changed, self._on_db_relation_changed 

405# ) 

406# self.framework.observe( 

407# self.on.db_relation_broken, self._on_db_relation_broken 

408# ) 

409# self.framework.observe( 

410# self.on.keystone_relation_joined, self._publish_keystone_info 

411# ) 

412 

413# def _publish_keystone_info(self, event: EventBase) -> NoReturn: 

414# """Publishes keystone information for NBI usage through the keystone 

415# relation. 

416 

417# Args: 

418# event (EventBase): Keystone relation event to update NBI. 

419# """ 

420# config = self.model.config 

421# rel_data = { 

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"], 

434# } 

435# for k, v in rel_data.items(): 

436# event.relation.data[self.model.unit][k] = v 

437 

438# def _on_db_relation_changed(self, event: EventBase) -> NoReturn: 

439# """Reads information about the DB relation, in order for keystone to 

440# access it. 

441 

442# Args: 

443# event (EventBase): DB relation event to access database 

444# information. 

445# """ 

446# if not event.unit in event.relation.data: 

447# return 

448# relation_data = event.relation.data[event.unit] 

449# db_host = relation_data.get("host") 

450# db_port = int(relation_data.get("port", 3306)) 

451# db_user = "root" 

452# db_password = relation_data.get("root_password") 

453 

454# if ( 

455# db_host 

456# and db_port 

457# and db_user 

458# and db_password 

459# and ( 

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 

464# ) 

465# ): 

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() 

471 

472 

473# def _on_db_relation_broken(self, event: EventBase) -> NoReturn: 

474# """Clears data from db relation. 

475 

476# Args: 

477# event (EventBase): DB relation event. 

478 

479# """ 

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() 

485 

486# def _check_settings(self) -> str: 

487# """Check if there any settings missing from Keystone configuration. 

488 

489# Returns: 

490# str: Information about the problems found (if any). 

491# """ 

492# problems = [] 

493# config = self.model.config 

494 

495# for setting in REQUIRED_SETTINGS: 

496# if not config.get(setting): 

497# problem = f"missing config {setting}" 

498# problems.append(problem) 

499 

500# return ";".join(problems) 

501 

502# def _make_pod_image_details(self) -> Dict[str, str]: 

503# """Generate the pod image details. 

504 

505# Returns: 

506# Dict[str, str]: pod image details. 

507# """ 

508# config = self.model.config 

509# image_details = { 

510# "imagePath": config["image"], 

511# } 

512# if config["image_username"]: 

513# image_details.update( 

514# { 

515# "username": config["image_username"], 

516# "password": config["image_password"], 

517# } 

518# ) 

519# return image_details 

520 

521# def _make_pod_ports(self) -> List[Dict[str, Any]]: 

522# """Generate the pod ports details. 

523 

524# Returns: 

525# List[Dict[str, Any]]: pod ports details. 

526# """ 

527# return [ 

528# {"name": "keystone", "containerPort": KEYSTONE_PORT, "protocol": "TCP"}, 

529# ] 

530 

531# def _make_pod_envconfig(self) -> Dict[str, Any]: 

532# """Generate pod environment configuraiton. 

533 

534# Returns: 

535# Dict[str, Any]: pod environment configuration. 

536# """ 

537# config = self.model.config 

538 

539# envconfig = { 

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"], 

553# } 

554 

555# if config.get("ldap_enabled"): 

556# envconfig["LDAP_AUTHENTICATION_DOMAIN_NAME"] = config[ 

557# "ldap_authentication_domain_name" 

558# ] 

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"] 

569 

570# if config["ldap_bind_user"]: 

571# envconfig["LDAP_BIND_USER"] = config["ldap_bind_user"] 

572 

573# if config["ldap_bind_password"]: 

574# envconfig["LDAP_BIND_PASSWORD"] = config["ldap_bind_password"] 

575 

576# if config["ldap_user_tree_dn"]: 

577# envconfig["LDAP_USER_TREE_DN"] = config["ldap_user_tree_dn"] 

578 

579# if config["ldap_user_filter"]: 

580# envconfig["LDAP_USER_FILTER"] = config["ldap_user_filter"] 

581 

582# if config["ldap_user_enabled_attribute"]: 

583# envconfig["LDAP_USER_ENABLED_ATTRIBUTE"] = config[ 

584# "ldap_user_enabled_attribute" 

585# ] 

586 

587# if config["ldap_chase_referrals"]: 

588# envconfig["LDAP_CHASE_REFERRALS"] = config["ldap_chase_referrals"] 

589 

590# if config["ldap_group_tree_dn"]: 

591# envconfig["LDAP_GROUP_TREE_DN"] = config["ldap_group_tree_dn"] 

592 

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"] 

597 

598# return envconfig 

599 

600# def _make_pod_ingress_resources(self) -> List[Dict[str, Any]]: 

601# """Generate pod ingress resources. 

602 

603# Returns: 

604# List[Dict[str, Any]]: pod ingress resources. 

605# """ 

606# site_url = self.model.config["site_url"] 

607 

608# if not site_url: 

609# return 

610 

611# parsed = urlparse(site_url) 

612 

613# if not parsed.scheme.startswith("http"): 

614# return 

615 

616# max_file_size = self.model.config["max_file_size"] 

617# ingress_whitelist_source_range = self.model.config[ 

618# "ingress_whitelist_source_range" 

619# ] 

620 

621# annotations = { 

622# "nginx.ingress.kubernetes.io/proxy-body-size": "{}m".format(max_file_size) 

623# } 

624 

625# if ingress_whitelist_source_range: 

626# annotations[ 

627# "nginx.ingress.kubernetes.io/whitelist-source-range" 

628# ] = ingress_whitelist_source_range 

629 

630# ingress_spec_tls = None 

631 

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 

637# else: 

638# annotations["nginx.ingress.kubernetes.io/ssl-redirect"] = "false" 

639 

640# ingress = { 

641# "name": "{}-ingress".format(self.app.name), 

642# "annotations": annotations, 

643# "spec": { 

644# "rules": [ 

645# { 

646# "host": parsed.hostname, 

647# "http": { 

648# "paths": [ 

649# { 

650# "path": "/", 

651# "backend": { 

652# "serviceName": self.app.name, 

653# "servicePort": KEYSTONE_PORT, 

654# }, 

655# } 

656# ] 

657# }, 

658# } 

659# ], 

660# }, 

661# } 

662# if ingress_spec_tls: 

663# ingress["spec"]["tls"] = ingress_spec_tls 

664 

665# return [ingress] 

666 

667# def _generate_keys(self) -> Tuple[List[str], List[str]]: 

668# """Generating new fernet tokens. 

669 

670# Returns: 

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. 

676# """ 

677# fernet_keys = [ 

678# Fernet.generate_key().decode() for _ in range(NUMBER_FERNET_KEYS) 

679# ] 

680# credential_keys = [ 

681# Fernet.generate_key().decode() for _ in range(NUMBER_CREDENTIAL_KEYS) 

682# ] 

683 

684# return (fernet_keys, credential_keys) 

685 

686# def configure_pod(self, event: EventBase) -> NoReturn: 

687# """Assemble the pod spec and apply it, if possible. 

688 

689# Args: 

690# event (EventBase): Hook or Relation event that started the 

691# function. 

692# """ 

693# if not self.state.db_host: 

694# self.unit.status = WaitingStatus("Waiting for database relation") 

695# event.defer() 

696# return 

697 

698# if not self.unit.is_leader(): 

699# self.unit.status = ActiveStatus("ready") 

700# return 

701 

702# if fernet_keys := self.state.fernet_keys: 

703# fernet_keys = json.loads(fernet_keys) 

704 

705# if credential_keys := self.state.credential_keys: 

706# credential_keys = json.loads(credential_keys) 

707 

708# now = datetime.now().timestamp() 

709# keys_timestamp = self.state.keys_timestamp 

710# token_expiration = self.model.config["token_expiration"] 

711 

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 

718 

719# # Check problems in the settings 

720# problems = self._check_settings() 

721# if problems: 

722# self.unit.status = BlockedStatus(problems) 

723# return 

724 

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) 

731 

732# pod_spec = { 

733# "version": 3, 

734# "containers": [ 

735# { 

736# "name": self.framework.model.app.name, 

737# "imageDetails": image_details, 

738# "ports": ports, 

739# "envConfig": env_config, 

740# "volumeConfig": files, 

741# } 

742# ], 

743# "kubernetesResources": {"ingressResources": ingress_resources or []}, 

744# } 

745 

746# if self.state.pod_spec != ( 

747# pod_spec_json := json.dumps(pod_spec, sort_keys=True) 

748# ): 

749# self.state.pod_spec = pod_spec_json 

750# self.model.pod.set_spec(pod_spec) 

751 

752# self.unit.status = ActiveStatus("ready") 

753 

754 

755# if __name__ == "__main__": 

756# main(KeystoneCharm)