blob: ce48927ad1e13b747df90ee7ccfe665daefededa [file] [log] [blame]
David Garcia081f4692020-07-02 18:17:56 +02001#!/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
16import sys
17import logging
David Garciafa55bd72020-07-07 11:14:19 +020018import base64
David Garcia081f4692020-07-02 18:17:56 +020019
20sys.path.append("lib")
21
22from ops.charm import CharmBase
23from ops.framework import StoredState, Object
24from ops.main import main
25from ops.model import (
26 ActiveStatus,
27 MaintenanceStatus,
David Garcia889e8bb2020-07-03 15:01:08 +020028 BlockedStatus,
29 ModelError,
David Garciafa55bd72020-07-07 11:14:19 +020030 WaitingStatus,
David Garcia081f4692020-07-02 18:17:56 +020031)
32
33from glob import glob
34from pathlib import Path
35from string import Template
36
37logger = logging.getLogger(__name__)
38
39
40class NGUICharm(CharmBase):
41 state = StoredState()
42
43 def __init__(self, framework, key):
44 super().__init__(framework, key)
45 self.state.set_default(spec=None)
David Garcia889e8bb2020-07-03 15:01:08 +020046 self.state.set_default(nbi_host=None)
47 self.state.set_default(nbi_port=None)
David Garcia081f4692020-07-02 18:17:56 +020048
49 # Observe Charm related events
50 self.framework.observe(self.on.config_changed, self.on_config_changed)
51 self.framework.observe(self.on.start, self.on_start)
52 self.framework.observe(self.on.upgrade_charm, self.on_upgrade_charm)
David Garcia889e8bb2020-07-03 15:01:08 +020053 self.framework.observe(
54 self.on.nbi_relation_changed, self.on_nbi_relation_changed
55 )
56
57 # SSL Certificate path
58 self.ssl_folder = "/certs"
David Garciafa55bd72020-07-07 11:14:19 +020059 self.ssl_crt_name = "ssl_certificate.crt"
60 self.ssl_key_name = "ssl_certificate.key"
David Garcia081f4692020-07-02 18:17:56 +020061
62 def _apply_spec(self):
63 # Only apply the spec if this unit is a leader.
David Garcia889e8bb2020-07-03 15:01:08 +020064 unit = self.model.unit
65 if not unit.is_leader():
beierlm838e3fd2020-07-28 09:21:07 -040066 unit.status = ActiveStatus("ready")
David Garcia081f4692020-07-02 18:17:56 +020067 return
David Garcia889e8bb2020-07-03 15:01:08 +020068 if not self.state.nbi_host or not self.state.nbi_port:
David Garciafa55bd72020-07-07 11:14:19 +020069 unit.status = WaitingStatus("Waiting for NBI")
David Garcia889e8bb2020-07-03 15:01:08 +020070 return
71 unit.status = MaintenanceStatus("Applying new pod spec")
72
David Garcia081f4692020-07-02 18:17:56 +020073 new_spec = self.make_pod_spec()
74 if new_spec == self.state.spec:
beierlm838e3fd2020-07-28 09:21:07 -040075 unit.status = ActiveStatus("ready")
David Garcia081f4692020-07-02 18:17:56 +020076 return
77 self.framework.model.pod.set_spec(new_spec)
78 self.state.spec = new_spec
beierlm838e3fd2020-07-28 09:21:07 -040079 unit.status = ActiveStatus("ready")
David Garcia081f4692020-07-02 18:17:56 +020080
81 def make_pod_spec(self):
82 config = self.framework.model.config
83
David Garcia081f4692020-07-02 18:17:56 +020084 config_spec = {
David Garciafa55bd72020-07-07 11:14:19 +020085 "http_port": config["port"],
86 "https_port": config["https_port"],
David Garcia081f4692020-07-02 18:17:56 +020087 "server_name": config["server_name"],
88 "client_max_body_size": config["client_max_body_size"],
David Garcia889e8bb2020-07-03 15:01:08 +020089 "nbi_host": self.state.nbi_host or config["nbi_host"],
90 "nbi_port": self.state.nbi_port or config["nbi_port"],
91 "ssl_crt": "",
92 "ssl_crt_key": "",
David Garcia081f4692020-07-02 18:17:56 +020093 }
94
David Garciafa55bd72020-07-07 11:14:19 +020095 ssl_certificate = None
96 ssl_certificate_key = None
97 ssl_enabled = False
98
99 if "ssl_certificate" in config and "ssl_certificate_key" in config:
100 # Get bytes of cert and key
101 cert_b = base64.b64decode(config["ssl_certificate"])
102 key_b = base64.b64decode(config["ssl_certificate_key"])
103 # Decode key and cert
104 ssl_certificate = cert_b.decode("utf-8")
105 ssl_certificate_key = key_b.decode("utf-8")
106 # Get paths
107 cert_path = "{}/{}".format(self.ssl_folder, self.ssl_crt_name)
108 key_path = "{}/{}".format(self.ssl_folder, self.ssl_key_name)
109
110 config_spec["port"] = "{} ssl".format(config["https_port"])
111 config_spec["ssl_crt"] = "ssl_certificate {};".format(cert_path)
112 config_spec["ssl_crt_key"] = "ssl_certificate_key {};".format(key_path)
113 ssl_enabled = True
114 else:
115 config_spec["ssl_crt"] = ""
116 config_spec["ssl_crt_key"] = ""
David Garcia889e8bb2020-07-03 15:01:08 +0200117
David Garcia081f4692020-07-02 18:17:56 +0200118 files = [
119 {
120 "name": "configuration",
121 "mountPath": "/etc/nginx/sites-available/",
122 "files": {
123 Path(filename)
124 .name: Template(Path(filename).read_text())
125 .substitute(config_spec)
126 for filename in glob("files/*")
127 },
David Garcia889e8bb2020-07-03 15:01:08 +0200128 }
David Garcia081f4692020-07-02 18:17:56 +0200129 ]
David Garciafa55bd72020-07-07 11:14:19 +0200130 port = config["https_port"] if ssl_enabled else config["port"]
131 ports = [
beierlm838e3fd2020-07-28 09:21:07 -0400132 {"name": "port", "containerPort": port, "protocol": "TCP", },
David Garciafa55bd72020-07-07 11:14:19 +0200133 ]
134
135 kubernetes = {
136 "readinessProbe": {
137 "tcpSocket": {"port": port},
138 "timeoutSeconds": 5,
139 "periodSeconds": 5,
140 "initialDelaySeconds": 10,
141 },
142 "livenessProbe": {
143 "tcpSocket": {"port": port},
144 "timeoutSeconds": 5,
145 "initialDelaySeconds": 45,
146 },
147 }
David Garcia889e8bb2020-07-03 15:01:08 +0200148
149 if ssl_certificate and ssl_certificate_key:
150 files.append(
151 {
152 "name": "ssl",
153 "mountPath": self.ssl_folder,
154 "files": {
David Garciafa55bd72020-07-07 11:14:19 +0200155 self.ssl_crt_name: ssl_certificate,
156 self.ssl_key_name: ssl_certificate_key,
David Garcia889e8bb2020-07-03 15:01:08 +0200157 },
158 }
159 )
David Garcia081f4692020-07-02 18:17:56 +0200160 logger.debug(files)
161 spec = {
162 "version": 2,
163 "containers": [
164 {
165 "name": self.framework.model.app.name,
166 "image": "{}".format(config["image"]),
167 "ports": ports,
168 "kubernetes": kubernetes,
169 "files": files,
170 }
171 ],
172 }
173
174 return spec
175
176 def on_config_changed(self, event):
177 """Handle changes in configuration"""
David Garcia081f4692020-07-02 18:17:56 +0200178 self._apply_spec()
David Garcia081f4692020-07-02 18:17:56 +0200179
180 def on_start(self, event):
181 """Called when the charm is being installed"""
David Garcia081f4692020-07-02 18:17:56 +0200182 self._apply_spec()
David Garcia081f4692020-07-02 18:17:56 +0200183
184 def on_upgrade_charm(self, event):
185 """Upgrade the charm."""
186 unit = self.model.unit
187 unit.status = MaintenanceStatus("Upgrading charm")
188 self.on_start(event)
189
David Garcia889e8bb2020-07-03 15:01:08 +0200190 def on_nbi_relation_changed(self, event):
191 unit = self.model.unit
192 if not unit.is_leader():
193 return
194 self.state.nbi_host = event.relation.data[event.unit].get("host")
195 self.state.nbi_port = event.relation.data[event.unit].get("port")
196 self._apply_spec()
David Garcia081f4692020-07-02 18:17:56 +0200197
David Garcia081f4692020-07-02 18:17:56 +0200198
199if __name__ == "__main__":
200 main(NGUICharm)