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 }