Add image_username and image_password to charms
[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 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_name = "ssl_certificate.crt"
58 self.ssl_key_name = "ssl_certificate.key"
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 = WaitingStatus("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 config_spec = {
83 "http_port": config["port"],
84 "https_port": config["https_port"],
85 "server_name": config["server_name"],
86 "client_max_body_size": config["client_max_body_size"],
87 "nbi_host": self.state.nbi_host or config["nbi_host"],
88 "nbi_port": self.state.nbi_port or config["nbi_port"],
89 "ssl_crt": "",
90 "ssl_crt_key": "",
91 }
92
93 ssl_certificate = None
94 ssl_certificate_key = None
95 ssl_enabled = False
96
97 if "ssl_certificate" in config and "ssl_certificate_key" in config:
98 # Get bytes of cert and key
99 cert_b = base64.b64decode(config["ssl_certificate"])
100 key_b = base64.b64decode(config["ssl_certificate_key"])
101 # Decode key and cert
102 ssl_certificate = cert_b.decode("utf-8")
103 ssl_certificate_key = key_b.decode("utf-8")
104 # Get paths
105 cert_path = "{}/{}".format(self.ssl_folder, self.ssl_crt_name)
106 key_path = "{}/{}".format(self.ssl_folder, self.ssl_key_name)
107
108 config_spec["port"] = "{} ssl".format(config["https_port"])
109 config_spec["ssl_crt"] = "ssl_certificate {};".format(cert_path)
110 config_spec["ssl_crt_key"] = "ssl_certificate_key {};".format(key_path)
111 ssl_enabled = True
112 else:
113 config_spec["ssl_crt"] = ""
114 config_spec["ssl_crt_key"] = ""
115
116 files = [
117 {
118 "name": "configuration",
119 "mountPath": "/etc/nginx/sites-available/",
120 "files": {
121 Path(filename)
122 .name: Template(Path(filename).read_text())
123 .substitute(config_spec)
124 for filename in glob("files/*")
125 },
126 }
127 ]
128 port = config["https_port"] if ssl_enabled else config["port"]
129 ports = [
130 {
131 "name": "port",
132 "containerPort": port,
133 "protocol": "TCP",
134 },
135 ]
136
137 kubernetes = {
138 "readinessProbe": {
139 "tcpSocket": {"port": port},
140 "timeoutSeconds": 5,
141 "periodSeconds": 5,
142 "initialDelaySeconds": 10,
143 },
144 "livenessProbe": {
145 "tcpSocket": {"port": port},
146 "timeoutSeconds": 5,
147 "initialDelaySeconds": 45,
148 },
149 }
150
151 if ssl_certificate and ssl_certificate_key:
152 files.append(
153 {
154 "name": "ssl",
155 "mountPath": self.ssl_folder,
156 "files": {
157 self.ssl_crt_name: ssl_certificate,
158 self.ssl_key_name: ssl_certificate_key,
159 },
160 }
161 )
162
163 logger.debug(files)
164
165 spec = {
166 "version": 2,
167 "containers": [
168 {
169 "name": self.framework.model.app.name,
170 "imageDetails": {
171 "imagePath": config["image"],
172 "username": config["image_username"],
173 "password": config["image_password"],
174 },
175 "ports": ports,
176 "kubernetes": kubernetes,
177 "files": files,
178 }
179 ],
180 }
181
182 return spec
183
184 def on_config_changed(self, event):
185 """Handle changes in configuration"""
186 self._apply_spec()
187
188 def on_start(self, event):
189 """Called when the charm is being installed"""
190 self._apply_spec()
191
192 def on_upgrade_charm(self, event):
193 """Upgrade the charm."""
194 unit = self.model.unit
195 unit.status = MaintenanceStatus("Upgrading charm")
196 self.on_start(event)
197
198 def on_nbi_relation_changed(self, event):
199 nbi_host = event.relation.data[event.unit].get("host")
200 nbi_port = event.relation.data[event.unit].get("port")
201 if nbi_host and self.state.nbi_host != nbi_host:
202 self.state.nbi_host = nbi_host
203 if nbi_port and self.state.nbi_port != nbi_port:
204 self.state.nbi_port = nbi_port
205 self._apply_spec()
206
207
208 if __name__ == "__main__":
209 main(NGUICharm)