github.com/verrazzano/verrazzano@v1.7.0/application-operator/controllers/namespace/fluentd_configmap.go (about)

     1  // Copyright (c) 2022, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package namespace
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"fmt"
    10  	"strings"
    11  	"text/template"
    12  
    13  	"github.com/verrazzano/verrazzano/pkg/constants"
    14  	corev1 "k8s.io/api/core/v1"
    15  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    16  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  	"k8s.io/apimachinery/pkg/runtime/schema"
    18  	"sigs.k8s.io/controller-runtime/pkg/client"
    19  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    20  )
    21  
    22  const (
    23  	fluentdConfigMapName = "fluentd-config"
    24  
    25  	nsConfigKeyTemplate         = "oci-logging-ns-%s.conf"
    26  	fluentdConfigKey            = "fluentd-standalone.conf"
    27  	startNamespaceConfigsMarker = "# Start namespace logging configs"
    28  )
    29  
    30  const loggingTemplateBody = `|
    31  <match kubernetes.**_{{ .namespace }}_**>
    32    @type oci_logging
    33    log_object_id {{ .logId }}
    34    <buffer>
    35      @type file
    36      path /fluentd/log/oci-logging-ns-{{ .namespace }}
    37      disable_chunk_backup true
    38      chunk_limit_size 5MB
    39      flush_interval 180s
    40      total_limit_size 1GB
    41      overflow_action throw_exception
    42      retry_type exponential_backoff
    43    </buffer>
    44  </match>
    45  `
    46  
    47  var loggingTemplate *template.Template
    48  
    49  // init creates a logging template.
    50  func init() {
    51  	loggingTemplate, _ = template.New("loggingConfig").Parse(loggingTemplateBody)
    52  }
    53  
    54  // addNamespaceLogging updates the system Fluentd config map to include a match section that directs all logs for the given
    55  // namespace to the given OCI Log object id. It returns true if the config map was updated.
    56  func addNamespaceLogging(ctx context.Context, cli client.Client, namespace string, ociLogID string) (bool, error) {
    57  	cm := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: fluentdConfigMapName, Namespace: constants.VerrazzanoSystemNamespace}}
    58  
    59  	opResult, err := controllerutil.CreateOrUpdate(ctx, cli, cm, func() error {
    60  		if cm.ObjectMeta.CreationTimestamp.IsZero() {
    61  			return fmt.Errorf("configmap '%s' in namespace '%s' must exist", cm.ObjectMeta.Name, cm.ObjectMeta.Namespace)
    62  		}
    63  		return addNamespaceLoggingToConfigMap(cm, namespace, ociLogID)
    64  	})
    65  
    66  	if err != nil {
    67  		return false, err
    68  	}
    69  
    70  	return opResult != controllerutil.OperationResultNone, nil
    71  }
    72  
    73  // removeNamespaceLogging updates the system Fluentd config map, removing the namespace-specific logging configuration.
    74  // It returns true if the config map was updated.
    75  func removeNamespaceLogging(ctx context.Context, cli client.Client, namespace string) (bool, error) {
    76  	cm := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: fluentdConfigMapName, Namespace: constants.VerrazzanoSystemNamespace}}
    77  
    78  	opResult, err := controllerutil.CreateOrUpdate(ctx, cli, cm, func() error {
    79  		// if the config map exists, remove the namespace logging config
    80  		if !cm.ObjectMeta.CreationTimestamp.IsZero() {
    81  			removeNamespaceLoggingFromConfigMap(cm, namespace)
    82  			return nil
    83  		}
    84  		// return an error here, otherwise the configmap will get created and we don't want that
    85  		return k8serrors.NewNotFound(schema.ParseGroupResource("ConfigMap"), fluentdConfigMapName)
    86  	})
    87  
    88  	if err != nil {
    89  		return false, client.IgnoreNotFound(err)
    90  	}
    91  
    92  	return opResult != controllerutil.OperationResultNone, nil
    93  }
    94  
    95  // addNamespaceLoggingToConfigMap adds a config map key for the namespace-specific logging configuration and
    96  // adds an include directive in the main Fluentd config. This function is idempotent.
    97  func addNamespaceLoggingToConfigMap(configMap *corev1.ConfigMap, namespace string, ociLogID string) error {
    98  	// make sure the logging template parsed
    99  	if loggingTemplate == nil {
   100  		return fmt.Errorf("logging config template is empty")
   101  	}
   102  
   103  	// use the template to create the logging config for the namespace and add it to the config map
   104  	pairs := map[string]string{"namespace": namespace, "logId": ociLogID}
   105  	var buff bytes.Buffer
   106  	if err := loggingTemplate.Execute(&buff, pairs); err != nil {
   107  		return err
   108  	}
   109  
   110  	nsConfigKey := fmt.Sprintf(nsConfigKeyTemplate, namespace)
   111  	configMap.Data[nsConfigKey] = buff.String()
   112  
   113  	// if the logging config isn't already included in the main Fluentd config, include it
   114  	if fluentdConfig, ok := configMap.Data[fluentdConfigKey]; ok {
   115  		includeLine := fmt.Sprintf("@include %s", nsConfigKey)
   116  		if !strings.Contains(fluentdConfig, includeLine) {
   117  			replace := fmt.Sprintf("%s\n%s", startNamespaceConfigsMarker, includeLine)
   118  			fluentdConfig = strings.Replace(fluentdConfig, startNamespaceConfigsMarker, replace, 1)
   119  			configMap.Data[fluentdConfigKey] = fluentdConfig
   120  		}
   121  	}
   122  
   123  	return nil
   124  }
   125  
   126  // removeNamespaceLoggingFromConfigMap removes the config map key for the namespace-specific logging configuration and
   127  // removes the include directive in the main Fluentd config. This function is idempotent.
   128  func removeNamespaceLoggingFromConfigMap(configMap *corev1.ConfigMap, namespace string) {
   129  	// remove the map entry for the logging config
   130  	nsConfigKey := fmt.Sprintf(nsConfigKeyTemplate, namespace)
   131  	delete(configMap.Data, nsConfigKey)
   132  
   133  	// if the logging config is included in the main Fluentd config, remove it
   134  	if fluentdConfig, ok := configMap.Data[fluentdConfigKey]; ok {
   135  		includeLine := fmt.Sprintf("@include %s", nsConfigKey)
   136  		if strings.Contains(fluentdConfig, includeLine) {
   137  			toRemove := fmt.Sprintf("%s\n", includeLine)
   138  			fluentdConfig = strings.Replace(fluentdConfig, toRemove, "", 1)
   139  			configMap.Data[fluentdConfigKey] = fluentdConfig
   140  		}
   141  	}
   142  }