Fix bugs in NG-UI charm
[osm/devops.git] / installers / charm / ng-ui / src / charm.py
1 #!/usr/bin/env python3
2 # Copyright 2020 Canonical Ltd.
3 #
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
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,
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.
15
16 import sys
17 import logging
18
19 sys.path.append("lib")
20
21 from ops.charm import CharmBase
22 from ops.framework import StoredState, Object
23 from ops.main import main
24 from ops.model import (
25 ActiveStatus,
26 MaintenanceStatus,
27 BlockedStatus,
28 ModelError,
29 )
30
31 from glob import glob
32 from pathlib import Path
33 from string import Template
34
35 logger = logging.getLogger(__name__)
36
37
38 class NGUICharm(CharmBase):
39 state = StoredState()
40
41 def __init__(self, framework, key):
42 super().__init__(framework, key)
43 self.state.set_default(spec=None)
44 self.state.set_default(nbi_host=None)
45 self.state.set_default(nbi_port=None)
46
47 # Observe Charm related events
48 self.framework.observe(self.on.config_changed, self.on_config_changed)
49 self.framework.observe(self.on.start, self.on_start)
50 self.framework.observe(self.on.upgrade_charm, self.on_upgrade_charm)
51 self.framework.observe(
52 self.on.nbi_relation_changed, self.on_nbi_relation_changed
53 )
54
55 # SSL Certificate path
56 self.ssl_folder = "/certs"
57 self.ssl_crt = "{}/ssl_certificate.crt".format(self.ssl_folder)
58 self.ssl_key = "{}/ssl_certificate.key".format(self.ssl_folder)
59
60 def _apply_spec(self):
61 # Only apply the spec if this unit is a leader.
62 unit = self.model.unit
63 if not unit.is_leader():
64 unit.status = ActiveStatus("Ready")
65 return
66 if not self.state.nbi_host or not self.state.nbi_port:
67 unit.status = MaintenanceStatus("Waiting for NBI")
68 return
69 unit.status = MaintenanceStatus("Applying new pod spec")
70
71 new_spec = self.make_pod_spec()
72 if new_spec == self.state.spec:
73 unit.status = ActiveStatus("Ready")
74 return
75 self.framework.model.pod.set_spec(new_spec)
76 self.state.spec = new_spec
77 unit.status = ActiveStatus("Ready")
78
79 def make_pod_spec(self):
80 config = self.framework.model.config
81
82 ports = [
83 {"name": "port", "containerPort": config["port"], "protocol": "TCP",},
84 ]
85
86 kubernetes = {
87 "readinessProbe": {
88 "tcpSocket": {"port": config["port"]},
89 "timeoutSeconds": 5,
90 "periodSeconds": 5,
91 "initialDelaySeconds": 10,
92 },
93 "livenessProbe": {
94 "tcpSocket": {"port": config["port"]},
95 "timeoutSeconds": 5,
96 "initialDelaySeconds": 45,
97 },
98 }
99
100 ssl_certificate = None
101 ssl_certificate_key = None
102 try:
103 ssl_certificate = self.model.resources.fetch("ssl_certificate")
104 ssl_certificate_key = self.model.resources.fetch("ssl_certificate_key")
105 except ModelError as e:
106 logger.info(e)
107
108 config_spec = {
109 "port": config["port"],
110 "server_name": config["server_name"],
111 "client_max_body_size": config["client_max_body_size"],
112 "nbi_host": self.state.nbi_host or config["nbi_host"],
113 "nbi_port": self.state.nbi_port or config["nbi_port"],
114 "ssl_crt": "",
115 "ssl_crt_key": "",
116 }
117
118 if ssl_certificate and ssl_certificate_key:
119 config_spec["ssl_crt"] = "ssl_certificate {};".format(self.ssl_crt)
120 config_spec["ssl_crt_key"] = "ssl_certificate_key {};".format(self.ssl_key)
121 config_spec["port"] = "{} ssl".format(config_spec["port"])
122
123 files = [
124 {
125 "name": "configuration",
126 "mountPath": "/etc/nginx/sites-available/",
127 "files": {
128 Path(filename)
129 .name: Template(Path(filename).read_text())
130 .substitute(config_spec)
131 for filename in glob("files/*")
132 },
133 }
134 ]
135
136 if ssl_certificate and ssl_certificate_key:
137 files.append(
138 {
139 "name": "ssl",
140 "mountPath": self.ssl_folder,
141 "files": {
142 Path(filename)
143 .name: Template(Path(filename).read_text())
144 .substitute(config_spec)
145 for filename in [ssl_certificate, ssl_certificate_key]
146 },
147 }
148 )
149 logger.debug(files)
150 spec = {
151 "version": 2,
152 "containers": [
153 {
154 "name": self.framework.model.app.name,
155 "image": "{}".format(config["image"]),
156 "ports": ports,
157 "kubernetes": kubernetes,
158 "files": files,
159 }
160 ],
161 }
162
163 return spec
164
165 def on_config_changed(self, event):
166 """Handle changes in configuration"""
167 self._apply_spec()
168
169 def on_start(self, event):
170 """Called when the charm is being installed"""
171 self._apply_spec()
172
173 def on_upgrade_charm(self, event):
174 """Upgrade the charm."""
175 unit = self.model.unit
176 unit.status = MaintenanceStatus("Upgrading charm")
177 self.on_start(event)
178
179 def on_nbi_relation_changed(self, event):
180 unit = self.model.unit
181 if not unit.is_leader():
182 return
183 self.state.nbi_host = event.relation.data[event.unit].get("host")
184 self.state.nbi_port = event.relation.data[event.unit].get("port")
185 self._apply_spec()
186
187 def resource_get(self, resource_name: str) -> Path:
188 from pathlib import Path
189 from subprocess import run
190
191 result = run(["resource-get", resource_name], output=True, text=True)
192 return Path(result.stdout.strip())
193
194
195 if __name__ == "__main__":
196 main(NGUICharm)