1 # See LICENSE file for licensing details.
2 # http://www.apache.org/licenses/LICENSE-2.0
3 """Library for the ingress relation.
5 This library contains the Requires and Provides classes for handling
8 Import `IngressRequires` in your charm, with two required options:
9 - "self" (the charm itself)
12 `config_dict` accepts the following keys:
13 - service-hostname (required)
14 - service-name (required)
15 - service-port (required)
16 - additional-hostnames
20 - owasp-modsecurity-crs
26 - session-cookie-max-age
29 See [the config section](https://charmhub.io/nginx-ingress-integrator/configure) for descriptions
30 of each, along with the required type.
32 As an example, add the following to `src/charm.py`:
34 from charms.nginx_ingress_integrator.v0.ingress import IngressRequires
36 # In your charm's `__init__` method.
37 self.ingress = IngressRequires(self, {"service-hostname": self.config["external_hostname"],
38 "service-name": self.app.name,
41 # In your charm's `config-changed` handler.
42 self.ingress.update_config({"service-hostname": self.config["external_hostname"]})
44 And then add the following to `metadata.yaml`:
50 You _must_ register the IngressRequires class as part of the `__init__` method
51 rather than, for instance, a config-changed event handler. This is because
52 doing so won't get the current relation changed event, because it wasn't
53 registered to handle the event (because it wasn't created in `__init__` when
59 from ops
.charm
import CharmEvents
60 from ops
.framework
import EventBase
, EventSource
, Object
61 from ops
.model
import BlockedStatus
63 # The unique Charmhub library identifier, never change it
64 LIBID
= "db0af4367506491c91663468fb5caa4c"
66 # Increment this major API version when introducing breaking changes
69 # Increment this PATCH version before using `charmcraft publish-lib` or reset
70 # to 0 if you are raising the major API version
73 logger
= logging
.getLogger(__name__
)
75 REQUIRED_INGRESS_RELATION_FIELDS
= {
81 OPTIONAL_INGRESS_RELATION_FIELDS
= {
82 "additional-hostnames",
86 "owasp-modsecurity-crs",
92 "session-cookie-max-age",
97 class IngressAvailableEvent(EventBase
):
101 class IngressBrokenEvent(EventBase
):
105 class IngressCharmEvents(CharmEvents
):
106 """Custom charm events."""
108 ingress_available
= EventSource(IngressAvailableEvent
)
109 ingress_broken
= EventSource(IngressBrokenEvent
)
112 class IngressRequires(Object
):
113 """This class defines the functionality for the 'requires' side of the 'ingress' relation.
115 Hook events observed:
119 def __init__(self
, charm
, config_dict
):
120 super().__init
__(charm
, "ingress")
122 self
.framework
.observe(charm
.on
["ingress"].relation_changed
, self
._on
_relation
_changed
)
124 self
.config_dict
= config_dict
126 def _config_dict_errors(self
, update_only
=False):
127 """Check our config dict for errors."""
128 blocked_message
= "Error in ingress relation, check `juju debug-log`"
131 for x
in self
.config_dict
132 if x
not in REQUIRED_INGRESS_RELATION_FIELDS | OPTIONAL_INGRESS_RELATION_FIELDS
136 "Ingress relation error, unknown key(s) in config dictionary found: %s",
139 self
.model
.unit
.status
= BlockedStatus(blocked_message
)
142 missing
= [x
for x
in REQUIRED_INGRESS_RELATION_FIELDS
if x
not in self
.config_dict
]
145 "Ingress relation error, missing required key(s) in config dictionary: %s",
146 ", ".join(sorted(missing
)),
148 self
.model
.unit
.status
= BlockedStatus(blocked_message
)
152 def _on_relation_changed(self
, event
):
153 """Handle the relation-changed event."""
154 # `self.unit` isn't available here, so use `self.model.unit`.
155 if self
.model
.unit
.is_leader():
156 if self
._config
_dict
_errors
():
158 for key
in self
.config_dict
:
159 event
.relation
.data
[self
.model
.app
][key
] = str(self
.config_dict
[key
])
161 def update_config(self
, config_dict
):
162 """Allow for updates to relation."""
163 if self
.model
.unit
.is_leader():
164 self
.config_dict
= config_dict
165 if self
._config
_dict
_errors
(update_only
=True):
167 relation
= self
.model
.get_relation("ingress")
169 for key
in self
.config_dict
:
170 relation
.data
[self
.model
.app
][key
] = str(self
.config_dict
[key
])
173 class IngressProvides(Object
):
174 """This class defines the functionality for the 'provides' side of the 'ingress' relation.
176 Hook events observed:
180 def __init__(self
, charm
):
181 super().__init
__(charm
, "ingress")
182 # Observe the relation-changed hook event and bind
183 # self.on_relation_changed() to handle the event.
184 self
.framework
.observe(charm
.on
["ingress"].relation_changed
, self
._on
_relation
_changed
)
185 self
.framework
.observe(charm
.on
["ingress"].relation_broken
, self
._on
_relation
_broken
)
188 def _on_relation_changed(self
, event
):
189 """Handle a change to the ingress relation.
191 Confirm we have the fields we expect to receive."""
192 # `self.unit` isn't available here, so use `self.model.unit`.
193 if not self
.model
.unit
.is_leader():
197 field
: event
.relation
.data
[event
.app
].get(field
)
198 for field
in REQUIRED_INGRESS_RELATION_FIELDS | OPTIONAL_INGRESS_RELATION_FIELDS
201 missing_fields
= sorted(
204 for field
in REQUIRED_INGRESS_RELATION_FIELDS
205 if ingress_data
.get(field
) is None
211 "Missing required data fields for ingress relation: {}".format(
212 ", ".join(missing_fields
)
215 self
.model
.unit
.status
= BlockedStatus(
216 "Missing fields for ingress: {}".format(", ".join(missing_fields
))
219 # Create an event that our charm can use to decide it's okay to
220 # configure the ingress.
221 self
.charm
.on
.ingress_available
.emit()
223 def _on_relation_broken(self
, _
):
224 """Handle a relation-broken event in the ingress relation."""
225 if not self
.model
.unit
.is_leader():
228 # Create an event that our charm can use to remove the ingress resource.
229 self
.charm
.on
.ingress_broken
.emit()