From 1f811678cf3a2fa0d4f5c10b64fe7334ad62b6b9 Mon Sep 17 00:00:00 2001 From: Laimonas Rastenis Date: Thu, 5 Dec 2024 09:10:26 +0200 Subject: [PATCH] feat: store cluster id in a metadata secret --- cmd/agent/run.go | 17 +++- internal/config/config.go | 12 +++ internal/services/metadata/store.go | 55 +++++++++++++ internal/services/metadata/store_test.go | 98 ++++++++++++++++++++++++ internal/services/metadata/types.go | 5 ++ 5 files changed, 185 insertions(+), 2 deletions(-) create mode 100644 internal/services/metadata/store.go create mode 100644 internal/services/metadata/store_test.go create mode 100644 internal/services/metadata/types.go diff --git a/cmd/agent/run.go b/cmd/agent/run.go index 54a9269..9ad93b3 100644 --- a/cmd/agent/run.go +++ b/cmd/agent/run.go @@ -24,6 +24,7 @@ import ( "castai-agent/internal/services/controller" "castai-agent/internal/services/controller/scheme" "castai-agent/internal/services/discovery" + "castai-agent/internal/services/metadata" "castai-agent/internal/services/monitor" "castai-agent/internal/services/providers" "castai-agent/internal/services/replicas" @@ -174,6 +175,18 @@ func runAgentMode(ctx context.Context, castaiclient castai.Client, log *logrus.E return fmt.Errorf("getting provider: %w", err) } + metadataStore := metadata.New(clientset, cfg) + + clusterIDChangedHandler := func(clusterID string) { + if err := metadataStore.StoreMetadataConfigMap(ctx, &metadata.Metadata{ + ClusterID: clusterID, + }); err != nil { + log.Warnf("failed to store metadata in a config map: %v", err) + } + + clusterIDChanged(clusterID) + } + log.Data["provider"] = provider.Name() log.Infof("using provider %q", provider.Name()) @@ -189,10 +202,10 @@ func runAgentMode(ctx context.Context, castaiclient castai.Client, log *logrus.E return fmt.Errorf("registering cluster: %w", err) } clusterID = reg.ClusterID - clusterIDChanged(clusterID) + clusterIDChangedHandler(clusterID) log.Infof("cluster registered: %v, clusterID: %s", reg, clusterID) } else { - clusterIDChanged(clusterID) + clusterIDChangedHandler(clusterID) log.Infof("clusterID: %s provided by env variable", clusterID) } diff --git a/internal/config/config.go b/internal/config/config.go index 1347a2e..1f451d5 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -34,6 +34,8 @@ type Config struct { PprofPort int `mapstructure:"pprof.port"` HealthzPort int `mapstructure:"healthz_port"` + MetadataStore *MetadataStoreConfig `mapstructure:"metadata_store"` + LeaderElection LeaderElectionConfig `mapstructure:"leader_election"` MonitorMetadata string `mapstructure:"monitor_metadata"` @@ -110,6 +112,12 @@ type Static struct { ClusterID string `mapstructure:"cluster_id"` } +type MetadataStoreConfig struct { + Enabled bool `mapstructure:"enabled"` + ConfigMapNamespace string `mapstructure:"config_map_namespace"` + ConfigMapName string `mapstructure:"config_map_name"` +} + type Anywhere struct { ClusterName string `mapstructure:"cluster_name"` } @@ -169,6 +177,10 @@ func Get() Config { viper.SetDefault("leader_election.lock_name", "agent-leader-election-lock") viper.SetDefault("leader_election.namespace", "castai-agent") + viper.SetDefault("metadata_store.enabled", false) + viper.SetDefault("metadata_store.config_map_name", "castai-agent-metadata") + viper.SetDefault("metadata_store.config_map_namespace", "castai-agent") + viper.AutomaticEnv() viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) viper.AllowEmptyEnv(true) diff --git a/internal/services/metadata/store.go b/internal/services/metadata/store.go new file mode 100644 index 0000000..ab1452b --- /dev/null +++ b/internal/services/metadata/store.go @@ -0,0 +1,55 @@ +package metadata + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + + "castai-agent/internal/config" +) + +type Store interface { + // StoreMetadataConfigMap stores relevant agent runtime metadata in a config map. + StoreMetadataConfigMap(ctx context.Context, metadata *Metadata) error +} + +var _ Store = (*StoreImpl)(nil) + +type StoreImpl struct { + clientset kubernetes.Interface + cfg config.Config +} + +func New(clientset kubernetes.Interface, cfg config.Config) *StoreImpl { + return &StoreImpl{ + clientset: clientset, + cfg: cfg, + } +} + +func (s *StoreImpl) StoreMetadataConfigMap(ctx context.Context, metadata *Metadata) error { + if !s.cfg.MetadataStore.Enabled { + return nil + } + + configMapNamespace := s.cfg.MetadataStore.ConfigMapNamespace + configMap := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: s.cfg.MetadataStore.ConfigMapName, + Namespace: configMapNamespace, + }, + Data: map[string]string{ + "CLUSTER_ID": metadata.ClusterID, + }, + } + + _, err := s.clientset.CoreV1().ConfigMaps(configMapNamespace).Update(ctx, configMap, metav1.UpdateOptions{}) + if errors.IsNotFound(err) { + _, err = s.clientset.CoreV1().ConfigMaps(configMapNamespace).Create(ctx, configMap, metav1.CreateOptions{}) + } + + return err +} diff --git a/internal/services/metadata/store_test.go b/internal/services/metadata/store_test.go new file mode 100644 index 0000000..f93103e --- /dev/null +++ b/internal/services/metadata/store_test.go @@ -0,0 +1,98 @@ +package metadata + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + fakeclientset "k8s.io/client-go/kubernetes/fake" + + "castai-agent/internal/config" +) + +func TestStoreImpl_StoreMetadataConfigMap(t *testing.T) { + tests := []struct { + name string + cfg config.Config + metadata *Metadata + existingConfigMap *corev1.ConfigMap + expectedError bool + }{ + { + name: "should store metadata successfully when config map does not exist", + cfg: config.Config{ + MetadataStore: &config.MetadataStoreConfig{ + Enabled: true, + ConfigMapName: "castai-agent-metadata", + ConfigMapNamespace: "default", + }, + }, + metadata: &Metadata{ + ClusterID: "test-cluster-id", + }, + expectedError: false, + }, + { + name: "should store metadata successfully when config map exists", + cfg: config.Config{ + MetadataStore: &config.MetadataStoreConfig{ + Enabled: true, + ConfigMapName: "castai-agent-metadata", + ConfigMapNamespace: "default", + }, + }, + metadata: &Metadata{ + ClusterID: "test-cluster-id", + }, + existingConfigMap: &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "castai-agent-metadata", + Namespace: "default", + }, + Data: map[string]string{ + "CLUSTER_ID": "original-test-cluster-id", + }, + }, + expectedError: false, + }, + { + name: "should not store metadata when store is disabled", + cfg: config.Config{ + MetadataStore: &config.MetadataStoreConfig{ + Enabled: false, + }, + }, + metadata: &Metadata{ + ClusterID: "test-cluster-id", + }, + expectedError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := require.New(t) + clientset := fakeclientset.NewSimpleClientset() + store := New(clientset, tt.cfg) + + if tt.existingConfigMap != nil { + _, err := clientset.CoreV1().ConfigMaps(tt.cfg.MetadataStore.ConfigMapNamespace).Create(context.Background(), tt.existingConfigMap, metav1.CreateOptions{}) + r.NoError(err) + } + + err := store.StoreMetadataConfigMap(context.Background(), tt.metadata) + if tt.expectedError { + r.Error(err) + } else { + r.NoError(err) + if tt.cfg.MetadataStore.Enabled { + configMap, err := clientset.CoreV1().ConfigMaps(tt.cfg.MetadataStore.ConfigMapNamespace).Get(context.Background(), tt.cfg.MetadataStore.ConfigMapName, metav1.GetOptions{}) + r.NoError(err) + r.Equal(tt.metadata.ClusterID, configMap.Data["CLUSTER_ID"]) + } + } + }) + } +} diff --git a/internal/services/metadata/types.go b/internal/services/metadata/types.go new file mode 100644 index 0000000..66c4d27 --- /dev/null +++ b/internal/services/metadata/types.go @@ -0,0 +1,5 @@ +package metadata + +type Metadata struct { + ClusterID string +}