2 # Copyright 2021 Canonical Ltd.
4 # Licensed under the Apache License, Version 2.0 (the "License"); you may
5 # not use this file except in compliance with the License. You may obtain
6 # a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 # License for the specific language governing permissions and limitations
16 # For those usages not covered by the Apache License, Version 2.0 please
17 # contact: legal@canonical.com
19 # To get in touch with the maintainers, please contact:
20 # osm-charmers@lists.launchpad.net
23 # pylint: disable=E0213
26 from typing
import Optional
, NoReturn
27 from ipaddress
import ip_network
29 from ops
.framework
import EventBase
30 from ops
.main
import main
32 from opslib
.osm
.charm
import CharmedOsmBase
34 from opslib
.osm
.pod
import (
35 IngressResourceV3Builder
,
42 from opslib
.osm
.validator
import (
47 from opslib
.osm
.interfaces
.prometheus
import PrometheusServer
48 from urllib
.parse
import urlparse
50 logger
= logging
.getLogger(__name__
)
55 class ConfigModel(ModelValidator
):
59 site_url
: Optional
[str]
60 ingress_whitelist_source_range
: Optional
[str]
61 tls_secret_name
: Optional
[str]
62 enable_web_admin_api
: bool
64 @validator("web_subpath")
65 def validate_web_subpath(cls
, v
):
67 raise ValueError("web-subpath must be a non-empty string")
70 @validator("max_file_size")
71 def validate_max_file_size(cls
, v
):
73 raise ValueError("value must be equal or greater than 0")
76 @validator("site_url")
77 def validate_site_url(cls
, v
):
80 if not parsed
.scheme
.startswith("http"):
81 raise ValueError("value must start with http")
84 @validator("ingress_whitelist_source_range")
85 def validate_ingress_whitelist_source_range(cls
, v
):
91 class PrometheusCharm(CharmedOsmBase
):
93 """Prometheus Charm."""
95 def __init__(self
, *args
) -> NoReturn
:
96 """Prometheus Charm constructor."""
97 super().__init
__(*args
, oci_image
="image")
99 # Registering provided relation events
100 self
.prometheus
= PrometheusServer(self
, "prometheus")
101 self
.framework
.observe(
102 self
.on
.prometheus_relation_joined
, # pylint: disable=E1101
103 self
._publish
_prometheus
_info
,
106 def _publish_prometheus_info(self
, event
: EventBase
) -> NoReturn
:
107 self
.prometheus
.publish_info(self
.app
.name
, PORT
)
109 def _build_files(self
, config
: ConfigModel
):
110 files_builder
= FilesV3Builder()
111 files_builder
.add_file(
115 " scrape_interval: 15s\n"
116 " evaluation_interval: 15s\n"
119 " - static_configs:\n"
123 " - job_name: 'prometheus'\n"
125 f
" - targets: [{config.default_target}]\n"
128 return files_builder
.build()
130 def build_pod_spec(self
, image_info
):
132 config
= ConfigModel(**dict(self
.config
))
133 # Create Builder for the PodSpec
134 pod_spec_builder
= PodSpecV3Builder()
136 container_builder
= ContainerV3Builder(self
.app
.name
, image_info
)
137 container_builder
.add_port(name
=self
.app
.name
, port
=PORT
)
138 container_builder
.add_http_readiness_probe(
141 initial_delay_seconds
=10,
144 container_builder
.add_http_liveness_probe(
147 initial_delay_seconds
=30,
152 "--config.file=/etc/prometheus/prometheus.yml",
153 "--storage.tsdb.path=/prometheus",
154 "--web.console.libraries=/usr/share/prometheus/console_libraries",
155 "--web.console.templates=/usr/share/prometheus/consoles",
156 f
"--web.route-prefix={config.web_subpath}",
157 f
"--web.external-url=http://localhost:{PORT}{config.web_subpath}",
159 if config
.enable_web_admin_api
:
160 command
.append("--web.enable-admin-api")
161 container_builder
.add_command(command
)
162 container_builder
.add_volume_config(
163 "config", "/etc/prometheus", self
._build
_files
(config
)
165 container
= container_builder
.build()
166 # Add container to pod spec
167 pod_spec_builder
.add_container(container
)
168 # Add ingress resources to pod spec if site url exists
170 parsed
= urlparse(config
.site_url
)
172 "nginx.ingress.kubernetes.io/proxy-body-size": "{}".format(
173 str(config
.max_file_size
) + "m"
174 if config
.max_file_size
> 0
175 else config
.max_file_size
178 ingress_resource_builder
= IngressResourceV3Builder(
179 f
"{self.app.name}-ingress", annotations
182 if config
.ingress_whitelist_source_range
:
184 "nginx.ingress.kubernetes.io/whitelist-source-range"
185 ] = config
.ingress_whitelist_source_range
187 if parsed
.scheme
== "https":
188 ingress_resource_builder
.add_tls(
189 [parsed
.hostname
], config
.tls_secret_name
192 annotations
["nginx.ingress.kubernetes.io/ssl-redirect"] = "false"
194 ingress_resource_builder
.add_rule(parsed
.hostname
, self
.app
.name
, PORT
)
195 ingress_resource
= ingress_resource_builder
.build()
196 pod_spec_builder
.add_ingress_resource(ingress_resource
)
197 return pod_spec_builder
.build()
200 if __name__
== "__main__":
201 main(PrometheusCharm
)