fix: reconcile ingress/route when annotations changed (#862)

* fix: reconcile ingress/route when annotations changes

Signed-off-by: Anatolii Bazko <abazko@redhat.com>
pull/869/head
Anatolii Bazko 2021-06-11 10:54:18 +03:00 committed by GitHub
parent af0d7c83b7
commit 0b4fef03e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 99 additions and 111 deletions

View File

@ -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"

View File

@ -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),
}
}

View File

@ -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)
}

View File

@ -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",

View File

@ -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)

View File

@ -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")
}
}

View File

@ -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))
}