123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406 |
- from typing import List
- import enum
- import os
- import pathlib
- import uuid
- import dagger
- import jinja2
- import imageutils
- class Platform:
- def __init__(self, platform: str):
- self.platform = dagger.Platform(platform)
- def escaped(self) -> str:
- return str(self.platform).removeprefix("linux/").replace("/", "_")
- def __eq__(self, other):
- if isinstance(other, Platform):
- return self.platform == other.platform
- elif isinstance(other, dagger.Platform):
- return self.platform == other
- else:
- return NotImplemented
- def __ne__(self, other):
- return not (self == other)
- def __hash__(self):
- return hash(self.platform)
- def __str__(self) -> str:
- return str(self.platform)
- SUPPORTED_PLATFORMS = set(
- [
- Platform("linux/x86_64"),
- Platform("linux/arm64"),
- Platform("linux/i386"),
- Platform("linux/arm/v7"),
- Platform("linux/arm/v6"),
- Platform("linux/ppc64le"),
- Platform("linux/s390x"),
- Platform("linux/riscv64"),
- ]
- )
- SUPPORTED_DISTRIBUTIONS = set(
- [
- "alpine_3_18",
- "alpine_3_19",
- "amazonlinux2",
- "centos7",
- "centos-stream8",
- "centos-stream9",
- "debian10",
- "debian11",
- "debian12",
- "fedora37",
- "fedora38",
- "fedora39",
- "opensuse15.4",
- "opensuse15.5",
- "opensusetumbleweed",
- "oraclelinux8",
- "oraclelinux9",
- "rockylinux8",
- "rockylinux9",
- "ubuntu20.04",
- "ubuntu22.04",
- "ubuntu23.04",
- "ubuntu23.10",
- ]
- )
- class Distribution:
- def __init__(self, display_name):
- self.display_name = display_name
- if self.display_name == "alpine_3_18":
- self.docker_tag = "alpine:3.18"
- self.builder = imageutils.build_alpine_3_18
- self.platforms = SUPPORTED_PLATFORMS
- elif self.display_name == "alpine_3_19":
- self.docker_tag = "alpine:3.19"
- self.builder = imageutils.build_alpine_3_19
- self.platforms = SUPPORTED_PLATFORMS
- elif self.display_name == "amazonlinux2":
- self.docker_tag = "amazonlinux:2"
- self.builder = imageutils.build_amazon_linux_2
- self.platforms = SUPPORTED_PLATFORMS
- elif self.display_name == "centos7":
- self.docker_tag = "centos:7"
- self.builder = imageutils.build_centos_7
- self.platforms = SUPPORTED_PLATFORMS
- elif self.display_name == "centos-stream8":
- self.docker_tag = "quay.io/centos/centos:stream8"
- self.builder = imageutils.build_centos_stream_8
- self.platforms = SUPPORTED_PLATFORMS
- elif self.display_name == "centos-stream9":
- self.docker_tag = "quay.io/centos/centos:stream9"
- self.builder = imageutils.build_centos_stream_9
- self.platforms = SUPPORTED_PLATFORMS
- elif self.display_name == "debian10":
- self.docker_tag = "debian:10"
- self.builder = imageutils.build_debian_10
- self.platforms = SUPPORTED_PLATFORMS
- elif self.display_name == "debian11":
- self.docker_tag = "debian:11"
- self.builder = imageutils.build_debian_11
- self.platforms = SUPPORTED_PLATFORMS
- elif self.display_name == "debian12":
- self.docker_tag = "debian:12"
- self.builder = imageutils.build_debian_12
- self.platforms = SUPPORTED_PLATFORMS
- elif self.display_name == "fedora37":
- self.docker_tag = "fedora:37"
- self.builder = imageutils.build_fedora_37
- self.platforms = SUPPORTED_PLATFORMS
- elif self.display_name == "fedora38":
- self.docker_tag = "fedora:38"
- self.builder = imageutils.build_fedora_38
- self.platforms = SUPPORTED_PLATFORMS
- elif self.display_name == "fedora39":
- self.docker_tag = "fedora:39"
- self.platforms = SUPPORTED_PLATFORMS
- self.builder = imageutils.build_fedora_39
- elif self.display_name == "opensuse15.4":
- self.docker_tag = "opensuse/leap:15.4"
- self.builder = imageutils.build_opensuse_15_4
- self.platforms = SUPPORTED_PLATFORMS
- elif self.display_name == "opensuse15.5":
- self.docker_tag = "opensuse/leap:15.5"
- self.builder = imageutils.build_opensuse_15_5
- self.platforms = SUPPORTED_PLATFORMS
- elif self.display_name == "opensusetumbleweed":
- self.docker_tag = "opensuse/tumbleweed:latest"
- self.builder = imageutils.build_opensuse_tumbleweed
- self.platforms = SUPPORTED_PLATFORMS
- elif self.display_name == "oraclelinux8":
- self.docker_tag = "oraclelinux:8"
- self.builder = imageutils.build_oracle_linux_8
- self.platforms = SUPPORTED_PLATFORMS
- elif self.display_name == "oraclelinux9":
- self.docker_tag = "oraclelinux:9"
- self.builder = imageutils.build_oracle_linux_9
- self.platforms = SUPPORTED_PLATFORMS
- elif self.display_name == "rockylinux8":
- self.docker_tag = "rockylinux:8"
- self.builder = imageutils.build_rocky_linux_8
- self.platforms = SUPPORTED_PLATFORMS
- elif self.display_name == "rockylinux9":
- self.docker_tag = "rockylinux:9"
- self.builder = imageutils.build_rocky_linux_9
- self.platforms = SUPPORTED_PLATFORMS
- elif self.display_name == "ubuntu20.04":
- self.docker_tag = "ubuntu:20.04"
- self.builder = imageutils.build_ubuntu_20_04
- self.platforms = SUPPORTED_PLATFORMS
- elif self.display_name == "ubuntu22.04":
- self.docker_tag = "ubuntu:22.04"
- self.builder = imageutils.build_ubuntu_22_04
- self.platforms = SUPPORTED_PLATFORMS
- elif self.display_name == "ubuntu23.04":
- self.docker_tag = "ubuntu:23.04"
- self.builder = imageutils.build_ubuntu_23_04
- self.platforms = SUPPORTED_PLATFORMS
- elif self.display_name == "ubuntu23.10":
- self.docker_tag = "ubuntu:23.10"
- self.builder = imageutils.build_ubuntu_23_10
- self.platforms = SUPPORTED_PLATFORMS
- else:
- raise ValueError(f"Unknown distribution: {self.display_name}")
- def _cache_volume(
- self, client: dagger.Client, platform: dagger.Platform, path: str
- ) -> dagger.CacheVolume:
- tag = "_".join([self.display_name, Platform(platform).escaped()])
- return client.cache_volume(f"{path}-{tag}")
- def build(
- self, client: dagger.Client, platform: dagger.Platform
- ) -> dagger.Container:
- if platform not in self.platforms:
- raise ValueError(
- f"Building {self.display_name} is not supported on {platform}."
- )
- ctr = self.builder(client, platform)
- ctr = imageutils.install_cargo(ctr)
- return ctr
- class FeatureFlags(enum.Flag):
- DBEngine = enum.auto()
- GoPlugin = enum.auto()
- ExtendedBPF = enum.auto()
- LogsManagement = enum.auto()
- MachineLearning = enum.auto()
- BundledProtobuf = enum.auto()
- class NetdataInstaller:
- def __init__(
- self,
- platform: Platform,
- distro: Distribution,
- repo_root: pathlib.Path,
- prefix: pathlib.Path,
- features: FeatureFlags,
- ):
- self.platform = platform
- self.distro = distro
- self.repo_root = repo_root
- self.prefix = prefix
- self.features = features
- def _mount_repo(
- self, client: dagger.Client, ctr: dagger.Container, repo_root: pathlib.Path
- ) -> dagger.Container:
- host_repo_root = pathlib.Path(__file__).parent.parent.parent.as_posix()
- exclude_dirs = ["build", "fluent-bit/build", "packaging/dag"]
- # The installer builds/stores intermediate artifacts under externaldeps/
- # We add a volume to speed up rebuilds. The volume has to be unique
- # per platform/distro in order to avoid mixing unrelated artifacts
- # together.
- externaldeps = self.distro._cache_volume(client, self.platform, "externaldeps")
- ctr = (
- ctr.with_directory(
- self.repo_root.as_posix(), client.host().directory(host_repo_root)
- )
- .with_workdir(self.repo_root.as_posix())
- .with_mounted_cache(
- os.path.join(self.repo_root, "externaldeps"), externaldeps
- )
- )
- return ctr
- def install(self, client: dagger.Client, ctr: dagger.Container) -> dagger.Container:
- args = ["--dont-wait", "--dont-start-it", "--disable-telemetry"]
- if FeatureFlags.DBEngine not in self.features:
- args.append("--disable-dbengine")
- if FeatureFlags.GoPlugin not in self.features:
- args.append("--disable-go")
- if FeatureFlags.ExtendedBPF not in self.features:
- args.append("--disable-ebpf")
- if FeatureFlags.MachineLearning not in self.features:
- args.append("--disable-ml")
- if FeatureFlags.BundledProtobuf not in self.features:
- args.append("--use-system-protobuf")
- args.extend(["--install-prefix", self.prefix.parent.as_posix()])
- ctr = self._mount_repo(client, ctr, self.repo_root.as_posix())
- ctr = ctr.with_env_variable(
- "NETDATA_CMAKE_OPTIONS", "-DCMAKE_BUILD_TYPE=Debug"
- ).with_exec(["./netdata-installer.sh"] + args)
- return ctr
- class Endpoint:
- def __init__(self, hostname: str, port: int):
- self.hostname = hostname
- self.port = port
- def __str__(self):
- return ":".join([self.hostname, str(self.port)])
- class ChildStreamConf:
- def __init__(
- self,
- installer: NetdataInstaller,
- destinations: List[Endpoint],
- api_key: uuid.UUID,
- ):
- self.installer = installer
- self.substitutions = {
- "enabled": "yes",
- "destination": " ".join([str(dst) for dst in destinations]),
- "api_key": api_key,
- "timeout_seconds": 60,
- "default_port": 19999,
- "send_charts_matching": "*",
- "buffer_size_bytes": 1024 * 1024,
- "reconnect_delay_seconds": 5,
- "initial_clock_resync_iterations": 60,
- }
- def render(self) -> str:
- tmpl_path = pathlib.Path(__file__).parent / "files/child_stream.conf"
- with open(tmpl_path) as fp:
- tmpl = jinja2.Template(fp.read())
- return tmpl.render(**self.substitutions)
- class ParentStreamConf:
- def __init__(self, installer: NetdataInstaller, api_key: uuid.UUID):
- self.installer = installer
- self.substitutions = {
- "api_key": str(api_key),
- "enabled": "yes",
- "allow_from": "*",
- "default_history": 3600,
- "health_enabled_by_default": "auto",
- "default_postpone_alarms_on_connect_seconds": 60,
- "multiple_connections": "allow",
- }
- def render(self) -> str:
- tmpl_path = pathlib.Path(__file__).parent / "files/parent_stream.conf"
- with open(tmpl_path) as fp:
- tmpl = jinja2.Template(fp.read())
- return tmpl.render(**self.substitutions)
- class StreamConf:
- def __init__(self, child_conf: ChildStreamConf, parent_conf: ParentStreamConf):
- self.child_conf = child_conf
- self.parent_conf = parent_conf
- def render(self) -> str:
- child_section = self.child_conf.render() if self.child_conf else ""
- parent_section = self.parent_conf.render() if self.parent_conf else ""
- return "\n".join([child_section, parent_section])
- class AgentContext:
- def __init__(
- self,
- client: dagger.Client,
- platform: dagger.Platform,
- distro: Distribution,
- installer: NetdataInstaller,
- endpoint: Endpoint,
- api_key: uuid.UUID,
- allow_children: bool,
- ):
- self.client = client
- self.platform = platform
- self.distro = distro
- self.installer = installer
- self.endpoint = endpoint
- self.api_key = api_key
- self.allow_children = allow_children
- self.parent_contexts = []
- self.built_distro = False
- self.built_agent = False
- def add_parent(self, parent_context: "AgentContext"):
- self.parent_contexts.append(parent_context)
- def build_container(self) -> dagger.Container:
- ctr = self.distro.build(self.client, self.platform)
- ctr = self.installer.install(self.client, ctr)
- if len(self.parent_contexts) == 0 and not self.allow_children:
- return ctr.with_exposed_port(self.endpoint.port)
- destinations = [parent_ctx.endpoint for parent_ctx in self.parent_contexts]
- child_stream_conf = ChildStreamConf(self.installer, destinations, self.api_key)
- parent_stream_conf = None
- if self.allow_children:
- parent_stream_conf = ParentStreamConf(self.installer, self.api_key)
- stream_conf = StreamConf(child_stream_conf, parent_stream_conf)
- # write the stream conf to localhost and cp it in the container
- host_stream_conf_path = pathlib.Path(
- f"/tmp/{self.endpoint.hostname}_stream.conf"
- )
- with open(host_stream_conf_path, "w") as fp:
- fp.write(stream_conf.render())
- ctr_stream_conf_path = self.installer.prefix / "etc/netdata/stream.conf"
- ctr = ctr.with_file(
- ctr_stream_conf_path.as_posix(),
- self.client.host().file(host_stream_conf_path.as_posix()),
- )
- ctr = ctr.with_exposed_port(self.endpoint.port)
- return ctr
|