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