blob: d9ad8f2a040091e90b94832ec52a783d0ccb69ff [file] [log] [blame]
beierlma4a37f72020-06-26 12:55:01 -04001#!/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
beierlma4a37f72020-06-26 12:55:01 -040016import base64
beierlmb1a1c462020-10-23 14:54:56 -040017from glob import glob
18import logging
19from pathlib import Path
20from string import Template
21import sys
beierlma4a37f72020-06-26 12:55:01 -040022
23from ops.charm import CharmBase
24from ops.framework import StoredState, Object
25from ops.main import main
26from ops.model import (
27 ActiveStatus,
28 MaintenanceStatus,
29 BlockedStatus,
30 ModelError,
31 WaitingStatus,
32)
33
beierlmb1a1c462020-10-23 14:54:56 -040034
35sys.path.append("lib")
36
beierlma4a37f72020-06-26 12:55:01 -040037
38logger = logging.getLogger(__name__)
39
40
41class 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 )
David Garcia68faf8d2020-09-01 10:12:16 +0200161
David Garcia081f4692020-07-02 18:17:56 +0200162 logger.debug(files)
beierlmb1a1c462020-10-23 14:54:56 -0400163
beierlma4a37f72020-06-26 12:55:01 -0400164 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):
David Garcia68faf8d2020-09-01 10:12:16 +0200194 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
beierlma4a37f72020-06-26 12:55:01 -0400200 self._apply_spec()
201
202
203if __name__ == "__main__":
204 main(NGUICharm)