diff --git a/pkg/deploy/defaults.go b/pkg/deploy/defaults.go index 660f22421..a802c0384 100644 --- a/pkg/deploy/defaults.go +++ b/pkg/deploy/defaults.go @@ -105,14 +105,15 @@ const ( OAuthScmConfiguration = "oauth-scm-configuration" // che.eclipse.org annotations - CheEclipseOrgMountPath = "che.eclipse.org/mount-path" - CheEclipseOrgMountAs = "che.eclipse.org/mount-as" - CheEclipseOrgEnvName = "che.eclipse.org/env-name" - CheEclipseOrgNamespace = "che.eclipse.org/namespace" - CheEclipseOrgGithubOAuthCredentials = "che.eclipse.org/github-oauth-credentials" - CheEclipseOrgOAuthScmServer = "che.eclipse.org/oauth-scm-server" - CheEclipseOrgScmServerEndpoint = "che.eclipse.org/scm-server-endpoint" - CheEclipseOrgHash256 = "che.eclipse.org/hash256" + CheEclipseOrgMountPath = "che.eclipse.org/mount-path" + CheEclipseOrgMountAs = "che.eclipse.org/mount-as" + CheEclipseOrgEnvName = "che.eclipse.org/env-name" + CheEclipseOrgNamespace = "che.eclipse.org/namespace" + CheEclipseOrgGithubOAuthCredentials = "che.eclipse.org/github-oauth-credentials" + CheEclipseOrgOAuthScmServer = "che.eclipse.org/oauth-scm-server" + CheEclipseOrgScmServerEndpoint = "che.eclipse.org/scm-server-endpoint" + CheEclipseOrgHash256 = "che.eclipse.org/hash256" + CheEclipseOrgManagedAnnotationsDigest = "che.eclipse.org/managed-annotations-digest" // components IdentityProviderName = "keycloak" diff --git a/pkg/deploy/dev-workspace/dev_workspace.go b/pkg/deploy/dev-workspace/dev_workspace.go index a5361a0b6..e737094cc 100644 --- a/pkg/deploy/dev-workspace/dev_workspace.go +++ b/pkg/deploy/dev-workspace/dev_workspace.go @@ -16,6 +16,7 @@ import ( "context" "errors" "fmt" + "io/ioutil" orgv1 "github.com/eclipse-che/che-operator/pkg/apis/org/v1" "github.com/eclipse-che/che-operator/pkg/deploy" @@ -33,6 +34,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" ) var ( @@ -474,18 +476,19 @@ func isOnlyOneOperatorManagesDWResources(deployContext *deploy.DeployContext) (b func readK8SObject(yamlFile string, obj interface{}) (*Object2Sync, error) { _, exists := cachedObj[yamlFile] if !exists { - if err := util.ReadObject(yamlFile, obj); err != nil { + data, err := ioutil.ReadFile(yamlFile) + if err != nil { return nil, err } - hash256, err := util.ComputeHash256(yamlFile) + err = yaml.Unmarshal(data, obj) if err != nil { return nil, err } cachedObj[yamlFile] = &Object2Sync{ obj.(metav1.Object), - hash256, + util.ComputeHash256(data), } } diff --git a/pkg/deploy/ingres_test.go b/pkg/deploy/ingres_test.go index 90510bc96..8caf70ed1 100644 --- a/pkg/deploy/ingres_test.go +++ b/pkg/deploy/ingres_test.go @@ -13,7 +13,6 @@ package deploy import ( "context" - "os" "reflect" "github.com/google/go-cmp/cmp" @@ -24,10 +23,6 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" "testing" ) @@ -41,7 +36,6 @@ func TestIngressSpec(t *testing.T) { ingressComponent string serviceName string servicePort int - initObjects []runtime.Object ingressCustomSettings orgv1.IngressCustomSettings expectedIngress *v1beta1.Ingress } @@ -49,6 +43,7 @@ func TestIngressSpec(t *testing.T) { cheCluster := &orgv1.CheCluster{ ObjectMeta: metav1.ObjectMeta{ Namespace: "eclipse-che", + Name: "eclipse-che", }, } @@ -65,7 +60,6 @@ func TestIngressSpec(t *testing.T) { Labels: "type=default", Annotations: map[string]string{"annotation-key": "annotation-value"}, }, - initObjects: []runtime.Object{}, expectedIngress: &v1beta1.Ingress{ ObjectMeta: metav1.ObjectMeta{ Name: "test", @@ -78,6 +72,7 @@ func TestIngressSpec(t *testing.T) { "app.kubernetes.io/name": DefaultCheFlavor(cheCluster), }, Annotations: map[string]string{ + "che.eclipse.org/managed-annotations-digest": "0000", "kubernetes.io/ingress.class": "nginx", "nginx.ingress.kubernetes.io/proxy-connect-timeout": "3600", "nginx.ingress.kubernetes.io/proxy-read-timeout": "3600", @@ -115,18 +110,7 @@ func TestIngressSpec(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - logf.SetLogger(zap.LoggerTo(os.Stdout, true)) - orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) - testCase.initObjects = append(testCase.initObjects) - cli := fake.NewFakeClientWithScheme(scheme.Scheme, testCase.initObjects...) - - deployContext := &DeployContext{ - CheCluster: cheCluster, - ClusterAPI: ClusterAPI{ - Client: cli, - Scheme: scheme.Scheme, - }, - } + deployContext := GetTestDeployContext(cheCluster, []runtime.Object{}) _, actualIngress := GetIngressSpec(deployContext, testCase.ingressName, @@ -146,21 +130,7 @@ func TestIngressSpec(t *testing.T) { } func TestSyncIngressToCluster(t *testing.T) { - orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) - cli := fake.NewFakeClientWithScheme(scheme.Scheme) - deployContext := &DeployContext{ - CheCluster: &orgv1.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - }, - ClusterAPI: ClusterAPI{ - Client: cli, - NonCachedClient: cli, - Scheme: scheme.Scheme, - }, - } + deployContext := GetTestDeployContext(nil, []runtime.Object{}) _, done, err := SyncIngressToCluster(deployContext, "test", "host-1", "", "service-1", 8080, orgv1.IngressCustomSettings{}, "component") if !done || err != nil { @@ -173,7 +143,7 @@ func TestSyncIngressToCluster(t *testing.T) { } actual := &v1beta1.Ingress{} - err = cli.Get(context.TODO(), types.NamespacedName{Name: "test"}, actual) + err = deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: "test"}, actual) if err != nil { t.Fatalf("Failed to get ingress: %v", err) } diff --git a/pkg/deploy/ingress.go b/pkg/deploy/ingress.go index b24c10b0c..2469aef2b 100644 --- a/pkg/deploy/ingress.go +++ b/pkg/deploy/ingress.go @@ -13,6 +13,7 @@ package deploy import ( "reflect" + "sort" "strconv" orgv1 "github.com/eclipse-che/che-operator/pkg/apis/org/v1" @@ -27,7 +28,8 @@ import ( var ingressDiffOpts = cmp.Options{ cmpopts.IgnoreFields(v1beta1.Ingress{}, "TypeMeta", "Status"), cmp.Comparer(func(x, y metav1.ObjectMeta) bool { - return reflect.DeepEqual(x.Labels, y.Labels) + return reflect.DeepEqual(x.Labels, y.Labels) && + x.Annotations[CheEclipseOrgManagedAnnotationsDigest] == y.Annotations[CheEclipseOrgManagedAnnotationsDigest] }), } @@ -101,12 +103,30 @@ func GetIngressSpec( if ingressStrategy != "multi-host" && (component == DevfileRegistryName || component == PluginRegistryName) { annotations["nginx.ingress.kubernetes.io/rewrite-target"] = "/$1" } - - // add custom annotations for k, v := range ingressCustomSettings.Annotations { annotations[k] = v } + // add 'che.eclipse.org/managed-annotations-digest' annotation + // to store and compare annotations managed by operator only + annotationsKeys := make([]string, 0, len(annotations)) + for k := range annotations { + annotationsKeys = append(annotationsKeys, k) + } + if len(annotationsKeys) > 0 { + sort.Strings(annotationsKeys) + + data := "" + for _, k := range annotationsKeys { + data += k + ":" + annotations[k] + "," + } + if util.IsTestMode() { + annotations[CheEclipseOrgManagedAnnotationsDigest] = "0000" + } else { + annotations[CheEclipseOrgManagedAnnotationsDigest] = util.ComputeHash256([]byte(data)) + } + } + ingress := &v1beta1.Ingress{ TypeMeta: metav1.TypeMeta{ Kind: "Ingress", diff --git a/pkg/deploy/route.go b/pkg/deploy/route.go index efd9b31ec..056b05b54 100644 --- a/pkg/deploy/route.go +++ b/pkg/deploy/route.go @@ -15,8 +15,10 @@ import ( "context" "fmt" "reflect" + "sort" orgv1 "github.com/eclipse-che/che-operator/pkg/apis/org/v1" + "github.com/eclipse-che/che-operator/pkg/util" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" routev1 "github.com/openshift/api/route/v1" @@ -35,14 +37,16 @@ var routeDiffOpts = cmp.Options{ cmpopts.IgnoreFields(routev1.Route{}, "TypeMeta", "Status"), cmpopts.IgnoreFields(routev1.RouteSpec{}, "Host", "WildcardPolicy"), cmp.Comparer(func(x, y metav1.ObjectMeta) bool { - return reflect.DeepEqual(x.Labels, y.Labels) + return reflect.DeepEqual(x.Labels, y.Labels) && + x.Annotations[CheEclipseOrgManagedAnnotationsDigest] == y.Annotations[CheEclipseOrgManagedAnnotationsDigest] }), } var routeWithHostDiffOpts = cmp.Options{ cmpopts.IgnoreFields(routev1.Route{}, "TypeMeta", "Status"), cmpopts.IgnoreFields(routev1.RouteSpec{}, "WildcardPolicy"), cmp.Comparer(func(x, y metav1.ObjectMeta) bool { - return reflect.DeepEqual(x.Labels, y.Labels) + return reflect.DeepEqual(x.Labels, y.Labels) && + x.Annotations[CheEclipseOrgManagedAnnotationsDigest] == y.Annotations[CheEclipseOrgManagedAnnotationsDigest] }), } @@ -84,9 +88,32 @@ func GetRouteSpec( MergeLabels(labels, routeCustomSettings.Labels) // add custom annotations - annotations := map[string]string{} - for k, v := range routeCustomSettings.Annotations { - annotations[k] = v + var annotations map[string]string + if len(routeCustomSettings.Annotations) > 0 { + annotations = make(map[string]string) + for k, v := range routeCustomSettings.Annotations { + annotations[k] = v + } + } + + // add 'che.eclipse.org/managed-annotations-digest' annotation + // to store and compare annotations managed by operator only + annotationsKeys := make([]string, 0, len(annotations)) + for k := range annotations { + annotationsKeys = append(annotationsKeys, k) + } + if len(annotationsKeys) > 0 { + sort.Strings(annotationsKeys) + + data := "" + for _, k := range annotationsKeys { + data += k + ":" + annotations[k] + "," + } + if util.IsTestMode() { + annotations[CheEclipseOrgManagedAnnotationsDigest] = "0000" + } else { + annotations[CheEclipseOrgManagedAnnotationsDigest] = util.ComputeHash256([]byte(data)) + } } weight := int32(100) diff --git a/pkg/deploy/route_test.go b/pkg/deploy/route_test.go index 9c7493306..e1afe0fc4 100644 --- a/pkg/deploy/route_test.go +++ b/pkg/deploy/route_test.go @@ -13,7 +13,6 @@ package deploy import ( "context" - "os" "reflect" "github.com/google/go-cmp/cmp" @@ -24,10 +23,6 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" "testing" ) @@ -43,7 +38,6 @@ func TestRouteSpec(t *testing.T) { routeComponent string serviceName string servicePort int32 - initObjects []runtime.Object routeCustomSettings orgv1.RouteCustomSettings expectedRoute *routev1.Route } @@ -51,6 +45,7 @@ func TestRouteSpec(t *testing.T) { cheCluster := &orgv1.CheCluster{ ObjectMeta: metav1.ObjectMeta{ Namespace: "eclipse-che", + Name: "eclipse-che", }, } @@ -62,18 +57,13 @@ func TestRouteSpec(t *testing.T) { serviceName: "che", servicePort: 8080, routeCustomSettings: orgv1.RouteCustomSettings{ - Labels: "type=default", - Domain: "route-domain", - Annotations: map[string]string{"annotation-key": "annotation-value"}, + Labels: "type=default", + Domain: "route-domain", }, - initObjects: []runtime.Object{}, expectedRoute: &routev1.Route{ ObjectMeta: metav1.ObjectMeta{ Name: "test", Namespace: "eclipse-che", - Annotations: map[string]string{ - "annotation-key": "annotation-value", - }, Labels: map[string]string{ "type": "default", "app.kubernetes.io/component": "test-component", @@ -110,10 +100,8 @@ func TestRouteSpec(t *testing.T) { serviceName: "che", servicePort: 8080, routeCustomSettings: orgv1.RouteCustomSettings{ - Labels: "type=default", - Annotations: map[string]string{"annotation-key": "annotation-value"}, + Labels: "type=default", }, - initObjects: []runtime.Object{}, expectedRoute: &routev1.Route{ ObjectMeta: metav1.ObjectMeta{ Name: "test", @@ -125,9 +113,6 @@ func TestRouteSpec(t *testing.T) { "app.kubernetes.io/managed-by": DefaultCheFlavor(cheCluster) + "-operator", "app.kubernetes.io/name": DefaultCheFlavor(cheCluster), }, - Annotations: map[string]string{ - "annotation-key": "annotation-value", - }, }, TypeMeta: metav1.TypeMeta{ Kind: "Route", @@ -153,18 +138,7 @@ func TestRouteSpec(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - logf.SetLogger(zap.LoggerTo(os.Stdout, true)) - orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) - testCase.initObjects = append(testCase.initObjects) - cli := fake.NewFakeClientWithScheme(scheme.Scheme, testCase.initObjects...) - - deployContext := &DeployContext{ - CheCluster: cheCluster, - ClusterAPI: ClusterAPI{ - Client: cli, - Scheme: scheme.Scheme, - }, - } + deployContext := GetTestDeployContext(cheCluster, []runtime.Object{}) actualRoute, err := GetRouteSpec(deployContext, testCase.routeName, @@ -187,22 +161,9 @@ func TestRouteSpec(t *testing.T) { } func TestSyncRouteToCluster(t *testing.T) { - orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) - routev1.AddToScheme(scheme.Scheme) - cli := fake.NewFakeClientWithScheme(scheme.Scheme) - deployContext := &DeployContext{ - CheCluster: &orgv1.CheCluster{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "eclipse-che", - Name: "eclipse-che", - }, - }, - ClusterAPI: ClusterAPI{ - Client: cli, - NonCachedClient: cli, - Scheme: scheme.Scheme, - }, - } + // init context + deployContext := GetTestDeployContext(nil, []runtime.Object{}) + routev1.AddToScheme(deployContext.ClusterAPI.Scheme) done, err := SyncRouteToCluster(deployContext, "test", "", "", "service", 80, orgv1.RouteCustomSettings{}, "test") if !done || err != nil { @@ -216,7 +177,7 @@ func TestSyncRouteToCluster(t *testing.T) { } actual := &routev1.Route{} - err = cli.Get(context.TODO(), types.NamespacedName{Name: "test"}, actual) + err = deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: "test"}, actual) if err != nil { t.Fatalf("Failed to get route: %v", err) } @@ -231,7 +192,7 @@ func TestSyncRouteToCluster(t *testing.T) { } actual = &routev1.Route{} - err = cli.Get(context.TODO(), types.NamespacedName{Name: "test"}, actual) + err = deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: "test"}, actual) if err != nil { t.Fatalf("Failed to get route: %v", err) } @@ -241,4 +202,16 @@ func TestSyncRouteToCluster(t *testing.T) { if actual.Spec.Host != "test-eclipse-che.domain" { t.Fatalf("Failed to sync route") } + + // sync route with annotations + done, err = SyncRouteToCluster(deployContext, "test", "", "", "service", 90, orgv1.RouteCustomSettings{Annotations: map[string]string{"a": "b"}}, "test") + + actual = &routev1.Route{} + err = deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: "test"}, actual) + if !done || err != nil { + t.Fatalf("Failed to sync route: %v", err) + } + if actual.ObjectMeta.Annotations["a"] != "b" || actual.ObjectMeta.Annotations[CheEclipseOrgManagedAnnotationsDigest] == "" { + t.Fatalf("Failed to sync route") + } } diff --git a/pkg/util/util.go b/pkg/util/util.go index 1a1f41a10..2e9f181a8 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -431,14 +431,8 @@ func ReadObject(yamlFile string, obj interface{}) error { return nil } -func ComputeHash256(yamlFile string) (string, error) { - data, err := ioutil.ReadFile(yamlFile) - if err != nil { - return "", err - } - +func ComputeHash256(data []byte) string { hasher := sha256.New() hasher.Write(data) - sha := base64.URLEncoding.EncodeToString(hasher.Sum(nil)) - return sha, nil + return base64.URLEncoding.EncodeToString(hasher.Sum(nil)) }