diff --git a/axosyslog.yaml b/axosyslog.yaml new file mode 100644 index 000000000..f487edc85 --- /dev/null +++ b/axosyslog.yaml @@ -0,0 +1,13 @@ +apiVersion: logging.banzaicloud.io/v1beta1 +kind: AxoSyslog +metadata: + name: axosyslog + namespace: axosyslog +spec: + logPaths: + - filterx: "1==1;" + destination: "axosyslog-test" + destinations: + - name: "axosyslog-test" + config: | + file ("/tmp/log/axosyslog-test.log"); diff --git a/charts/logging-operator/charts/logging-operator-crds/templates/logging.banzaicloud.io_axosyslogs.yaml b/charts/logging-operator/charts/logging-operator-crds/templates/logging.banzaicloud.io_axosyslogs.yaml index 2ee54ae4a..7a409b99a 100644 --- a/charts/logging-operator/charts/logging-operator-crds/templates/logging.banzaicloud.io_axosyslogs.yaml +++ b/charts/logging-operator/charts/logging-operator-crds/templates/logging.banzaicloud.io_axosyslogs.yaml @@ -36,6 +36,13 @@ spec: type: object spec: properties: + configReloadImage: + properties: + repository: + type: string + tag: + type: string + type: object destinations: items: properties: @@ -45,6 +52,13 @@ spec: type: string type: object type: array + image: + properties: + repository: + type: string + tag: + type: string + type: object logPaths: items: properties: diff --git a/charts/logging-operator/crds/logging.banzaicloud.io_axosyslogs.yaml b/charts/logging-operator/crds/logging.banzaicloud.io_axosyslogs.yaml index eb25dc90d..2fda8676f 100644 --- a/charts/logging-operator/crds/logging.banzaicloud.io_axosyslogs.yaml +++ b/charts/logging-operator/crds/logging.banzaicloud.io_axosyslogs.yaml @@ -33,6 +33,13 @@ spec: type: object spec: properties: + configReloadImage: + properties: + repository: + type: string + tag: + type: string + type: object destinations: items: properties: @@ -42,6 +49,13 @@ spec: type: string type: object type: array + image: + properties: + repository: + type: string + tag: + type: string + type: object logPaths: items: properties: diff --git a/config/crd/bases/logging.banzaicloud.io_axosyslogs.yaml b/config/crd/bases/logging.banzaicloud.io_axosyslogs.yaml index eb25dc90d..2fda8676f 100644 --- a/config/crd/bases/logging.banzaicloud.io_axosyslogs.yaml +++ b/config/crd/bases/logging.banzaicloud.io_axosyslogs.yaml @@ -33,6 +33,13 @@ spec: type: object spec: properties: + configReloadImage: + properties: + repository: + type: string + tag: + type: string + type: object destinations: items: properties: @@ -42,6 +49,13 @@ spec: type: string type: object type: array + image: + properties: + repository: + type: string + tag: + type: string + type: object logPaths: items: properties: diff --git a/controllers/logging/axosyslog_controller.go b/controllers/logging/axosyslog_controller.go index eeb96ab40..5c7ef1dae 100644 --- a/controllers/logging/axosyslog_controller.go +++ b/controllers/logging/axosyslog_controller.go @@ -16,55 +16,125 @@ package controllers import ( "context" + "fmt" + "reflect" + "runtime" + "emperror.dev/errors" + "github.com/cisco-open/operator-tools/pkg/reconciler" "github.com/go-logr/logr" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/kube-logging/logging-operator/pkg/sdk/logging/api/v1beta1" + "github.com/kube-logging/logging-operator/pkg/resources" + axosyslogresources "github.com/kube-logging/logging-operator/pkg/resources/axosyslog" + v1beta1 "github.com/kube-logging/logging-operator/pkg/sdk/logging/api/v1beta1" ) // NewAxoSyslogReconciler creates a new AxoSyslogReconciler instance -func NewAxoSyslogReconciler(client client.Client, log logr.Logger) *AxoSyslogReconciler { +func NewAxoSyslogReconciler(client client.Client, log logr.Logger, opts reconciler.ReconcilerOpts) *AxoSyslogReconciler { return &AxoSyslogReconciler{ - Client: client, - Log: log, + Client: client, + GenericResourceReconciler: reconciler.NewGenericReconciler(client, log, opts), + Log: log, } } type AxoSyslogReconciler struct { client.Client + *reconciler.GenericResourceReconciler Log logr.Logger } // +kubebuilder:rbac:groups=logging.banzaicloud.io,resources=axosyslogs,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=logging.banzaicloud.io,resources=axosyslogs/status,verbs=get;update;patch -// +kubebuilder:rbac:groups="",resources=configmaps;secrets,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=apps,resources=statefulsets,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups="",resources=services;persistentvolumeclaims;serviceaccounts;pods,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=clusterroles;clusterrolebindings;roles;rolebindings,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=roles;rolebindings,verbs=get;list;watch;create;update;patch;delete -// Reconciles axosyslog resource +// Reconcile implements the reconciliation logic for AxoSyslog resources func (r *AxoSyslogReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - log := r.Log.WithValues("axosyslog", req.NamespacedName) + r.Log.V(1).Info("Reconciling AxoSyslog") - log.V(1).Info("Reconciling AxoSyslog") - - var axosyslog v1beta1.AxoSyslog - if err := r.Get(ctx, req.NamespacedName, &axosyslog); err != nil { + var axoSyslog v1beta1.AxoSyslog + if err := r.Get(ctx, req.NamespacedName, &axoSyslog); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) } - if err := axosyslog.SetDefaults(); err != nil { + log := r.Log.WithValues("axosyslog", fmt.Sprintf("%s/%s", axoSyslog.Namespace, axoSyslog.Name)) + + if err := axoSyslog.SetDefaults(); err != nil { + return ctrl.Result{}, errors.WrapIf(err, "failed to set defaults") + } + + // TODO: config-check ? + + if result, err := r.reconcileWorkloadResources(log, &axoSyslog); err != nil { return ctrl.Result{}, err + } else if result != nil { + return *result, nil } return ctrl.Result{}, nil } +// reconcileWorkloadResources handles resources related to AxoSyslog and requires it's spec +func (r *AxoSyslogReconciler) reconcileWorkloadResources(log logr.Logger, axoSyslog *v1beta1.AxoSyslog) (*ctrl.Result, error) { + resourceBuilders := []resources.ResourceWithSpec{ + axosyslogresources.CreateAxoSyslogConfig, + axosyslogresources.StatefulSet, + axosyslogresources.Service, + axosyslogresources.HeadlessService, + // TODO: service-metrics & buffer-metrics ? + // axosyslogresources.ServiceMetrics, + // axosyslogresources.ServiceBufferMetrics, + } + + for _, buildObject := range resourceBuilders { + builderName := getFunctionName(buildObject) + log.V(2).Info("Processing resource", "builder", builderName) + + o, state, err := buildObject(axoSyslog) + if err != nil { + return nil, errors.WrapIff(err, "failed to build object with %s", builderName) + } + if o == nil { + return nil, errors.Errorf("reconcile error: %s returned nil object", builderName) + } + + metaObj, ok := o.(metav1.Object) + if !ok { + return nil, errors.Errorf("reconcile error: %s returned non-metav1.Object", builderName) + } + + if metaObj.GetNamespace() == "" { + return nil, errors.Errorf("reconcile error: %s returned resource without namespace set", builderName) + } + + if err := ctrl.SetControllerReference(axoSyslog, metaObj, r.Scheme()); err != nil { + return nil, errors.WrapIff(err, "failed to set controller reference for %s", metaObj.GetName()) + } + + result, err := r.ReconcileResource(o, state) + if err != nil { + return nil, errors.WrapIff(err, "failed to reconcile resource %s/%s", metaObj.GetNamespace(), metaObj.GetName()) + } + if result != nil { + return result, nil + } + } + + return nil, nil +} + func SetupAxoSyslogWithManager(mgr ctrl.Manager, logger logr.Logger) error { return ctrl.NewControllerManagedBy(mgr). For(&v1beta1.AxoSyslog{}). - Named("AxoSyslogController"). - Complete(NewAxoSyslogReconciler(mgr.GetClient(), logger)) + Named("axosyslog"). + Complete(NewAxoSyslogReconciler(mgr.GetClient(), logger, reconciler.ReconcilerOpts{})) +} + +func getFunctionName(i any) string { + return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name() } diff --git a/docs/configuration/crds/v1beta1/axosyslog_types.md b/docs/configuration/crds/v1beta1/axosyslog_types.md index 886b398cb..7f13e3be6 100644 --- a/docs/configuration/crds/v1beta1/axosyslog_types.md +++ b/docs/configuration/crds/v1beta1/axosyslog_types.md @@ -19,11 +19,21 @@ AxoSyslog is the Schema for the AxoSyslogs API AxoSyslogSpec defines the desired state of AxoSyslog +### configReloadImage (*BasicImageSpec, optional) {#axosyslogspec-configreloadimage} + +ConfigReloadImage is the image specification for the config reload + + ### destinations ([]Destination, optional) {#axosyslogspec-destinations} Destinations is a list of destinations to be rendered in the AxoSyslog configuration +### image (*BasicImageSpec, optional) {#axosyslogspec-image} + +Image is the image specification for AxoSyslog + + ### logPaths ([]LogPath, optional) {#axosyslogspec-logpaths} LogPaths is a list of log paths to be rendered in the AxoSyslog configuration diff --git a/go.mod b/go.mod index 88aca8a4e..19c9ff458 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.24.1 require ( emperror.dev/errors v0.8.1 github.com/MakeNowJust/heredoc v1.0.0 + github.com/Masterminds/sprig/v3 v3.3.0 github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 github.com/cisco-open/operator-tools v0.37.0 github.com/go-logr/logr v1.4.2 @@ -31,6 +32,7 @@ require ( require ( cloud.google.com/go/compute/metadata v0.6.0 // indirect dario.cat/mergo v1.0.1 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.3.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/briandowns/spinner v1.23.2 // indirect @@ -58,13 +60,16 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/hashicorp/go-version v1.7.0 // indirect + github.com/huandu/xstrings v1.5.0 // indirect github.com/iancoleman/orderedmap v0.3.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // 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 @@ -77,6 +82,7 @@ require ( github.com/prometheus/procfs v0.16.1 // indirect github.com/prometheus/prometheus v1.8.2-0.20210621150501-ff58416a0b02 // indirect github.com/sergi/go-diff v1.3.1 // indirect + github.com/shopspring/decimal v1.4.0 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/wayneashleyberry/terminal-dimensions v1.1.0 // indirect github.com/x448/float16 v0.8.4 // indirect @@ -106,6 +112,7 @@ require ( go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect + golang.org/x/crypto v0.37.0 // indirect golang.org/x/net v0.39.0 // indirect golang.org/x/oauth2 v0.29.0 // indirect golang.org/x/sync v0.13.0 // indirect diff --git a/go.sum b/go.sum index 07235ce47..181c6920b 100644 --- a/go.sum +++ b/go.sum @@ -107,10 +107,14 @@ github.com/HdrHistogram/hdrhistogram-go v1.0.1/go.mod h1:BWJ+nMSHY3L41Zj7CA3uXnl github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +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 v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/sprig v2.16.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= @@ -667,6 +671,8 @@ github.com/hetznercloud/hcloud-go v1.26.2 h1:fI8BXAGJI4EFeCDd2a/I4EhqyK32cDdxGeW github.com/hetznercloud/hcloud-go v1.26.2/go.mod h1:2C5uMtBiMoFr3m7lBFPf7wXTdh33CevmZpQIIDPGYJI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= @@ -800,6 +806,8 @@ github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXb github.com/mileusna/useragent v0.0.0-20190129205925-3e331f0949a5/go.mod h1:JWhYAp2EXqUtsxTKdeGlY8Wp44M7VxThC9FEoNGi2IE= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -816,6 +824,8 @@ github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= @@ -999,6 +1009,8 @@ github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfP github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= diff --git a/pkg/resources/axosyslog/config.go b/pkg/resources/axosyslog/config.go new file mode 100644 index 000000000..4300f7d4e --- /dev/null +++ b/pkg/resources/axosyslog/config.go @@ -0,0 +1,119 @@ +// Copyright © 2025 Kube logging authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package axosyslog + +import ( + "bytes" + "fmt" + "text/template" + + "github.com/MakeNowJust/heredoc" + "github.com/Masterminds/sprig/v3" + "github.com/cisco-open/operator-tools/pkg/reconciler" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + + "github.com/kube-logging/logging-operator/pkg/sdk/logging/api/v1beta1" +) + +const ( + axoSyslogConfigName = "axosyslog-config" + axoSyslogConfigKey = "axosyslog.conf" +) + +func CreateAxoSyslogConfig(object any) (runtime.Object, reconciler.DesiredState, error) { + axoSyslog, ok := object.(*v1beta1.AxoSyslog) + if !ok { + return nil, reconciler.StateAbsent, fmt.Errorf("expected *v1beta1.AxoSyslog, got %T", axoSyslog) + } + + tmpl, err := template.New("AxoSyslogConfig").Funcs(sprig.FuncMap()).Parse(heredoc.Doc(` + @version: current + @include "scl.conf" + + options { + stats(level(2) freq(0)); + keep-hostname(yes); + keep-timestamp(yes); + log-msg-size(64KiB); + trim-large-messages(yes); + time-reopen(1); + dns-cache(yes); + log-level("default"); + use-uniqid(yes); + create-dirs(yes); + }; + + source "axosyslog-otlp" { + channel { + source { opentelemetry(port(4317) log-iw-size(1000000) log-fetch-limit(100000) workers(10) ` + "`__VARARGS__`" + `); }; + if { + filterx { + declare resource = otel_resource(${.otel_raw.resource}); + declare scope = otel_scope(${.otel_raw.scope}); + declare log = otel_logrecord(${.otel_raw.log}); + + if ((log.observed_time_unix_nano == 0) ?? true) { + log.observed_time_unix_nano = $R_UNIXTIME; + }; + }; + }; + }; + }; + + {{ range .Destinations }} + destination "{{ .Name }}" { {{ .Config | nindent 2 }} + }; + {{ end }} + + {{ range .LogPaths }} + log "axosyslog-default" { + source("axosyslog-otlp"); + {{ if .Filterx }} + filterx { {{ .Filterx | nindent 4 }} + }; + {{ end }} + {{ if .Destination }} + log { + destination("{{ .Destination }}"); + flags(flow-control); + }; + {{ end }} + }; + {{ end }}`)) + if err != nil { + return nil, reconciler.StateAbsent, err + } + + var configBuffer bytes.Buffer + if err := tmpl.Execute(&configBuffer, axoSyslog.Spec); err != nil { + return nil, reconciler.StateAbsent, err + } + + return &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: axoSyslogConfigName, + Namespace: axoSyslog.Namespace, + Labels: map[string]string{ + LabelAppName: commonAxoSyslogObjectValue, + LabelAppComponent: commonAxoSyslogObjectValue, + }, + }, + Data: map[string]string{ + axoSyslogConfigKey: configBuffer.String(), + }, + }, reconciler.StatePresent, nil +} diff --git a/pkg/resources/axosyslog/service.go b/pkg/resources/axosyslog/service.go new file mode 100644 index 000000000..ffa794e28 --- /dev/null +++ b/pkg/resources/axosyslog/service.go @@ -0,0 +1,127 @@ +// Copyright © 2025 Kube logging authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package axosyslog + +import ( + "fmt" + + "github.com/cisco-open/operator-tools/pkg/reconciler" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/intstr" + + "github.com/kube-logging/logging-operator/pkg/sdk/logging/api/v1beta1" +) + +func Service(object any) (runtime.Object, reconciler.DesiredState, error) { + axoSyslog, ok := object.(*v1beta1.AxoSyslog) + if !ok { + return nil, reconciler.StateAbsent, fmt.Errorf("expected *v1beta1.AxoSyslog, got %T", axoSyslog) + } + + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: commonAxoSyslogObjectValue, + Namespace: axoSyslog.Namespace, + Labels: map[string]string{ + LabelAppName: commonAxoSyslogObjectValue, + LabelAppComponent: commonAxoSyslogObjectValue, + }, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "otlp-grpc", + Protocol: corev1.ProtocolTCP, + Port: 4317, + TargetPort: intstr.IntOrString{IntVal: 4317}, + }, + { + Name: "otlp-http", + Protocol: corev1.ProtocolTCP, + Port: 4318, + TargetPort: intstr.IntOrString{IntVal: 4318}, + }, + }, + Selector: map[string]string{ + LabelAppName: commonAxoSyslogObjectValue, + LabelAppComponent: commonAxoSyslogObjectValue, + }, + Type: corev1.ServiceTypeClusterIP, + }, + } + + beforeUpdateHook := reconciler.DesiredStateHook(func(current runtime.Object) error { + if s, ok := current.(*corev1.Service); ok { + service.Spec.ClusterIP = s.Spec.ClusterIP + } else { + return fmt.Errorf("failed to cast service object %+v", current) + } + return nil + }) + + return service, beforeUpdateHook, nil +} + +func HeadlessService(object any) (runtime.Object, reconciler.DesiredState, error) { + axoSyslog, ok := object.(*v1beta1.AxoSyslog) + if !ok { + return nil, reconciler.StateAbsent, fmt.Errorf("expected *v1beta1.AxoSyslog, got %T", axoSyslog) + } + + return &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: commonAxoSyslogObjectValue + "-headless", + Namespace: axoSyslog.Namespace, + Labels: map[string]string{ + LabelAppName: commonAxoSyslogObjectValue, + LabelAppComponent: commonAxoSyslogObjectValue, + }, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "otlp-grpc", + Protocol: corev1.ProtocolTCP, + Port: 4317, + TargetPort: intstr.IntOrString{IntVal: 4317}, + }, + { + Name: "otlp-http", + Protocol: corev1.ProtocolTCP, + Port: 4318, + TargetPort: intstr.IntOrString{IntVal: 4318}, + }, + }, + Selector: map[string]string{ + LabelAppName: commonAxoSyslogObjectValue, + LabelAppComponent: commonAxoSyslogObjectValue, + }, + Type: corev1.ServiceTypeClusterIP, + ClusterIP: corev1.ClusterIPNone, + }, + }, reconciler.StatePresent, nil +} + +func ServiceMetrics(object any) (runtime.Object, reconciler.DesiredState, error) { + // TODO: Implement metrics service + return &corev1.Service{}, reconciler.StateAbsent, nil +} + +func ServiceBufferMetrics(object any) (runtime.Object, reconciler.DesiredState, error) { + // TODO: Implement buffer metrics service + return &corev1.Service{}, reconciler.StateAbsent, nil +} diff --git a/pkg/resources/axosyslog/statefulset.go b/pkg/resources/axosyslog/statefulset.go new file mode 100644 index 000000000..a169962db --- /dev/null +++ b/pkg/resources/axosyslog/statefulset.go @@ -0,0 +1,181 @@ +// Copyright © 2025 Kube logging authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package axosyslog + +import ( + "fmt" + "path/filepath" + + "github.com/cisco-open/operator-tools/pkg/reconciler" + "github.com/cisco-open/operator-tools/pkg/utils" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + + "github.com/kube-logging/logging-operator/pkg/sdk/logging/api/v1beta1" +) + +const ( + LabelAppName = "app.kubernetes.io/name" + LabelAppComponent = "app.kubernetes.io/component" + commonAxoSyslogObjectValue = "axosyslog" +) + +func StatefulSet(object any) (runtime.Object, reconciler.DesiredState, error) { + axoSyslog, ok := object.(*v1beta1.AxoSyslog) + if !ok { + return nil, reconciler.StateAbsent, fmt.Errorf("expected *v1beta1.AxoSyslog, got %T", axoSyslog) + } + + statefulset := &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: commonAxoSyslogObjectValue, + Namespace: axoSyslog.Namespace, + Labels: map[string]string{ + LabelAppName: commonAxoSyslogObjectValue, + LabelAppComponent: commonAxoSyslogObjectValue, + }, + }, + Spec: appsv1.StatefulSetSpec{ + PodManagementPolicy: appsv1.OrderedReadyPodManagement, + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + LabelAppName: commonAxoSyslogObjectValue, + LabelAppComponent: commonAxoSyslogObjectValue, + }, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + LabelAppName: commonAxoSyslogObjectValue, + LabelAppComponent: commonAxoSyslogObjectValue, + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: commonAxoSyslogObjectValue, + Image: axoSyslog.Spec.Image.RepositoryWithTag(), + ImagePullPolicy: corev1.PullIfNotPresent, + Ports: []corev1.ContainerPort{ + { + Name: "otlp-grpc", + ContainerPort: 4317, + Protocol: corev1.ProtocolTCP, + }, + }, + Args: []string{ + "--cfgfile=/etc/axosyslog/config/axosyslog.conf", + "--no-caps", + "-Fe", + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "config", + MountPath: "/etc/axosyslog/config", + }, + }, + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("400M"), + corev1.ResourceCPU: resource.MustParse("1000m"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("100M"), + corev1.ResourceCPU: resource.MustParse("500m"), + }, + }, + Env: []corev1.EnvVar{{Name: "BUFFER_PATH", Value: "/buffers"}}, + LivenessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + Exec: &corev1.ExecAction{ + Command: []string{"/usr/sbin/syslog-ng-ctl", "query", "get", "global.sdata_updates.processed"}, + }, + }, + InitialDelaySeconds: 30, + TimeoutSeconds: 0, + PeriodSeconds: 10, + SuccessThreshold: 0, + FailureThreshold: 3, + }, + }, + { + Name: "config-reloader", + Image: axoSyslog.Spec.ConfigReloadImage.RepositoryWithTag(), + ImagePullPolicy: corev1.PullIfNotPresent, + Args: []string{ + "-cfgjson", + generateConfigReloaderConfig(), + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "config", + MountPath: "/etc/axosyslog/config", + }, + }, + }, + // TODO: Add syslog-ng-metrics sidecar + }, + Volumes: []corev1.Volume{ + { + Name: "config", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: axoSyslogConfigName, + }, + }, + }, + }, + }, + SecurityContext: &corev1.PodSecurityContext{ + FSGroup: utils.IntPointer64(101), + }, + }, + }, + ServiceName: fmt.Sprintf("%s-headless", commonAxoSyslogObjectValue), + }, + } + // TODO: merge with sts overrides + + return statefulset, reconciler.StatePresent, nil +} + +func generateConfigReloaderConfig() string { + return fmt.Sprintf(` + { + "events": { + "onFileCreate": { + "%s" : [ + { + "exec": { + "key": "info", + "command": "echo $(date) config secret changed!" + } + }, + { + "exec": { + "key": "reload", + "command": "echo RELOAD | socat - UNIX-CONNECT:%s" + } + } + ] + } + } + } + `, filepath.Join("/etc/syslog-ng/config", "..data"), "/tmp/syslog-ng/syslog-ng.ctl") +} diff --git a/pkg/resources/reconciler.go b/pkg/resources/reconciler.go index d218186d4..eee78112a 100644 --- a/pkg/resources/reconciler.go +++ b/pkg/resources/reconciler.go @@ -33,3 +33,7 @@ type Resource func() (runtime.Object, reconciler.DesiredState, error) // ResourceWithLog redeclaration of function with logging parameter and return type kubernetes Object type ResourceWithLog func(log logr.Logger) runtime.Object + +type ResourceWithNamespace func(namespace string) (runtime.Object, reconciler.DesiredState, error) + +type ResourceWithSpec func(spec any) (runtime.Object, reconciler.DesiredState, error) diff --git a/pkg/sdk/logging/api/v1beta1/axosyslog_types.go b/pkg/sdk/logging/api/v1beta1/axosyslog_types.go index fccf86098..950a6bcf9 100644 --- a/pkg/sdk/logging/api/v1beta1/axosyslog_types.go +++ b/pkg/sdk/logging/api/v1beta1/axosyslog_types.go @@ -36,6 +36,12 @@ type AxoSyslogSpec struct { // LogPaths is a list of log paths to be rendered in the AxoSyslog configuration LogPaths []LogPath `json:"logPaths,omitempty"` + // Image is the image specification for AxoSyslog + Image *BasicImageSpec `json:"image,omitempty"` + + // ConfigReloadImage is the image specification for the config reload + ConfigReloadImage *BasicImageSpec `json:"configReloadImage,omitempty"` + // Destinations is a list of destinations to be rendered in the AxoSyslog configuration Destinations []Destination `json:"destinations,omitempty"` } @@ -92,11 +98,33 @@ func init() { } func (a *AxoSyslog) SetDefaults() error { - if a.Spec.LogPaths == nil { - a.Spec.LogPaths = []LogPath{} - } - if a.Spec.Destinations == nil { - a.Spec.Destinations = []Destination{} + if a != nil { + if a.Spec.LogPaths == nil { + a.Spec.LogPaths = []LogPath{} + } + if a.Spec.Destinations == nil { + a.Spec.Destinations = []Destination{} + } + + if a.Spec.Image == nil { + a.Spec.Image = &BasicImageSpec{ + Repository: defaultAxoSyslogImageRepository, + Tag: defaultAxoSyslogImageTag, + } + } + if a.Spec.ConfigReloadImage == nil { + a.Spec.ConfigReloadImage = &BasicImageSpec{} + } + if a.Spec.ConfigReloadImage.Repository == "" { + a.Spec.ConfigReloadImage.Repository = defaultConfigReloaderImageRepository + } + if a.Spec.ConfigReloadImage.Tag == "" { + if Version == "" { + a.Spec.ConfigReloadImage.Tag = defaultConfigReloaderImageTag + } else { + a.Spec.ConfigReloadImage.Tag = Version + } + } } return nil diff --git a/pkg/sdk/logging/api/v1beta1/syslogng_types.go b/pkg/sdk/logging/api/v1beta1/syslogng_types.go index 9d0a2d396..04431af70 100644 --- a/pkg/sdk/logging/api/v1beta1/syslogng_types.go +++ b/pkg/sdk/logging/api/v1beta1/syslogng_types.go @@ -33,8 +33,8 @@ type _hugoSyslogNGSpec interface{} //nolint:deadcode,unused type _metaSyslogNGSpec interface{} //nolint:deadcode,unused const ( - defaultSyslogngImageRepository = "ghcr.io/axoflow/axosyslog" - defaultSyslogngImageTag = "4.11.0" + defaultAxoSyslogImageRepository = "ghcr.io/axoflow/axosyslog" + defaultAxoSyslogImageTag = "4.11.0" defaultPrometheusExporterImageRepository = "ghcr.io/axoflow/axosyslog-metrics-exporter" defaultPrometheusExporterImageTag = "0.0.9" defaultConfigReloaderImageRepository = "ghcr.io/kube-logging/logging-operator/syslog-ng-reloader" @@ -144,8 +144,8 @@ func (s *SyslogNGSpec) SetDefaults() { } if s.SyslogNGImage == nil { s.SyslogNGImage = &BasicImageSpec{ - Repository: defaultSyslogngImageRepository, - Tag: defaultSyslogngImageTag, + Repository: defaultAxoSyslogImageRepository, + Tag: defaultAxoSyslogImageTag, } } if s.ConfigReloadImage == nil { diff --git a/pkg/sdk/logging/api/v1beta1/zz_generated.deepcopy.go b/pkg/sdk/logging/api/v1beta1/zz_generated.deepcopy.go index 15362e71e..e64aeb968 100644 --- a/pkg/sdk/logging/api/v1beta1/zz_generated.deepcopy.go +++ b/pkg/sdk/logging/api/v1beta1/zz_generated.deepcopy.go @@ -101,6 +101,16 @@ func (in *AxoSyslogSpec) DeepCopyInto(out *AxoSyslogSpec) { *out = make([]LogPath, len(*in)) copy(*out, *in) } + if in.Image != nil { + in, out := &in.Image, &out.Image + *out = new(BasicImageSpec) + **out = **in + } + if in.ConfigReloadImage != nil { + in, out := &in.ConfigReloadImage, &out.ConfigReloadImage + *out = new(BasicImageSpec) + **out = **in + } if in.Destinations != nil { in, out := &in.Destinations, &out.Destinations *out = make([]Destination, len(*in))