From 2a829b48b5a6b7edc5ac1006e31f272341a51e40 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 20 Oct 2023 09:12:28 +0100 Subject: [PATCH] feat: support go templates in config sources fixes #37 This allows the config sources, like ConfigMaps to be `go` templates. All the functions from sprig lib are supported, there's also a k8sLookup function to get values from k8s objects, and you can access environment variables from the template under .Env object. Signed-off-by: Luis Davim --- README.md | 335 ++++++++++-------- config-reloader/datasource/fake.go | 4 +- config-reloader/datasource/kube_informer.go | 17 +- .../datasource/kubedatasource/configmap.go | 2 +- config-reloader/fluentd/validator.go | 5 +- config-reloader/generator/generator.go | 2 +- config-reloader/go.mod | 17 +- config-reloader/go.sum | 60 +++- config-reloader/processors/labels.go | 13 +- config-reloader/processors/share.go | 2 +- config-reloader/template/template.go | 147 ++++++++ config-reloader/template/template_test.go | 97 +++++ config-reloader/util/util.go | 3 +- 13 files changed, 520 insertions(+), 184 deletions(-) create mode 100644 config-reloader/template/template.go create mode 100644 config-reloader/template/template_test.go diff --git a/README.md b/README.md index c6d52e76..9e6d32c9 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,11 @@ Kubernetes Fluentd Operator (KFO) is a Fluentd config manager with batteries included, config validation, no needs to restart, with sensible defaults and best practices built-in. Use Kubernetes labels to filter/route logs per namespace! -*kube-fluentd-operator* configures Fluentd in a Kubernetes environment. It compiles a Fluentd configuration from configmaps (one per namespace) - similar to how an Ingress controller would compile nginx configuration from several Ingress resources. This way only one instance of Fluentd can handle all log shipping for an entire cluster and the cluster admin does NOT need to coordinate with namespace admins. +_kube-fluentd-operator_ configures Fluentd in a Kubernetes environment. It compiles a Fluentd configuration from configmaps (one per namespace) - similar to how an Ingress controller would compile nginx configuration from several Ingress resources. This way only one instance of Fluentd can handle all log shipping for an entire cluster and the cluster admin does NOT need to coordinate with namespace admins. -Cluster administrators set up Fluentd only once and namespace owners can configure log routing as they wish. *KFO* will re-configure Fluentd accordingly and make sure logs originating from a namespace will not be accessible by other tenants/namespaces. +Cluster administrators set up Fluentd only once and namespace owners can configure log routing as they wish. _KFO_ will re-configure Fluentd accordingly and make sure logs originating from a namespace will not be accessible by other tenants/namespaces. -*KFO* also extends the Fluentd configuration language making it possible to refer to pods based on their labels and the container name pattern. This enables for very fined-grained targeting of log streams for the purpose of pre-processing before shipping. Writing a custom processor, adding a new Fluentd plugin, or writing a custom Fluentd plugin allow KFO to be extendable for any use case and any external logging ingestion system. +_KFO_ also extends the Fluentd configuration language making it possible to refer to pods based on their labels and the container name pattern. This enables for very fined-grained targeting of log streams for the purpose of pre-processing before shipping. Writing a custom processor, adding a new Fluentd plugin, or writing a custom Fluentd plugin allow KFO to be extendable for any use case and any external logging ingestion system. Finally, it is possible to ingest logs from a file on the container filesystem. While this is not recommended, there are still legacy or misconfigured apps that insist on logging to the local filesystem. @@ -117,24 +117,24 @@ ls -l tmp/ ### Project structure -* `charts/log-router`: Builds the Helm chart -* `base-image`: Builds a Fluentd 1.2.x image with a curated list of plugins -* `config-reloader`: Builds the daemon that generates fluentd configuration files +- `charts/log-router`: Builds the Helm chart +- `base-image`: Builds a Fluentd 1.2.x image with a curated list of plugins +- `config-reloader`: Builds the daemon that generates fluentd configuration files ### Config-reloader This is where interesting work happens. The [dependency graph](config-reloader/godepgraph.png) shows the high-level package interaction and general dataflow. -* `config`: handles startup configuration, reading and validation -* `datasource`: fetches Pods, Namespaces, ConfigMaps from Kubernetes -* `fluentd`: parses Fluentd config files into an object graph -* `processors`: walks this object graph doing validations and modifications. All features are implemented as chained `Processor` subtypes -* `generator`: serializes the processed object graph to the filesystem for Fluentd to read -* `controller`: orchestrates the high-level `datasource` -> `processor` -> `generator` pipeline. +- `config`: handles startup configuration, reading and validation +- `datasource`: fetches Pods, Namespaces, ConfigMaps from Kubernetes +- `fluentd`: parses Fluentd config files into an object graph +- `processors`: walks this object graph doing validations and modifications. All features are implemented as chained `Processor` subtypes +- `generator`: serializes the processed object graph to the filesystem for Fluentd to read +- `controller`: orchestrates the high-level `datasource` -> `processor` -> `generator` pipeline. ### How does it work -It works be rewriting the user-provided configuration. This is possible because *kube-fluentd-operator* knows about the kubernetes cluster, the current namespace and +It works be rewriting the user-provided configuration. This is possible because _kube-fluentd-operator_ knows about the kubernetes cluster, the current namespace and also has some sensible defaults built in. To get a quick idea what happens behind the scenes consider this configuration deployed in a namespace called `monitoring`: ```xml @@ -219,7 +219,7 @@ To give the illusion that every namespace runs a dedicated Fluentd the user-prov ### The admin namespace -Kube-fluentd-operator defines one namespace to be the *admin* namespace. By default this is set to `kube-system`. The *admin* namespace is treated differently. Its configuration is not processed further as it is assumed only the cluster admin can manipulate resources in this namespace. If you don't plan to use any of the advanced features described bellow, you can just route all logs from all namespaces using this snippet in the *admin* namespace: +Kube-fluentd-operator defines one namespace to be the _admin_ namespace. By default this is set to `kube-system`. The _admin_ namespace is treated differently. Its configuration is not processed further as it is assumed only the cluster admin can manipulate resources in this namespace. If you don't plan to use any of the advanced features described bellow, you can just route all logs from all namespaces using this snippet in the _admin_ namespace: ```xml @@ -228,17 +228,17 @@ Kube-fluentd-operator defines one namespace to be the *admin* namespace. By defa ``` -`**` in this context is not processed and it means *literally* everything. +`**` in this context is not processed and it means _literally_ everything. Fluentd assumes it is running in a distro with systemd and generates logs with these Fluentd tags: -* `systemd.{unit}`: the journal of a systemd unit, for example `systemd.docker.service` -* `docker`: all docker logs, not containers. If systemd is used, the docker logs are in `systemd.docker.service` -* `k8s.{component}`: logs from a K8S component, for example `k8s.kube-apiserver` -* `kube.{namespace}.{pod_name}.{container_name}`: a log originating from (namespace, pod, container) +- `systemd.{unit}`: the journal of a systemd unit, for example `systemd.docker.service` +- `docker`: all docker logs, not containers. If systemd is used, the docker logs are in `systemd.docker.service` +- `k8s.{component}`: logs from a K8S component, for example `k8s.kube-apiserver` +- `kube.{namespace}.{pod_name}.{container_name}`: a log originating from (namespace, pod, container) -As the *admin* namespace is processed first, a match-all directive would consume all logs and any other namespace configuration will become irrelevant (unless `` is used). -A recommended configuration for the *admin* namespace is this one (assuming it is set to `kube-system`) - it captures all but the user namespaces' logs: +As the _admin_ namespace is processed first, a match-all directive would consume all logs and any other namespace configuration will become irrelevant (unless `` is used). +A recommended configuration for the _admin_ namespace is this one (assuming it is set to `kube-system`) - it captures all but the user namespaces' logs: ```xml @@ -248,7 +248,7 @@ A recommended configuration for the *admin* namespace is this one (assuming it i ``` -Note the ` @@ -427,11 +427,11 @@ Most log streams are line-oriented. However, stacktraces always span multiple li Notice how `filter` is used instead of `match` as described in [fluent-plugin-detect-exceptions](https://github.com/GoogleCloudPlatform/fluent-plugin-detect-exceptions). Internally, this filter is translated into several `match` directives so that the end user doesn't need to bother with rewriting the Fluentd tag. -Also, users don't need to bother with setting the correct `stream` parameter. *kube-fluentd-operator* generates one internally based on the container id and the stream. +Also, users don't need to bother with setting the correct `stream` parameter. _kube-fluentd-operator_ generates one internally based on the container id and the stream. ### Reusing output plugin definitions (since v1.6.0) -Sometimes you only have a few valid options for log sinks: a dedicated S3 bucket, the ELK stack you manage, etc. The only flexibility you're after is letting namespace owners filter and parse their logs. In such cases you can abstract over an output plugin configuration - basically reducing it to a simple name which can be referenced from any namespace. For example, let's assume you have an S3 bucket for a "test" environment and you use logz.io for a "staging" environment. The first thing you do is define these two output in the *admin* namespace: +Sometimes you only have a few valid options for log sinks: a dedicated S3 bucket, the ELK stack you manage, etc. The only flexibility you're after is letting namespace owners filter and parse their logs. In such cases you can abstract over an output plugin configuration - basically reducing it to a simple name which can be referenced from any namespace. For example, let's assume you have an S3 bucket for a "test" environment and you use logz.io for a "staging" environment. The first thing you do is define these two output in the _admin_ namespace: ```xml admin-ns.conf: @@ -513,11 +513,11 @@ Logs that are emitted by this plugin can be consequently filtered and processed ``` -*kube-fluentd-operator* ensures that tags specified using the `$tag` macro never conflict with tags from other namespaces, even if the tag itself is equivalent. +_kube-fluentd-operator_ ensures that tags specified using the `$tag` macro never conflict with tags from other namespaces, even if the tag itself is equivalent. ### Sharing logs between namespaces -By default, you can consume logs only from your namespaces. Often it is useful for multiple namespaces (tenants) to get access to the logs streams of a shared resource (pod, namespace). *kube-fluentd-operator* makes it possible using two constructs: the source namespace expresses its intent to share logs with a destination namespace and the destination namespace expresses its desire to consume logs from a source. As a result logs are streamed only when both sides agree. +By default, you can consume logs only from your namespaces. Often it is useful for multiple namespaces (tenants) to get access to the logs streams of a shared resource (pod, namespace). _kube-fluentd-operator_ makes it possible using two constructs: the source namespace expresses its intent to share logs with a destination namespace and the destination namespace expresses its desire to consume logs from a source. As a result logs are streamed only when both sides agree. A source namespace can share with another namespace using the `@type share` macro: @@ -569,7 +569,7 @@ Every log event, be it from a pod, mounted-file or a systemd unit, will now carr "metadata": { "region": "us-east-1", "env": "staging", - "cluster": "legacy", + "cluster": "legacy" } } ``` @@ -578,29 +578,62 @@ All logs originating from a file look exactly as all other Kubernetes logs. Howe ```json { - "message": "Some message from the welcome-logger pod", - "stream": "/var/log/welcome.log", - "kubernetes": { - "container_name": "test-container", - "host": "ip-11-11-11-11.us-east-2.compute.internal", - "namespace_name": "kfo-test", - "pod_id": "723dd34a-4ac0-11e8-8a81-0a930dd884b0", - "pod_name": "welcome-logger", - "labels": { - "msg": "welcome", - "test-case": "b" - }, - "namespace_labels": {} + "message": "Some message from the welcome-logger pod", + "stream": "/var/log/welcome.log", + "kubernetes": { + "container_name": "test-container", + "host": "ip-11-11-11-11.us-east-2.compute.internal", + "namespace_name": "kfo-test", + "pod_id": "723dd34a-4ac0-11e8-8a81-0a930dd884b0", + "pod_name": "welcome-logger", + "labels": { + "msg": "welcome", + "test-case": "b" }, - "metadata": { - "region": "us-east-2", - "cluster": "legacy", - "env": "staging" - } + "namespace_labels": {} + }, + "metadata": { + "region": "us-east-2", + "cluster": "legacy", + "env": "staging" + } } ``` +### Go templting + +The `ConfigMap` holding the fluentd configuration can be templated using `go` templting, you can use this for example to get a value from another kubernetes resource, like a secret, for example: + +```yaml +kind: ConfigMap +apiVersion: v1 +metadata: + annotations: {} + name: fluentd-config + namespace: my-namespace +data: + fluent.conf: | + {{- $s := k8sLookup "Secret.v1" "my-namespace" "my-secret" -}} + + @type logzio_buffered + endpoint_url https://listener.logz.io:8071?token={{ $s.data.token }}&type=log-router + output_include_time true + output_include_tags false + http_idle_timeout 10 + + + @type file + path /var/log/my_namespace.log.buf + flush_thread_count 4 + flush_interval 10s + chunk_limit_size 16m + queue_limit_length 4096 + + +``` + ### Custom resource definition(CRD) support (since v1.13.0) + Custom resources are introduced from v1.13.0 release onwards. It allows to have a dedicated resource for fluentd configurations, which enables to manage them in a more consistent way and move away from the generic ConfigMaps. It is possible to create configs for a new application simply by attaching a FluentdConfig resource to the application manifests, rather than using a more generic ConfigMap with specific names and/or labels. @@ -622,71 +655,72 @@ spec: ``` + The "crd" has been introduced as a new datasource, configurable through the helm chart values, to allow users that are currently set up with ConfigMaps and do not want to perform the switchover to FluentdConfigs, to be able to keep on using them. The config-reloader has been equipped with the capability of installing the CRD at startup if requested, so no manual actions to enable it on the cluster are needed. The existing configurations though ConfigMaps can be migrated to CRDs through the following migration flow -* A new user, who is installing kube-fluentd-operator for the first time, should set the datasource: crd option in the chart. This enables the crd support -* A user who is already using kube-fluentd-operator with either datasource: default or datasource: multimap will have update to the new chart and set the 'crdMigrationMode' property to 'true'. This enables the config-reloader to launch with the crd datasource and the legacy datasource (either default or multimap depending on what was configured in the datasource property). The user can slowly migrate one by one all configmap resources to the corresponding fluentdconfig resources. When the migration is complete, the Helm release can be upgraded by changing the 'crdMigrationMode' property to 'false' and switching the datasource property to 'crd'. This will effectively disable the legacy datasource and set the config-reloader to only watch fluentdconfig resources. +- A new user, who is installing kube-fluentd-operator for the first time, should set the datasource: crd option in the chart. This enables the crd support +- A user who is already using kube-fluentd-operator with either datasource: default or datasource: multimap will have update to the new chart and set the 'crdMigrationMode' property to 'true'. This enables the config-reloader to launch with the crd datasource and the legacy datasource (either default or multimap depending on what was configured in the datasource property). The user can slowly migrate one by one all configmap resources to the corresponding fluentdconfig resources. When the migration is complete, the Helm release can be upgraded by changing the 'crdMigrationMode' property to 'false' and switching the datasource property to 'crd'. This will effectively disable the legacy datasource and set the config-reloader to only watch fluentdconfig resources. ## Tracking Fluentd version This projects tries to keep up with major releases for [Fluentd docker image](https://github.com/fluent/fluentd-docker-image/). -| Fluentd version | Operator version | -|----------------------------|-------------------------| -| 0.12.x | 1.0.0 | -| 1.15.3 | 1.17.1 | -| 1.16.1 | 1.17.6 | -| 1.16.1 | 1.18.0 | +| Fluentd version | Operator version | +| --------------- | ---------------- | +| 0.12.x | 1.0.0 | +| 1.15.3 | 1.17.1 | +| 1.16.1 | 1.17.6 | +| 1.16.1 | 1.18.0 | ## Plugins in latest release (1.18.0) `kube-fluentd-operator` aims to be easy to use and flexible. It also favors sending logs to multiple destinations using `` and as such comes with many plugins pre-installed: -* fluentd (1.16.1) -* fluent-plugin-amqp (0.14.0) -* fluent-plugin-azure-loganalytics (0.7.0) -* fluent-plugin-cloudwatch-logs (0.14.3) -* fluent-plugin-concat (2.5.0) -* fluent-plugin-datadog (0.14.2) -* fluent-plugin-elasticsearch (5.3.0) -* fluent-plugin-opensearch (1.1.0) -* fluent-plugin-gelf-hs (1.0.8) -* fluent-plugin-google-cloud (0.13.0) - forked to allow fluentd v1.14.x -* fluent-plugin-grafana-loki (1.2.20) -* fluent-plugin-grok-parser (2.6.2) -* fluent-plugin-json-in-json-2 (1.0.2) -* fluent-plugin-kafka (0.18.1) -* fluent-plugin-kinesis (3.4.2) -* fluent-plugin-kubernetes_metadata_filter (3.2.0) -* fluent-plugin-kubernetes_sumologic (2.4.2) -* fluent-plugin-kubernetes (0.3.1) -* fluent-plugin-logentries (0.2.10) -* fluent-plugin-logzio (0.0.22) -* fluent-plugin-mail (0.3.0) -* fluent-plugin-mongo (1.5.0) -* fluent-plugin-multi-format-parser (1.0.0) -* fluent-plugin-papertrail (0.2.8) -* fluent-plugin-prometheus (2.1.0) -* fluent-plugin-record-modifier (2.1.0) -* fluent-plugin-record-reformer (0.9.1) -* fluent-plugin-redis (0.3.5) -* fluent-plugin-remote_syslog (1.0.0) -* fluent-plugin-rewrite-tag-filter (2.4.0) -* fluent-plugin-route (1.0.0) -* fluent-plugin-s3 (1.7.2) -* fluent-plugin-splunk-hec (1.3.1) -* fluent-plugin-splunkhec (2.3) -* fluent-plugin-sumologic_output (1.7.3) -* fluent-plugin-systemd (1.0.5) -* fluent-plugin-uri-parser (0.3.0) -* fluent-plugin-verticajson (0.0.6) -* fluent-plugin-vmware-loginsight (1.4.1) -* fluent-plugin-vmware-log-intelligence (2.0.8) -* fluent-plugin-mysqlslowquery (0.0.9) -* fluent-plugin-throttle (0.0.5) -* fluent-plugin-webhdfs (1.5.0) -* fluent-plugin-detect-exceptions (0.0.15) +- fluentd (1.16.1) +- fluent-plugin-amqp (0.14.0) +- fluent-plugin-azure-loganalytics (0.7.0) +- fluent-plugin-cloudwatch-logs (0.14.3) +- fluent-plugin-concat (2.5.0) +- fluent-plugin-datadog (0.14.2) +- fluent-plugin-elasticsearch (5.3.0) +- fluent-plugin-opensearch (1.1.0) +- fluent-plugin-gelf-hs (1.0.8) +- fluent-plugin-google-cloud (0.13.0) - forked to allow fluentd v1.14.x +- fluent-plugin-grafana-loki (1.2.20) +- fluent-plugin-grok-parser (2.6.2) +- fluent-plugin-json-in-json-2 (1.0.2) +- fluent-plugin-kafka (0.18.1) +- fluent-plugin-kinesis (3.4.2) +- fluent-plugin-kubernetes_metadata_filter (3.2.0) +- fluent-plugin-kubernetes_sumologic (2.4.2) +- fluent-plugin-kubernetes (0.3.1) +- fluent-plugin-logentries (0.2.10) +- fluent-plugin-logzio (0.0.22) +- fluent-plugin-mail (0.3.0) +- fluent-plugin-mongo (1.5.0) +- fluent-plugin-multi-format-parser (1.0.0) +- fluent-plugin-papertrail (0.2.8) +- fluent-plugin-prometheus (2.1.0) +- fluent-plugin-record-modifier (2.1.0) +- fluent-plugin-record-reformer (0.9.1) +- fluent-plugin-redis (0.3.5) +- fluent-plugin-remote_syslog (1.0.0) +- fluent-plugin-rewrite-tag-filter (2.4.0) +- fluent-plugin-route (1.0.0) +- fluent-plugin-s3 (1.7.2) +- fluent-plugin-splunk-hec (1.3.1) +- fluent-plugin-splunkhec (2.3) +- fluent-plugin-sumologic_output (1.7.3) +- fluent-plugin-systemd (1.0.5) +- fluent-plugin-uri-parser (0.3.0) +- fluent-plugin-verticajson (0.0.6) +- fluent-plugin-vmware-loginsight (1.4.1) +- fluent-plugin-vmware-log-intelligence (2.0.8) +- fluent-plugin-mysqlslowquery (0.0.9) +- fluent-plugin-throttle (0.0.5) +- fluent-plugin-webhdfs (1.5.0) +- fluent-plugin-detect-exceptions (0.0.15) When customizing the image be careful not to uninstall plugins that are used internally to implement the macros. @@ -740,46 +774,46 @@ Flags: Path to fluentd binary used to validate configuration --prometheus-enabled Prometheus metrics enabled (default: false) --admin-namespace="kube-system" - The namespace to be treated as admin namespace + The namespace to be treated as admin namespace ``` ## Helm chart -| Parameter | Description | Default | -|------------------------------------------|-------------------------------------|---------------------------------------------------| -| `rbac.create` | Create a serviceaccount+role, use if K8s is using RBAC | `false` | -| `serviceAccountName` | Reuse an existing service account | `""` | -| `defaultConfigmap` | Read the configmap by this name if the namespace is not annotated | `"fluentd-config"` | -| `image.repositiry` | Repository | `vmware/kube-fluentd-operator` | -| `image.tag` | Image tag | `latest` | -| `image.pullPolicy` | Pull policy | `Always` | -| `image.pullSecret` | Optional pull secret name | `""` | -| `logLevel` | Default log level for config-reloader | `info` | -| `fluentdLogLevel` | Default log level for fluentd | `info` | -| `bufferMountFolder` | Folder in /var/log/{} where to create all fluentd buffers | `""` | -| `kubeletRoot` | The home dir of the kubelet, usually set using `--root-dir` on the kubelet | `/var/lib/kubelet` | -| `namespaces` | List of namespaces to operate on. Empty means all namespaces | `[]` | -| `interval` | How often to check for config changes (seconds) | `45` | -| `meta.key` | The metadata key (optional) | `""` | -| `meta.values` | Metadata to use for the key | `{}` -| `extraVolumes` | Extra volumes | | -| `fluentd.extraVolumeMounts` | Mount extra volumes for the fluentd container, required to mount ssl certificates when elasticsearch has tls enabled | | -| `fluentd.resources` | Resource definitions for the fluentd container | `{}`| -| `fluentd.extraEnv` | Extra env vars to pass to the fluentd container | `{}` | -| `reloader.extraVolumeMounts` | Mount extra volumes for the reloader container | | -| `reloader.resources` | Resource definitions for the reloader container | `{}` | -| `reloader.extraEnv` | Extra env vars to pass to the reloader container | `{}` | -| `tolerations` | Pod tolerations | `[]` | -| `updateStrategy` | UpdateStrategy for the daemonset. Leave empty to get the K8S' default (probably the safest choice) | `{}` | -| `podAnnotations` | Pod annotations for the daemonset | | -| `adminNamespace` | The namespace to be treated as admin namespace | `kube-system` | +| Parameter | Description | Default | +| ---------------------------- | -------------------------------------------------------------------------------------------------------------------- | ------------------------------ | +| `rbac.create` | Create a serviceaccount+role, use if K8s is using RBAC | `false` | +| `serviceAccountName` | Reuse an existing service account | `""` | +| `defaultConfigmap` | Read the configmap by this name if the namespace is not annotated | `"fluentd-config"` | +| `image.repositiry` | Repository | `vmware/kube-fluentd-operator` | +| `image.tag` | Image tag | `latest` | +| `image.pullPolicy` | Pull policy | `Always` | +| `image.pullSecret` | Optional pull secret name | `""` | +| `logLevel` | Default log level for config-reloader | `info` | +| `fluentdLogLevel` | Default log level for fluentd | `info` | +| `bufferMountFolder` | Folder in /var/log/{} where to create all fluentd buffers | `""` | +| `kubeletRoot` | The home dir of the kubelet, usually set using `--root-dir` on the kubelet | `/var/lib/kubelet` | +| `namespaces` | List of namespaces to operate on. Empty means all namespaces | `[]` | +| `interval` | How often to check for config changes (seconds) | `45` | +| `meta.key` | The metadata key (optional) | `""` | +| `meta.values` | Metadata to use for the key | `{}` | +| `extraVolumes` | Extra volumes | | +| `fluentd.extraVolumeMounts` | Mount extra volumes for the fluentd container, required to mount ssl certificates when elasticsearch has tls enabled | | +| `fluentd.resources` | Resource definitions for the fluentd container | `{}` | +| `fluentd.extraEnv` | Extra env vars to pass to the fluentd container | `{}` | +| `reloader.extraVolumeMounts` | Mount extra volumes for the reloader container | | +| `reloader.resources` | Resource definitions for the reloader container | `{}` | +| `reloader.extraEnv` | Extra env vars to pass to the reloader container | `{}` | +| `tolerations` | Pod tolerations | `[]` | +| `updateStrategy` | UpdateStrategy for the daemonset. Leave empty to get the K8S' default (probably the safest choice) | `{}` | +| `podAnnotations` | Pod annotations for the daemonset | | +| `adminNamespace` | The namespace to be treated as admin namespace | `kube-system` | ## Cookbook ### I want to use one destination for everything -Simple, define configuration only for the *admin* namespace (by default `kube-system`): +Simple, define configuration only for the _admin_ namespace (by default `kube-system`): ```bash kube-system.conf: @@ -790,7 +824,7 @@ kube-system.conf: ### I dont't care for systemd and docker logs -Simple, exclude them at the *admin* namespace level (by default `kube-system`): +Simple, exclude them at the _admin_ namespace level (by default `kube-system`): ```bash kube-system.conf: @@ -863,19 +897,20 @@ metadata: msg: hello spec: containers: - - image: ubuntu - name: greeter - command: - - bash - - -c - - while true; do echo `date -R` [INFO] "Random hello number $((var++)) to file"; sleep 2; [[ $(($var % 100)) == 0 ]] && :> /var/log/hello.log ;done > /var/log/hello.log - volumeMounts: - - mountPath: /var/log - name: logs + - image: ubuntu + name: greeter + command: + - bash + - -c + - while true; do echo `date -R` [INFO] "Random hello number $((var++)) to file"; sleep 2; [[ $(($var % 100)) == 0 ]] && :> /var/log/hello.log ;done > /var/log/hello.log + volumeMounts: + - mountPath: /var/log + name: logs volumes: - - name: logs - emptyDir: {} + - name: logs + emptyDir: {} ``` + To get the hello.log ingested by Fluentd you need at least this in the configuration for `kfo-test` namespace: ```xml @@ -932,11 +967,11 @@ The built-in `remote_syslog` plugin cannot be used as the fluentd tag may be lon To get the general idea how truncation works, consider this table: -| Original Tag | Truncated tag | -|-------------|----------------| -| `kube.demo.test.test` | `demo.test.test` | -| `kube.demo.nginx-65899c769f-5zj6d.nginx` | `demo.nginx-65899c769f-5zj*.nginx` | -| `kube.demo.test.nginx11111111._lablels.hello` | `demo.test.nginx11111111` | +| Original Tag | Truncated tag | +| --------------------------------------------- | ---------------------------------- | +| `kube.demo.test.test` | `demo.test.test` | +| `kube.demo.nginx-65899c769f-5zj6d.nginx` | `demo.nginx-65899c769f-5zj*.nginx` | +| `kube.demo.test.nginx11111111._lablels.hello` | `demo.test.nginx11111111` | ### I want to push logs to Humio @@ -998,7 +1033,6 @@ For details you should consult the plugin documentation. The container comes with a file validation command. To use it put all your \*.conf file in a directory. Use the namespace name for the filename. Then use this one-liner, bind-mounting the folder and feeding it as a `DATASOURCE_DIR` env var: - ```bash docker run --entrypoint=/bin/validate-from-dir.sh \ --net=host --rm \ @@ -1152,16 +1186,15 @@ Use `--annotation=acme.com/fancy-config` to use acme.com/fancy-config as annotat Currently space-delimited tags are not supported. For example, instead of ``, you need to use `` and ``. This limitation will be addressed in a later version. - ## Releases -* [CHANGELOG.md](CHANGELOG.md). +- [CHANGELOG.md](CHANGELOG.md). ## Resoures -* This plugin is used to provide kubernetes metadata https://github.com/fabric8io/fluent-plugin-kubernetes_metadata_filter -* This daemonset definition is used as a template: https://github.com/fluent/fluentd-kubernetes-daemonset/tree/master/docker-image/v0.12/debian-elasticsearch, however `kube-fluentd-operator` uses version 1.x version of fluentd and all the compatible plugin versions. -* This [Github issue](https://github.com/fabric8io/fluent-plugin-kubernetes_metadata_filter/issues/73) was the inspiration for the project. In particular it borrows the tag rewriting based on Kubernetes metadata to allow easier routing after that. +- This plugin is used to provide kubernetes metadata https://github.com/fabric8io/fluent-plugin-kubernetes_metadata_filter +- This daemonset definition is used as a template: https://github.com/fluent/fluentd-kubernetes-daemonset/tree/master/docker-image/v0.12/debian-elasticsearch, however `kube-fluentd-operator` uses version 1.x version of fluentd and all the compatible plugin versions. +- This [Github issue](https://github.com/fabric8io/fluent-plugin-kubernetes_metadata_filter/issues/73) was the inspiration for the project. In particular it borrows the tag rewriting based on Kubernetes metadata to allow easier routing after that. ## Contributing diff --git a/config-reloader/datasource/fake.go b/config-reloader/datasource/fake.go index 90f58238..0efc5631 100644 --- a/config-reloader/datasource/fake.go +++ b/config-reloader/datasource/fake.go @@ -11,7 +11,7 @@ import ( "github.com/sirupsen/logrus" ) -var template = ` +var logzTemplate = ` @type logzio_buffered endpoint_url https://listener.logz.io:8071?token=secret @@ -36,7 +36,7 @@ func NewFakeDatasource(ctx context.Context) Datasource { } func makeFakeConfig(namespace string) string { - contents := template + contents := logzTemplate contents = strings.ReplaceAll(contents, "$ns$", namespace) contents = strings.ReplaceAll(contents, "$ts$", time.Now().String()) diff --git a/config-reloader/datasource/kube_informer.go b/config-reloader/datasource/kube_informer.go index e9add757..d543bf19 100644 --- a/config-reloader/datasource/kube_informer.go +++ b/config-reloader/datasource/kube_informer.go @@ -9,6 +9,7 @@ import ( "time" "github.com/vmware/kube-fluentd-operator/config-reloader/fluentd" + "github.com/vmware/kube-fluentd-operator/config-reloader/template" "github.com/vmware/kube-fluentd-operator/config-reloader/util" "k8s.io/apimachinery/pkg/api/errors" @@ -107,7 +108,7 @@ func NewKubernetesInformerDatasource(ctx context.Context, cfg *config.Config, up factory.Core().V1().Pods().Informer().HasSynced, factory.Core().V1().ConfigMaps().Informer().HasSynced, kubeds.IsReady) { - return nil, fmt.Errorf("Failed to sync local informer with upstream Kubernetes API") + return nil, fmt.Errorf("failed to sync local informer with upstream Kubernetes API") } logrus.Infof("Synced local informer with upstream Kubernetes API") @@ -301,7 +302,7 @@ func (d *kubeInformerConnection) discoverNamespaces(ctx context.Context) ([]stri // Find the configmaps that exist on this cluster to find namespaces: confMapsList, err := d.cmlist.List(labels.NewSelector()) if err != nil { - return nil, fmt.Errorf("Failed to list all configmaps in cluster: %v", err) + return nil, fmt.Errorf("failed to list all configmaps in cluster: %v", err) } // If default configmap name is defined get all namespaces for those configmaps: if d.cfg.DefaultConfigmapName != "" { @@ -328,11 +329,11 @@ func (d *kubeInformerConnection) discoverNamespaces(ctx context.Context) ([]stri // get all namespaces and iterrate through them like before: nses, err := d.nslist.List(labels.NewSelector()) if err != nil { - return nil, fmt.Errorf("Failed to list all namespaces in cluster: %v", err) + return nil, fmt.Errorf("failed to list all namespaces in cluster: %v", err) } namespaces = make([]string, 0) for _, ns := range nses { - namespaces = append(namespaces, ns.ObjectMeta.Name) + namespaces = append(namespaces, ns.Name) } } } @@ -355,6 +356,10 @@ func (d *kubeInformerConnection) handlePodChange(ctx context.Context, obj interf mObj := obj.(*core.Pod) logrus.Tracef("Detected pod change %s in namespace: %s", mObj.GetName(), mObj.GetNamespace()) configdata, err := d.kubeds.GetFluentdConfig(ctx, mObj.GetNamespace()) + buf := new(strings.Builder) + if err := template.Render(buf, configdata, nil); err == nil { + configdata = buf.String() + } nsConfigStr := fmt.Sprintf("%#v", configdata) if err == nil { @@ -386,11 +391,11 @@ func matchAny(contLabels map[string]string, mountedLabelsInNs []map[string]strin func (d *kubeInformerConnection) discoverFluentdConfigNamespaces() ([]string, error) { if d.fdlist == nil { - return nil, fmt.Errorf("Failed to initialize the fluentdconfig crd client, d.fclient = nil") + return nil, fmt.Errorf("failed to initialize the fluentdconfig crd client, d.fclient = nil") } fcList, err := d.fdlist.List(labels.NewSelector()) if err != nil { - return nil, fmt.Errorf("Failed to list all fluentdconfig crds in cluster: %v", err) + return nil, fmt.Errorf("failed to list all fluentdconfig crds in cluster: %v", err) } nsList := make([]string, 0) for _, crd := range fcList { diff --git a/config-reloader/datasource/kubedatasource/configmap.go b/config-reloader/datasource/kubedatasource/configmap.go index f7df0d00..86cd8819 100644 --- a/config-reloader/datasource/kubedatasource/configmap.go +++ b/config-reloader/datasource/kubedatasource/configmap.go @@ -97,7 +97,7 @@ func (c *ConfigMapDS) fetchConfigMaps(ctx context.Context, ns string) ([]*core.C // Get all configmaps which match a specified label, but only if we have a selector mapslist, err := nsmaps.List(c.cfg.ParsedLabelSelector.AsSelector()) if err != nil { - return nil, fmt.Errorf("Failed to list configmaps in namespace '%s': %v", ns, err) + return nil, fmt.Errorf("failed to list configmaps in namespace '%s': %v", ns, err) } confMapByName := make(map[string]*core.ConfigMap) sortedConfMaps := make([]string, 0, len(mapslist)) diff --git a/config-reloader/fluentd/validator.go b/config-reloader/fluentd/validator.go index 6b503bcc..91beccbc 100644 --- a/config-reloader/fluentd/validator.go +++ b/config-reloader/fluentd/validator.go @@ -7,7 +7,6 @@ import ( "context" "errors" "fmt" - "io/ioutil" "os" "strings" "time" @@ -54,7 +53,7 @@ func (v *validatorState) ValidateConfigExtremely(config string, namespace string return nil } - tmpfile, err := ioutil.TempFile("", "validate-ext-"+namespace) + tmpfile, err := os.CreateTemp("", "validate-ext-"+namespace) if err != nil { logrus.Errorf("error creating temporary file for namespace %s: %s", namespace, err.Error()) return err @@ -98,7 +97,7 @@ func (v *validatorState) ValidateConfig(config string, namespace string) error { return nil } - tmpfile, err := ioutil.TempFile("", "validate-"+namespace) + tmpfile, err := os.CreateTemp("", "validate-"+namespace) if err != nil { logrus.Errorf("error creating temporary file for namespace %s: %s", namespace, err.Error()) return err diff --git a/config-reloader/generator/generator.go b/config-reloader/generator/generator.go index adee271a..230ebf5b 100644 --- a/config-reloader/generator/generator.go +++ b/config-reloader/generator/generator.go @@ -10,7 +10,6 @@ import ( "path" "path/filepath" "strings" - "text/template" "time" "github.com/vmware/kube-fluentd-operator/config-reloader/config" @@ -18,6 +17,7 @@ import ( "github.com/vmware/kube-fluentd-operator/config-reloader/fluentd" "github.com/vmware/kube-fluentd-operator/config-reloader/metrics" "github.com/vmware/kube-fluentd-operator/config-reloader/processors" + "github.com/vmware/kube-fluentd-operator/config-reloader/template" "github.com/vmware/kube-fluentd-operator/config-reloader/util" "github.com/sirupsen/logrus" diff --git a/config-reloader/go.mod b/config-reloader/go.mod index 3f49cb16..8d2bdd82 100644 --- a/config-reloader/go.mod +++ b/config-reloader/go.mod @@ -3,6 +3,7 @@ module github.com/vmware/kube-fluentd-operator/config-reloader go 1.19 require ( + github.com/Masterminds/sprig/v3 v3.2.3 github.com/alecthomas/kingpin v2.2.6+incompatible github.com/prometheus/client_golang v1.15.1 github.com/sirupsen/logrus v1.9.0 @@ -11,9 +12,13 @@ require ( k8s.io/apiextensions-apiserver v0.27.0 k8s.io/apimachinery v0.27.0 k8s.io/client-go v0.27.0 + sigs.k8s.io/controller-runtime v0.14.2 + sigs.k8s.io/yaml v1.3.0 ) require ( + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.2.0 // indirect github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -21,6 +26,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect + github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.1 // indirect @@ -31,11 +37,14 @@ require ( github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.1.0 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/imdario/mergo v0.3.6 // indirect + github.com/huandu/xstrings v1.3.3 // indirect + github.com/imdario/mergo v0.3.11 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mitchellh/copystructure v1.0.0 // indirect + github.com/mitchellh/reflectwalk v1.0.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -44,13 +53,16 @@ require ( github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect + github.com/shopspring/decimal v1.2.0 // indirect + github.com/spf13/cast v1.3.1 // indirect github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/crypto v0.14.0 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.8.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/term v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect - golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect + golang.org/x/time v0.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect @@ -61,5 +73,4 @@ require ( k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/config-reloader/go.sum b/config-reloader/go.sum index 07581353..067b6891 100644 --- a/config-reloader/go.sum +++ b/config-reloader/go.sum @@ -1,5 +1,11 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= +github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/alecthomas/kingpin v2.2.6+incompatible h1:5svnBTFgJjZvGKyYBtMB0+m5wvrbUHiqye8wRJMlnYI= github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= @@ -23,9 +29,13 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= +github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= @@ -63,10 +73,14 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= -github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= +github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -84,6 +98,10 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -93,6 +111,7 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.9.1 h1:zie5Ly042PD3bsCvsSOPvRnFwyo3rKe64TJlD6nu0mk= github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -107,14 +126,19 @@ github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= @@ -126,15 +150,24 @@ github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -144,6 +177,9 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -155,22 +191,33 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= -golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -179,11 +226,13 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= @@ -215,6 +264,7 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -237,6 +287,8 @@ k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a h1:gmovKNur38vgoWfGtP5QOG k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a/go.mod h1:y5VtZWM9sHHc2ZodIH/6SHzXj+TPU5USoA8lcIeKEKY= k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY= k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.14.2 h1:P6IwDhbsRWsBClt/8/h8Zy36bCuGuW5Op7MHpFrN/60= +sigs.k8s.io/controller-runtime v0.14.2/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= diff --git a/config-reloader/processors/labels.go b/config-reloader/processors/labels.go index 02bc6441..e2eee35d 100644 --- a/config-reloader/processors/labels.go +++ b/config-reloader/processors/labels.go @@ -6,12 +6,11 @@ package processors import ( "bytes" "fmt" - "reflect" "regexp" "strings" - "text/template" "github.com/vmware/kube-fluentd-operator/config-reloader/fluentd" + "github.com/vmware/kube-fluentd-operator/config-reloader/template" "github.com/vmware/kube-fluentd-operator/config-reloader/util" ) @@ -27,19 +26,13 @@ var reSafe = regexp.MustCompile(`[.-]|^$`) // an alphanumeric character (e.g. 'MyValue', or 'my_value', or '12345', regex used for validation is // '(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?' -var fns = template.FuncMap{ - "last": func(x int, a interface{}) bool { - return x == reflect.ValueOf(a).Len()-1 - }, -} - -var retagTemplate = template.Must(template.New("retagTemplate").Funcs(fns).Parse( +var retagTemplate = template.Must(template.New("retagTemplate").Parse( ` @type record_transformer enable_ruby true - kubernetes_pod_label_values {{range $i, $e := .Labels -}}${record.dig('kubernetes','labels','{{$e}}')&.gsub(/[.-]/, '_') || '_'}{{if last $i $.Labels }}{{else}}.{{end}}{{- end}} + kubernetes_pod_label_values {{range $i, $e := .Labels -}}${record.dig('kubernetes','labels','{{$e}}')&.gsub(/[.-]/, '_') || '_'}{{if isLast $i $.Labels }}{{else}}.{{end}}{{- end}} diff --git a/config-reloader/processors/share.go b/config-reloader/processors/share.go index 1c164f9a..cb782258 100644 --- a/config-reloader/processors/share.go +++ b/config-reloader/processors/share.go @@ -7,9 +7,9 @@ import ( "bytes" "fmt" "strings" - "text/template" "github.com/vmware/kube-fluentd-operator/config-reloader/fluentd" + "github.com/vmware/kube-fluentd-operator/config-reloader/template" "github.com/vmware/kube-fluentd-operator/config-reloader/util" ) diff --git a/config-reloader/template/template.go b/config-reloader/template/template.go new file mode 100644 index 00000000..ddd51d5c --- /dev/null +++ b/config-reloader/template/template.go @@ -0,0 +1,147 @@ +package template + +import ( + "context" + "fmt" + "io" + "os" + "reflect" + "strings" + "text/template" + + "github.com/Masterminds/sprig/v3" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/client" + konfig "sigs.k8s.io/controller-runtime/pkg/client/config" + "sigs.k8s.io/yaml" +) + +var k8sClient client.Reader + +// Render is a go template rendering function it includes all the sprig lib functions +// as well as some extras like a k8sLookup function to get values from k8s objects +// you can access environment variables from the template under .Env +// The passed values will be available under .Values in the templates +func Render(out io.Writer, tmpl string, values interface{}) error { + t, err := New("tmpl").Parse(tmpl) + if err != nil { + return err + } + return t.Execute(out, map[string]interface{}{ + "Env": envMap(), + "Values": values, + }) +} + +func Must(t *template.Template, err error) *template.Template { + if err != nil { + panic(err) + } + return t +} + +func New(name string) *template.Template { + tpl := template.New(name) + funcMap := sprig.TxtFuncMap() + funcMap["isLast"] = func(x int, a interface{}) bool { + return x == reflect.ValueOf(a).Len()-1 + } + funcMap["include"] = func(name string, data interface{}) (string, error) { + buf := new(strings.Builder) + if err := tpl.ExecuteTemplate(buf, name, data); err != nil { + return "", err + } + return buf.String(), nil + } + funcMap["tpl"] = func(tmpl string, data interface{}) (string, error) { + t, err := template.New("").Parse(tmpl) + if err != nil { + return "", err + } + buf := new(strings.Builder) + if err := t.Execute(buf, data); err != nil { + return "", err + } + return buf.String(), nil + } + funcMap["toYaml"] = toYaml + funcMap["fromYaml"] = fromYaml + funcMap["k8sLookup"] = k8sLookup + return tpl.Funcs(funcMap).Delims("{{", "}}") +} + +func toYaml(v interface{}) (string, error) { + b, err := yaml.Marshal(v) + if err != nil { + return "", err + } + return strings.TrimSuffix(string(b), "\n"), nil +} + +func fromYaml(str string) (map[string]interface{}, error) { + m := map[string]interface{}{} + err := yaml.Unmarshal([]byte(str), &m) + return m, err +} + +func k8sLookup(kind, namespace, name string) (map[string]interface{}, error) { + if k8sClient == nil { + kfg, err := konfig.GetConfig() + if err != nil { + return nil, err + } + if c, err := client.New(kfg, client.Options{}); err != nil { + return nil, fmt.Errorf("failed to create client: %w", err) + } else { + k8sClient = c + } + } + gvk, gk := schema.ParseKindArg(kind) + if gvk == nil { + // this looks strange but it should make sense if you read the ParseKindArg docs + gvk = &schema.GroupVersionKind{ + Kind: gk.Kind, + Version: gk.Group, + } + } + if name != "" { + // fetching a single resource by name + u := unstructured.Unstructured{} + u.SetGroupVersionKind(schema.GroupVersionKind{ + Group: gvk.Group, + Kind: gvk.Kind, + Version: gvk.Version, + }) + if err := k8sClient.Get(context.Background(), client.ObjectKey{ + Namespace: namespace, + Name: name, + }, &u); err != nil { + return nil, fmt.Errorf("failed to get: %w", err) + } + return u.UnstructuredContent(), nil + } + ul := &unstructured.UnstructuredList{} + ul.SetGroupVersionKind(schema.GroupVersionKind{ + Group: gvk.Group, + Version: gvk.Version, + Kind: gvk.Kind + "List", // TODO: is there a better way? + }) + opts := &client.ListOptions{ + Namespace: namespace, + } + if err := k8sClient.List(context.Background(), ul, opts); err != nil { + return nil, fmt.Errorf("failed to list: %w", err) + } + return ul.UnstructuredContent(), nil +} + +func envMap() map[string]string { + envMap := make(map[string]string) + + for _, v := range os.Environ() { + kv := strings.SplitN(v, "=", 2) + envMap[kv[0]] = kv[1] + } + return envMap +} diff --git a/config-reloader/template/template_test.go b/config-reloader/template/template_test.go new file mode 100644 index 00000000..c2dc7f2c --- /dev/null +++ b/config-reloader/template/template_test.go @@ -0,0 +1,97 @@ +package template + +import ( + "os" + "strings" + "testing" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +func TestRender(t *testing.T) { + tests := []struct { + name string + template string + data interface{} + expected string + }{ + { + name: "use env", + template: `var FOO is {{ .Env.FOO }}`, + expected: "var FOO is BAR", + }, + { + name: "use env and values", + template: `var FOO is {{ .Env.FOO }} +value BAR is {{ .Values.BAR }}`, + expected: "var FOO is BAR\nvalue BAR is FOO", + data: map[string]string{ + "BAR": "FOO", + }, + }, + { + name: "lookup ConfigMap", + template: ` +{{- $cm := k8sLookup "ConfigMap.v1" "default" "my-config-map" -}} +{{- $cfg := index $cm.data "conf.yaml" | fromYaml -}} +key1 is {{ $cm.data.key1 }} +foobar key is {{ $cfg.foobar.key }}`, + expected: "key1 is val1\nfoobar key is val", + }, + { + name: "to yaml", + data: struct { + Foo string + Bar string + Foobar map[string]string + }{ + Foo: "bar", + Bar: "foo", + Foobar: map[string]string{ + "Key": "val", + }, + }, + template: `{{ toYaml .Values }}`, + expected: "Bar: foo\nFoo: bar\nFoobar:\n Key: val", + }, + { + name: "static", + template: "static string", + expected: "static string", + }, + } + + os.Setenv("FOO", "BAR") + jcm := `{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "my-config-map", + "namespace": "default" + }, + "data": { + "key1": "val1", + "conf.yaml": "foo: bar\nbar: foo\nfoobar:\n key: val\n" + } +}` + + cm, _, err := unstructured.UnstructuredJSONScheme.Decode([]byte(jcm), nil, nil) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + k8sClient = fake.NewClientBuilder().WithObjects(cm.(*unstructured.Unstructured)).Build() + buf := new(strings.Builder) + err := Render(buf, tt.template, tt.data) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if buf.String() != tt.expected { + t.Errorf("unexpected result\n\twanted: %s\n\tgot: %s", tt.expected, buf.String()) + } + }) + } +} diff --git a/config-reloader/util/util.go b/config-reloader/util/util.go index fe606f9b..6e20a5e6 100644 --- a/config-reloader/util/util.go +++ b/config-reloader/util/util.go @@ -9,7 +9,6 @@ import ( "encoding/hex" "errors" "fmt" - "io/ioutil" "os" "os/exec" "regexp" @@ -122,7 +121,7 @@ func ExecAndGetOutput(cmd string, timeout time.Duration, args ...string) (string } func WriteStringToFile(filename string, data string) error { - return ioutil.WriteFile(filename, []byte(data), maskFile) + return os.WriteFile(filename, []byte(data), maskFile) } func TrimTrailingComment(line string) string {