diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 308931a..2742151 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -7,7 +7,7 @@ on: branches: - "main" env: - GOLANG_VERSION: '1.19' + GOLANG_VERSION: '1.20' jobs: unit-tests: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index bc6be66..42d86fb 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -6,7 +6,7 @@ on: - "release-v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+" env: - GOLANG_VERSION: "1.19" + GOLANG_VERSION: "1.20" jobs: release-creation: diff --git a/internal/defaults/defaults.go b/internal/defaults/defaults.go new file mode 100644 index 0000000..e15c347 --- /dev/null +++ b/internal/defaults/defaults.go @@ -0,0 +1,3 @@ +package defaults + +const ConfigMap = "argo-gatewayapi-configmap" diff --git a/internal/utils/common.go b/internal/utils/common.go new file mode 100644 index 0000000..3c1a56d --- /dev/null +++ b/internal/utils/common.go @@ -0,0 +1,119 @@ +package utils + +import ( + "encoding/json" + "strings" + + pluginTypes "github.com/argoproj/argo-rollouts/utils/plugin/types" + log "github.com/sirupsen/logrus" + v1 "k8s.io/api/core/v1" + kubeErrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" +) + +func GetKubeConfig() (*rest.Config, error) { + loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() + // if you want to change the loading rules (which files in which order), you can do so here + configOverrides := &clientcmd.ConfigOverrides{} + // if you want to change override values or bind them to flags, there are methods to help you + kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides) + config, err := kubeConfig.ClientConfig() + if err != nil { + return nil, pluginTypes.RpcError{ErrorString: err.Error()} + } + return config, nil +} + +func SetLogLevel(logLevel string) { + level, err := log.ParseLevel(logLevel) + if err != nil { + log.Fatal(err) + } + log.SetLevel(level) +} + +func CreateFormatter(logFormat string) log.Formatter { + var formatType log.Formatter + switch strings.ToLower(logFormat) { + case "json": + formatType = &log.JSONFormatter{} + case "text": + formatType = &log.TextFormatter{ + FullTimestamp: true, + } + default: + log.Infof("Unknown format: %s. Using text logformat", logFormat) + formatType = &log.TextFormatter{ + FullTimestamp: true, + } + } + return formatType +} + +func CreateConfigMap(name string, options CreateConfigMapOptions) (*v1.ConfigMap, error) { + clientset := options.Clientset + ctx := options.Ctx + configMap, err := clientset.Get(ctx, name, metav1.GetOptions{}) + if err != nil && !kubeErrors.IsNotFound(err) { + return nil, err + } + if err == nil { + return configMap, err + } + configMap.Name = name + configMap, err = clientset.Create(ctx, configMap, metav1.CreateOptions{}) + if err != nil { + return nil, err + } + return configMap, err +} + +func GetConfigMapData(configMap *v1.ConfigMap, configMapKey string, destination any) error { + if configMap.Data != nil && configMap.Data[configMapKey] != "" { + err := json.Unmarshal([]byte(configMap.Data[configMapKey]), &destination) + if err != nil { + return err + } + } + return nil +} + +func UpdateConfigMapData(configMap *v1.ConfigMap, configMapData any, options UpdateConfigMapOptions) error { + clientset := options.Clientset + rawConfigMapData, err := json.Marshal(configMapData) + if err != nil { + return err + } + if configMap.Data == nil { + configMap.Data = make(map[string]string) + } + configMap.Data[options.ConfigMapKey] = string(rawConfigMapData) + _, err = clientset.Update(options.Ctx, configMap, metav1.UpdateOptions{}) + return err +} + +func RemoveIndex[T any](original []T, index int) []T { + result := original[:index] + return append(result, original[index+1:]...) +} + +func DoTransaction(logCtx *log.Entry, taskList ...Task) error { + var err, reverseErr error + for index, task := range taskList { + err = task.Action() + if err == nil { + continue + } + logCtx.Error(err.Error()) + for i := index - 1; i > -1; i-- { + reverseErr = taskList[i].ReverseAction() + if err != nil { + return reverseErr + } + } + return err + } + return nil +} diff --git a/internal/utils/types.go b/internal/utils/types.go new file mode 100644 index 0000000..ec82fde --- /dev/null +++ b/internal/utils/types.go @@ -0,0 +1,23 @@ +package utils + +import ( + "context" + + v1 "k8s.io/client-go/kubernetes/typed/core/v1" +) + +type CreateConfigMapOptions struct { + Clientset v1.ConfigMapInterface + Ctx context.Context +} + +type UpdateConfigMapOptions struct { + Clientset v1.ConfigMapInterface + ConfigMapKey string + Ctx context.Context +} + +type Task struct { + Action func() error + ReverseAction func() error +} diff --git a/main.go b/main.go index f2b3e0b..f43027e 100644 --- a/main.go +++ b/main.go @@ -1,8 +1,8 @@ package main import ( + "github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/internal/utils" "github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/pkg/plugin" - "github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/utils" rolloutsPlugin "github.com/argoproj/argo-rollouts/rollout/trafficrouting/plugin/rpc" goPlugin "github.com/hashicorp/go-plugin" diff --git a/pkg/mocks/plugin.go b/pkg/mocks/plugin.go index 560df0a..061a055 100644 --- a/pkg/mocks/plugin.go +++ b/pkg/mocks/plugin.go @@ -1,6 +1,7 @@ package mocks import ( + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/gateway-api/apis/v1alpha2" @@ -9,18 +10,26 @@ import ( ) const ( - HTTPRoute = "HTTPRoute" - TCPRoute = "TCPRoute" - StableServiceName = "argo-rollouts-stable-service" - CanaryServiceName = "argo-rollouts-canary-service" - HTTPRouteName = "argo-rollouts-http-route" - TCPRouteName = "argo-rollouts-tcp-route" - Namespace = "default" + HTTPRoute = "HTTPRoute" + TCPRoute = "TCPRoute" + StableServiceName = "argo-rollouts-stable-service" + CanaryServiceName = "argo-rollouts-canary-service" + HTTPRouteName = "argo-rollouts-http-route" + TCPRouteName = "argo-rollouts-tcp-route" + Namespace = "default" + ConfigMapName = "test-config" + HTTPManagedRouteName = "test-http-header-route" ) var ( - port = v1beta1.PortNumber(80) - weight int32 = 0 + port = v1beta1.PortNumber(80) + weight int32 = 0 + httpPathMatchType = v1beta1.PathMatchPathPrefix + httpPathMatchValue = "/" + httpPathMatch = v1beta1.HTTPPathMatch{ + Type: &httpPathMatchType, + Value: &httpPathMatchValue, + } ) var HTTPRouteObj = v1beta1.HTTPRoute{ @@ -58,6 +67,11 @@ var HTTPRouteObj = v1beta1.HTTPRoute{ }, }, }, + Matches: []v1beta1.HTTPRouteMatch{ + { + Path: &httpPathMatch, + }, + }, }, }, }, @@ -98,3 +112,10 @@ var TCPPRouteObj = v1alpha2.TCPRoute{ }, }, } + +var ConfigMapObj = v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: ConfigMapName, + Namespace: Namespace, + }, +} diff --git a/pkg/plugin/errors.go b/pkg/plugin/errors.go new file mode 100644 index 0000000..5a60521 --- /dev/null +++ b/pkg/plugin/errors.go @@ -0,0 +1,12 @@ +package plugin + +const ( + GatewayAPIUpdateError = "error updating Gateway API %q: %s" + GatewayAPIManifestError = "httpRoute and tcpRoute fields are empty. tcpRoute or httpRoute should be set" + HTTPRouteFieldIsEmptyError = "httpRoute field is empty. It has to be set to remove managed routes" + InvalidHeaderMatchTypeError = "invalid header match type" + BackendRefWasNotFoundInHTTPRouteError = "backendRef was not found in httpRoute" + BackendRefWasNotFoundInTCPRouteError = "backendRef was not found in tcpRoute" + BackendRefListWasNotFoundInTCPRouteError = "backendRef list was not found in tcpRoute" + ManagedRouteMapEntryDeleteError = "can't delete key %q from managedRouteMap. The key %q is not in the managedRouteMap" +) diff --git a/pkg/plugin/httproute.go b/pkg/plugin/httproute.go index 61e6780..0145de4 100644 --- a/pkg/plugin/httproute.go +++ b/pkg/plugin/httproute.go @@ -4,17 +4,31 @@ import ( "context" "errors" "fmt" + "sync" + "github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/internal/utils" "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" pluginTypes "github.com/argoproj/argo-rollouts/utils/plugin/types" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +const ( + HTTPConfigMapKey = "httpManagedRoutes" +) + +var ( + httpHeaderRoute = HTTPHeaderRoute{ + mutex: sync.Mutex{}, + managedRouteMap: make(map[string]int), + } ) func (r *RpcPlugin) setHTTPRouteWeight(rollout *v1alpha1.Rollout, desiredWeight int32, additionalDestinations []v1alpha1.WeightDestination, gatewayAPIConfig *GatewayAPITrafficRouting) pluginTypes.RpcError { ctx := context.TODO() httpRouteClient := r.HTTPRouteClient if !r.IsTest { - gatewayV1beta1 := r.Client.GatewayV1beta1() + gatewayV1beta1 := r.GatewayAPIClientset.GatewayV1beta1() httpRouteClient = gatewayV1beta1.HTTPRoutes(gatewayAPIConfig.Namespace) } httpRoute, err := httpRouteClient.Get(ctx, gatewayAPIConfig.HTTPRoute, metav1.GetOptions{}) @@ -26,20 +40,14 @@ func (r *RpcPlugin) setHTTPRouteWeight(rollout *v1alpha1.Rollout, desiredWeight canaryServiceName := rollout.Spec.Strategy.Canary.CanaryService stableServiceName := rollout.Spec.Strategy.Canary.StableService routeRuleList := HTTPRouteRuleList(httpRoute.Spec.Rules) - backendRefList, err := getBackendRefList[HTTPBackendRefList](routeRuleList) - if err != nil { - return pluginTypes.RpcError{ - ErrorString: err.Error(), - } - } - canaryBackendRef, err := getBackendRef[*HTTPBackendRef](canaryServiceName, backendRefList) + canaryBackendRef, err := getBackendRef[*HTTPBackendRef, *HTTPRouteRule](canaryServiceName, routeRuleList) if err != nil { return pluginTypes.RpcError{ ErrorString: err.Error(), } } canaryBackendRef.Weight = &desiredWeight - stableBackendRef, err := getBackendRef[*HTTPBackendRef](stableServiceName, backendRefList) + stableBackendRef, err := getBackendRef[*HTTPBackendRef, *HTTPRouteRule](stableServiceName, routeRuleList) if err != nil { return pluginTypes.RpcError{ ErrorString: err.Error(), @@ -52,32 +60,307 @@ func (r *RpcPlugin) setHTTPRouteWeight(rollout *v1alpha1.Rollout, desiredWeight r.UpdatedHTTPRouteMock = updatedHTTPRoute } if err != nil { - msg := fmt.Sprintf("Error updating Gateway API %q: %s", httpRoute.GetName(), err) + msg := fmt.Sprintf(GatewayAPIUpdateError, httpRoute.GetName(), err) r.LogCtx.Error(msg) } return pluginTypes.RpcError{} } -func (r HTTPRouteRuleList) Iterator() (GatewayAPIRouteRuleIterator[HTTPBackendRefList], bool) { - ruleList := r - index := 0 - next := func() (HTTPBackendRefList, bool) { - if len(ruleList) == index { - return nil, false +func (r *RpcPlugin) setHTTPHeaderRoute(rollout *v1alpha1.Rollout, headerRouting *v1alpha1.SetHeaderRoute, gatewayAPIConfig *GatewayAPITrafficRouting) pluginTypes.RpcError { + if headerRouting.Match == nil { + managedRouteList := []v1alpha1.MangedRoutes{ + { + Name: headerRouting.Name, + }, + } + return r.removeHTTPManagedRoutes(managedRouteList, gatewayAPIConfig) + } + ctx := context.TODO() + httpRouteClient := r.HTTPRouteClient + managedRouteMap := httpHeaderRoute.managedRouteMap + clientset := r.TestClientset + if !r.IsTest { + gatewayV1beta1 := r.GatewayAPIClientset.GatewayV1beta1() + httpRouteClient = gatewayV1beta1.HTTPRoutes(gatewayAPIConfig.Namespace) + clientset = r.Clientset.CoreV1().ConfigMaps(gatewayAPIConfig.Namespace) + } + configMap, err := utils.CreateConfigMap(gatewayAPIConfig.ConfigMap, utils.CreateConfigMapOptions{ + Clientset: clientset, + Ctx: ctx, + }) + if err != nil { + return pluginTypes.RpcError{ + ErrorString: err.Error(), + } + } + err = utils.GetConfigMapData(configMap, HTTPConfigMapKey, &managedRouteMap) + if err != nil { + return pluginTypes.RpcError{ + ErrorString: err.Error(), + } + } + httpRoute, err := httpRouteClient.Get(ctx, gatewayAPIConfig.HTTPRoute, metav1.GetOptions{}) + if err != nil { + return pluginTypes.RpcError{ + ErrorString: err.Error(), + } + } + canaryServiceName := v1beta1.ObjectName(rollout.Spec.Strategy.Canary.CanaryService) + stableServiceName := rollout.Spec.Strategy.Canary.StableService + canaryServiceKind := v1beta1.Kind("Service") + canaryServiceGroup := v1beta1.Group("") + httpHeaderRouteRuleList, rpcError := getHTTPHeaderRouteRuleList(headerRouting) + if rpcError.HasError() { + return rpcError + } + httpRouteRuleList := HTTPRouteRuleList(httpRoute.Spec.Rules) + backendRefNameList := []string{string(canaryServiceName), stableServiceName} + httpRouteRule, err := getRouteRule[*HTTPBackendRef, *HTTPRouteRule](httpRouteRuleList, backendRefNameList...) + if err != nil { + return pluginTypes.RpcError{ + ErrorString: err.Error(), } - backendRefList := HTTPBackendRefList(ruleList[index].BackendRefs) - index = index + 1 - return backendRefList, len(ruleList) > index } - return next, len(ruleList) != index + var canaryBackendRef *HTTPBackendRef + for i := 0; i < len(httpRouteRule.BackendRefs); i++ { + backendRef := httpRouteRule.BackendRefs[i] + if canaryServiceName == backendRef.Name { + canaryBackendRef = (*HTTPBackendRef)(&backendRef) + } + } + httpHeaderRouteRule := v1beta1.HTTPRouteRule{ + Matches: []v1beta1.HTTPRouteMatch{}, + BackendRefs: []v1beta1.HTTPBackendRef{}, + } + for i := 0; i < len(httpRouteRule.Matches); i++ { + httpHeaderRouteRule.Matches = append(httpHeaderRouteRule.Matches, v1beta1.HTTPRouteMatch{ + Path: httpRouteRule.Matches[i].Path, + Headers: httpHeaderRouteRuleList, + QueryParams: httpRouteRule.Matches[i].QueryParams, + }) + } + httpHeaderRouteRule.BackendRefs = []v1beta1.HTTPBackendRef{ + { + BackendRef: v1beta1.BackendRef{ + BackendObjectReference: v1beta1.BackendObjectReference{ + Group: &canaryServiceGroup, + Kind: &canaryServiceKind, + Name: canaryServiceName, + Port: canaryBackendRef.Port, + }, + }, + }, + } + httpRouteRuleList = append(httpRouteRuleList, httpHeaderRouteRule) + oldHTTPRuleList := httpRoute.Spec.Rules + httpRoute.Spec.Rules = httpRouteRuleList + oldConfigMapData := make(map[string]int) + err = utils.GetConfigMapData(configMap, HTTPConfigMapKey, &oldConfigMapData) + if err != nil { + return pluginTypes.RpcError{ + ErrorString: err.Error(), + } + } + taskList := []utils.Task{ + { + Action: func() error { + updatedHTTPRoute, err := httpRouteClient.Update(ctx, httpRoute, metav1.UpdateOptions{}) + if r.IsTest { + r.UpdatedHTTPRouteMock = updatedHTTPRoute + } + if err != nil { + return err + } + return nil + }, + ReverseAction: func() error { + httpRoute.Spec.Rules = oldHTTPRuleList + updatedHTTPRoute, err := httpRouteClient.Update(ctx, httpRoute, metav1.UpdateOptions{}) + if r.IsTest { + r.UpdatedHTTPRouteMock = updatedHTTPRoute + } + if err != nil { + return err + } + return nil + }, + }, + { + Action: func() error { + managedRouteMap[headerRouting.Name] = len(httpRouteRuleList) - 1 + err = utils.UpdateConfigMapData(configMap, managedRouteMap, utils.UpdateConfigMapOptions{ + Clientset: clientset, + ConfigMapKey: HTTPConfigMapKey, + Ctx: ctx, + }) + if err != nil { + return err + } + return nil + }, + ReverseAction: func() error { + err = utils.UpdateConfigMapData(configMap, oldConfigMapData, utils.UpdateConfigMapOptions{ + Clientset: clientset, + ConfigMapKey: HTTPConfigMapKey, + Ctx: ctx, + }) + if err != nil { + return err + } + return nil + }, + }, + } + err = utils.DoTransaction(r.LogCtx, taskList...) + if err != nil { + return pluginTypes.RpcError{ + ErrorString: err.Error(), + } + } + return pluginTypes.RpcError{} } -func (r HTTPRouteRuleList) Error() error { - return errors.New("backendRefs was not found in httpRoute") +func getHTTPHeaderRouteRuleList(headerRouting *v1alpha1.SetHeaderRoute) ([]v1beta1.HTTPHeaderMatch, pluginTypes.RpcError) { + httpHeaderRouteRuleList := []v1beta1.HTTPHeaderMatch{} + for _, headerRule := range headerRouting.Match { + httpHeaderRouteRule := v1beta1.HTTPHeaderMatch{ + Name: v1beta1.HTTPHeaderName(headerRule.HeaderName), + } + switch { + case headerRule.HeaderValue.Exact != "": + headerMatchType := v1beta1.HeaderMatchExact + httpHeaderRouteRule.Type = &headerMatchType + httpHeaderRouteRule.Value = headerRule.HeaderValue.Exact + case headerRule.HeaderValue.Prefix != "": + headerMatchType := v1beta1.HeaderMatchRegularExpression + httpHeaderRouteRule.Type = &headerMatchType + httpHeaderRouteRule.Value = headerRule.HeaderValue.Prefix + ".*" + case headerRule.HeaderValue.Regex != "": + headerMatchType := v1beta1.HeaderMatchRegularExpression + httpHeaderRouteRule.Type = &headerMatchType + httpHeaderRouteRule.Value = headerRule.HeaderValue.Regex + default: + return nil, pluginTypes.RpcError{ + ErrorString: InvalidHeaderMatchTypeError, + } + } + httpHeaderRouteRuleList = append(httpHeaderRouteRuleList, httpHeaderRouteRule) + } + return httpHeaderRouteRuleList, pluginTypes.RpcError{} } -func (r HTTPBackendRefList) Iterator() (GatewayAPIBackendRefIterator[*HTTPBackendRef], bool) { - backendRefList := r +func (r *RpcPlugin) removeHTTPManagedRoutes(managedRouteNameList []v1alpha1.MangedRoutes, gatewayAPIConfig *GatewayAPITrafficRouting) pluginTypes.RpcError { + ctx := context.TODO() + httpRouteClient := r.HTTPRouteClient + clientset := r.TestClientset + managedRouteMap := httpHeaderRoute.managedRouteMap + if !r.IsTest { + gatewayV1beta1 := r.GatewayAPIClientset.GatewayV1beta1() + httpRouteClient = gatewayV1beta1.HTTPRoutes(gatewayAPIConfig.Namespace) + clientset = r.Clientset.CoreV1().ConfigMaps(gatewayAPIConfig.Namespace) + } + configMap, err := clientset.Get(ctx, gatewayAPIConfig.ConfigMap, metav1.GetOptions{}) + if err != nil { + return pluginTypes.RpcError{ + ErrorString: err.Error(), + } + } + err = utils.GetConfigMapData(configMap, HTTPConfigMapKey, &managedRouteMap) + if err != nil { + return pluginTypes.RpcError{ + ErrorString: err.Error(), + } + } + httpRoute, err := httpRouteClient.Get(ctx, gatewayAPIConfig.HTTPRoute, metav1.GetOptions{}) + if err != nil { + return pluginTypes.RpcError{ + ErrorString: err.Error(), + } + } + httpRouteRuleList := HTTPRouteRuleList(httpRoute.Spec.Rules) + for _, managedRoute := range managedRouteNameList { + managedRouteName := managedRoute.Name + _, isOk := managedRouteMap[managedRouteName] + if !isOk { + r.LogCtx.Logger.Info(fmt.Sprintf("%s is not in httpHeaderManagedRouteMap", managedRouteName)) + continue + } + httpRouteRuleList, err = removeManagedRouteEntry(managedRouteMap, httpRouteRuleList, managedRouteName) + if err != nil { + return pluginTypes.RpcError{ + ErrorString: err.Error(), + } + } + } + oldHTTPRuleList := httpRoute.Spec.Rules + httpRoute.Spec.Rules = httpRouteRuleList + oldConfigMapData := make(map[string]int) + err = utils.GetConfigMapData(configMap, HTTPConfigMapKey, &oldConfigMapData) + if err != nil { + return pluginTypes.RpcError{ + ErrorString: err.Error(), + } + } + taskList := []utils.Task{ + { + Action: func() error { + updatedHTTPRoute, err := httpRouteClient.Update(ctx, httpRoute, metav1.UpdateOptions{}) + if r.IsTest { + r.UpdatedHTTPRouteMock = updatedHTTPRoute + } + if err != nil { + return err + } + return nil + }, + ReverseAction: func() error { + httpRoute.Spec.Rules = oldHTTPRuleList + updatedHTTPRoute, err := httpRouteClient.Update(ctx, httpRoute, metav1.UpdateOptions{}) + if r.IsTest { + r.UpdatedHTTPRouteMock = updatedHTTPRoute + } + if err != nil { + return err + } + return nil + }, + }, + { + Action: func() error { + err = utils.UpdateConfigMapData(configMap, managedRouteMap, utils.UpdateConfigMapOptions{ + Clientset: clientset, + ConfigMapKey: HTTPConfigMapKey, + Ctx: ctx, + }) + if err != nil { + return err + } + return nil + }, + ReverseAction: func() error { + err = utils.UpdateConfigMapData(configMap, oldConfigMapData, utils.UpdateConfigMapOptions{ + Clientset: clientset, + ConfigMapKey: HTTPConfigMapKey, + Ctx: ctx, + }) + if err != nil { + return err + } + return nil + }, + }, + } + err = utils.DoTransaction(r.LogCtx, taskList...) + if err != nil { + return pluginTypes.RpcError{ + ErrorString: err.Error(), + } + } + return pluginTypes.RpcError{} +} + +func (r *HTTPRouteRule) Iterator() (GatewayAPIRouteRuleIterator[*HTTPBackendRef], bool) { + backendRefList := r.BackendRefs index := 0 next := func() (*HTTPBackendRef, bool) { if len(backendRefList) == index { @@ -90,8 +373,22 @@ func (r HTTPBackendRefList) Iterator() (GatewayAPIBackendRefIterator[*HTTPBacken return next, len(backendRefList) > index } -func (r HTTPBackendRefList) Error() error { - return errors.New("backendRef was not found in httpRoute") +func (r HTTPRouteRuleList) Iterator() (GatewayAPIRouteRuleListIterator[*HTTPBackendRef, *HTTPRouteRule], bool) { + routeRuleList := r + index := 0 + next := func() (*HTTPRouteRule, bool) { + if len(routeRuleList) == index { + return nil, false + } + routeRule := (*HTTPRouteRule)(&routeRuleList[index]) + index++ + return routeRule, len(routeRuleList) > index + } + return next, len(routeRuleList) != index +} + +func (r HTTPRouteRuleList) Error() error { + return errors.New(BackendRefWasNotFoundInHTTPRouteError) } func (r *HTTPBackendRef) GetName() string { diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go index d79d0eb..773ba4e 100644 --- a/pkg/plugin/plugin.go +++ b/pkg/plugin/plugin.go @@ -2,20 +2,19 @@ package plugin import ( "encoding/json" + "fmt" - "github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/utils" + "github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/internal/defaults" + "github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/internal/utils" "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" pluginTypes "github.com/argoproj/argo-rollouts/utils/plugin/types" + "k8s.io/client-go/kubernetes" gatewayApiClientset "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned" ) const ( - // Type holds this controller type Type = "GatewayAPI" PluginName = "argoproj-labs/gatewayAPI" - - GatewayAPIUpdateError = "GatewayAPIUpdateError" - GatewayAPIManifestError = "httpRoute and tcpRoute fields are empty. tcpRoute or httpRoute should be set" ) func (r *RpcPlugin) InitPlugin() pluginTypes.RpcError { @@ -28,13 +27,20 @@ func (r *RpcPlugin) InitPlugin() pluginTypes.RpcError { ErrorString: err.Error(), } } - clientset, err := gatewayApiClientset.NewForConfig(kubeConfig) + gatewayAPIClientset, err := gatewayApiClientset.NewForConfig(kubeConfig) + if err != nil { + return pluginTypes.RpcError{ + ErrorString: err.Error(), + } + } + clientset, err := kubernetes.NewForConfig(kubeConfig) if err != nil { return pluginTypes.RpcError{ ErrorString: err.Error(), } } - r.Client = clientset + r.GatewayAPIClientset = gatewayAPIClientset + r.Clientset = clientset return pluginTypes.RpcError{} } @@ -43,25 +49,52 @@ func (r *RpcPlugin) UpdateHash(rollout *v1alpha1.Rollout, canaryHash, stableHash } func (r *RpcPlugin) SetWeight(rollout *v1alpha1.Rollout, desiredWeight int32, additionalDestinations []v1alpha1.WeightDestination) pluginTypes.RpcError { - gatewayAPIConfig := GatewayAPITrafficRouting{} - err := json.Unmarshal(rollout.Spec.Strategy.Canary.TrafficRouting.Plugins[PluginName], &gatewayAPIConfig) + gatewayAPIConfig, err := getGatewayAPITracfficRoutingConfig(rollout) if err != nil { return pluginTypes.RpcError{ ErrorString: err.Error(), } } - if gatewayAPIConfig.HTTPRoute != "" { - return r.setHTTPRouteWeight(rollout, desiredWeight, additionalDestinations, &gatewayAPIConfig) - } - if gatewayAPIConfig.TCPRoute != "" { - return r.setTCPRouteWeight(rollout, desiredWeight, additionalDestinations, &gatewayAPIConfig) - } - return pluginTypes.RpcError{ - ErrorString: GatewayAPIManifestError, + switch { + case gatewayAPIConfig.HTTPRoute != "": + rpcError := r.setHTTPRouteWeight(rollout, desiredWeight, additionalDestinations, &gatewayAPIConfig) + if rpcError.HasError() { + return rpcError + } + case gatewayAPIConfig.TCPRoute != "": + rpcError := r.setTCPRouteWeight(rollout, desiredWeight, additionalDestinations, &gatewayAPIConfig) + if rpcError.HasError() { + return rpcError + } + default: + return pluginTypes.RpcError{ + ErrorString: GatewayAPIManifestError, + } } + return pluginTypes.RpcError{} } func (r *RpcPlugin) SetHeaderRoute(rollout *v1alpha1.Rollout, headerRouting *v1alpha1.SetHeaderRoute) pluginTypes.RpcError { + gatewayAPIConfig, err := getGatewayAPITracfficRoutingConfig(rollout) + if err != nil { + return pluginTypes.RpcError{ + ErrorString: err.Error(), + } + } + switch { + case gatewayAPIConfig.HTTPRoute != "": + httpHeaderRoute.mutex.Lock() + rpcError := r.setHTTPHeaderRoute(rollout, headerRouting, &gatewayAPIConfig) + if rpcError.HasError() { + httpHeaderRoute.mutex.Unlock() + return rpcError + } + httpHeaderRoute.mutex.Unlock() + default: + return pluginTypes.RpcError{ + ErrorString: HTTPRouteFieldIsEmptyError, + } + } return pluginTypes.RpcError{} } @@ -74,6 +107,26 @@ func (r *RpcPlugin) VerifyWeight(rollout *v1alpha1.Rollout, desiredWeight int32, } func (r *RpcPlugin) RemoveManagedRoutes(rollout *v1alpha1.Rollout) pluginTypes.RpcError { + gatewayAPIConfig, err := getGatewayAPITracfficRoutingConfig(rollout) + if err != nil { + return pluginTypes.RpcError{ + ErrorString: err.Error(), + } + } + switch { + case gatewayAPIConfig.HTTPRoute != "": + httpHeaderRoute.mutex.Lock() + rpcError := r.removeHTTPManagedRoutes(rollout.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes, &gatewayAPIConfig) + if rpcError.HasError() { + httpHeaderRoute.mutex.Unlock() + return rpcError + } + httpHeaderRoute.mutex.Unlock() + default: + return pluginTypes.RpcError{ + ErrorString: HTTPRouteFieldIsEmptyError, + } + } return pluginTypes.RpcError{} } @@ -81,30 +134,68 @@ func (r *RpcPlugin) Type() string { return Type } -func getBackendRefList[T GatewayAPIBackendRefList](ruleList GatewayAPIRouteRuleCollection[T]) (T, error) { - var backendRefList T - for next, hasNext := ruleList.Iterator(); hasNext; { - backendRefList, hasNext = next() - if backendRefList == nil { +func getGatewayAPITracfficRoutingConfig(rollout *v1alpha1.Rollout) (GatewayAPITrafficRouting, error) { + gatewayAPIConfig := GatewayAPITrafficRouting{ + ConfigMap: defaults.ConfigMap, + } + err := json.Unmarshal(rollout.Spec.Strategy.Canary.TrafficRouting.Plugins[PluginName], &gatewayAPIConfig) + return gatewayAPIConfig, err +} + +func getRouteRule[T1 GatewayAPIBackendRef, T2 GatewayAPIRouteRule[T1], T3 GatewayAPIRouteRuleList[T1, T2]](routeRuleList T3, backendRefNameList ...string) (T2, error) { + var backendRef T1 + var routeRule T2 + isFound := false + for next, hasNext := routeRuleList.Iterator(); hasNext; { + routeRule, hasNext = next() + _, hasNext := routeRule.Iterator() + if !hasNext { continue } - return backendRefList, nil + for _, backendRefName := range backendRefNameList { + isFound = false + for next, hasNext := routeRule.Iterator(); hasNext; { + backendRef, hasNext = next() + if backendRefName == backendRef.GetName() { + isFound = true + continue + } + } + if !isFound { + break + } + } + return routeRule, nil } - return nil, ruleList.Error() + return nil, routeRuleList.Error() } -func getBackendRef[T GatewayAPIBackendRef](backendRefName string, backendRefList GatewayAPIBackendRefCollection[T]) (T, error) { - var selectedService, backendRef T - for next, hasNext := backendRefList.Iterator(); hasNext; { - backendRef, hasNext = next() - nameOfCurrentService := backendRef.GetName() - if nameOfCurrentService == backendRefName { - selectedService = backendRef - break +func getBackendRef[T1 GatewayAPIBackendRef, T2 GatewayAPIRouteRule[T1], T3 GatewayAPIRouteRuleList[T1, T2]](backendRefName string, routeRuleList T3) (T1, error) { + var backendRef T1 + var routeRule T2 + for next, hasNext := routeRuleList.Iterator(); hasNext; { + routeRule, hasNext = next() + for next, hasNext := routeRule.Iterator(); hasNext; { + backendRef, hasNext = next() + if backendRefName == backendRef.GetName() { + return backendRef, nil + } } } - if selectedService == nil { - return nil, backendRefList.Error() + return nil, routeRuleList.Error() +} + +func removeManagedRouteEntry(managedRouteMap map[string]int, routeRuleList HTTPRouteRuleList, managedRouteName string) (HTTPRouteRuleList, error) { + managedRouteIndex, isOk := managedRouteMap[managedRouteName] + if !isOk { + return nil, fmt.Errorf(ManagedRouteMapEntryDeleteError, managedRouteName, managedRouteName) + } + delete(managedRouteMap, managedRouteName) + for key, value := range managedRouteMap { + if value > managedRouteIndex { + managedRouteMap[key]-- + } } - return selectedService, nil + routeRuleList = utils.RemoveIndex(routeRuleList, managedRouteIndex) + return routeRuleList, nil } diff --git a/pkg/plugin/plugin_test.go b/pkg/plugin/plugin_test.go index 9d5baf6..3651774 100644 --- a/pkg/plugin/plugin_test.go +++ b/pkg/plugin/plugin_test.go @@ -6,12 +6,14 @@ import ( "testing" "time" + "github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/internal/utils" "github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/pkg/mocks" - "github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/utils" "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" rolloutsPlugin "github.com/argoproj/argo-rollouts/rollout/trafficrouting/plugin/rpc" "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" + "sigs.k8s.io/gateway-api/apis/v1beta1" log "github.com/sirupsen/logrus" gwFake "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned/fake" @@ -36,8 +38,9 @@ func TestRunSuccessfully(t *testing.T) { rpcPluginImp := &RpcPlugin{ LogCtx: logCtx, IsTest: true, - HTTPRouteClient: gwFake.NewSimpleClientset(&(mocks.HTTPRouteObj)).GatewayV1beta1().HTTPRoutes(mocks.Namespace), - TCPRouteClient: gwFake.NewSimpleClientset(&(mocks.TCPPRouteObj)).GatewayV1alpha2().TCPRoutes(mocks.Namespace), + HTTPRouteClient: gwFake.NewSimpleClientset(&mocks.HTTPRouteObj).GatewayV1beta1().HTTPRoutes(mocks.Namespace), + TCPRouteClient: gwFake.NewSimpleClientset(&mocks.TCPPRouteObj).GatewayV1alpha2().TCPRoutes(mocks.Namespace), + TestClientset: fake.NewSimpleClientset(&mocks.ConfigMapObj).CoreV1().ConfigMaps(mocks.Namespace), } // pluginMap is the map of plugins we can dispense. @@ -118,6 +121,36 @@ func TestRunSuccessfully(t *testing.T) { assert.Equal(t, 100-desiredWeight, *(rpcPluginImp.UpdatedTCPRouteMock.Spec.Rules[0].BackendRefs[0].Weight)) assert.Equal(t, desiredWeight, *(rpcPluginImp.UpdatedTCPRouteMock.Spec.Rules[0].BackendRefs[1].Weight)) }) + t.Run("SetHTTPHeaderRoute", func(t *testing.T) { + headerName := "X-Test" + headerValue := "test" + headerValueType := v1beta1.HeaderMatchRegularExpression + prefixedHeaderValue := headerValue + ".*" + headerMatch := v1alpha1.StringMatch{ + Prefix: headerValue, + } + headerRouting := v1alpha1.SetHeaderRoute{ + Name: mocks.HTTPManagedRouteName, + Match: []v1alpha1.HeaderRoutingMatch{ + { + HeaderName: headerName, + HeaderValue: &headerMatch, + }, + }, + } + err := pluginInstance.SetHeaderRoute(newRollout(mocks.StableServiceName, mocks.CanaryServiceName, mocks.HTTPRoute, mocks.HTTPRouteName), &headerRouting) + + assert.Empty(t, err.Error()) + assert.Equal(t, headerName, string(rpcPluginImp.UpdatedHTTPRouteMock.Spec.Rules[1].Matches[0].Headers[0].Name)) + assert.Equal(t, prefixedHeaderValue, rpcPluginImp.UpdatedHTTPRouteMock.Spec.Rules[1].Matches[0].Headers[0].Value) + assert.Equal(t, headerValueType, *rpcPluginImp.UpdatedHTTPRouteMock.Spec.Rules[1].Matches[0].Headers[0].Type) + }) + t.Run("RemoveHTTPManagedRoutes", func(t *testing.T) { + err := pluginInstance.RemoveManagedRoutes(newRollout(mocks.StableServiceName, mocks.CanaryServiceName, mocks.HTTPRoute, mocks.HTTPRouteName)) + + assert.Empty(t, err.Error()) + assert.Equal(t, 1, len(rpcPluginImp.UpdatedHTTPRouteMock.Spec.Rules)) + }) // Canceling should cause an exit cancel() @@ -127,6 +160,7 @@ func TestRunSuccessfully(t *testing.T) { func newRollout(stableSvc, canarySvc, routeType, routeName string) *v1alpha1.Rollout { gatewayAPIConfig := GatewayAPITrafficRouting{ Namespace: mocks.Namespace, + ConfigMap: mocks.ConfigMapName, } switch routeType { case mocks.HTTPRoute: @@ -149,6 +183,11 @@ func newRollout(stableSvc, canarySvc, routeType, routeName string) *v1alpha1.Rol StableService: stableSvc, CanaryService: canarySvc, TrafficRouting: &v1alpha1.RolloutTrafficRouting{ + ManagedRoutes: []v1alpha1.MangedRoutes{ + { + Name: mocks.HTTPManagedRouteName, + }, + }, Plugins: map[string]json.RawMessage{ PluginName: encodedGatewayAPIConfig, }, diff --git a/pkg/plugin/tcproute.go b/pkg/plugin/tcproute.go index c9d6790..7a9aa3a 100644 --- a/pkg/plugin/tcproute.go +++ b/pkg/plugin/tcproute.go @@ -14,7 +14,7 @@ func (r *RpcPlugin) setTCPRouteWeight(rollout *v1alpha1.Rollout, desiredWeight i ctx := context.TODO() tcpRouteClient := r.TCPRouteClient if !r.IsTest { - gatewayV1alpha2 := r.Client.GatewayV1alpha2() + gatewayV1alpha2 := r.GatewayAPIClientset.GatewayV1alpha2() tcpRouteClient = gatewayV1alpha2.TCPRoutes(gatewayAPIConfig.Namespace) } tcpRoute, err := tcpRouteClient.Get(ctx, gatewayAPIConfig.TCPRoute, metav1.GetOptions{}) @@ -26,20 +26,14 @@ func (r *RpcPlugin) setTCPRouteWeight(rollout *v1alpha1.Rollout, desiredWeight i canaryServiceName := rollout.Spec.Strategy.Canary.CanaryService stableServiceName := rollout.Spec.Strategy.Canary.StableService routeRuleList := TCPRouteRuleList(tcpRoute.Spec.Rules) - backendRefList, err := getBackendRefList[TCPBackendRefList](routeRuleList) - if err != nil { - return pluginTypes.RpcError{ - ErrorString: err.Error(), - } - } - canaryBackendRef, err := getBackendRef[*TCPBackendRef](canaryServiceName, backendRefList) + canaryBackendRef, err := getBackendRef[*TCPBackendRef, *TCPRouteRule](canaryServiceName, routeRuleList) if err != nil { return pluginTypes.RpcError{ ErrorString: err.Error(), } } canaryBackendRef.Weight = &desiredWeight - stableBackendRef, err := getBackendRef[*TCPBackendRef](stableServiceName, backendRefList) + stableBackendRef, err := getBackendRef[*TCPBackendRef, *TCPRouteRule](stableServiceName, routeRuleList) if err != nil { return pluginTypes.RpcError{ ErrorString: err.Error(), @@ -52,46 +46,42 @@ func (r *RpcPlugin) setTCPRouteWeight(rollout *v1alpha1.Rollout, desiredWeight i r.UpdatedTCPRouteMock = updatedTCPRoute } if err != nil { - msg := fmt.Sprintf("Error updating Gateway API %q: %s", tcpRoute.GetName(), err) + msg := fmt.Sprintf(GatewayAPIUpdateError, tcpRoute.GetName(), err) r.LogCtx.Error(msg) } return pluginTypes.RpcError{} } -func (r TCPRouteRuleList) Iterator() (GatewayAPIRouteRuleIterator[TCPBackendRefList], bool) { - ruleList := r +func (r *TCPRouteRule) Iterator() (GatewayAPIRouteRuleIterator[*TCPBackendRef], bool) { + backendRefList := r.BackendRefs index := 0 - next := func() (TCPBackendRefList, bool) { - if len(ruleList) == index { + next := func() (*TCPBackendRef, bool) { + if len(backendRefList) == index { return nil, false } - backendRefList := TCPBackendRefList(ruleList[index].BackendRefs) + backendRef := (*TCPBackendRef)(&backendRefList[index]) index = index + 1 - return backendRefList, len(ruleList) > index + return backendRef, len(backendRefList) > index } - return next, len(ruleList) > index -} - -func (r TCPRouteRuleList) Error() error { - return errors.New("backendRefs was not found in tcpRoute") + return next, len(backendRefList) > index } -func (r TCPBackendRefList) Iterator() (GatewayAPIBackendRefIterator[*TCPBackendRef], bool) { - backendRefList := r +func (r TCPRouteRuleList) Iterator() (GatewayAPIRouteRuleListIterator[*TCPBackendRef, *TCPRouteRule], bool) { + routeRuleList := r index := 0 - next := func() (*TCPBackendRef, bool) { - if len(backendRefList) == index { + next := func() (*TCPRouteRule, bool) { + if len(routeRuleList) == index { return nil, false } - backendRef := (*TCPBackendRef)(&backendRefList[index]) + routeRule := (*TCPRouteRule)(&routeRuleList[index]) index = index + 1 - return backendRef, len(backendRefList) > index + return routeRule, len(routeRuleList) > index } - return next, len(backendRefList) > index + return next, len(routeRuleList) > index } -func (r TCPBackendRefList) Error() error { - return errors.New("backendRef was not found in tcpRoute") +func (r TCPRouteRuleList) Error() error { + return errors.New(BackendRefListWasNotFoundInTCPRouteError) } func (r *TCPBackendRef) GetName() string { diff --git a/pkg/plugin/types.go b/pkg/plugin/types.go index bdb7561..af5d0f9 100644 --- a/pkg/plugin/types.go +++ b/pkg/plugin/types.go @@ -1,10 +1,14 @@ package plugin import ( + "sync" + "github.com/sirupsen/logrus" + "k8s.io/client-go/kubernetes" + v1 "k8s.io/client-go/kubernetes/typed/core/v1" "sigs.k8s.io/gateway-api/apis/v1alpha2" "sigs.k8s.io/gateway-api/apis/v1beta1" - gatewayApiClientset "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned" + gatewayAPIClientset "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned" gatewayApiv1alpha2 "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned/typed/apis/v1alpha2" gatewayApiv1beta1 "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned/typed/apis/v1beta1" ) @@ -12,7 +16,9 @@ import ( type RpcPlugin struct { IsTest bool LogCtx *logrus.Entry - Client *gatewayApiClientset.Clientset + GatewayAPIClientset *gatewayAPIClientset.Clientset + Clientset *kubernetes.Clientset + TestClientset v1.ConfigMapInterface UpdatedHTTPRouteMock *v1beta1.HTTPRoute UpdatedTCPRouteMock *v1alpha2.TCPRoute HTTPRouteClient gatewayApiv1beta1.HTTPRouteInterface @@ -28,39 +34,43 @@ type GatewayAPITrafficRouting struct { TCPRoute string `json:"tcpRoute,omitempty"` // Namespace refers to the namespace of the specified resource Namespace string `json:"namespace"` + // ConfigMap name refers to the config map where plugin stores data about managed routes + ConfigMap string `json:"configMap,omitempty"` } -type HTTPBackendRef v1beta1.HTTPBackendRef +type HTTPHeaderRoute struct { + mutex sync.Mutex + managedRouteMap map[string]int +} -type TCPBackendRef v1beta1.BackendRef +type HTTPRouteRule v1beta1.HTTPRouteRule + +type TCPRouteRule v1alpha2.TCPRouteRule type HTTPRouteRuleList []v1beta1.HTTPRouteRule type TCPRouteRuleList []v1alpha2.TCPRouteRule -type HTTPBackendRefList []v1beta1.HTTPBackendRef - -type TCPBackendRefList []v1beta1.BackendRef +type HTTPBackendRef v1beta1.HTTPBackendRef -type GatewayAPIBackendRef interface { - *HTTPBackendRef | *TCPBackendRef - GetName() string -} +type TCPBackendRef v1beta1.BackendRef -type GatewayAPIBackendRefList interface { - HTTPBackendRefList | TCPBackendRefList +type GatewayAPIRouteRule[T1 GatewayAPIBackendRef] interface { + *HTTPRouteRule | *TCPRouteRule + Iterator() (GatewayAPIRouteRuleIterator[T1], bool) } -type GatewayAPIRouteRuleCollection[T GatewayAPIBackendRefList] interface { - Iterator() (GatewayAPIRouteRuleIterator[T], bool) +type GatewayAPIRouteRuleList[T1 GatewayAPIBackendRef, T2 GatewayAPIRouteRule[T1]] interface { + HTTPRouteRuleList | TCPRouteRuleList + Iterator() (GatewayAPIRouteRuleListIterator[T1, T2], bool) Error() error } -type GatewayAPIBackendRefCollection[T GatewayAPIBackendRef] interface { - Iterator() (GatewayAPIBackendRefIterator[T], bool) - Error() error +type GatewayAPIBackendRef interface { + *HTTPBackendRef | *TCPBackendRef + GetName() string } -type GatewayAPIRouteRuleIterator[T GatewayAPIBackendRefList] func() (T, bool) +type GatewayAPIRouteRuleListIterator[T1 GatewayAPIBackendRef, T2 GatewayAPIRouteRule[T1]] func() (T2, bool) -type GatewayAPIBackendRefIterator[T GatewayAPIBackendRef] func() (T, bool) +type GatewayAPIRouteRuleIterator[T1 GatewayAPIBackendRef] func() (T1, bool) diff --git a/utils/common.go b/utils/common.go deleted file mode 100644 index 80e986b..0000000 --- a/utils/common.go +++ /dev/null @@ -1,50 +0,0 @@ -package utils - -import ( - "strings" - - pluginTypes "github.com/argoproj/argo-rollouts/utils/plugin/types" - log "github.com/sirupsen/logrus" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" -) - -func GetKubeConfig() (*rest.Config, error) { - loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() - // if you want to change the loading rules (which files in which order), you can do so here - configOverrides := &clientcmd.ConfigOverrides{} - // if you want to change override values or bind them to flags, there are methods to help you - kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides) - config, err := kubeConfig.ClientConfig() - if err != nil { - return nil, pluginTypes.RpcError{ErrorString: err.Error()} - } - return config, nil -} - -func SetLogLevel(logLevel string) { - level, err := log.ParseLevel(logLevel) - if err != nil { - log.Fatal(err) - } - log.SetLevel(level) -} - -func CreateFormatter(logFormat string) log.Formatter { - var formatType log.Formatter - switch strings.ToLower(logFormat) { - case "json": - formatType = &log.JSONFormatter{} - case "text": - formatType = &log.TextFormatter{ - FullTimestamp: true, - } - default: - log.Infof("Unknown format: %s. Using text logformat", logFormat) - formatType = &log.TextFormatter{ - FullTimestamp: true, - } - } - - return formatType -}