2 # Copyright 2020 Canonical Ltd.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain 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,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
18 from urllib
.parse
import urlparse
20 from ops
.charm
import CharmBase
22 # from ops.framework import StoredState
23 from ops
.main
import main
24 from ops
.model
import (
31 from ops
.framework
import StoredState
33 logger
= logging
.getLogger(__name__
)
35 REQUIRED_SETTINGS
= []
37 DATABASE_NAME
= "keystone" # This is hardcoded in the keystone container script
38 # We expect the keystone container to use the default port
42 class KeystoneCharm(CharmBase
):
46 def __init__(self
, *args
):
47 super().__init
__(*args
)
49 # Register all of the events we want to observe
50 self
.framework
.observe(self
.on
.config_changed
, self
.configure_pod
)
51 self
.framework
.observe(self
.on
.start
, self
.configure_pod
)
52 self
.framework
.observe(self
.on
.upgrade_charm
, self
.configure_pod
)
54 # Register relation events
55 self
.state
.set_default(
56 db_host
=None, db_port
=None, db_user
=None, db_password
=None
58 self
.framework
.observe(
59 self
.on
.db_relation_changed
, self
._on
_db
_relation
_changed
61 self
.framework
.observe(
62 self
.on
.keystone_relation_joined
, self
._publish
_keystone
_info
65 def _publish_keystone_info(self
, event
):
66 config
= self
.model
.config
67 if self
.unit
.is_leader():
69 "host": f
"http://{self.app.name}:{KEYSTONE_PORT}/v3",
70 "port": str(KEYSTONE_PORT
),
71 "keystone_db_password": config
["keystone_db_password"],
72 "region_id": config
["region_id"],
73 "user_domain_name": config
["user_domain_name"],
74 "project_domain_name": config
["project_domain_name"],
75 "admin_username": config
["admin_username"],
76 "admin_password": config
["admin_password"],
77 "admin_project_name": config
["admin_project"],
78 "username": config
["service_username"],
79 "password": config
["service_password"],
80 "service": config
["service_project"],
82 for k
, v
in rel_data
.items():
83 event
.relation
.data
[self
.model
.unit
][k
] = v
85 def _on_db_relation_changed(self
, event
):
86 self
.state
.db_host
= event
.relation
.data
[event
.unit
].get("host")
87 self
.state
.db_port
= event
.relation
.data
[event
.unit
].get("port", 3306)
88 self
.state
.db_user
= "root" # event.relation.data[event.unit].get("user")
89 self
.state
.db_password
= event
.relation
.data
[event
.unit
].get("root_password")
90 if self
.state
.db_host
:
91 self
.configure_pod(event
)
93 def _check_settings(self
):
95 config
= self
.model
.config
97 for setting
in REQUIRED_SETTINGS
:
98 if not config
.get(setting
):
99 problem
= f
"missing config {setting}"
100 problems
.append(problem
)
102 return ";".join(problems
)
104 def _make_pod_image_details(self
):
105 config
= self
.model
.config
107 "imagePath": config
["image"],
109 if config
["image_username"]:
110 image_details
.update(
112 "username": config
["image_username"],
113 "password": config
["image_password"],
118 def _make_pod_ports(self
):
120 {"name": "keystone", "containerPort": KEYSTONE_PORT
, "protocol": "TCP"},
123 def _make_pod_envconfig(self
):
124 config
= self
.model
.config
127 "DB_HOST": self
.state
.db_host
,
128 "DB_PORT": self
.state
.db_port
,
129 "ROOT_DB_USER": self
.state
.db_user
,
130 "ROOT_DB_PASSWORD": self
.state
.db_password
,
131 "KEYSTONE_DB_PASSWORD": config
["keystone_db_password"],
132 "REGION_ID": config
["region_id"],
133 "KEYSTONE_HOST": self
.app
.name
,
134 "ADMIN_USERNAME": config
["admin_username"],
135 "ADMIN_PASSWORD": config
["admin_password"],
136 "ADMIN_PROJECT": config
["admin_project"],
137 "SERVICE_USERNAME": config
["service_username"],
138 "SERVICE_PASSWORD": config
["service_password"],
139 "SERVICE_PROJECT": config
["service_project"],
142 def _make_pod_ingress_resources(self
):
143 site_url
= self
.model
.config
["site_url"]
148 parsed
= urlparse(site_url
)
150 if not parsed
.scheme
.startswith("http"):
153 max_file_size
= self
.model
.config
["max_file_size"]
154 ingress_whitelist_source_range
= self
.model
.config
[
155 "ingress_whitelist_source_range"
159 "nginx.ingress.kubernetes.io/proxy-body-size": "{}m".format(max_file_size
)
162 if ingress_whitelist_source_range
:
164 "nginx.ingress.kubernetes.io/whitelist-source-range"
165 ] = ingress_whitelist_source_range
167 ingress_spec_tls
= None
169 if parsed
.scheme
== "https":
170 ingress_spec_tls
= [{"hosts": [parsed
.hostname
]}]
171 tls_secret_name
= self
.model
.config
["tls_secret_name"]
173 ingress_spec_tls
[0]["secretName"] = tls_secret_name
175 annotations
["nginx.ingress.kubernetes.io/ssl-redirect"] = "false"
178 "name": "{}-ingress".format(self
.app
.name
),
179 "annotations": annotations
,
183 "host": parsed
.hostname
,
189 "serviceName": self
.app
.name
,
190 "servicePort": KEYSTONE_PORT
,
200 ingress
["spec"]["tls"] = ingress_spec_tls
204 def configure_pod(self
, event
):
205 """Assemble the pod spec and apply it, if possible."""
207 if not self
.state
.db_host
:
208 self
.unit
.status
= WaitingStatus("Waiting for database relation")
212 if not self
.unit
.is_leader():
213 self
.unit
.status
= ActiveStatus()
216 # Check problems in the settings
217 problems
= self
._check
_settings
()
219 self
.unit
.status
= BlockedStatus(problems
)
222 self
.unit
.status
= BlockedStatus("Assembling pod spec")
223 image_details
= self
._make
_pod
_image
_details
()
224 ports
= self
._make
_pod
_ports
()
225 env_config
= self
._make
_pod
_envconfig
()
226 ingress_resources
= self
._make
_pod
_ingress
_resources
()
232 "name": self
.framework
.model
.app
.name
,
233 "imageDetails": image_details
,
235 "envConfig": env_config
,
238 "kubernetesResources": {"ingressResources": ingress_resources
or []},
240 self
.model
.pod
.set_spec(pod_spec
)
241 self
.unit
.status
= ActiveStatus()
244 if __name__
== "__main__":