diff --git a/deploy/helm-chart/kubernetes-replicator/values.yaml b/deploy/helm-chart/kubernetes-replicator/values.yaml index 7dbc69ef..1f80d743 100644 --- a/deploy/helm-chart/kubernetes-replicator/values.yaml +++ b/deploy/helm-chart/kubernetes-replicator/values.yaml @@ -22,10 +22,10 @@ serviceAccount: create: true annotations: {} name: - privileges: [] + privileges: + - apiGroups: ["networking.istio.io"] + resources: ["envoyfilters"] automountServiceAccountToken: true - # - apiGroups: [""] - # resources: ["configmaps"] podSecurityContext: {} # fsGroup: 2000 diff --git a/deploy/rbac.yaml b/deploy/rbac.yaml index 5b0caa45..761551a6 100644 --- a/deploy/rbac.yaml +++ b/deploy/rbac.yaml @@ -18,6 +18,9 @@ rules: - apiGroups: ["rbac.authorization.k8s.io"] resources: ["roles", "rolebindings"] verbs: ["get", "watch", "list", "create", "update", "patch", "delete"] +- apiGroups: ["networking.istio.io"] + resources: ["envoyfilters"] + verbs: ["get", "watch", "list", "create", "update", "patch", "delete"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/go.mod b/go.mod index b534d6d8..5433adba 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.8.4 + istio.io/client-go v1.19.0 k8s.io/api v0.28.1 k8s.io/apimachinery v0.28.1 k8s.io/client-go v0.28.1 @@ -35,21 +36,24 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/net v0.13.0 // indirect + golang.org/x/net v0.14.0 // indirect golang.org/x/oauth2 v0.8.0 // indirect - golang.org/x/sys v0.10.0 // indirect - golang.org/x/term v0.10.0 // indirect - golang.org/x/text v0.11.0 // indirect + golang.org/x/sys v0.11.0 // indirect + golang.org/x/term v0.11.0 // indirect + golang.org/x/text v0.12.0 // indirect golang.org/x/time v0.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.30.0 // indirect + google.golang.org/genproto v0.0.0-20230815205213-6bfd019c3878 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5 // indirect + google.golang.org/protobuf v1.31.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + istio.io/api v1.19.0-beta.1.0.20230821193953-6d232ba686ad // indirect k8s.io/klog/v2 v2.100.1 // indirect k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index fbed111b..d688bc3c 100644 --- a/go.sum +++ b/go.sum @@ -91,8 +91,8 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= -golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -102,15 +102,15 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= -golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -124,10 +124,14 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20230815205213-6bfd019c3878 h1:Iveh6tGCJkHAjJgEqUQYGDGgbwmhjoAOz8kO/ajxefY= +google.golang.org/genproto v0.0.0-20230815205213-6bfd019c3878/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= +google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5 h1:nIgk/EEq3/YlnmVVXVnm14rC2oxgs1o0ong4sD/rd44= +google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5/go.mod h1:5DZzOUPCLYL3mNkQ0ms0F3EuUNZ7py1Bqeq6sxzI7/Q= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -139,6 +143,10 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +istio.io/api v1.19.0-beta.1.0.20230821193953-6d232ba686ad h1:4Hf6TxxeM1pi2mqoWU0nR4lezZXs9lCdLrgFdBH+moQ= +istio.io/api v1.19.0-beta.1.0.20230821193953-6d232ba686ad/go.mod h1:KstZe4bKbXouALUJ5PqpjNEhu5nj90HrDFitZfpNhlU= +istio.io/client-go v1.19.0 h1:vdiwlg+cocs7J2aVlkYz3ebjpBpRAorfZUOzqjE2Z+U= +istio.io/client-go v1.19.0/go.mod h1:zG9fwlp6qSvxlErRgc8X46CLC3Ga91cGR5ADUqEAQYU= k8s.io/api v0.28.1 h1:i+0O8k2NPBCPYaMB+uCkseEbawEt/eFaiRqUx8aB108= k8s.io/api v0.28.1/go.mod h1:uBYwID+66wiL28Kn2tBjBYQdEU0Xk0z5qF8bIBqk/Dg= k8s.io/apimachinery v0.28.1 h1:EJD40og3GizBSV3mkIoXQBsws32okPOy+MkRyzh6nPY= @@ -153,7 +161,7 @@ k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrC k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk= +sigs.k8s.io/structured-merge-diff/v4 v4.3.0/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/main.go b/main.go index 5ba788c6..d63a087e 100644 --- a/main.go +++ b/main.go @@ -8,10 +8,12 @@ import ( "github.com/mittwald/kubernetes-replicator/replicate/common" "github.com/mittwald/kubernetes-replicator/replicate/configmap" + "github.com/mittwald/kubernetes-replicator/replicate/envoyfilter" "github.com/mittwald/kubernetes-replicator/replicate/role" "github.com/mittwald/kubernetes-replicator/replicate/rolebinding" "github.com/mittwald/kubernetes-replicator/replicate/secret" "github.com/mittwald/kubernetes-replicator/replicate/serviceaccount" + "istio.io/client-go/pkg/clientset/versioned" log "github.com/sirupsen/logrus" @@ -80,12 +82,14 @@ func main() { } client = kubernetes.NewForConfigOrDie(config) + istioClient := versioned.NewForConfigOrDie(config) secretRepl := secret.NewReplicator(client, f.ResyncPeriod, f.AllowAll) configMapRepl := configmap.NewReplicator(client, f.ResyncPeriod, f.AllowAll) roleRepl := role.NewReplicator(client, f.ResyncPeriod, f.AllowAll) roleBindingRepl := rolebinding.NewReplicator(client, f.ResyncPeriod, f.AllowAll) serviceAccountRepl := serviceaccount.NewReplicator(client, f.ResyncPeriod, f.AllowAll) + envoyFilterRepl := envoyfilter.NewReplicator(client, f.ResyncPeriod, f.AllowAll, istioClient) go secretRepl.Run() @@ -97,8 +101,10 @@ func main() { go serviceAccountRepl.Run() + go envoyFilterRepl.Run() + h := liveness.Handler{ - Replicators: []common.Replicator{secretRepl, configMapRepl, roleRepl, roleBindingRepl, serviceAccountRepl}, + Replicators: []common.Replicator{secretRepl, configMapRepl, roleRepl, roleBindingRepl, serviceAccountRepl, envoyFilterRepl}, } log.Infof("starting liveness monitor at %s", f.StatusAddr) diff --git a/replicate/common/generic-replicator.go b/replicate/common/generic-replicator.go index c0375ec6..2963907b 100644 --- a/replicate/common/generic-replicator.go +++ b/replicate/common/generic-replicator.go @@ -3,13 +3,15 @@ package common import ( "context" "fmt" - "k8s.io/apimachinery/pkg/labels" "reflect" "regexp" "strconv" "strings" "time" + "istio.io/client-go/pkg/clientset/versioned" + "k8s.io/apimachinery/pkg/labels" + "github.com/hashicorp/go-multierror" "github.com/pkg/errors" log "github.com/sirupsen/logrus" @@ -24,6 +26,7 @@ import ( type ReplicatorConfig struct { Kind string Client kubernetes.Interface + IstioClient versioned.Interface ResyncPeriod time.Duration AllowAll bool ListFunc cache.ListFunc diff --git a/replicate/envoyfilter/envoyfilters.go b/replicate/envoyfilter/envoyfilters.go new file mode 100644 index 00000000..1d4a98cf --- /dev/null +++ b/replicate/envoyfilter/envoyfilters.go @@ -0,0 +1,227 @@ +package envoyfilter + +import ( + "context" + "encoding/json" + "fmt" + "time" + + "github.com/mittwald/kubernetes-replicator/replicate/common" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" + v1 "k8s.io/api/core/v1" + "k8s.io/client-go/kubernetes" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/watch" + + networkingv1alpha3 "istio.io/client-go/pkg/apis/networking/v1alpha3" + "istio.io/client-go/pkg/clientset/versioned" +) + +type Replicator struct { + *common.GenericReplicator +} + +// NewReplicator creates a new envoyfilter replicator +func NewReplicator(client kubernetes.Interface, resyncPeriod time.Duration, allowAll bool, istioClient versioned.Interface) common.Replicator { + repl := Replicator{ + GenericReplicator: common.NewGenericReplicator(common.ReplicatorConfig{ + Kind: "EnvoyFilter", + ObjType: &networkingv1alpha3.EnvoyFilter{}, + AllowAll: allowAll, + ResyncPeriod: resyncPeriod, + Client: client, + IstioClient: istioClient, + ListFunc: func(lo metav1.ListOptions) (runtime.Object, error) { + return istioClient.NetworkingV1alpha3().EnvoyFilters("").List(context.TODO(), lo) + }, + WatchFunc: func(lo metav1.ListOptions) (watch.Interface, error) { + return istioClient.NetworkingV1alpha3().EnvoyFilters("").Watch(context.TODO(), lo) + }, + }), + } + repl.UpdateFuncs = common.UpdateFuncs{ + ReplicateDataFrom: repl.ReplicateDataFrom, + ReplicateObjectTo: repl.ReplicateObjectTo, + PatchDeleteDependent: repl.PatchDeleteDependent, + DeleteReplicatedResource: repl.DeleteReplicatedResource, + } + + return &repl +} + +func (r *Replicator) ReplicateDataFrom(sourceObj interface{}, targetObj interface{}) error { + source := sourceObj.(*networkingv1alpha3.EnvoyFilter) + target := targetObj.(*networkingv1alpha3.EnvoyFilter) + + logger := log. + WithField("kind", r.Kind). + WithField("source", common.MustGetKey(source)). + WithField("target", common.MustGetKey(target)) + + // make sure replication is allowed + if ok, err := r.IsReplicationPermitted(&target.ObjectMeta, &source.ObjectMeta); !ok { + return errors.Wrapf(err, "replication of target %s is not permitted", common.MustGetKey(source)) + } + + targetVersion, ok := target.Annotations[common.ReplicatedFromVersionAnnotation] + sourceVersion := source.ResourceVersion + + if ok && targetVersion == sourceVersion { + logger.Debugf("target %s/%s is already up-to-date", target.Namespace, target.Name) + return nil + } + + targetCopy := target.DeepCopy() + + log.Infof("updating target %s/%s", target.Namespace, target.Name) + + targetCopy.Annotations[common.ReplicatedAtAnnotation] = time.Now().Format(time.RFC3339) + targetCopy.Annotations[common.ReplicatedFromVersionAnnotation] = source.ResourceVersion + targetCopy.Spec = source.Spec + + s, err := r.IstioClient.NetworkingV1alpha3().EnvoyFilters(target.Namespace).Update(context.TODO(), targetCopy, metav1.UpdateOptions{}) + if err != nil { + err = errors.Wrapf(err, "Failed updating target %s/%s", target.Namespace, targetCopy.Name) + } else if err = r.Store.Update(s); err != nil { + err = errors.Wrapf(err, "Failed to update cache for %s/%s: %v", target.Namespace, targetCopy, err) + } + + return err +} + +// ReplicateObjectTo copies the whole object to target namespace +func (r *Replicator) ReplicateObjectTo(sourceObj interface{}, target *v1.Namespace) error { + source := sourceObj.(*networkingv1alpha3.EnvoyFilter) + targetLocation := fmt.Sprintf("%s/%s", target.Name, source.Name) + + logger := log. + WithField("kind", r.Kind). + WithField("source", common.MustGetKey(source)). + WithField("target", targetLocation) + + targetResource, exists, err := r.Store.GetByKey(targetLocation) + if err != nil { + return errors.Wrapf(err, "Could not get %s from cache!", targetLocation) + } + logger.Infof("Checking if %s exists? %v", targetLocation, exists) + + var targetCopy *networkingv1alpha3.EnvoyFilter + if exists { + targetObject := targetResource.(*networkingv1alpha3.EnvoyFilter) + targetVersion, ok := targetObject.Annotations[common.ReplicatedFromVersionAnnotation] + sourceVersion := source.ResourceVersion + + if ok && targetVersion == sourceVersion { + logger.Debugf("EnvoyFilter %s is already up-to-date", common.MustGetKey(targetObject)) + return nil + } + + targetCopy = targetObject.DeepCopy() + } else { + targetCopy = new(networkingv1alpha3.EnvoyFilter) + } + + keepOwnerReferences, ok := source.Annotations[common.KeepOwnerReferences] + if ok && keepOwnerReferences == "true" { + targetCopy.OwnerReferences = source.OwnerReferences + } + + if targetCopy.Annotations == nil { + targetCopy.Annotations = make(map[string]string) + } + + labelsCopy := make(map[string]string) + + stripLabels, ok := source.Annotations[common.StripLabels] + if !ok && stripLabels != "true" { + if source.Labels != nil { + for key, value := range source.Labels { + labelsCopy[key] = value + } + } + + } + + targetCopy.Name = source.Name + targetCopy.Labels = labelsCopy + targetCopy.Spec = source.Spec + targetCopy.Annotations[common.ReplicatedAtAnnotation] = time.Now().Format(time.RFC3339) + targetCopy.Annotations[common.ReplicatedFromVersionAnnotation] = source.ResourceVersion + + var obj interface{} + + if exists { + if err == nil { + logger.Debugf("Updating existing envoyFilter %s/%s", target.Name, targetCopy.Name) + obj, err = r.IstioClient.NetworkingV1alpha3().EnvoyFilters(target.Name).Update(context.TODO(), targetCopy, metav1.UpdateOptions{}) + } + } else { + if err == nil { + logger.Debugf("Creating a new envoyFilter %s/%s", target.Name, targetCopy.Name) + obj, err = r.IstioClient.NetworkingV1alpha3().EnvoyFilters(target.Name).Create(context.TODO(), targetCopy, metav1.CreateOptions{}) + } + } + if err != nil { + return errors.Wrapf(err, "Failed to update envoyFilter %s/%s", target.Name, targetCopy.Name) + } + + if err := r.Store.Update(obj); err != nil { + return errors.Wrapf(err, "Failed to update cache for %s/%s", target.Name, targetCopy) + } + + return nil +} + +func (r *Replicator) PatchDeleteDependent(sourceKey string, target interface{}) (interface{}, error) { + dependentKey := common.MustGetKey(target) + logger := log.WithFields(log.Fields{ + "kind": r.Kind, + "source": sourceKey, + "target": dependentKey, + }) + + targetObject, ok := target.(*networkingv1alpha3.EnvoyFilter) + if !ok { + err := errors.Errorf("bad type returned from Store: %T", target) + return nil, err + } + + patch := []common.JSONPatchOperation{{Operation: "remove", Path: "/spec"}} + patchBody, err := json.Marshal(&patch) + + if err != nil { + return nil, errors.Wrapf(err, "error while building patch body for envoyfilter %s: %v", dependentKey, err) + + } + + logger.Debugf("clearing dependent envoyfilter %s", dependentKey) + logger.Tracef("patch body: %s", string(patchBody)) + + s, err := r.IstioClient.NetworkingV1alpha3().EnvoyFilters(targetObject.Namespace).Patch(context.TODO(), targetObject.Name, types.JSONPatchType, patchBody, metav1.PatchOptions{}) + if err != nil { + return nil, errors.Wrapf(err, "error while patching envoyfilter %s: %v", dependentKey, err) + + } + + return s, nil +} + +// DeleteReplicatedResource deletes a resource replicated by ReplicateTo annotation +func (r *Replicator) DeleteReplicatedResource(targetResource interface{}) error { + targetLocation := common.MustGetKey(targetResource) + logger := log.WithFields(log.Fields{ + "kind": r.Kind, + "target": targetLocation, + }) + + object := targetResource.(*networkingv1alpha3.EnvoyFilter) + logger.Debugf("Deleting %s", targetLocation) + if err := r.IstioClient.NetworkingV1alpha3().EnvoyFilters(object.Namespace).Delete(context.TODO(), object.Name, metav1.DeleteOptions{}); err != nil { + return errors.Wrapf(err, "Failed deleting %s: %v", targetLocation, err) + } + return nil +}