blob: 7d461307c199c17d3a7a06962aef7ee4325f4e3d [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
18
19sys.path.append("lib")
20
21from ops.charm import CharmBase
22from ops.framework import StoredState, Object
23from ops.main import main
24from ops.model import (
25 ActiveStatus,
26 MaintenanceStatus,
David Garcia889e8bb2020-07-03 15:01:08 +020027 BlockedStatus,
28 ModelError,
David Garcia081f4692020-07-02 18:17:56 +020029)
30
31from glob import glob
32from pathlib import Path
33from string import Template
34
35logger = logging.getLogger(__name__)
36
37
38class NGUICharm(CharmBase):
39 state = StoredState()
40
41 def __init__(self, framework, key):
42 super().__init__(framework, key)
43 self.state.set_default(spec=None)
David Garcia889e8bb2020-07-03 15:01:08 +020044 self.state.set_default(nbi_host=None)
45 self.state.set_default(nbi_port=None)
David Garcia081f4692020-07-02 18:17:56 +020046
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)
David Garcia889e8bb2020-07-03 15:01:08 +020051 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)
David Garcia081f4692020-07-02 18:17:56 +020059
60 def _apply_spec(self):
61 # Only apply the spec if this unit is a leader.
David Garcia889e8bb2020-07-03 15:01:08 +020062 unit = self.model.unit
63 if not unit.is_leader():
64 unit.status = ActiveStatus("Ready")
David Garcia081f4692020-07-02 18:17:56 +020065 return
David Garcia889e8bb2020-07-03 15:01:08 +020066 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
David Garcia081f4692020-07-02 18:17:56 +020071 new_spec = self.make_pod_spec()
72 if new_spec == self.state.spec:
David Garcia889e8bb2020-07-03 15:01:08 +020073 unit.status = ActiveStatus("Ready")
David Garcia081f4692020-07-02 18:17:56 +020074 return
75 self.framework.model.pod.set_spec(new_spec)
76 self.state.spec = new_spec
David Garcia889e8bb2020-07-03 15:01:08 +020077 unit.status = ActiveStatus("Ready")
David Garcia081f4692020-07-02 18:17:56 +020078
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 }
David Garcia889e8bb2020-07-03 15:01:08 +020099
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
David Garcia081f4692020-07-02 18:17:56 +0200108 config_spec = {
109 "port": config["port"],
110 "server_name": config["server_name"],
111 "client_max_body_size": config["client_max_body_size"],
David Garcia889e8bb2020-07-03 15:01:08 +0200112 "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": "",
David Garcia081f4692020-07-02 18:17:56 +0200116 }
117
David Garcia889e8bb2020-07-03 15:01:08 +0200118 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
David Garcia081f4692020-07-02 18:17:56 +0200123 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 },
David Garcia889e8bb2020-07-03 15:01:08 +0200133 }
David Garcia081f4692020-07-02 18:17:56 +0200134 ]
David Garcia889e8bb2020-07-03 15:01:08 +0200135
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 )
David Garcia081f4692020-07-02 18:17:56 +0200149 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"""
David Garcia081f4692020-07-02 18:17:56 +0200167 self._apply_spec()
David Garcia081f4692020-07-02 18:17:56 +0200168
169 def on_start(self, event):
170 """Called when the charm is being installed"""
David Garcia081f4692020-07-02 18:17:56 +0200171 self._apply_spec()
David Garcia081f4692020-07-02 18:17:56 +0200172
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
David Garcia889e8bb2020-07-03 15:01:08 +0200179 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()
David Garcia081f4692020-07-02 18:17:56 +0200186
David Garcia889e8bb2020-07-03 15:01:08 +0200187 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())
David Garcia081f4692020-07-02 18:17:56 +0200193
194
195if __name__ == "__main__":
196 main(NGUICharm)