From b3dee6dfb2103a0600b5725d22b7c461d3ea87de Mon Sep 17 00:00:00 2001 From: Oleksand Matviienko Date: Thu, 26 Sep 2024 09:05:07 +0200 Subject: [PATCH 01/21] feat: Removed unused variables --- operator/api/v1/hotnews_types.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/operator/api/v1/hotnews_types.go b/operator/api/v1/hotnews_types.go index c1203bb..b38321b 100644 --- a/operator/api/v1/hotnews_types.go +++ b/operator/api/v1/hotnews_types.go @@ -32,15 +32,9 @@ const ( // TypeHotNewsUpdated represents the reason for created condition TypeHotNewsUpdated = "Updated" - // TypeHotNewsFailedToCreate represents the reason for failed to create condition - TypeHotNewsFailedToCreate = "FailedToCreate" - // HotNewsSuccessfullyCreated represents the reason for created condition HotNewsSuccessfullyCreated = "HotNews was successfully created" - // HotNewsSuccessfullyUpdated represents the reason for updated condition - HotNewsSuccessfullyUpdated = "HotNews was successfully updated" - // HotNewsError indicates that there were an error during Reconciliation of hot news object HotNewsError = "Error during processing of hot news creation" From 1b2eda350ae79a36e19612ba433372b33d7ffef2 Mon Sep 17 00:00:00 2001 From: Oleksand Matviienko Date: Thu, 26 Sep 2024 09:13:57 +0200 Subject: [PATCH 02/21] feat: Adde labels + namespace in GetFeedGroupNAmes --- operator/api/v1/hotnews_types.go | 1 + 1 file changed, 1 insertion(+) diff --git a/operator/api/v1/hotnews_types.go b/operator/api/v1/hotnews_types.go index b38321b..11455a7 100644 --- a/operator/api/v1/hotnews_types.go +++ b/operator/api/v1/hotnews_types.go @@ -157,6 +157,7 @@ func (r *HotNews) GetFeedGroupNames(ctx context.Context) ([]string, error) { var configMaps v1.ConfigMapList err = k8sClient.List(ctx, &configMaps, &client.ListOptions{ LabelSelector: labels.NewSelector().Add(*s), + Namespace: r.Namespace, }) if err != nil { return nil, err From b2042ec2c3ae16b1cea46b23a5bf343b67f838ba Mon Sep 17 00:00:00 2001 From: Oleksand Matviienko Date: Thu, 26 Sep 2024 09:19:31 +0200 Subject: [PATCH 03/21] feat: Added namespace usage during list operation --- .../internal/controller/hotnews_controller.go | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/operator/internal/controller/hotnews_controller.go b/operator/internal/controller/hotnews_controller.go index 61e42ec..a569c37 100644 --- a/operator/internal/controller/hotnews_controller.go +++ b/operator/internal/controller/hotnews_controller.go @@ -216,7 +216,7 @@ func (r *HotNewsReconciler) processHotNews(ctx context.Context, hotNews *newsagg logger := log.FromContext(ctx) logger.Info("handling update") - requestUrl, err := r.constructRequestUrl(ctx, hotNews.Spec) + requestUrl, err := r.constructRequestUrl(ctx, hotNews) if err != nil { logger.Error(err, errFailedToConstructRequestUrl) return err @@ -287,37 +287,37 @@ func (r *HotNewsReconciler) processHotNews(ctx context.Context, hotNews *newsagg // Example: // http://server.com/news?keywords=bitcoin&dateFrom=2024-08-05&dateEnd=2024-08-06&sources=abc,bbc // http://server.com/news?keywords=bitcoin&dateFrom=2024-08-05&sources=abc,bbc -func (r *HotNewsReconciler) constructRequestUrl(ctx context.Context, spec newsaggregatorv1.HotNewsSpec) (string, error) { +func (r *HotNewsReconciler) constructRequestUrl(ctx context.Context, hotNews *newsaggregatorv1.HotNews) (string, error) { var requestUrl strings.Builder requestUrl.WriteString(r.serverUrl) var keywordsStr strings.Builder - for _, keyword := range spec.Keywords { + for _, keyword := range hotNews.Spec.Keywords { keywordsStr.WriteString(keyword) keywordsStr.WriteRune(',') } requestUrl.WriteString("?keywords=" + keywordsStr.String()[:len(keywordsStr.String())-1]) var feedStr strings.Builder - if spec.FeedGroups != nil { - feedGroupsStr, err := r.processFeedGroups(ctx, spec) + if hotNews.Spec.FeedGroups != nil { + feedGroupsStr, err := r.processFeedGroups(ctx, hotNews) if err != nil { return "", err } feedStr.WriteString(feedGroupsStr) } else { - feedsStr := r.processFeeds(spec) + feedsStr := r.processFeeds(hotNews.Spec) feedStr.WriteString(feedsStr) } requestUrl.WriteString("&sources=" + feedStr.String()) - if spec.DateStart != "" { - requestUrl.WriteString("&dateFrom=" + spec.DateStart) + if hotNews.Spec.DateStart != "" { + requestUrl.WriteString("&dateFrom=" + hotNews.Spec.DateStart) } - if spec.DateEnd != "" { - requestUrl.WriteString("&dateEnd=" + spec.DateEnd) + if hotNews.Spec.DateEnd != "" { + requestUrl.WriteString("&dateEnd=" + hotNews.Spec.DateEnd) } return requestUrl.String(), nil @@ -489,15 +489,15 @@ func (r *HotNewsReconciler) processFeeds(spec newsaggregatorv1.HotNewsSpec) stri // processFeedGroups function processes feed groups from the ConfigMap and returns a string containing comma-separated // feed sources -func (r *HotNewsReconciler) processFeedGroups(ctx context.Context, spec newsaggregatorv1.HotNewsSpec) (string, error) { +func (r *HotNewsReconciler) processFeedGroups(ctx context.Context, hotNews *newsaggregatorv1.HotNews) (string, error) { var sourcesBuilder strings.Builder - configMaps, err := r.getFeedGroups(ctx) + configMaps, err := r.getFeedGroups(ctx, hotNews) if err != nil { return "", err } - for _, feedGroup := range spec.FeedGroups { + for _, feedGroup := range hotNews.Spec.FeedGroups { for _, configMap := range configMaps.Items { if _, ok := configMap.Data[feedGroup]; !ok { return "", fmt.Errorf(errWrongFeedGroupName) @@ -511,8 +511,8 @@ func (r *HotNewsReconciler) processFeedGroups(ctx context.Context, spec newsaggr return sourcesBuilder.String()[:len(sourcesBuilder.String())-1], nil } -// getConfigMapData returns all data from config map named FeedGroupsConfigMapName in FeedGroupsNamespace -func (r *HotNewsReconciler) getFeedGroups(ctx context.Context) (v1.ConfigMapList, error) { +// getFeedGroups returns all data from config map named FeedGroupsConfigMapName in FeedGroupsNamespace +func (r *HotNewsReconciler) getFeedGroups(ctx context.Context, hotNews *newsaggregatorv1.HotNews) (v1.ConfigMapList, error) { s, err := labels.NewRequirement(newsaggregatorv1.FeedGroupLabel, selection.Exists, nil) if err != nil { return v1.ConfigMapList{}, err @@ -521,6 +521,7 @@ func (r *HotNewsReconciler) getFeedGroups(ctx context.Context) (v1.ConfigMapList var configMaps v1.ConfigMapList err = r.Client.List(ctx, &configMaps, &client.ListOptions{ LabelSelector: labels.NewSelector().Add(*s), + Namespace: hotNews.Namespace, }) if err != nil { From 7c7dda08cc40a9f69fc5f51fd76ee857bc83103a Mon Sep 17 00:00:00 2001 From: Oleksand Matviienko Date: Thu, 26 Sep 2024 09:49:05 +0200 Subject: [PATCH 04/21] refactor: Using namespace during list operation. Retrieving config map once and then reusing it --- operator/api/v1/hotnews_types.go | 24 +---- operator/api/v1/hotnews_webhook.go | 17 +++- .../internal/controller/hotnews_controller.go | 99 +++++++++---------- 3 files changed, 64 insertions(+), 76 deletions(-) diff --git a/operator/api/v1/hotnews_types.go b/operator/api/v1/hotnews_types.go index 11455a7..9da001d 100644 --- a/operator/api/v1/hotnews_types.go +++ b/operator/api/v1/hotnews_types.go @@ -17,12 +17,8 @@ limitations under the License. package v1 import ( - "context" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/selection" - "sigs.k8s.io/controller-runtime/pkg/client" ) const ( @@ -148,23 +144,9 @@ func init() { } // GetFeedGroupNames returns all config maps which contain hotNew groups names -func (r *HotNews) GetFeedGroupNames(ctx context.Context) ([]string, error) { - s, err := labels.NewRequirement(FeedGroupLabel, selection.Exists, nil) - if err != nil { - return nil, err - } - - var configMaps v1.ConfigMapList - err = k8sClient.List(ctx, &configMaps, &client.ListOptions{ - LabelSelector: labels.NewSelector().Add(*s), - Namespace: r.Namespace, - }) - if err != nil { - return nil, err - } - +func (r *HotNews) GetFeedGroupNames(configMapList v1.ConfigMapList) []string { var feedGroups []string - for _, configMap := range configMaps.Items { + for _, configMap := range configMapList.Items { for _, source := range r.Spec.FeedGroups { if _, exists := configMap.Data[source]; exists { feedGroups = append(feedGroups, source) @@ -172,7 +154,7 @@ func (r *HotNews) GetFeedGroupNames(ctx context.Context) ([]string, error) { } } - return feedGroups, nil + return feedGroups } // SetCondition initializes status.Conditions if they are empty. diff --git a/operator/api/v1/hotnews_webhook.go b/operator/api/v1/hotnews_webhook.go index 0a790e2..5a5fa7b 100644 --- a/operator/api/v1/hotnews_webhook.go +++ b/operator/api/v1/hotnews_webhook.go @@ -19,7 +19,10 @@ package v1 import ( "context" "errors" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/selection" "k8s.io/apimachinery/pkg/util/validation/field" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -221,10 +224,22 @@ func (r *HotNews) feedGroupsExists() error { if r.Spec.FeedGroups == nil { return nil } + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() - feedGroups, err := r.GetFeedGroupNames(ctx) + s, err := labels.NewRequirement(FeedGroupLabel, selection.Exists, nil) + if err != nil { + return err + } + + var configMaps v1.ConfigMapList + err = k8sClient.List(ctx, &configMaps, &client.ListOptions{ + LabelSelector: labels.NewSelector().Add(*s), + Namespace: r.Namespace, + }) + + feedGroups := r.GetFeedGroupNames(configMaps) if err != nil { return err } diff --git a/operator/internal/controller/hotnews_controller.go b/operator/internal/controller/hotnews_controller.go index a569c37..63a9b7a 100644 --- a/operator/internal/controller/hotnews_controller.go +++ b/operator/internal/controller/hotnews_controller.go @@ -104,6 +104,15 @@ func (r *HotNewsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct return ctrl.Result{}, err } + configMapList, err := r.retrieveConfigMap(ctx, hotNews.Namespace) + if err != nil { + updateErr := r.setFailedStatus(ctx, &hotNews, "Failed to retrieve config map", err.Error()) + if updateErr != nil { + return ctrl.Result{}, updateErr + } + return ctrl.Result{}, err + } + if !controllerutil.ContainsFinalizer(&hotNews, HotNewsFinalizer) { controllerutil.AddFinalizer(&hotNews, HotNewsFinalizer) err := r.Client.Update(ctx, &hotNews) @@ -113,7 +122,7 @@ func (r *HotNewsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct } if !hotNews.DeletionTimestamp.IsZero() { - err = r.removeFeedReference(ctx, hotNews) + err = r.removeFeedReference(ctx, hotNews, configMapList) if err != nil { updateErr := r.setFailedStatus(ctx, &hotNews, "Failed to remove feed reference for feeds", err.Error()) if updateErr != nil { @@ -135,7 +144,7 @@ func (r *HotNewsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct return ctrl.Result{}, nil } - err = r.processHotNews(ctx, &hotNews) + err = r.processHotNews(ctx, &hotNews, configMapList) if err != nil { updateErr := r.setFailedStatus(ctx, &hotNews, "Failed to process hotnews", err.Error()) if updateErr != nil { @@ -145,7 +154,7 @@ func (r *HotNewsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct } logger.Info("HotNews object has been updated") - err = r.setFeedReference(ctx, hotNews) + err = r.setFeedReference(ctx, hotNews, configMapList) if err != nil { updateErr := r.setFailedStatus(ctx, &hotNews, "Failed to set feed reference for hotnews", err.Error()) if updateErr != nil { @@ -212,11 +221,11 @@ type article struct { } // processHotNews function updates the HotNews object and returns an error if something goes wrong. -func (r *HotNewsReconciler) processHotNews(ctx context.Context, hotNews *newsaggregatorv1.HotNews) error { +func (r *HotNewsReconciler) processHotNews(ctx context.Context, hotNews *newsaggregatorv1.HotNews, configMapList v1.ConfigMapList) error { logger := log.FromContext(ctx) logger.Info("handling update") - requestUrl, err := r.constructRequestUrl(ctx, hotNews) + requestUrl, err := r.constructRequestUrl(ctx, hotNews, configMapList) if err != nil { logger.Error(err, errFailedToConstructRequestUrl) return err @@ -287,7 +296,8 @@ func (r *HotNewsReconciler) processHotNews(ctx context.Context, hotNews *newsagg // Example: // http://server.com/news?keywords=bitcoin&dateFrom=2024-08-05&dateEnd=2024-08-06&sources=abc,bbc // http://server.com/news?keywords=bitcoin&dateFrom=2024-08-05&sources=abc,bbc -func (r *HotNewsReconciler) constructRequestUrl(ctx context.Context, hotNews *newsaggregatorv1.HotNews) (string, error) { +func (r *HotNewsReconciler) constructRequestUrl(ctx context.Context, hotNews *newsaggregatorv1.HotNews, + configMapList v1.ConfigMapList) (string, error) { var requestUrl strings.Builder requestUrl.WriteString(r.serverUrl) @@ -300,7 +310,7 @@ func (r *HotNewsReconciler) constructRequestUrl(ctx context.Context, hotNews *ne var feedStr strings.Builder if hotNews.Spec.FeedGroups != nil { - feedGroupsStr, err := r.processFeedGroups(ctx, hotNews) + feedGroupsStr, err := r.processFeedGroups(hotNews, configMapList) if err != nil { return "", err } @@ -354,22 +364,16 @@ func (r *HotNewsReconciler) setFailedStatus(ctx context.Context, hotNews *newsag } // setFeedReference sets the owner references for each Feed in the HotNewsSpec.Feeds array. -func (r *HotNewsReconciler) setFeedReference(ctx context.Context, hotNews newsaggregatorv1.HotNews) error { +func (r *HotNewsReconciler) setFeedReference(ctx context.Context, hotNews newsaggregatorv1.HotNews, + configMapList v1.ConfigMapList) error { + var feeds = hotNews.Spec.Feeds if hotNews.Spec.FeedGroups != nil { - feedGroups, err := hotNews.GetFeedGroupNames(ctx) - if err != nil { - return err - } + feeds = hotNews.GetFeedGroupNames(configMapList) + } - err = r.setOwnerReferenceForFeeds(ctx, hotNews, feedGroups) - if err != nil { - return err - } - } else { - err := r.setOwnerReferenceForFeeds(ctx, hotNews, hotNews.Spec.Feeds) - if err != nil { - return err - } + err := r.setOwnerReferenceForFeeds(ctx, hotNews, feeds) + if err != nil { + return err } return nil @@ -388,7 +392,11 @@ func (r *HotNewsReconciler) setOwnerReferenceForFeeds(ctx context.Context, hotNe Namespace: hotNews.Namespace, }, feed) if err != nil { - errList = append(errList, field.InternalError(field.NewPath("feeds").Child(feedName), err)) + if k8sErrors.IsNotFound(err) { + errList = append(errList, field.Invalid(field.NewPath("spec.feeds").Child(feedName), feedName, "feed not found")) + } else { + return err + } continue } @@ -397,7 +405,7 @@ func (r *HotNewsReconciler) setOwnerReferenceForFeeds(ctx context.Context, hotNe err = r.Update(ctx, feed) if err != nil { - errList = append(errList, field.InternalError(field.NewPath("feeds").Child(feedName), err)) + return err } } } @@ -420,22 +428,16 @@ func hasOwnerReference(feed *newsaggregatorv1.Feed, ownerRef *metav1.OwnerRefere } // removeFeedReference removes the owner references for each Feed in the HotNewsSpec.Feeds array. -func (r *HotNewsReconciler) removeFeedReference(ctx context.Context, hotNews newsaggregatorv1.HotNews) error { +func (r *HotNewsReconciler) removeFeedReference(ctx context.Context, hotNews newsaggregatorv1.HotNews, + configMapList v1.ConfigMapList) error { + var feeds = hotNews.Spec.Feeds if hotNews.Spec.FeedGroups != nil { - feedGroups, err := hotNews.GetFeedGroupNames(ctx) - if err != nil { - return err - } + feeds = hotNews.GetFeedGroupNames(configMapList) + } - err = r.removeOwnerReferenceFromFeeds(ctx, &hotNews, feedGroups) - if err != nil { - return err - } - } else { - err := r.removeOwnerReferenceFromFeeds(ctx, &hotNews, hotNews.Spec.Feeds) - if err != nil { - return err - } + err := r.removeOwnerReferenceFromFeeds(ctx, &hotNews, feeds) + if err != nil { + return err } return nil @@ -489,16 +491,12 @@ func (r *HotNewsReconciler) processFeeds(spec newsaggregatorv1.HotNewsSpec) stri // processFeedGroups function processes feed groups from the ConfigMap and returns a string containing comma-separated // feed sources -func (r *HotNewsReconciler) processFeedGroups(ctx context.Context, hotNews *newsaggregatorv1.HotNews) (string, error) { +func (r *HotNewsReconciler) processFeedGroups(hotNews *newsaggregatorv1.HotNews, + configMapList v1.ConfigMapList) (string, error) { var sourcesBuilder strings.Builder - configMaps, err := r.getFeedGroups(ctx, hotNews) - if err != nil { - return "", err - } - for _, feedGroup := range hotNews.Spec.FeedGroups { - for _, configMap := range configMaps.Items { + for _, configMap := range configMapList.Items { if _, ok := configMap.Data[feedGroup]; !ok { return "", fmt.Errorf(errWrongFeedGroupName) } else { @@ -511,8 +509,8 @@ func (r *HotNewsReconciler) processFeedGroups(ctx context.Context, hotNews *news return sourcesBuilder.String()[:len(sourcesBuilder.String())-1], nil } -// getFeedGroups returns all data from config map named FeedGroupsConfigMapName in FeedGroupsNamespace -func (r *HotNewsReconciler) getFeedGroups(ctx context.Context, hotNews *newsaggregatorv1.HotNews) (v1.ConfigMapList, error) { +// retrieveConfigMap +func (r *HotNewsReconciler) retrieveConfigMap(ctx context.Context, namespace string) (v1.ConfigMapList, error) { s, err := labels.NewRequirement(newsaggregatorv1.FeedGroupLabel, selection.Exists, nil) if err != nil { return v1.ConfigMapList{}, err @@ -521,19 +519,12 @@ func (r *HotNewsReconciler) getFeedGroups(ctx context.Context, hotNews *newsaggr var configMaps v1.ConfigMapList err = r.Client.List(ctx, &configMaps, &client.ListOptions{ LabelSelector: labels.NewSelector().Add(*s), - Namespace: hotNews.Namespace, + Namespace: namespace, }) if err != nil { return v1.ConfigMapList{}, err } - logger := log.FromContext(ctx) - for _, configMap := range configMaps.Items { - for key, item := range configMap.Data { - logger.Info("ConfigMap data", key, item) - } - } - return configMaps, nil } From 945a6e69daf526cb14d60d91c5740437a2e34e50 Mon Sep 17 00:00:00 2001 From: Oleksand Matviienko Date: Thu, 26 Sep 2024 09:56:58 +0200 Subject: [PATCH 05/21] test: Repaired tests --- .../controller/hotnews_controller_test.go | 55 ++++++++++++++++--- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/operator/internal/controller/hotnews_controller_test.go b/operator/internal/controller/hotnews_controller_test.go index 95a0f08..34d385c 100644 --- a/operator/internal/controller/hotnews_controller_test.go +++ b/operator/internal/controller/hotnews_controller_test.go @@ -231,6 +231,7 @@ func TestHotNewsReconciler_Reconcile_NegativeCases(t *testing.T) { func TestRemoveFeedReference(t *testing.T) { scheme := runtime.NewScheme() newsaggregatorv1.AddToScheme(scheme) + v1.AddToScheme(scheme) r := &HotNewsReconciler{ Client: fake.NewClientBuilder().WithScheme(scheme).Build(), @@ -247,6 +248,20 @@ func TestRemoveFeedReference(t *testing.T) { }, } + configMapList := v1.ConfigMapList{ + Items: []v1.ConfigMap{ + { + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + newsaggregatorv1.FeedGroupLabel: "true", + }, + Namespace: hotNews.ObjectMeta.Namespace, + }, + Data: nil, + }, + }, + } + ctx := context.TODO() tests := []struct { @@ -350,7 +365,7 @@ func TestRemoveFeedReference(t *testing.T) { } r.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objs...).Build() - err := r.removeFeedReference(ctx, hotNews) + err := r.removeFeedReference(ctx, hotNews, configMapList) if test.expectedError { assert.Error(t, err) @@ -377,6 +392,19 @@ func TestHotNewsReconciler_constructRequestUrl(t *testing.T) { spec newsaggregatorv1.HotNewsSpec } + configMapList := v1.ConfigMapList{ + Items: []v1.ConfigMap{ + { + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + newsaggregatorv1.FeedGroupLabel: "true", + }, + }, + Data: map[string]string{"sport": "aaaa"}, + }, + }, + } + tests := []struct { name string fields fields @@ -455,7 +483,12 @@ func TestHotNewsReconciler_constructRequestUrl(t *testing.T) { Scheme: tt.fields.Scheme, serverUrl: serverNewsEndpoint, } - got, err := r.constructRequestUrl(context.Background(), tt.args.spec) + got, err := r.constructRequestUrl(context.Background(), &newsaggregatorv1.HotNews{ + Spec: tt.args.spec, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns", + }, + }, configMapList) if tt.wantErr { assert.NotNil(t, err) @@ -479,6 +512,7 @@ func TestHotNewsReconciler_getFeedGroups(t *testing.T) { Labels: map[string]string{ newsaggregatorv1.FeedGroupLabel: "true", }, + Namespace: "default", }, Data: map[string]string{ "sport": "washingtontimes", @@ -552,7 +586,7 @@ func TestHotNewsReconciler_getFeedGroups(t *testing.T) { Scheme: tt.fields.Scheme, } - got, err := r.getFeedGroups(context.Background()) + got, err := r.retrieveConfigMap(context.Background(), "default") if (err != nil) != tt.wantErr { t.Errorf("getFeedGroups() error = %v, wantErr %v", err, tt.wantErr) return @@ -596,12 +630,13 @@ func TestHotNewsReconciler_processHotNews(t *testing.T) { } FeedGroupsNamespace := "go-gator" - FeedGroupsConfigMapName := "feed-group-source" existingConfigMap := v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Namespace: FeedGroupsNamespace, - Name: FeedGroupsConfigMapName, + Labels: map[string]string{ + newsaggregatorv1.FeedGroupLabel: "true", + }, }, Data: map[string]string{ "sport": "washingtontimes", @@ -730,7 +765,9 @@ func TestHotNewsReconciler_processHotNews(t *testing.T) { Scheme: tt.fields.Scheme, serverUrl: serverNewsEndpoint, } - if err := r.processHotNews(tt.args.ctx, tt.args.hotNews); (err != nil) != tt.wantErr { + if err := r.processHotNews(tt.args.ctx, tt.args.hotNews, v1.ConfigMapList{Items: []v1.ConfigMap{ + existingConfigMap, + }}); (err != nil) != tt.wantErr { t.Errorf("processHotNews() error = %v, wantErr %v", err, tt.wantErr) } }) @@ -878,7 +915,11 @@ func TestHotNewsReconciler_processFeedGroups(t *testing.T) { Client: tt.fields.Client, Scheme: tt.fields.Scheme, } - got, err := r.processFeedGroups(context.Background(), tt.args.spec) + got, err := r.processFeedGroups(&newsaggregatorv1.HotNews{ + Spec: tt.args.spec, + }, v1.ConfigMapList{Items: []v1.ConfigMap{ + existingConfigMap, + }}) if (err != nil) != tt.wantErr { t.Errorf("processFeedGroups() error = %v, wantErr %v", err, tt.wantErr) return From 5a5c3ebfe64626e8774470bd4e8b36f41531ad1b Mon Sep 17 00:00:00 2001 From: Oleksand Matviienko Date: Thu, 26 Sep 2024 10:01:45 +0200 Subject: [PATCH 06/21] docs: Added more description to methods --- operator/internal/controller/hotnews_controller.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/operator/internal/controller/hotnews_controller.go b/operator/internal/controller/hotnews_controller.go index 63a9b7a..67634a9 100644 --- a/operator/internal/controller/hotnews_controller.go +++ b/operator/internal/controller/hotnews_controller.go @@ -351,7 +351,7 @@ func (r *HotNewsReconciler) setSuccessfulStatus(ctx context.Context, hotNews *ne return nil } -// setFailedStatus sets +// setFailedStatus sets the status of hotNews.Condition to Error, and updates hotNews object after that func (r *HotNewsReconciler) setFailedStatus(ctx context.Context, hotNews *newsaggregatorv1.HotNews, reason, message string) error { hotNews.SetCondition(newsaggregatorv1.HotNewsError, newsaggregatorv1.StatusError, reason, message) @@ -509,7 +509,11 @@ func (r *HotNewsReconciler) processFeedGroups(hotNews *newsaggregatorv1.HotNews, return sourcesBuilder.String()[:len(sourcesBuilder.String())-1], nil } -// retrieveConfigMap +// retrieveConfigMap retrieves all config maps from given namespace, based on FeedGroupLabel. +// If config map has this label, it will be retrieved. +// +// This function will be used on the start of HotNewsReconciliation loop, since this config maps will be used often. +// Returns an error either if failed to construct label Requirement, or during listing of config map. func (r *HotNewsReconciler) retrieveConfigMap(ctx context.Context, namespace string) (v1.ConfigMapList, error) { s, err := labels.NewRequirement(newsaggregatorv1.FeedGroupLabel, selection.Exists, nil) if err != nil { From ed471d40d47c4fdebe403b3cf2d02a763ed17751 Mon Sep 17 00:00:00 2001 From: Oleksand Matviienko Date: Thu, 26 Sep 2024 10:17:01 +0200 Subject: [PATCH 07/21] docs: added more documentation for hotnews_controller methods --- .../internal/controller/hotnews_controller.go | 149 +++++++++++++++--- 1 file changed, 128 insertions(+), 21 deletions(-) diff --git a/operator/internal/controller/hotnews_controller.go b/operator/internal/controller/hotnews_controller.go index 67634a9..fd764f7 100644 --- a/operator/internal/controller/hotnews_controller.go +++ b/operator/internal/controller/hotnews_controller.go @@ -83,14 +83,20 @@ type HotNewsReconciler struct { // +kubebuilder:rbac:groups=newsaggregator.teamdev.com,resources=configmaps,verbs=get;list;watch // +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch -// Reconcile is part of the main kubernetes reconciliation loop which aims to -// move the current state of the cluster closer to the desired state +// Reconcile is part of the Kubernetes reconciliation loop, which ensures that +// the current state of the cluster matches the desired state specified by the HotNews resource. // -// This function will be called when a HotNews object is created, updated or deleted -// It will send a request to the news aggregator server to retrieve news with the parameters, -// specified in the HotNews object. -// Additionally, it is watching for changes in the ConfigMap with the feed groups, and in the Feed CRD. -// If there were any changes, it will also affect the HotNews object. +// This function is triggered when a HotNews object is created, updated, or deleted. +// It performs the following tasks: +// 1. Retrieves the HotNews object and corresponding ConfigMap containing feed group information. +// 2. If the HotNews object lacks a finalizer, it adds one to handle cleanup on deletion. +// 3. If the object is marked for deletion, it removes the associated feed reference and updates the object. +// 4. If the object is active, it processes the HotNews by sending a request to the news aggregator server +// and updating the object based on the response (handled in processHotNews). +// 5. The function also tracks any changes in the ConfigMap and Feed CRD, ensuring they reflect in the HotNews object. +// +// Any errors encountered during retrieval, processing, or updating are logged, +// and appropriate status updates are made to reflect the success or failure of the operation. func (r *HotNewsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { logger := log.FromContext(ctx) @@ -211,16 +217,37 @@ func (r *HotNewsReconciler) SetupWithManager(mgr ctrl.Manager, serverUrl string) Complete(r) } -// articles struct is used to parse the response from the news aggregator server +// article struct is used to parse and represent individual news articles received from the news aggregator server. +// The struct fields are annotated with both JSON and XML tags to allow parsing of response data in either format. +// Each article contains details such as the title, publication date, description, publisher/source, and a link to the full article. type article struct { - Title string `json:"title" xml:"title"` - PubDate string `json:"publishedAt" xml:"pubDate"` + // Title The title of the news article. + Title string `json:"title" xml:"title"` + + // PubDate The date and time when the article was published. + PubDate string `json:"publishedAt" xml:"pubDate"` + + // Description A brief summary or description of the article's content. Description string `json:"description" xml:"description"` - Publisher string `xml:"source" json:"Publisher"` - Link string `json:"url" xml:"link"` + + // Publisher The name of the source or publisher of the article. + Publisher string `xml:"source" json:"Publisher"` + + // Link The URL link to the full article. + Link string `json:"url" xml:"link"` } -// processHotNews function updates the HotNews object and returns an error if something goes wrong. +// processHotNews updates the given HotNews object by sending an HTTP GET request to a constructed URL +// and processing the response. +// +// The function constructs the request URL based on the HotNews and ConfigMap data, +// sends the request to an external server, and handles the server response. +// +// If the response is successful, it decodes the JSON response body containing articles, +// processes the data (e.g., titles and total count), and updates the HotNews object's status with this information. +// +// Any errors during the process, such as URL construction, request creation, sending, decoding, or closing +// the response body, are logged and returned as errors. func (r *HotNewsReconciler) processHotNews(ctx context.Context, hotNews *newsaggregatorv1.HotNews, configMapList v1.ConfigMapList) error { logger := log.FromContext(ctx) logger.Info("handling update") @@ -333,8 +360,19 @@ func (r *HotNewsReconciler) constructRequestUrl(ctx context.Context, hotNews *ne return requestUrl.String(), nil } -// setSuccessfulStatus checks if condition should be of type Created or Updated. -// After that, it updates the status of given hot news, than updates hotNews object in K8s Cluster. +// setSuccessfulStatus checks if the condition should be of type "Created" or "Updated". +// It examines the current status of the HotNews object to determine if it has been successfully created before. +// If it has been created, it updates the condition to "Updated"; otherwise, it sets the condition to "Created". +// After setting the appropriate condition, it updates the HotNews object in the Kubernetes cluster. +// +// Parameters: +// - ctx: A context to manage request cancellation and timeouts. +// - hotNews: The HotNews resource whose status needs to be updated. +// - reason: A short string indicating the reason for the status change. +// - message: A descriptive message explaining the status update. +// +// Returns: +// - error: If the update operation on the HotNews object fails, an error is returned. Otherwise, it returns nil. func (r *HotNewsReconciler) setSuccessfulStatus(ctx context.Context, hotNews *newsaggregatorv1.HotNews, reason, message string) error { var condition = newsaggregatorv1.TypeHotNewsCreated @@ -351,7 +389,18 @@ func (r *HotNewsReconciler) setSuccessfulStatus(ctx context.Context, hotNews *ne return nil } -// setFailedStatus sets the status of hotNews.Condition to Error, and updates hotNews object after that +// setFailedStatus sets the status condition of the HotNews resource to "Error". +// It is used when the operation on the HotNews object has failed, and the failure needs to be reflected +// in the status of the resource. +// +// Parameters: +// - ctx: A context to manage request cancellation and timeouts. +// - hotNews: The HotNews resource whose status needs to be updated. +// - reason: A short string indicating the reason for the failure. +// - message: A descriptive message explaining the failure. +// +// Returns: +// - error: If the update operation on the HotNews object fails, an error is returned. Otherwise, it returns nil. func (r *HotNewsReconciler) setFailedStatus(ctx context.Context, hotNews *newsaggregatorv1.HotNews, reason, message string) error { hotNews.SetCondition(newsaggregatorv1.HotNewsError, newsaggregatorv1.StatusError, reason, message) @@ -364,6 +413,12 @@ func (r *HotNewsReconciler) setFailedStatus(ctx context.Context, hotNews *newsag } // setFeedReference sets the owner references for each Feed in the HotNewsSpec.Feeds array. +// +// This function is used to set the feed reference for the feed. It determines if feeds or feedGroups should be used as +// reference, because we support only one of these values. +// It initializes array of feeds which will be passed further for setting owner reference. By default, it is array of +// spec.Feeds, but if feedGroups were not nil (meaning that feeds were nil), it retrieves feed names with +// HotNews method GetFeedGroupNames. func (r *HotNewsReconciler) setFeedReference(ctx context.Context, hotNews newsaggregatorv1.HotNews, configMapList v1.ConfigMapList) error { var feeds = hotNews.Spec.Feeds @@ -379,7 +434,22 @@ func (r *HotNewsReconciler) setFeedReference(ctx context.Context, hotNews newsag return nil } -// setOwnerReferenceForFeeds sets the owner references for each Feed in the HotNewsSpec.Feeds array. +// setOwnerReferenceForFeeds sets the owner references for each Feed in the given array containing feed names. +// +// This method iterates over the list of feeds specified in the `HotNews` custom resource and ensures +// that each `Feed` resource has the current `HotNews` object set as its owner. The purpose of this +// owner reference is to establish a parent-child relationship between `HotNews` and its associated +// feeds, which enables Kubernetes' garbage collection to automatically delete orphaned feeds +// when the `HotNews` object is deleted. +// +// Parameters: +// - ctx: A context to control cancellation signals and request-scoped values across API boundaries. +// - hotNews: The `HotNews` resource containing metadata and spec about the feeds. +// - feeds: A list of feed names (strings) to update with owner references. +// +// Returns: +// - error: Returns an error if there's an issue retrieving or updating a feed. +// Returns an aggregated error if multiple feeds cannot be found. func (r *HotNewsReconciler) setOwnerReferenceForFeeds(ctx context.Context, hotNews newsaggregatorv1.HotNews, feeds []string) error { var errList field.ErrorList @@ -418,6 +488,16 @@ func (r *HotNewsReconciler) setOwnerReferenceForFeeds(ctx context.Context, hotNe } // hasOwnerReference checks if the given Feed already has the provided OwnerReference. +// +// This utility function helps prevent duplication of the owner reference. It compares the UID of the existing +// owner references in the `Feed` object against the `UID` of the provided owner reference. +// +// Parameters: +// - feed: The feed resource to check. +// - ownerRef: The owner reference to check for in the feed's list of owner references. +// +// Returns: +// - bool: Returns `true` if the feed already has the given owner reference, otherwise returns `false`. func hasOwnerReference(feed *newsaggregatorv1.Feed, ownerRef *metav1.OwnerReference) bool { for _, ref := range feed.GetOwnerReferences() { if ref.UID == ownerRef.UID { @@ -428,6 +508,18 @@ func hasOwnerReference(feed *newsaggregatorv1.Feed, ownerRef *metav1.OwnerRefere } // removeFeedReference removes the owner references for each Feed in the HotNewsSpec.Feeds array. +// +// This function is designed to handle the cleanup process when a `HotNews` resource is deleted. It ensures that the +// owner references for the given `HotNews` resource are removed from each associated feed. +// +// Parameters: +// - ctx: A context to control cancellation signals and request-scoped values. +// - hotNews: The `HotNews` object containing details about the feeds to update. +// - configMapList: A list of ConfigMaps that may define feed groups. This is used if `FeedGroups` +// are defined in the `HotNewsSpec`. +// +// Returns: +// - error: Returns an error if any feed fails to be updated. func (r *HotNewsReconciler) removeFeedReference(ctx context.Context, hotNews newsaggregatorv1.HotNews, configMapList v1.ConfigMapList) error { var feeds = hotNews.Spec.Feeds @@ -443,7 +535,18 @@ func (r *HotNewsReconciler) removeFeedReference(ctx context.Context, hotNews new return nil } -// removeOwnerReferenceFromFeeds removes the owner references for each Feed in the given feeds array +// removeOwnerReferenceFromFeeds removes the owner references for each Feed in the given feeds array. +// +// This method iterates through the list of feeds provided, removes any existing owner references +// pointing to the given `HotNews` resource, and updates the feed resource in the cluster. +// +// Parameters: +// - ctx: A context to control cancellation signals and request-scoped values. +// - hotNews: A pointer to the `HotNews` resource from which the owner reference is being removed. +// - feeds: A list of feed names whose owner references will be cleared. +// +// Returns: +// - error: Returns an aggregated error if one or more feeds could not be found or updated. func (r *HotNewsReconciler) removeOwnerReferenceFromFeeds(ctx context.Context, hotNews *newsaggregatorv1.HotNews, feeds []string) error { var errList field.ErrorList @@ -477,7 +580,9 @@ func (r *HotNewsReconciler) removeOwnerReferenceFromFeeds(ctx context.Context, h return nil } -// processFeeds returns a string containing comma-separated feed sources +// processFeeds concatenates the feed sources from the HotNewsSpec.Feeds array into a single +// comma-separated string. It iterates through each feed in the spec and appends it to a +// string builder, followed by a comma. The resulting string is returned without the trailing comma. func (r *HotNewsReconciler) processFeeds(spec newsaggregatorv1.HotNewsSpec) string { var sourcesBuilder strings.Builder @@ -489,8 +594,10 @@ func (r *HotNewsReconciler) processFeeds(spec newsaggregatorv1.HotNewsSpec) stri return sourcesBuilder.String()[:len(sourcesBuilder.String())-1] } -// processFeedGroups function processes feed groups from the ConfigMap and returns a string containing comma-separated -// feed sources +// processFeedGroups processes the feed groups defined in the HotNews object by fetching the corresponding +// feeds from the given ConfigMap list. It checks if each feed group exists in the ConfigMap's data, +// and if found, it concatenates the feed sources into a comma-separated string. If a feed group is not found +// in any ConfigMap, it returns an error indicating the wrong feed group name. func (r *HotNewsReconciler) processFeedGroups(hotNews *newsaggregatorv1.HotNews, configMapList v1.ConfigMapList) (string, error) { var sourcesBuilder strings.Builder From 463b1157c5cfd1040bb73cf7d9c7a0d1ac5b1b5f Mon Sep 17 00:00:00 2001 From: Oleksand Matviienko Date: Thu, 26 Sep 2024 10:32:06 +0200 Subject: [PATCH 08/21] refactor: Using more dynamic StatusSetting func --- operator/internal/controller/hotnews_controller.go | 12 ++++++------ .../internal/controller/hotnews_controller_test.go | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/operator/internal/controller/hotnews_controller.go b/operator/internal/controller/hotnews_controller.go index fd764f7..264ea19 100644 --- a/operator/internal/controller/hotnews_controller.go +++ b/operator/internal/controller/hotnews_controller.go @@ -179,8 +179,7 @@ func (r *HotNewsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct return ctrl.Result{}, err } - err = r.setSuccessfulStatus(ctx, &hotNews, "HotNews successfully updated", - "Successfully Reconciled Hot News object") + err = r.setSuccessfulStatus(ctx, &hotNews, "Successfully Reconciled Hot News object") if err != nil { updateErr := r.setFailedStatus(ctx, &hotNews, "Failed to update hotnews", err.Error()) if updateErr != nil { @@ -252,7 +251,7 @@ func (r *HotNewsReconciler) processHotNews(ctx context.Context, hotNews *newsagg logger := log.FromContext(ctx) logger.Info("handling update") - requestUrl, err := r.constructRequestUrl(ctx, hotNews, configMapList) + requestUrl, err := r.constructRequestUrl(hotNews, configMapList) if err != nil { logger.Error(err, errFailedToConstructRequestUrl) return err @@ -323,7 +322,7 @@ func (r *HotNewsReconciler) processHotNews(ctx context.Context, hotNews *newsagg // Example: // http://server.com/news?keywords=bitcoin&dateFrom=2024-08-05&dateEnd=2024-08-06&sources=abc,bbc // http://server.com/news?keywords=bitcoin&dateFrom=2024-08-05&sources=abc,bbc -func (r *HotNewsReconciler) constructRequestUrl(ctx context.Context, hotNews *newsaggregatorv1.HotNews, +func (r *HotNewsReconciler) constructRequestUrl(hotNews *newsaggregatorv1.HotNews, configMapList v1.ConfigMapList) (string, error) { var requestUrl strings.Builder @@ -373,11 +372,12 @@ func (r *HotNewsReconciler) constructRequestUrl(ctx context.Context, hotNews *ne // // Returns: // - error: If the update operation on the HotNews object fails, an error is returned. Otherwise, it returns nil. -func (r *HotNewsReconciler) setSuccessfulStatus(ctx context.Context, hotNews *newsaggregatorv1.HotNews, - reason, message string) error { +func (r *HotNewsReconciler) setSuccessfulStatus(ctx context.Context, hotNews *newsaggregatorv1.HotNews, message string) error { var condition = newsaggregatorv1.TypeHotNewsCreated + var reason = "Hot news was successfully created" if _, exists := hotNews.Status.Conditions[newsaggregatorv1.HotNewsSuccessfullyCreated]; exists { condition = newsaggregatorv1.TypeHotNewsUpdated + reason = "Hot news was successfully updated" } hotNews.SetCondition(condition, newsaggregatorv1.StatusSuccess, reason, message) diff --git a/operator/internal/controller/hotnews_controller_test.go b/operator/internal/controller/hotnews_controller_test.go index 34d385c..e477f6e 100644 --- a/operator/internal/controller/hotnews_controller_test.go +++ b/operator/internal/controller/hotnews_controller_test.go @@ -483,7 +483,7 @@ func TestHotNewsReconciler_constructRequestUrl(t *testing.T) { Scheme: tt.fields.Scheme, serverUrl: serverNewsEndpoint, } - got, err := r.constructRequestUrl(context.Background(), &newsaggregatorv1.HotNews{ + got, err := r.constructRequestUrl(&newsaggregatorv1.HotNews{ Spec: tt.args.spec, ObjectMeta: metav1.ObjectMeta{ Namespace: "ns", From e7fbc4ae83b660887bd6682d41e48426d8c1320c Mon Sep 17 00:00:00 2001 From: Oleksand Matviienko Date: Thu, 26 Sep 2024 13:43:07 +0200 Subject: [PATCH 09/21] feat: Removed double label check --- operator/internal/controller/handlers.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/operator/internal/controller/handlers.go b/operator/internal/controller/handlers.go index d8e3c80..5f29c01 100644 --- a/operator/internal/controller/handlers.go +++ b/operator/internal/controller/handlers.go @@ -65,10 +65,6 @@ type ConfigMapHandler struct { // UpdateHotNews is a method in ConfigMapHandler, for verifying if object contains particular labels // and, if validation was passed successfully it will trigger Hot News Reconciler. func (r *ConfigMapHandler) UpdateHotNews(ctx context.Context, obj client.Object) []reconcile.Request { - if _, exists := obj.GetLabels()[newsaggregatorv1.FeedGroupLabel]; !exists { - return nil - } - logger := log.FromContext(ctx) var hotNewsList newsaggregatorv1.HotNewsList From 464fe69c999617a0f390bf87dbb3bb16adbd0319 Mon Sep 17 00:00:00 2001 From: Oleksand Matviienko Date: Thu, 26 Sep 2024 13:58:57 +0200 Subject: [PATCH 10/21] feat: Using one handlers for both resources --- operator/internal/controller/handlers.go | 38 ------------------- .../internal/controller/hotnews_controller.go | 3 +- 2 files changed, 1 insertion(+), 40 deletions(-) diff --git a/operator/internal/controller/handlers.go b/operator/internal/controller/handlers.go index 5f29c01..57d6caf 100644 --- a/operator/internal/controller/handlers.go +++ b/operator/internal/controller/handlers.go @@ -50,41 +50,3 @@ func (r *HotNewsHandler) UpdateHotNews(ctx context.Context, obj client.Object) [ return requests } - -// ConfigMapHandler struct is used for triggering HotNews Reconciler whenever Config Map with -// particular labels are updated. -// -// Fields: -// ConfigMapHandler holds a Kubernetes client. -// The client is used to interact with the Kubernetes API, allowing the handler to fetch -// and reconcile HotNews objects. -type ConfigMapHandler struct { - Client client.Client -} - -// UpdateHotNews is a method in ConfigMapHandler, for verifying if object contains particular labels -// and, if validation was passed successfully it will trigger Hot News Reconciler. -func (r *ConfigMapHandler) UpdateHotNews(ctx context.Context, obj client.Object) []reconcile.Request { - logger := log.FromContext(ctx) - var hotNewsList newsaggregatorv1.HotNewsList - - err := r.Client.List(ctx, &hotNewsList, client.InNamespace(obj.GetNamespace())) - - if err != nil { - logger.Error(err, "Error during listing hot news:") - return nil - } - - var requests []reconcile.Request - - for _, hotNews := range hotNewsList.Items { - requests = append(requests, ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: hotNews.Namespace, - Name: hotNews.Name, - }, - }) - } - - return requests -} diff --git a/operator/internal/controller/hotnews_controller.go b/operator/internal/controller/hotnews_controller.go index 264ea19..edd08f8 100644 --- a/operator/internal/controller/hotnews_controller.go +++ b/operator/internal/controller/hotnews_controller.go @@ -199,7 +199,6 @@ func (r *HotNewsReconciler) SetupWithManager(mgr ctrl.Manager, serverUrl string) r.serverUrl = serverUrl hotNewsHandler := &HotNewsHandler{Client: mgr.GetClient()} - configMapHandler := &ConfigMapHandler{Client: mgr.GetClient()} return ctrl.NewControllerManagedBy(mgr). For(&newsaggregatorv1.HotNews{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). @@ -210,7 +209,7 @@ func (r *HotNewsReconciler) SetupWithManager(mgr ctrl.Manager, serverUrl string) ). Watches( &v1.ConfigMap{}, - handler.EnqueueRequestsFromMapFunc(configMapHandler.UpdateHotNews), + handler.EnqueueRequestsFromMapFunc(hotNewsHandler.UpdateHotNews), builder.WithPredicates(ConfigMapStatusPredicate{}), ). Complete(r) From e6e04d14e30b758e98d0bc84df66005561fddbad Mon Sep 17 00:00:00 2001 From: Oleksand Matviienko Date: Sun, 29 Sep 2024 12:44:06 +0200 Subject: [PATCH 11/21] feat: Added validation for webhook, tests, and fixed some logging PR comments --- operator/internal/controller/handlers.go | 112 +++++++++++- operator/internal/controller/handlers_test.go | 164 +++++++++++++++++- .../internal/controller/hotnews_controller.go | 18 +- 3 files changed, 283 insertions(+), 11 deletions(-) diff --git a/operator/internal/controller/handlers.go b/operator/internal/controller/handlers.go index 57d6caf..d636f4b 100644 --- a/operator/internal/controller/handlers.go +++ b/operator/internal/controller/handlers.go @@ -2,11 +2,17 @@ package controller import ( "context" + "fmt" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/validation/field" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "slices" + "strings" newsaggregatorv1 "teamdev.com/go-gator/api/v1" ) @@ -28,10 +34,114 @@ type HotNewsHandler struct { func (r *HotNewsHandler) UpdateHotNews(ctx context.Context, obj client.Object) []reconcile.Request { logger := log.FromContext(ctx) + feed := obj.(*newsaggregatorv1.Feed) + + var configMapList v1.ConfigMapList + err := r.Client.List(ctx, &configMapList) + var hotNewsList newsaggregatorv1.HotNewsList + err = r.Client.List(ctx, &hotNewsList, client.InNamespace(obj.GetNamespace())) + if err != nil { + logger.Error(err, "Error during listing hot news:") + return nil + } + + var requests []reconcile.Request + + for _, hotNews := range hotNewsList.Items { + if r.isFeedUsedInHotNews(feed, hotNews, configMapList) { + requests = append(requests, ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: hotNews.Namespace, + Name: hotNews.Name, + }, + }) + } + } - err := r.Client.List(ctx, &hotNewsList, client.InNamespace(obj.GetNamespace())) + return requests +} + +// isFeedUsedInHotNews checks whether the provided feed is used in the given HotNews object. +// It returns true if the feed's name is either explicitly listed in HotNews.Spec.Feeds +// or if the feed belongs to a group whose name is listed in HotNews' feed groups, +// which are fetched using the GetFeedGroupNames method with the provided configMapList. +// +// Parameters: +// - feed: The Feed object to check. +// - hotNews: The HotNews object containing feed information. +// - configMapList: A list of ConfigMaps used to resolve feed group names. +// +// Returns: +// - bool: True if the feed is used in the HotNews object, false otherwise. +func (r *HotNewsHandler) isFeedUsedInHotNews(feed *newsaggregatorv1.Feed, hotNews newsaggregatorv1.HotNews, + configMapList v1.ConfigMapList) bool { + if slices.Contains(hotNews.Spec.Feeds, feed.Name) { + return true + } + + if slices.Contains(hotNews.GetFeedGroupNames(configMapList), feed.Spec.Name) { + return true + } + + return false +} + +// ConfigMapHandler is a struct responsible for handling and validating ConfigMap objects +// in relation to their association with feeds in a Kubernetes cluster. +// +// Fields: +// - Client: A Kubernetes client used to interact with the Kubernetes API. +// It is used to fetch and validate ConfigMap data and its relation to Feed resources. +type ConfigMapHandler struct { + Client client.Client +} + +// validateConfigMapFeeds validates the feeds listed in a ConfigMap's data field, ensuring that +// the feeds exist in the Kubernetes cluster. +// Afterward, it generates reconcile requests for HotNews resources +// associated with the validated feeds. +// +// Parameters: +// - ctx: The context for carrying deadlines, cancellation signals, and other request-scoped values. +// - obj: The Kubernetes ConfigMap object to be validated. +// +// Returns: +// - []reconcile.Request: A list of reconcile requests generated from HotNews resources. Returns nil if validation fails. +func (r *ConfigMapHandler) validateConfigMapFeeds(ctx context.Context, obj client.Object) []reconcile.Request { + logger := log.FromContext(ctx) + + configMap := obj.(*v1.ConfigMap) + + var errList field.ErrorList + + for _, feedGroupValues := range configMap.Data { + feedNames := strings.Split(feedGroupValues, ",") + + var feed newsaggregatorv1.Feed + for _, feedName := range feedNames { + err := r.Client.Get(ctx, client.ObjectKey{ + Name: feedName, + }, &feed) + + if errors.IsNotFound(err) { + errList = append(errList, field.Invalid(field.NewPath("data"), feedName, fmt.Sprintf( + "feed %s is not found. please, create this feed first", feedName))) + } else { + logger.Error(err, fmt.Sprintf("Failed to get feed %s from ConfigMap (Client Error)", feedName)) + return nil + } + } + } + + if len(errList) > 0 { + logger.Error(errList.ToAggregate(), "Feeds were not found in ConfigMap. Please, create them first") + return nil + } + + var hotNewsList newsaggregatorv1.HotNewsList + err := r.Client.List(ctx, &hotNewsList, client.InNamespace(configMap.GetNamespace())) if err != nil { logger.Error(err, "Error during listing hot news:") return nil diff --git a/operator/internal/controller/handlers_test.go b/operator/internal/controller/handlers_test.go index 5e65cd6..0398d3a 100644 --- a/operator/internal/controller/handlers_test.go +++ b/operator/internal/controller/handlers_test.go @@ -59,7 +59,7 @@ func TestHotNewsHandler_UpdateHotNews(t *testing.T) { }, }, ).Build(), - inputObject: &v1.Pod{ + inputObject: &newsaggregatorv1.Feed{ ObjectMeta: metav1.ObjectMeta{ Name: "test-pod", Namespace: "default", @@ -85,7 +85,7 @@ func TestHotNewsHandler_UpdateHotNews(t *testing.T) { name: "No HotNews in the namespace", existingHotNews: []newsaggregatorv1.HotNews{}, client: fake.NewClientBuilder().WithScheme(scheme).Build(), - inputObject: &v1.Pod{ + inputObject: &newsaggregatorv1.Feed{ ObjectMeta: metav1.ObjectMeta{ Name: "test-pod", Namespace: "default", @@ -104,7 +104,7 @@ func TestHotNewsHandler_UpdateHotNews(t *testing.T) { }, }, }, - inputObject: &v1.Pod{ + inputObject: &newsaggregatorv1.Feed{ ObjectMeta: metav1.ObjectMeta{ Name: "test-pod", Namespace: "default", @@ -143,3 +143,161 @@ func TestHotNewsHandler_UpdateHotNews(t *testing.T) { }) } } + +func TestConfigMapHandler_ValidateConfigMapFeeds(t *testing.T) { + scheme := runtime.NewScheme() + _ = newsaggregatorv1.AddToScheme(scheme) + _ = v1.AddToScheme(scheme) + + tests := []struct { + name string + existingFeeds []newsaggregatorv1.Feed + existingHotNews []newsaggregatorv1.HotNews + configMapData map[string]string + expectedRequests []reconcile.Request + expectedError bool + client client.Client + }{ + { + name: "Successfully generate reconcile requests for all HotNews", + existingFeeds: []newsaggregatorv1.Feed{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "feed1", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "feed2", + }, + }, + }, + existingHotNews: []newsaggregatorv1.HotNews{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "hotnews1", + Namespace: "default", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "hotnews2", + Namespace: "default", + }, + }, + }, + configMapData: map[string]string{ + "feeds": "feed1,feed2", + }, + client: fake.NewClientBuilder().WithScheme(scheme).WithObjects( + &newsaggregatorv1.Feed{ + ObjectMeta: metav1.ObjectMeta{ + Name: "feed1", + }, + }, + &newsaggregatorv1.Feed{ + ObjectMeta: metav1.ObjectMeta{ + Name: "feed2", + }, + }, + &newsaggregatorv1.HotNews{ + ObjectMeta: metav1.ObjectMeta{ + Name: "hotnews1", + Namespace: "default", + }, + }, + &newsaggregatorv1.HotNews{ + ObjectMeta: metav1.ObjectMeta{ + Name: "hotnews2", + Namespace: "default", + }, + }, + ).Build(), + expectedRequests: []reconcile.Request{ + { + NamespacedName: types.NamespacedName{ + Namespace: "default", + Name: "hotnews1", + }, + }, + { + NamespacedName: types.NamespacedName{ + Namespace: "default", + Name: "hotnews2", + }, + }, + }, + expectedError: false, + }, + { + name: "Feeds not found in the cluster", + existingFeeds: []newsaggregatorv1.Feed{}, + existingHotNews: []newsaggregatorv1.HotNews{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "hotnews1", + Namespace: "default", + }, + }, + }, + configMapData: map[string]string{ + "feeds": "nonexistent-feed", + }, + client: fake.NewClientBuilder().WithScheme(scheme).WithObjects( + &newsaggregatorv1.HotNews{ + ObjectMeta: metav1.ObjectMeta{ + Name: "hotnews1", + Namespace: "default", + }, + }, + ).Build(), + expectedRequests: nil, + expectedError: true, + }, + { + name: "Error while listing HotNews", + existingFeeds: []newsaggregatorv1.Feed{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "feed1", + }, + }, + }, + configMapData: map[string]string{ + "feeds": "feed1", + }, + client: fake.NewClientBuilder().WithScheme(scheme). + WithInterceptorFuncs(interceptor.Funcs{ + List: func(ctx context.Context, client client.WithWatch, list client.ObjectList, opts ...client.ListOption) error { + return errors.New("listing error") + }, + }).Build(), + expectedRequests: nil, + expectedError: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + handler := &ConfigMapHandler{ + Client: test.client, + } + + ctx := context.Background() + configMap := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-configmap", + Namespace: "default", + }, + Data: test.configMapData, + } + requests := handler.validateConfigMapFeeds(ctx, configMap) + + if test.expectedError { + assert.Nil(t, requests) + } else { + assert.Equal(t, test.expectedRequests, requests) + } + }) + } +} diff --git a/operator/internal/controller/hotnews_controller.go b/operator/internal/controller/hotnews_controller.go index edd08f8..bf2f8e2 100644 --- a/operator/internal/controller/hotnews_controller.go +++ b/operator/internal/controller/hotnews_controller.go @@ -101,6 +101,7 @@ func (r *HotNewsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct logger := log.FromContext(ctx) var hotNews newsaggregatorv1.HotNews + var configMapList v1.ConfigMapList err := r.Client.Get(ctx, req.NamespacedName, &hotNews) if err != nil { @@ -110,13 +111,15 @@ func (r *HotNewsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct return ctrl.Result{}, err } - configMapList, err := r.retrieveConfigMap(ctx, hotNews.Namespace) - if err != nil { - updateErr := r.setFailedStatus(ctx, &hotNews, "Failed to retrieve config map", err.Error()) - if updateErr != nil { - return ctrl.Result{}, updateErr + if hotNews.Spec.FeedGroups != nil { + configMapList, err = r.retrieveConfigMap(ctx, hotNews.Namespace) + if err != nil { + updateErr := r.setFailedStatus(ctx, &hotNews, "Failed to retrieve config map list", err.Error()) + if updateErr != nil { + return ctrl.Result{}, updateErr + } + return ctrl.Result{}, err } - return ctrl.Result{}, err } if !controllerutil.ContainsFinalizer(&hotNews, HotNewsFinalizer) { @@ -199,6 +202,7 @@ func (r *HotNewsReconciler) SetupWithManager(mgr ctrl.Manager, serverUrl string) r.serverUrl = serverUrl hotNewsHandler := &HotNewsHandler{Client: mgr.GetClient()} + configMapHandler := &ConfigMapHandler{Client: mgr.GetClient()} return ctrl.NewControllerManagedBy(mgr). For(&newsaggregatorv1.HotNews{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). @@ -209,7 +213,7 @@ func (r *HotNewsReconciler) SetupWithManager(mgr ctrl.Manager, serverUrl string) ). Watches( &v1.ConfigMap{}, - handler.EnqueueRequestsFromMapFunc(hotNewsHandler.UpdateHotNews), + handler.EnqueueRequestsFromMapFunc(configMapHandler.validateConfigMapFeeds), builder.WithPredicates(ConfigMapStatusPredicate{}), ). Complete(r) From b770b13514e6453b24155a088739585552aaef38 Mon Sep 17 00:00:00 2001 From: Oleksand Matviienko Date: Sun, 29 Sep 2024 12:45:35 +0200 Subject: [PATCH 12/21] feat: Added fast fail for getFeedGroupNames --- operator/api/v1/hotnews_types.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/operator/api/v1/hotnews_types.go b/operator/api/v1/hotnews_types.go index 9da001d..50e065e 100644 --- a/operator/api/v1/hotnews_types.go +++ b/operator/api/v1/hotnews_types.go @@ -145,6 +145,9 @@ func init() { // GetFeedGroupNames returns all config maps which contain hotNew groups names func (r *HotNews) GetFeedGroupNames(configMapList v1.ConfigMapList) []string { + if configMapList.Items == nil { + return nil + } var feedGroups []string for _, configMap := range configMapList.Items { for _, source := range r.Spec.FeedGroups { From 47813f0d5349cd3ecfa298b78537499e5eaab713 Mon Sep 17 00:00:00 2001 From: Oleksand Matviienko Date: Sun, 29 Sep 2024 12:46:23 +0200 Subject: [PATCH 13/21] refactor: Fixed logging error to more descriptive one --- operator/internal/controller/hotnews_controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operator/internal/controller/hotnews_controller.go b/operator/internal/controller/hotnews_controller.go index bf2f8e2..395f470 100644 --- a/operator/internal/controller/hotnews_controller.go +++ b/operator/internal/controller/hotnews_controller.go @@ -114,7 +114,7 @@ func (r *HotNewsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct if hotNews.Spec.FeedGroups != nil { configMapList, err = r.retrieveConfigMap(ctx, hotNews.Namespace) if err != nil { - updateErr := r.setFailedStatus(ctx, &hotNews, "Failed to retrieve config map list", err.Error()) + updateErr := r.setFailedStatus(ctx, &hotNews, "Failed to retrieve list of config maps", err.Error()) if updateErr != nil { return ctrl.Result{}, updateErr } From d8e7ceeee56d9380f2e8983804cf767a3ad08785 Mon Sep 17 00:00:00 2001 From: Oleksand Matviienko Date: Sun, 29 Sep 2024 12:50:23 +0200 Subject: [PATCH 14/21] docs: Added documentation for Condtitions field in HotNewsStatus --- operator/api/v1/hotnews_types.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/operator/api/v1/hotnews_types.go b/operator/api/v1/hotnews_types.go index 50e065e..eb23f25 100644 --- a/operator/api/v1/hotnews_types.go +++ b/operator/api/v1/hotnews_types.go @@ -95,6 +95,9 @@ type HotNewsStatus struct { // ArticlesTitles contains a list of titles of first 10 articles ArticlesTitles []string `json:"articlesTitles"` + // Conditions are used to describe current state of HotNews. + // In case of errors, this field is updated, indicating that error had occurred. + // If Reconciliation was successful - this fields is also updated, showing that everything had gone well. Conditions map[string]HotNewsConditions `json:"conditions,omitempty"` } From 87235ef0aa7ea8c63130b056887cb8b4093fd563 Mon Sep 17 00:00:00 2001 From: Oleksand Matviienko Date: Sun, 29 Sep 2024 13:19:16 +0200 Subject: [PATCH 15/21] test: Repaired test for GetFeedGroupNames --- operator/api/v1/hotnews_types_test.go | 55 +++++++-------------------- 1 file changed, 13 insertions(+), 42 deletions(-) diff --git a/operator/api/v1/hotnews_types_test.go b/operator/api/v1/hotnews_types_test.go index 7fb2af9..16aa747 100644 --- a/operator/api/v1/hotnews_types_test.go +++ b/operator/api/v1/hotnews_types_test.go @@ -1,15 +1,10 @@ package v1 import ( - "context" - "errors" "github.com/stretchr/testify/assert" v12 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - "sigs.k8s.io/controller-runtime/pkg/client/interceptor" "testing" ) @@ -17,65 +12,48 @@ func TestGetFeedGroupNames(t *testing.T) { scheme := runtime.NewScheme() _ = v12.AddToScheme(scheme) - c := fake.NewClientBuilder().WithScheme(scheme).Build() - var res []string testCases := []struct { name string + input v12.ConfigMapList k8sObjects []runtime.Object expectedGroups []string expectError bool setup func() - client client.Client feedGroups []string }{ { name: "No config maps exist", + input: v12.ConfigMapList{}, k8sObjects: []runtime.Object{}, feedGroups: []string{"group1", "group2"}, expectedGroups: res, - client: c, expectError: false, }, { name: "Config map with no matching feed groups", - k8sObjects: []runtime.Object{ - &v12.ConfigMap{ - ObjectMeta: v1.ObjectMeta{ - Name: "config2", - Labels: map[string]string{FeedGroupLabel: "true"}, - }, - Data: map[string]string{ - "othergroup": "some-data", + input: v12.ConfigMapList{ + Items: []v12.ConfigMap{ + { + ObjectMeta: v1.ObjectMeta{ + Name: "config2", + Labels: map[string]string{FeedGroupLabel: "true"}, + }, + Data: map[string]string{ + "othergroup": "some-data", + }, }, }, }, feedGroups: []string{"group1", "group2"}, expectedGroups: res, - client: c, setup: func() {}, expectError: false, }, - { - name: "Config map listing error", - k8sObjects: nil, - feedGroups: []string{}, - setup: func() { - scheme = runtime.NewScheme() - }, - expectedGroups: res, - client: fake.NewClientBuilder().WithInterceptorFuncs( - interceptor.Funcs{List: func(ctx context.Context, client client.WithWatch, list client.ObjectList, opts ...client.ListOption) error { - return errors.New("error") - }}, - ).Build(), - expectError: true, - }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - k8sClient = tc.client hotNews := &HotNews{ Spec: HotNewsSpec{ @@ -83,14 +61,7 @@ func TestGetFeedGroupNames(t *testing.T) { }, } - result, err := hotNews.GetFeedGroupNames(context.TODO()) - - if tc.expectError { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - + result := hotNews.GetFeedGroupNames(tc.input) assert.Equal(t, tc.expectedGroups, result) }) } From 9bc97109e6d8cdf688f5656f65122e2aee356504 Mon Sep 17 00:00:00 2001 From: Oleksand Matviienko Date: Sun, 29 Sep 2024 15:05:10 +0200 Subject: [PATCH 16/21] feat: added additional check if feedGroups exists in config map --- operator/internal/controller/handlers.go | 25 ++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/operator/internal/controller/handlers.go b/operator/internal/controller/handlers.go index d636f4b..05e6485 100644 --- a/operator/internal/controller/handlers.go +++ b/operator/internal/controller/handlers.go @@ -150,13 +150,26 @@ func (r *ConfigMapHandler) validateConfigMapFeeds(ctx context.Context, obj clien var requests []reconcile.Request for _, hotNews := range hotNewsList.Items { - requests = append(requests, ctrl.Request{ - NamespacedName: types.NamespacedName{ - Namespace: hotNews.Namespace, - Name: hotNews.Name, - }, - }) + if r.feedGroupsExistsInMap(hotNews, configMap) { + requests = append(requests, ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: hotNews.Namespace, + Name: hotNews.Name, + }, + }) + } } return requests } + +// feedGroupsExistsInMap returns true if hotNews contains feedGroups which exists in configMap +func (r *ConfigMapHandler) feedGroupsExistsInMap(hotNews newsaggregatorv1.HotNews, configMap *v1.ConfigMap) bool { + for _, feedGroupValues := range configMap.Data { + if slices.Contains(hotNews.Spec.FeedGroups, feedGroupValues) { + return true + } + } + + return false +} From 4452a20ca0ea1a25a874cccdb0a1eddb4670eb88 Mon Sep 17 00:00:00 2001 From: Oleksand Matviienko Date: Sun, 29 Sep 2024 15:06:29 +0200 Subject: [PATCH 17/21] refactor: Renamed variable in feedGroupsExistsInMap --- operator/internal/controller/handlers.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/operator/internal/controller/handlers.go b/operator/internal/controller/handlers.go index 05e6485..8f72d0a 100644 --- a/operator/internal/controller/handlers.go +++ b/operator/internal/controller/handlers.go @@ -165,8 +165,8 @@ func (r *ConfigMapHandler) validateConfigMapFeeds(ctx context.Context, obj clien // feedGroupsExistsInMap returns true if hotNews contains feedGroups which exists in configMap func (r *ConfigMapHandler) feedGroupsExistsInMap(hotNews newsaggregatorv1.HotNews, configMap *v1.ConfigMap) bool { - for _, feedGroupValues := range configMap.Data { - if slices.Contains(hotNews.Spec.FeedGroups, feedGroupValues) { + for feedGroupName, _ := range configMap.Data { + if slices.Contains(hotNews.Spec.FeedGroups, feedGroupName) { return true } } From db58f519f02408b8b9f4ff39de0401e9be59b8a3 Mon Sep 17 00:00:00 2001 From: Oleksand Matviienko Date: Sun, 29 Sep 2024 15:12:12 +0200 Subject: [PATCH 18/21] refactor: refactor feedGroupExistsInMap func --- operator/internal/controller/handlers.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/operator/internal/controller/handlers.go b/operator/internal/controller/handlers.go index 8f72d0a..00a5fee 100644 --- a/operator/internal/controller/handlers.go +++ b/operator/internal/controller/handlers.go @@ -165,8 +165,8 @@ func (r *ConfigMapHandler) validateConfigMapFeeds(ctx context.Context, obj clien // feedGroupsExistsInMap returns true if hotNews contains feedGroups which exists in configMap func (r *ConfigMapHandler) feedGroupsExistsInMap(hotNews newsaggregatorv1.HotNews, configMap *v1.ConfigMap) bool { - for feedGroupName, _ := range configMap.Data { - if slices.Contains(hotNews.Spec.FeedGroups, feedGroupName) { + for _, feedGroupName := range hotNews.Spec.Feeds { + if _, exists := configMap.Data[feedGroupName]; exists { return true } } From 7741ab0dff4fbe0b1d310383aec87e1b35595a0e Mon Sep 17 00:00:00 2001 From: Oleksand Matviienko Date: Sun, 29 Sep 2024 15:25:14 +0200 Subject: [PATCH 19/21] feat: changed iteration in feeds to feedGroups --- operator/internal/controller/handlers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operator/internal/controller/handlers.go b/operator/internal/controller/handlers.go index 00a5fee..62f7d2a 100644 --- a/operator/internal/controller/handlers.go +++ b/operator/internal/controller/handlers.go @@ -165,7 +165,7 @@ func (r *ConfigMapHandler) validateConfigMapFeeds(ctx context.Context, obj clien // feedGroupsExistsInMap returns true if hotNews contains feedGroups which exists in configMap func (r *ConfigMapHandler) feedGroupsExistsInMap(hotNews newsaggregatorv1.HotNews, configMap *v1.ConfigMap) bool { - for _, feedGroupName := range hotNews.Spec.Feeds { + for _, feedGroupName := range hotNews.Spec.FeedGroups { if _, exists := configMap.Data[feedGroupName]; exists { return true } From 4cb678dc1606d75e22c5a373ad94f99cd2bfbc5a Mon Sep 17 00:00:00 2001 From: Oleksand Matviienko Date: Sun, 29 Sep 2024 15:31:43 +0200 Subject: [PATCH 20/21] fix: Added object cast validation --- operator/internal/controller/handlers.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/operator/internal/controller/handlers.go b/operator/internal/controller/handlers.go index 62f7d2a..1874aba 100644 --- a/operator/internal/controller/handlers.go +++ b/operator/internal/controller/handlers.go @@ -2,6 +2,7 @@ package controller import ( "context" + errs "errors" "fmt" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -111,7 +112,13 @@ type ConfigMapHandler struct { func (r *ConfigMapHandler) validateConfigMapFeeds(ctx context.Context, obj client.Object) []reconcile.Request { logger := log.FromContext(ctx) - configMap := obj.(*v1.ConfigMap) + var configMap *v1.ConfigMap + var ok bool + configMap, ok = obj.(*v1.ConfigMap) + if !ok { + logger.Error(errs.New("object is not a ConfigMap"), "Invalid object type") + return nil + } var errList field.ErrorList From 2fc751a5f2257c9a4ad10528f1041b9219bd8b61 Mon Sep 17 00:00:00 2001 From: qniwerss <73220736+werniq@users.noreply.github.com> Date: Wed, 2 Oct 2024 08:26:37 +0200 Subject: [PATCH 21/21] chore(main): release 1.1.0 --- CHANGELOG.md | 145 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c01e2de..a4adb5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,150 @@ # Changelog +## [1.1.0](https://github.com/werniq/Go-Gator/compare/v1.0.0...v1.1.0) (2024-10-02) + + +### Features + +* Adde labels + namespace in GetFeedGroupNAmes ([1b2eda3](https://github.com/werniq/Go-Gator/commit/1b2eda350ae79a36e19612ba433372b33d7ffef2)) +* added additional check if feedGroups exists in config map ([9bc9710](https://github.com/werniq/Go-Gator/commit/9bc97109e6d8cdf688f5656f65122e2aee356504)) +* Added config map webhook and Feed validation ([1278f79](https://github.com/werniq/Go-Gator/commit/1278f792483044decc28cea240ca1dc61a0fa73f)) +* Added custom predicates for hotnews controller ([83346a7](https://github.com/werniq/Go-Gator/commit/83346a7c1bc82c8e420aa21fe1019efedda957dd)) +* added documentation on how to run job using taskfile, removed news_fetcher directory ([25fc884](https://github.com/werniq/Go-Gator/commit/25fc884052fddad97df0c536d20391e2421c8c53)) +* Added fast fail for getFeedGroupNames ([b770b13](https://github.com/werniq/Go-Gator/commit/b770b13514e6453b24155a088739585552aaef38)) +* Added HotNews conditions struct and related methods ([9fd5fe8](https://github.com/werniq/Go-Gator/commit/9fd5fe824841f50d10511d1243a0e4543cc26c3c)) +* Added initialization of client in separate func ([ee5ff55](https://github.com/werniq/Go-Gator/commit/ee5ff553aafcd88b855d1b780d3cbcb1fd531f0f)) +* Added kubebuilder annotation back, because deepcopyobject was not generating ([1bf1d69](https://github.com/werniq/Go-Gator/commit/1bf1d69b94bd6304e99333c3ba7f2ae3b3653f74)) +* Added more logging to feed reconciler ([1818e3a](https://github.com/werniq/Go-Gator/commit/1818e3ae2985b9a3fbd05a0d0dee73f8e2f6e680)) +* Added more test cases to feed controller ([85f517c](https://github.com/werniq/Go-Gator/commit/85f517c39de4254315a3c0fbf77742a1914c80e9)) +* Added more tests for controllers and webhooks ([ad9fe58](https://github.com/werniq/Go-Gator/commit/ad9fe58e5df2df1badaae5203a1229873ab4ee7a)) +* Added namespace usage during list operation ([b2042ec](https://github.com/werniq/Go-Gator/commit/b2042ec2c3ae16b1cea46b23a5bf343b67f838ba)) +* Added nil check in feedExists and feedGroupsExists ([9805232](https://github.com/werniq/Go-Gator/commit/9805232e86ba95e64e22518d1fca46a1939684ce)) +* added predicate to SetupWithManager func ([f975171](https://github.com/werniq/Go-Gator/commit/f9751712fe02478c2afa8828cff4333e8c6a7a3e)) +* Added proper feed validation on server ([e3807f1](https://github.com/werniq/Go-Gator/commit/e3807f1e1a19d609f4e78a33bab88a48f9567cd0)) +* Added section on how to run using taskfile (docker) ([a4ff2e3](https://github.com/werniq/Go-Gator/commit/a4ff2e31a9d9fd0e86060d122dd2a651d82b638e)) +* added setting of status conditions in hotnews reconciler ([e76d899](https://github.com/werniq/Go-Gator/commit/e76d89974f939a6c79c9e8649c41138f4df4b54a)) +* Added start command to simplify operator launching ([13acfa8](https://github.com/werniq/Go-Gator/commit/13acfa8556f3010068e005473084e9755594dad0)) +* Added test for config_map_webhook package ([2fb96ce](https://github.com/werniq/Go-Gator/commit/2fb96ced36d5f04eec961a45735292cdd9f1ecc8)) +* Added validation for webhook, tests, and fixed some logging PR comments ([e6e04d1](https://github.com/werniq/Go-Gator/commit/e6e04d14e30b758e98d0bc84df66005561fddbad)) +* Added verification which checks if given feed exists ([36d27df](https://github.com/werniq/Go-Gator/commit/36d27dfa6d646de141995c3fa397cd02b4aa995d)) +* Added watching by label in hot news reconciler ([c5830a5](https://github.com/werniq/Go-Gator/commit/c5830a58e518a6f3799fc2d0f21b29c67f9bfc43)) +* Adjusting TLS certs paths ([f4ae1d2](https://github.com/werniq/Go-Gator/commit/f4ae1d2206da60f87b4370b66b8f6e0e970d2f55)) +* Attempt with insecure webhook ([a455da1](https://github.com/werniq/Go-Gator/commit/a455da1bac832510f06a4672c5125bc284b2c9a9)) +* Changed certificate paths ([070cf2e](https://github.com/werniq/Go-Gator/commit/070cf2ee83c7600c68f329833a1a4921f6b3390d)) +* Changed CI to work on every push ([077099d](https://github.com/werniq/Go-Gator/commit/077099dc5307cad4d7d4073330084ddd017dac85)) +* Changed ClusterRole to Role and significantly reduced its permissions ([e818636](https://github.com/werniq/Go-Gator/commit/e81863677aceb3a45114b87389933aab9770cb23)) +* Changed ClusterRole to Role and significantly reduced its permissions. Removed redundant code from config map controller ([1c423a4](https://github.com/werniq/Go-Gator/commit/1c423a43e43ac7d284d308d9ac8e9f98dac3a96e)) +* changed container port and added hostPort with value 443 ([79a84b8](https://github.com/werniq/Go-Gator/commit/79a84b8e76921c47236903c52fd7ab12e33568df)) +* Changed docker image tag ([f742d7a](https://github.com/werniq/Go-Gator/commit/f742d7afe01cb6fa74aa83639dac5ee048c57460)) +* changed iteration in feeds to feedGroups ([7741ab0](https://github.com/werniq/Go-Gator/commit/7741ab0dff4fbe0b1d310383aec87e1b35595a0e)) +* Changed keywords from string to array of strings ([180d5d0](https://github.com/werniq/Go-Gator/commit/180d5d0bc2c8facdd2ba3480769bf7e298f1b5f6)) +* Changed SA name ([653348e](https://github.com/werniq/Go-Gator/commit/653348e124d361615420cd18b13d22838c5f03af)) +* Changed taskfile building section ([171c99d](https://github.com/werniq/Go-Gator/commit/171c99d57203a29c222c3ee3ab691263ca92ea3e)) +* changed volume dir to tmp instead of root ([9f982f7](https://github.com/werniq/Go-Gator/commit/9f982f77aeabfca0cb872834aa7d316bd71cb98e)) +* configured proper date validation in hotnews validating webhook ([5cff3d4](https://github.com/werniq/Go-Gator/commit/5cff3d41435701a6297e801952b87ff0f1176e89)) +* created k8s cluster configuration for pv and pvc ([7497a84](https://github.com/werniq/Go-Gator/commit/7497a84e60674f196a186e71297d44d43b48abda)) +* Created Service account for cron job ([63a6476](https://github.com/werniq/Go-Gator/commit/63a6476cb450ebcf4d3a99636e86b17aefe3e1f0)) +* Directly pointing to invalid field in feed_webhook ([23d6bff](https://github.com/werniq/Go-Gator/commit/23d6bffdd3d6e9fcc580876de48f7d4746809254)) +* Got tests from feature/add-pv-and-cronjob ([e20a774](https://github.com/werniq/Go-Gator/commit/e20a774aa2b95e63c7be05b2ca48be714fd38c14)) +* handled error in manage_sources ([bba2165](https://github.com/werniq/Go-Gator/commit/bba2165f5a8f786c5344032cf5fcba243533fb41)) +* Implemented handler for config map ([4fba78a](https://github.com/werniq/Go-Gator/commit/4fba78ae2290a90d4f899691c839e8d07da4b2c9)) +* Implemented owner reference for feeds in hot news ([2131781](https://github.com/werniq/Go-Gator/commit/2131781205dd79bda5a688c38369e5c14635af8e)) +* Implemented validating webhook for config map ([5aabcc5](https://github.com/werniq/Go-Gator/commit/5aabcc565f983e9cec70cf15cffdd948abed6964)) +* Implemented webhook for config map, which triggers hotnews reconciler ([56e856f](https://github.com/werniq/Go-Gator/commit/56e856f7e68a97032e50492dbde0e68aa70a2fa6)) +* Improving webhook for hot newses ([fee2b59](https://github.com/werniq/Go-Gator/commit/fee2b594a2a660bb7aba14a3e01d398e9009def8)) +* integrated cron job with pv, changed volume hostPath ([8dafeff](https://github.com/werniq/Go-Gator/commit/8dafeffaf10822512d4f818605b701525c05f1e8)) +* Integrated owner reference in feeds with Hot news object ([857c15a](https://github.com/werniq/Go-Gator/commit/857c15aec8dbe1544f2871c2f3375dea5a78901e)) +* Made keywords as array of strings ([bbca7b8](https://github.com/werniq/Go-Gator/commit/bbca7b89e431d3bc6ef04cb14c9222e94ab34fe9)) +* Made keywords as array, updated docker image tag ([cf6c734](https://github.com/werniq/Go-Gator/commit/cf6c73422007b9f78585214d1751697afb9e3876)) +* Making constructRequestUrl more readable ([e237b80](https://github.com/werniq/Go-Gator/commit/e237b80aa935c2591bde5192bc550ac86f0828a7)) +* Merged feature/add-pv-and-cronjob ([d639466](https://github.com/werniq/Go-Gator/commit/d639466af38133bf814034a5bee023daae763d89)) +* Moved GetFeedGroupNames into hotnews_types and added label to config map ([b877bd0](https://github.com/werniq/Go-Gator/commit/b877bd04bc0b091fbf5712d92e5cb2db808eddfa)) +* Moved HotNewsFinalizer to controller package ([f8230eb](https://github.com/werniq/Go-Gator/commit/f8230eb9085aa5fe879c801c2956e809d31d5b5d)) +* Moved logging after if condition ([53f9964](https://github.com/werniq/Go-Gator/commit/53f9964998e669e02e741b98e1c476a9af495691)) +* Moved PVC declaration to PV file ([1aef46f](https://github.com/werniq/Go-Gator/commit/1aef46ff3617e2e75d2751500087e06800cbb9ec)) +* Preparing code for review ([2632383](https://github.com/werniq/Go-Gator/commit/26323834b7fb246064acde4451c54318786e786d)) +* Preparing PR for review v2 ([da61dcf](https://github.com/werniq/Go-Gator/commit/da61dcf3036090f0235c361d7cbfd345afe0efd7)) +* Pulled data from feature/add-pv-and-cronjob ([057d778](https://github.com/werniq/Go-Gator/commit/057d7789d67f00e026c5e01746782a4edba21a94)) +* Pulled data from feature/add-pv-and-cronjob ([011fa6a](https://github.com/werniq/Go-Gator/commit/011fa6a31f140c829219bb6537e2394f1ed6af0e)) +* removed changes from cli pkg ([5dc01b2](https://github.com/werniq/Go-Gator/commit/5dc01b2237c2e2d69de1b5ccaa204ff993020701)) +* Removed double label check ([e7fbc4a](https://github.com/werniq/Go-Gator/commit/e7fbc4ae83b660887bd6682d41e48426d8c1320c)) +* Removed duplicate line ([79d1be7](https://github.com/werniq/Go-Gator/commit/79d1be77e79c0c6f8e0308237a5ea97c737ceec3)) +* Removed feed_group_source ([f06a267](https://github.com/werniq/Go-Gator/commit/f06a267f0175ee332f8566c740290b38897a8b5d)) +* Removed k8sClient from global vars, using local or Reconciler's client ([29d4cf6](https://github.com/werniq/Go-Gator/commit/29d4cf6396559a8237c049ea5e321633e96b13f3)) +* Removed labels check from config map webhook for test ([babae39](https://github.com/werniq/Go-Gator/commit/babae39757094cbcf616740d9b66209f9e0f8c71)) +* Removed permissions from cron job ([ebd024d](https://github.com/werniq/Go-Gator/commit/ebd024d5caf4f3024d6cf56c9319867ef8585e26)) +* removed redundant feed validation ([bdf334c](https://github.com/werniq/Go-Gator/commit/bdf334cfd1ed55f97c98783406f2ae8862ec585d)) +* Removed redundant file deletion ([0f016e6](https://github.com/werniq/Go-Gator/commit/0f016e664ade62063feffca5dc3c39728d9e9b52)) +* Removed redundant hotNews reconciliation trigger in Feed reconciler ([30bb971](https://github.com/werniq/Go-Gator/commit/30bb971c389c32c9d58e712d1fa996e7d5295b52)) +* Removed redundant logging and unused functions ([48a8cb7](https://github.com/werniq/Go-Gator/commit/48a8cb7403b078187665ee2409f302e1e687bf7a)) +* Removed redundant tasks from taskfile ([d42539e](https://github.com/werniq/Go-Gator/commit/d42539e7b75e2f8dc21c70f9f9be3f06f6482d99)) +* Removed redundant test ([7a5d070](https://github.com/werniq/Go-Gator/commit/7a5d070b1c48aa8abf42ef0abb9747393c6ee9b0)) +* Removed redundant tmp directory deletion ([3649826](https://github.com/werniq/Go-Gator/commit/36498263abe547ca4769a5c11cd5f1937ba375c3)) +* Removed redundant validation ([05c6d55](https://github.com/werniq/Go-Gator/commit/05c6d55185f64c1726a9b3198510671aa23de565)) +* Removed redundnat context fields from Create/Update/Delete handlers ([a6a91ab](https://github.com/werniq/Go-Gator/commit/a6a91abb6d894fe6d3b47f7272d133c8ad1b9e73)) +* Removed test case for CI to work ([e1ed3b8](https://github.com/werniq/Go-Gator/commit/e1ed3b8e8f5fa261f960ab5223849ac374386ac6)) +* Removed unused constant ([1033dd0](https://github.com/werniq/Go-Gator/commit/1033dd0b5b86988ae9361be6ebe2fca3989c1b1b)) +* removed unused handleCreate ([8d865e0](https://github.com/werniq/Go-Gator/commit/8d865e0123a0aed5f6aabfa19cb0abd1903d30d4)) +* Removed unused variables ([b3dee6d](https://github.com/werniq/Go-Gator/commit/b3dee6dfb2103a0600b5725d22b7c461d3ea87de)) +* removing deployment from changed files ([c86e210](https://github.com/werniq/Go-Gator/commit/c86e210df194626e1764b989d1747d28a9c8cfbc)) +* renamed FetchingJob to NewsFetchingJob, updating LastFetchedFileDate in docker ([b7aa0df](https://github.com/werniq/Go-Gator/commit/b7aa0dfbf2e92345771d79fdef9cb3c32e91434f)) +* Renamed validateHotNews to validate HotNewsSpec to avoid duplicates ([f9cae64](https://github.com/werniq/Go-Gator/commit/f9cae6431755ae8221941fdb3f5f7f824f7df826)) +* Resolved logging conflicts ([13a75b8](https://github.com/werniq/Go-Gator/commit/13a75b848404b98873d780c249b52d2b011dd047)) +* Resolved merge conflicts ([d06db94](https://github.com/werniq/Go-Gator/commit/d06db94926de804397f03b0ede389dfce2c1a796)) +* Resolved some lint errors ([b9073c4](https://github.com/werniq/Go-Gator/commit/b9073c447c332b6cb89d25364baf9d48844f796f)) +* Resolving merge commits ([8d15222](https://github.com/werniq/Go-Gator/commit/8d152220677a4680e7940ee88e636156749bfa72)) +* Retrieving config maps by label instead of name/namesapce ([8debe35](https://github.com/werniq/Go-Gator/commit/8debe3572354a7027f2ce4849fb1a9f3a5b65f39)) +* Reverted back cron job schedule ([401650c](https://github.com/werniq/Go-Gator/commit/401650cd045c95e64e5595177943c58919867b62)) +* Reverted changes from start_server and removed redundant errors package from manage_sources ([867385b](https://github.com/werniq/Go-Gator/commit/867385be22e3d976132fa6adc52576ad78f30c55)) +* Run make generate and repaired hotnews sample file ([6881872](https://github.com/werniq/Go-Gator/commit/68818722f11d8868d88ee9b2e3b7e1657041a528)) +* Run make manifests and make generate, moved config map to operator-system namespace ([b7369aa](https://github.com/werniq/Go-Gator/commit/b7369aa6a950c0c109470231aba109504509314f)) +* Running both reconcilers together ([9f25936](https://github.com/werniq/Go-Gator/commit/9f25936524b0598a062a4038970a2eb2fc3b6364)) +* separated news fetching job into different docker img, using that image to initialize k8s cron job ([e339d68](https://github.com/werniq/Go-Gator/commit/e339d684f6c2637001ad67c56a146f52e35c2436)) +* Setting HotNews.Status.Conditions in case of errors, or successful Reconciliation ([653bcfa](https://github.com/werniq/Go-Gator/commit/653bcfa7db420505edf6a377b61d39861c3cf528)) +* Storing 3 successful jobs in the history instead of 1 ([af7b3dd](https://github.com/werniq/Go-Gator/commit/af7b3ddcd10a1ebc98f81e5aa87e38bbeb5040bc)) +* Triggering HotNewsReconciler when used in HotNews feed is being triggered ([a30794a](https://github.com/werniq/Go-Gator/commit/a30794adb2e27f47757403b2ecf733917e1f6044)) +* Updated config map webhook functionality ([357900d](https://github.com/werniq/Go-Gator/commit/357900d6285f71cce4e29e6f72ba73ad5fcbc1ad)) +* Updated cron job permissions ([88b275b](https://github.com/werniq/Go-Gator/commit/88b275bb5445da570ba5a0d63e89ecbe12d314d9)) +* Updated docker image version and changed image name in manager/manager.yaml ([3f2799a](https://github.com/werniq/Go-Gator/commit/3f2799a8a4ca117c56e5b6b7fc784d031c66899c)) +* updated dockerfile ([307beb1](https://github.com/werniq/Go-Gator/commit/307beb1a7119c9ca20cd03ce6b2a9f9ef31ccd31)) +* updated image name to go-gator-controller ([bfb91d0](https://github.com/werniq/Go-Gator/commit/bfb91d0975f2530155d5265a40f35a0a6dbeafce)) +* Updated PersistentVolume, PersistentVolumeClaim, CronJob, and ServiceAccount for news fetching. ([bee87d8](https://github.com/werniq/Go-Gator/commit/bee87d87e028faa705d4f039222c2e1999a6d36c)) +* Updated storage path to correct data directory ([fc9eb7c](https://github.com/werniq/Go-Gator/commit/fc9eb7c2c06081d89cc1e83837f48f5cd953492d)) +* Updated tests, deferring file close operation ([1b3c239](https://github.com/werniq/Go-Gator/commit/1b3c2393725861c56d16d00388e7e4e7cf90f4e1)) +* updating all hot news with certain feed, if that feed was updated ([6fbf5ba](https://github.com/werniq/Go-Gator/commit/6fbf5bac7ba0507a2bbe39e20c89388e44dde064)) +* Using clientset instead of client.Client in validating webhook, added more tests ([3d21e16](https://github.com/werniq/Go-Gator/commit/3d21e165aa807a6572c5bcba7b1059478c052c37)) +* Using constants from api pkg ([22569ae](https://github.com/werniq/Go-Gator/commit/22569ae110a6d38b7cf5b9718594bdfb0aecbd99)) +* Using context from Reconciler ([63390a2](https://github.com/werniq/Go-Gator/commit/63390a28226d83f30d959aa348f63203b4e7089a)) +* Using either feeds or feedGroups in HotNews validating webhook ([ebd836e](https://github.com/werniq/Go-Gator/commit/ebd836e5b9479507b8c2bd9a00d06b40e9f07ea9)) +* Using errList in setOwnerReferenceForFeeds everywhere ([7c2415f](https://github.com/werniq/Go-Gator/commit/7c2415f72255a8333ffbacfd1aa00f101196de58)) +* Using errList in validateFeed ([3706197](https://github.com/werniq/Go-Gator/commit/3706197cbaa05ef4415b1fc0bc263d40aa49a1f2)) +* Using Error list in hotnews_webhook ([9b03fc8](https://github.com/werniq/Go-Gator/commit/9b03fc80479b2d2010245e9d780e359e38c2fcc4)) +* Using native client instead clientset in mutating/validating webhooks ([abfb31b](https://github.com/werniq/Go-Gator/commit/abfb31bfc8c15aeff97377e7053a12de431b9d4b)) +* Using one handlers for both resources ([464fe69](https://github.com/werniq/Go-Gator/commit/464fe69c999617a0f390bf87dbb3bb16adbd0319)) +* verifying if feed exists in webhook delete validation ([cb3b3f2](https://github.com/werniq/Go-Gator/commit/cb3b3f2097d01f72f475496714573b6a8c2d0e60)) + + +### Bug Fixes + +* Added object cast validation ([4cb678d](https://github.com/werniq/Go-Gator/commit/4cb678dc1606d75e22c5a373ad94f99cd2bfbc5a)) +* Added setting failed condition during update ([f074053](https://github.com/werniq/Go-Gator/commit/f074053118b8b60678a581454ab9f92deffc3e39)) +* Changed error message in removeOWnerReferenceFromFeeds ([08c9b85](https://github.com/werniq/Go-Gator/commit/08c9b85a35ee6a02ee1ac4872dce9db6933c075c)) +* Fixing certificate paths issues in config map webhook ([bc407be](https://github.com/werniq/Go-Gator/commit/bc407be2d01528c00aa7f69c695d5d37d102864d)) +* Modifying certs paths for config map webhook ([e77aaeb](https://github.com/werniq/Go-Gator/commit/e77aaeb5180d7e7cf9df1142cdf4bfbd0876ba65)) +* Passing storePath in tests ([0714f84](https://github.com/werniq/Go-Gator/commit/0714f84e4d69e1455eb998c6acd646d6720c4d9e)) +* Passing storePath in tests ([2d0b03e](https://github.com/werniq/Go-Gator/commit/2d0b03efcfd68cc36569838f998a07a8fb649001)) +* removed data file from news_fetcher ([0446eb5](https://github.com/werniq/Go-Gator/commit/0446eb5c48df23e1df5587c2e42feb6be8f9e34e)) +* removed duplicate fields in role_binding.yml ([b9f5765](https://github.com/werniq/Go-Gator/commit/b9f5765a78fc4e39ba8d05fd391edce85da01a29)) +* Removed pod.yml, applying all configurations in deployment ([4c6c5fc](https://github.com/werniq/Go-Gator/commit/4c6c5fc86266a0d9d7cd5b247fd6b1419f07959f)) +* removed updating last fetched file date ([a6c9f1f](https://github.com/werniq/Go-Gator/commit/a6c9f1f225ea04dbca1226badc8907e2453574ee)) +* removing unchanged files from git ([15abd8e](https://github.com/werniq/Go-Gator/commit/15abd8e03631967f33874fd547573776ccd9f12e)) +* resolved conflicts ([01a5cb1](https://github.com/werniq/Go-Gator/commit/01a5cb140b483ba697b4a8e0eda3fe75da3b760e)) +* resolved merge conflicts ([a18998a](https://github.com/werniq/Go-Gator/commit/a18998a5b5b2a023f930884a9a4c898b5aa84727)) +* Resolved merge conflicts ([2248c1e](https://github.com/werniq/Go-Gator/commit/2248c1e5479d664e0489b7be5488d0440cd76e2d)) +* resolved panic in json marshalling error test ([fb87908](https://github.com/werniq/Go-Gator/commit/fb87908d8c4297f0dba9242ce9c10751c8fbdab7)) +* resolving merge conflicts from feature/k8s ([0e0b2b3](https://github.com/werniq/Go-Gator/commit/0e0b2b322d371c463229d488a1fbb0d7996ce5dd)) +* Using context.WithTimeout and updated log message in Feed Reconciler ([e6cfa42](https://github.com/werniq/Go-Gator/commit/e6cfa42b8b1e7337046f40ab9c75214142e70477)) + ## 1.0.0 (2024-08-20)