From 8d4563e175d0d0fe52511f1c5e8bf64fe7be3696 Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Wed, 19 Sep 2018 18:37:12 -0400 Subject: [PATCH 01/33] feat(olm): add OperatorGroup types This includes strictly the non-generated code. --- Makefile | 3 +- pkg/api/apis/operators/v1alpha2/doc.go | 3 ++ .../operators/v1alpha2/operatorgroup_types.go | 34 +++++++++++++ pkg/api/apis/operators/v1alpha2/register.go | 50 +++++++++++++++++++ 4 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 pkg/api/apis/operators/v1alpha2/doc.go create mode 100644 pkg/api/apis/operators/v1alpha2/operatorgroup_types.go create mode 100644 pkg/api/apis/operators/v1alpha2/register.go diff --git a/Makefile b/Makefile index 58c34ddfe2..95dd7d6137 100644 --- a/Makefile +++ b/Makefile @@ -123,8 +123,9 @@ clean-openapi: codegen-openapi: clean-openapi pkg/package-server/generated/openapi/zz_generated.openapi.go +# our version of hack/update-codegen.sh codegen: $(CODEGEN) - $(CODEGEN) all $(PKG)/pkg/api/client $(PKG)/pkg/api/apis "operators:v1alpha1" + $(CODEGEN) all $(PKG)/pkg/api/client $(PKG)/pkg/api/apis "operators:v1alpha1,v1alpha2" $(CODEGEN) all $(PKG)/pkg/package-server/client $(PKG)/pkg/package-server/apis "packagemanifest:v1alpha1" verify-codegen: codegen diff --git a/pkg/api/apis/operators/v1alpha2/doc.go b/pkg/api/apis/operators/v1alpha2/doc.go new file mode 100644 index 0000000000..fcaeb24831 --- /dev/null +++ b/pkg/api/apis/operators/v1alpha2/doc.go @@ -0,0 +1,3 @@ +// +k8s:deepcopy-gen=package +// +groupName=operators.coreos.com +package v1alpha2 diff --git a/pkg/api/apis/operators/v1alpha2/operatorgroup_types.go b/pkg/api/apis/operators/v1alpha2/operatorgroup_types.go new file mode 100644 index 0000000000..84133e5d8c --- /dev/null +++ b/pkg/api/apis/operators/v1alpha2/operatorgroup_types.go @@ -0,0 +1,34 @@ +package v1alpha2 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type OperatorGroupSpec struct { + Selector metav1.LabelSelector `json:"selector,omitempty"` + ServiceAccount corev1.ServiceAccount `json:"serviceAccount,omitempty"` +} + +type OperatorGroupStatus struct { + Namespaces []corev1.Namespace `json:"namespaces"` + LastUpdated metav1.Time `json:"lastUpdated"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +genclient +type OperatorGroup struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + + Spec OperatorGroupSpec `json:"spec"` + Status OperatorGroupStatus `json:"status,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type OperatorGroupList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []OperatorGroup `json:"items"` +} diff --git a/pkg/api/apis/operators/v1alpha2/register.go b/pkg/api/apis/operators/v1alpha2/register.go new file mode 100644 index 0000000000..340f8b40db --- /dev/null +++ b/pkg/api/apis/operators/v1alpha2/register.go @@ -0,0 +1,50 @@ +package v1alpha2 + +import ( + "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + k8sscheme "k8s.io/client-go/kubernetes/scheme" + + "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators" +) + +const ( + GroupName = "operators.coreos.com" + GroupVersion = "v1alpha2" +) + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: operators.GroupName, Version: GroupVersion} + +// Kind takes an unqualified kind and returns back a Group qualified GroupKind +func Kind(kind string) schema.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme + serScheme = runtime.NewScheme() +) + +func init() { + k8sscheme.AddToScheme(serScheme) + scheme.AddToScheme(serScheme) +} + +// Adds the list of known types to Scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &OperatorGroup{}, + &OperatorGroupList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} From 0ec2c72dd0f7aea38eb65dec515edf1c188500e7 Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Wed, 19 Sep 2018 16:02:47 -0400 Subject: [PATCH 02/33] chore(olm): generated files Add generated files for operator group type. --- .../v1alpha2/zz_generated.deepcopy.go | 129 +++++++++++++ .../client/clientset/versioned/clientset.go | 14 ++ .../versioned/fake/clientset_generated.go | 7 + .../clientset/versioned/fake/register.go | 2 + .../clientset/versioned/scheme/register.go | 2 + .../versioned/typed/operators/v1alpha2/doc.go | 20 ++ .../typed/operators/v1alpha2/fake/doc.go | 20 ++ .../v1alpha2/fake/fake_operatorgroup.go | 140 ++++++++++++++ .../v1alpha2/fake/fake_operators_client.go | 40 ++++ .../operators/v1alpha2/generated_expansion.go | 21 +++ .../typed/operators/v1alpha2/operatorgroup.go | 174 ++++++++++++++++++ .../operators/v1alpha2/operators_client.go | 90 +++++++++ .../informers/externalversions/generic.go | 5 + .../externalversions/operators/interface.go | 8 + .../operators/v1alpha2/interface.go | 45 +++++ .../operators/v1alpha2/operatorgroup.go | 89 +++++++++ .../operators/v1alpha2/expansion_generated.go | 27 +++ .../operators/v1alpha2/operatorgroup.go | 94 ++++++++++ 18 files changed, 927 insertions(+) create mode 100644 pkg/api/apis/operators/v1alpha2/zz_generated.deepcopy.go create mode 100644 pkg/api/client/clientset/versioned/typed/operators/v1alpha2/doc.go create mode 100644 pkg/api/client/clientset/versioned/typed/operators/v1alpha2/fake/doc.go create mode 100644 pkg/api/client/clientset/versioned/typed/operators/v1alpha2/fake/fake_operatorgroup.go create mode 100644 pkg/api/client/clientset/versioned/typed/operators/v1alpha2/fake/fake_operators_client.go create mode 100644 pkg/api/client/clientset/versioned/typed/operators/v1alpha2/generated_expansion.go create mode 100644 pkg/api/client/clientset/versioned/typed/operators/v1alpha2/operatorgroup.go create mode 100644 pkg/api/client/clientset/versioned/typed/operators/v1alpha2/operators_client.go create mode 100644 pkg/api/client/informers/externalversions/operators/v1alpha2/interface.go create mode 100644 pkg/api/client/informers/externalversions/operators/v1alpha2/operatorgroup.go create mode 100644 pkg/api/client/listers/operators/v1alpha2/expansion_generated.go create mode 100644 pkg/api/client/listers/operators/v1alpha2/operatorgroup.go diff --git a/pkg/api/apis/operators/v1alpha2/zz_generated.deepcopy.go b/pkg/api/apis/operators/v1alpha2/zz_generated.deepcopy.go new file mode 100644 index 0000000000..3e5837f6f0 --- /dev/null +++ b/pkg/api/apis/operators/v1alpha2/zz_generated.deepcopy.go @@ -0,0 +1,129 @@ +// +build !ignore_autogenerated + +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + v1 "k8s.io/api/core/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OperatorGroup) DeepCopyInto(out *OperatorGroup) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OperatorGroup. +func (in *OperatorGroup) DeepCopy() *OperatorGroup { + if in == nil { + return nil + } + out := new(OperatorGroup) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OperatorGroup) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OperatorGroupList) DeepCopyInto(out *OperatorGroupList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]OperatorGroup, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OperatorGroupList. +func (in *OperatorGroupList) DeepCopy() *OperatorGroupList { + if in == nil { + return nil + } + out := new(OperatorGroupList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OperatorGroupList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OperatorGroupSpec) DeepCopyInto(out *OperatorGroupSpec) { + *out = *in + in.Selector.DeepCopyInto(&out.Selector) + in.ServiceAccount.DeepCopyInto(&out.ServiceAccount) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OperatorGroupSpec. +func (in *OperatorGroupSpec) DeepCopy() *OperatorGroupSpec { + if in == nil { + return nil + } + out := new(OperatorGroupSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OperatorGroupStatus) DeepCopyInto(out *OperatorGroupStatus) { + *out = *in + if in.Namespaces != nil { + in, out := &in.Namespaces, &out.Namespaces + *out = make([]v1.Namespace, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + in.LastUpdated.DeepCopyInto(&out.LastUpdated) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OperatorGroupStatus. +func (in *OperatorGroupStatus) DeepCopy() *OperatorGroupStatus { + if in == nil { + return nil + } + out := new(OperatorGroupStatus) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/api/client/clientset/versioned/clientset.go b/pkg/api/client/clientset/versioned/clientset.go index bd96eb1e1c..c95ecaa46e 100644 --- a/pkg/api/client/clientset/versioned/clientset.go +++ b/pkg/api/client/clientset/versioned/clientset.go @@ -20,6 +20,7 @@ package versioned import ( operatorsv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned/typed/operators/v1alpha1" + operatorsv1alpha2 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned/typed/operators/v1alpha2" discovery "k8s.io/client-go/discovery" rest "k8s.io/client-go/rest" flowcontrol "k8s.io/client-go/util/flowcontrol" @@ -30,6 +31,7 @@ type Interface interface { OperatorsV1alpha1() operatorsv1alpha1.OperatorsV1alpha1Interface // Deprecated: please explicitly pick a version if possible. Operators() operatorsv1alpha1.OperatorsV1alpha1Interface + OperatorsV1alpha2() operatorsv1alpha2.OperatorsV1alpha2Interface } // Clientset contains the clients for groups. Each group has exactly one @@ -37,6 +39,7 @@ type Interface interface { type Clientset struct { *discovery.DiscoveryClient operatorsV1alpha1 *operatorsv1alpha1.OperatorsV1alpha1Client + operatorsV1alpha2 *operatorsv1alpha2.OperatorsV1alpha2Client } // OperatorsV1alpha1 retrieves the OperatorsV1alpha1Client @@ -50,6 +53,11 @@ func (c *Clientset) Operators() operatorsv1alpha1.OperatorsV1alpha1Interface { return c.operatorsV1alpha1 } +// OperatorsV1alpha2 retrieves the OperatorsV1alpha2Client +func (c *Clientset) OperatorsV1alpha2() operatorsv1alpha2.OperatorsV1alpha2Interface { + return c.operatorsV1alpha2 +} + // Discovery retrieves the DiscoveryClient func (c *Clientset) Discovery() discovery.DiscoveryInterface { if c == nil { @@ -70,6 +78,10 @@ func NewForConfig(c *rest.Config) (*Clientset, error) { if err != nil { return nil, err } + cs.operatorsV1alpha2, err = operatorsv1alpha2.NewForConfig(&configShallowCopy) + if err != nil { + return nil, err + } cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy) if err != nil { @@ -83,6 +95,7 @@ func NewForConfig(c *rest.Config) (*Clientset, error) { func NewForConfigOrDie(c *rest.Config) *Clientset { var cs Clientset cs.operatorsV1alpha1 = operatorsv1alpha1.NewForConfigOrDie(c) + cs.operatorsV1alpha2 = operatorsv1alpha2.NewForConfigOrDie(c) cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c) return &cs @@ -92,6 +105,7 @@ func NewForConfigOrDie(c *rest.Config) *Clientset { func New(c rest.Interface) *Clientset { var cs Clientset cs.operatorsV1alpha1 = operatorsv1alpha1.New(c) + cs.operatorsV1alpha2 = operatorsv1alpha2.New(c) cs.DiscoveryClient = discovery.NewDiscoveryClient(c) return &cs diff --git a/pkg/api/client/clientset/versioned/fake/clientset_generated.go b/pkg/api/client/clientset/versioned/fake/clientset_generated.go index 2432a97b7f..361be4e7d3 100644 --- a/pkg/api/client/clientset/versioned/fake/clientset_generated.go +++ b/pkg/api/client/clientset/versioned/fake/clientset_generated.go @@ -22,6 +22,8 @@ import ( clientset "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" operatorsv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned/typed/operators/v1alpha1" fakeoperatorsv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned/typed/operators/v1alpha1/fake" + operatorsv1alpha2 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned/typed/operators/v1alpha2" + fakeoperatorsv1alpha2 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned/typed/operators/v1alpha2/fake" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/discovery" @@ -80,3 +82,8 @@ func (c *Clientset) OperatorsV1alpha1() operatorsv1alpha1.OperatorsV1alpha1Inter func (c *Clientset) Operators() operatorsv1alpha1.OperatorsV1alpha1Interface { return &fakeoperatorsv1alpha1.FakeOperatorsV1alpha1{Fake: &c.Fake} } + +// OperatorsV1alpha2 retrieves the OperatorsV1alpha2Client +func (c *Clientset) OperatorsV1alpha2() operatorsv1alpha2.OperatorsV1alpha2Interface { + return &fakeoperatorsv1alpha2.FakeOperatorsV1alpha2{Fake: &c.Fake} +} diff --git a/pkg/api/client/clientset/versioned/fake/register.go b/pkg/api/client/clientset/versioned/fake/register.go index 3f47492a50..60a2974bc4 100644 --- a/pkg/api/client/clientset/versioned/fake/register.go +++ b/pkg/api/client/clientset/versioned/fake/register.go @@ -20,6 +20,7 @@ package fake import ( operatorsv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" + operatorsv1alpha2 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha2" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" schema "k8s.io/apimachinery/pkg/runtime/schema" @@ -51,4 +52,5 @@ func init() { // correctly. func AddToScheme(scheme *runtime.Scheme) { operatorsv1alpha1.AddToScheme(scheme) + operatorsv1alpha2.AddToScheme(scheme) } diff --git a/pkg/api/client/clientset/versioned/scheme/register.go b/pkg/api/client/clientset/versioned/scheme/register.go index 213c024cdb..d47c71b65b 100644 --- a/pkg/api/client/clientset/versioned/scheme/register.go +++ b/pkg/api/client/clientset/versioned/scheme/register.go @@ -20,6 +20,7 @@ package scheme import ( operatorsv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" + operatorsv1alpha2 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha2" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" schema "k8s.io/apimachinery/pkg/runtime/schema" @@ -51,4 +52,5 @@ func init() { // correctly. func AddToScheme(scheme *runtime.Scheme) { operatorsv1alpha1.AddToScheme(scheme) + operatorsv1alpha2.AddToScheme(scheme) } diff --git a/pkg/api/client/clientset/versioned/typed/operators/v1alpha2/doc.go b/pkg/api/client/clientset/versioned/typed/operators/v1alpha2/doc.go new file mode 100644 index 0000000000..baaf2d9853 --- /dev/null +++ b/pkg/api/client/clientset/versioned/typed/operators/v1alpha2/doc.go @@ -0,0 +1,20 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +// This package has the automatically generated typed clients. +package v1alpha2 diff --git a/pkg/api/client/clientset/versioned/typed/operators/v1alpha2/fake/doc.go b/pkg/api/client/clientset/versioned/typed/operators/v1alpha2/fake/doc.go new file mode 100644 index 0000000000..16f4439906 --- /dev/null +++ b/pkg/api/client/clientset/versioned/typed/operators/v1alpha2/fake/doc.go @@ -0,0 +1,20 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +// Package fake has the automatically generated clients. +package fake diff --git a/pkg/api/client/clientset/versioned/typed/operators/v1alpha2/fake/fake_operatorgroup.go b/pkg/api/client/clientset/versioned/typed/operators/v1alpha2/fake/fake_operatorgroup.go new file mode 100644 index 0000000000..361b106a5d --- /dev/null +++ b/pkg/api/client/clientset/versioned/typed/operators/v1alpha2/fake/fake_operatorgroup.go @@ -0,0 +1,140 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha2 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeOperatorGroups implements OperatorGroupInterface +type FakeOperatorGroups struct { + Fake *FakeOperatorsV1alpha2 + ns string +} + +var operatorgroupsResource = schema.GroupVersionResource{Group: "operators.coreos.com", Version: "v1alpha2", Resource: "operatorgroups"} + +var operatorgroupsKind = schema.GroupVersionKind{Group: "operators.coreos.com", Version: "v1alpha2", Kind: "OperatorGroup"} + +// Get takes name of the operatorGroup, and returns the corresponding operatorGroup object, and an error if there is any. +func (c *FakeOperatorGroups) Get(name string, options v1.GetOptions) (result *v1alpha2.OperatorGroup, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(operatorgroupsResource, c.ns, name), &v1alpha2.OperatorGroup{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.OperatorGroup), err +} + +// List takes label and field selectors, and returns the list of OperatorGroups that match those selectors. +func (c *FakeOperatorGroups) List(opts v1.ListOptions) (result *v1alpha2.OperatorGroupList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(operatorgroupsResource, operatorgroupsKind, c.ns, opts), &v1alpha2.OperatorGroupList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha2.OperatorGroupList{ListMeta: obj.(*v1alpha2.OperatorGroupList).ListMeta} + for _, item := range obj.(*v1alpha2.OperatorGroupList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested operatorGroups. +func (c *FakeOperatorGroups) Watch(opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(operatorgroupsResource, c.ns, opts)) + +} + +// Create takes the representation of a operatorGroup and creates it. Returns the server's representation of the operatorGroup, and an error, if there is any. +func (c *FakeOperatorGroups) Create(operatorGroup *v1alpha2.OperatorGroup) (result *v1alpha2.OperatorGroup, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(operatorgroupsResource, c.ns, operatorGroup), &v1alpha2.OperatorGroup{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.OperatorGroup), err +} + +// Update takes the representation of a operatorGroup and updates it. Returns the server's representation of the operatorGroup, and an error, if there is any. +func (c *FakeOperatorGroups) Update(operatorGroup *v1alpha2.OperatorGroup) (result *v1alpha2.OperatorGroup, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(operatorgroupsResource, c.ns, operatorGroup), &v1alpha2.OperatorGroup{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.OperatorGroup), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeOperatorGroups) UpdateStatus(operatorGroup *v1alpha2.OperatorGroup) (*v1alpha2.OperatorGroup, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(operatorgroupsResource, "status", c.ns, operatorGroup), &v1alpha2.OperatorGroup{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.OperatorGroup), err +} + +// Delete takes name of the operatorGroup and deletes it. Returns an error if one occurs. +func (c *FakeOperatorGroups) Delete(name string, options *v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(operatorgroupsResource, c.ns, name), &v1alpha2.OperatorGroup{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeOperatorGroups) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(operatorgroupsResource, c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1alpha2.OperatorGroupList{}) + return err +} + +// Patch applies the patch and returns the patched operatorGroup. +func (c *FakeOperatorGroups) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha2.OperatorGroup, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(operatorgroupsResource, c.ns, name, data, subresources...), &v1alpha2.OperatorGroup{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.OperatorGroup), err +} diff --git a/pkg/api/client/clientset/versioned/typed/operators/v1alpha2/fake/fake_operators_client.go b/pkg/api/client/clientset/versioned/typed/operators/v1alpha2/fake/fake_operators_client.go new file mode 100644 index 0000000000..3439aca18b --- /dev/null +++ b/pkg/api/client/clientset/versioned/typed/operators/v1alpha2/fake/fake_operators_client.go @@ -0,0 +1,40 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha2 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned/typed/operators/v1alpha2" + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" +) + +type FakeOperatorsV1alpha2 struct { + *testing.Fake +} + +func (c *FakeOperatorsV1alpha2) OperatorGroups(namespace string) v1alpha2.OperatorGroupInterface { + return &FakeOperatorGroups{c, namespace} +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeOperatorsV1alpha2) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/pkg/api/client/clientset/versioned/typed/operators/v1alpha2/generated_expansion.go b/pkg/api/client/clientset/versioned/typed/operators/v1alpha2/generated_expansion.go new file mode 100644 index 0000000000..00cf395818 --- /dev/null +++ b/pkg/api/client/clientset/versioned/typed/operators/v1alpha2/generated_expansion.go @@ -0,0 +1,21 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha2 + +type OperatorGroupExpansion interface{} diff --git a/pkg/api/client/clientset/versioned/typed/operators/v1alpha2/operatorgroup.go b/pkg/api/client/clientset/versioned/typed/operators/v1alpha2/operatorgroup.go new file mode 100644 index 0000000000..5ba1a5085b --- /dev/null +++ b/pkg/api/client/clientset/versioned/typed/operators/v1alpha2/operatorgroup.go @@ -0,0 +1,174 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + v1alpha2 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha2" + scheme "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// OperatorGroupsGetter has a method to return a OperatorGroupInterface. +// A group's client should implement this interface. +type OperatorGroupsGetter interface { + OperatorGroups(namespace string) OperatorGroupInterface +} + +// OperatorGroupInterface has methods to work with OperatorGroup resources. +type OperatorGroupInterface interface { + Create(*v1alpha2.OperatorGroup) (*v1alpha2.OperatorGroup, error) + Update(*v1alpha2.OperatorGroup) (*v1alpha2.OperatorGroup, error) + UpdateStatus(*v1alpha2.OperatorGroup) (*v1alpha2.OperatorGroup, error) + Delete(name string, options *v1.DeleteOptions) error + DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error + Get(name string, options v1.GetOptions) (*v1alpha2.OperatorGroup, error) + List(opts v1.ListOptions) (*v1alpha2.OperatorGroupList, error) + Watch(opts v1.ListOptions) (watch.Interface, error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha2.OperatorGroup, err error) + OperatorGroupExpansion +} + +// operatorGroups implements OperatorGroupInterface +type operatorGroups struct { + client rest.Interface + ns string +} + +// newOperatorGroups returns a OperatorGroups +func newOperatorGroups(c *OperatorsV1alpha2Client, namespace string) *operatorGroups { + return &operatorGroups{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the operatorGroup, and returns the corresponding operatorGroup object, and an error if there is any. +func (c *operatorGroups) Get(name string, options v1.GetOptions) (result *v1alpha2.OperatorGroup, err error) { + result = &v1alpha2.OperatorGroup{} + err = c.client.Get(). + Namespace(c.ns). + Resource("operatorgroups"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of OperatorGroups that match those selectors. +func (c *operatorGroups) List(opts v1.ListOptions) (result *v1alpha2.OperatorGroupList, err error) { + result = &v1alpha2.OperatorGroupList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("operatorgroups"). + VersionedParams(&opts, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested operatorGroups. +func (c *operatorGroups) Watch(opts v1.ListOptions) (watch.Interface, error) { + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("operatorgroups"). + VersionedParams(&opts, scheme.ParameterCodec). + Watch() +} + +// Create takes the representation of a operatorGroup and creates it. Returns the server's representation of the operatorGroup, and an error, if there is any. +func (c *operatorGroups) Create(operatorGroup *v1alpha2.OperatorGroup) (result *v1alpha2.OperatorGroup, err error) { + result = &v1alpha2.OperatorGroup{} + err = c.client.Post(). + Namespace(c.ns). + Resource("operatorgroups"). + Body(operatorGroup). + Do(). + Into(result) + return +} + +// Update takes the representation of a operatorGroup and updates it. Returns the server's representation of the operatorGroup, and an error, if there is any. +func (c *operatorGroups) Update(operatorGroup *v1alpha2.OperatorGroup) (result *v1alpha2.OperatorGroup, err error) { + result = &v1alpha2.OperatorGroup{} + err = c.client.Put(). + Namespace(c.ns). + Resource("operatorgroups"). + Name(operatorGroup.Name). + Body(operatorGroup). + Do(). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + +func (c *operatorGroups) UpdateStatus(operatorGroup *v1alpha2.OperatorGroup) (result *v1alpha2.OperatorGroup, err error) { + result = &v1alpha2.OperatorGroup{} + err = c.client.Put(). + Namespace(c.ns). + Resource("operatorgroups"). + Name(operatorGroup.Name). + SubResource("status"). + Body(operatorGroup). + Do(). + Into(result) + return +} + +// Delete takes name of the operatorGroup and deletes it. Returns an error if one occurs. +func (c *operatorGroups) Delete(name string, options *v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("operatorgroups"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *operatorGroups) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("operatorgroups"). + VersionedParams(&listOptions, scheme.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Patch applies the patch and returns the patched operatorGroup. +func (c *operatorGroups) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha2.OperatorGroup, err error) { + result = &v1alpha2.OperatorGroup{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("operatorgroups"). + SubResource(subresources...). + Name(name). + Body(data). + Do(). + Into(result) + return +} diff --git a/pkg/api/client/clientset/versioned/typed/operators/v1alpha2/operators_client.go b/pkg/api/client/clientset/versioned/typed/operators/v1alpha2/operators_client.go new file mode 100644 index 0000000000..8db2a72fd9 --- /dev/null +++ b/pkg/api/client/clientset/versioned/typed/operators/v1alpha2/operators_client.go @@ -0,0 +1,90 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + v1alpha2 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha2" + "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned/scheme" + serializer "k8s.io/apimachinery/pkg/runtime/serializer" + rest "k8s.io/client-go/rest" +) + +type OperatorsV1alpha2Interface interface { + RESTClient() rest.Interface + OperatorGroupsGetter +} + +// OperatorsV1alpha2Client is used to interact with features provided by the operators.coreos.com group. +type OperatorsV1alpha2Client struct { + restClient rest.Interface +} + +func (c *OperatorsV1alpha2Client) OperatorGroups(namespace string) OperatorGroupInterface { + return newOperatorGroups(c, namespace) +} + +// NewForConfig creates a new OperatorsV1alpha2Client for the given config. +func NewForConfig(c *rest.Config) (*OperatorsV1alpha2Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := rest.RESTClientFor(&config) + if err != nil { + return nil, err + } + return &OperatorsV1alpha2Client{client}, nil +} + +// NewForConfigOrDie creates a new OperatorsV1alpha2Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *OperatorsV1alpha2Client { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new OperatorsV1alpha2Client for the given RESTClient. +func New(c rest.Interface) *OperatorsV1alpha2Client { + return &OperatorsV1alpha2Client{c} +} + +func setConfigDefaults(config *rest.Config) error { + gv := v1alpha2.SchemeGroupVersion + config.GroupVersion = &gv + config.APIPath = "/apis" + config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs} + + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + + return nil +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *OperatorsV1alpha2Client) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/pkg/api/client/informers/externalversions/generic.go b/pkg/api/client/informers/externalversions/generic.go index 6527f494ca..e41df75b33 100644 --- a/pkg/api/client/informers/externalversions/generic.go +++ b/pkg/api/client/informers/externalversions/generic.go @@ -22,6 +22,7 @@ import ( "fmt" v1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" + v1alpha2 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha2" schema "k8s.io/apimachinery/pkg/runtime/schema" cache "k8s.io/client-go/tools/cache" ) @@ -62,6 +63,10 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource case v1alpha1.SchemeGroupVersion.WithResource("subscriptions"): return &genericInformer{resource: resource.GroupResource(), informer: f.Operators().V1alpha1().Subscriptions().Informer()}, nil + // Group=operators.coreos.com, Version=v1alpha2 + case v1alpha2.SchemeGroupVersion.WithResource("operatorgroups"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Operators().V1alpha2().OperatorGroups().Informer()}, nil + } return nil, fmt.Errorf("no informer found for %v", resource) diff --git a/pkg/api/client/informers/externalversions/operators/interface.go b/pkg/api/client/informers/externalversions/operators/interface.go index 1758289a88..8ffded8769 100644 --- a/pkg/api/client/informers/externalversions/operators/interface.go +++ b/pkg/api/client/informers/externalversions/operators/interface.go @@ -21,12 +21,15 @@ package operators import ( internalinterfaces "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/informers/externalversions/internalinterfaces" v1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/informers/externalversions/operators/v1alpha1" + v1alpha2 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/informers/externalversions/operators/v1alpha2" ) // Interface provides access to each of this group's versions. type Interface interface { // V1alpha1 provides access to shared informers for resources in V1alpha1. V1alpha1() v1alpha1.Interface + // V1alpha2 provides access to shared informers for resources in V1alpha2. + V1alpha2() v1alpha2.Interface } type group struct { @@ -44,3 +47,8 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList func (g *group) V1alpha1() v1alpha1.Interface { return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions) } + +// V1alpha2 returns a new v1alpha2.Interface. +func (g *group) V1alpha2() v1alpha2.Interface { + return v1alpha2.New(g.factory, g.namespace, g.tweakListOptions) +} diff --git a/pkg/api/client/informers/externalversions/operators/v1alpha2/interface.go b/pkg/api/client/informers/externalversions/operators/v1alpha2/interface.go new file mode 100644 index 0000000000..2619496111 --- /dev/null +++ b/pkg/api/client/informers/externalversions/operators/v1alpha2/interface.go @@ -0,0 +1,45 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + internalinterfaces "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/informers/externalversions/internalinterfaces" +) + +// Interface provides access to all the informers in this group version. +type Interface interface { + // OperatorGroups returns a OperatorGroupInformer. + OperatorGroups() OperatorGroupInformer +} + +type version struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// OperatorGroups returns a OperatorGroupInformer. +func (v *version) OperatorGroups() OperatorGroupInformer { + return &operatorGroupInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} diff --git a/pkg/api/client/informers/externalversions/operators/v1alpha2/operatorgroup.go b/pkg/api/client/informers/externalversions/operators/v1alpha2/operatorgroup.go new file mode 100644 index 0000000000..952c737574 --- /dev/null +++ b/pkg/api/client/informers/externalversions/operators/v1alpha2/operatorgroup.go @@ -0,0 +1,89 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + time "time" + + operators_v1alpha2 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha2" + versioned "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" + internalinterfaces "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/informers/externalversions/internalinterfaces" + v1alpha2 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// OperatorGroupInformer provides access to a shared informer and lister for +// OperatorGroups. +type OperatorGroupInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha2.OperatorGroupLister +} + +type operatorGroupInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewOperatorGroupInformer constructs a new informer for OperatorGroup type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewOperatorGroupInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredOperatorGroupInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredOperatorGroupInformer constructs a new informer for OperatorGroup type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredOperatorGroupInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OperatorsV1alpha2().OperatorGroups(namespace).List(options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OperatorsV1alpha2().OperatorGroups(namespace).Watch(options) + }, + }, + &operators_v1alpha2.OperatorGroup{}, + resyncPeriod, + indexers, + ) +} + +func (f *operatorGroupInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredOperatorGroupInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *operatorGroupInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&operators_v1alpha2.OperatorGroup{}, f.defaultInformer) +} + +func (f *operatorGroupInformer) Lister() v1alpha2.OperatorGroupLister { + return v1alpha2.NewOperatorGroupLister(f.Informer().GetIndexer()) +} diff --git a/pkg/api/client/listers/operators/v1alpha2/expansion_generated.go b/pkg/api/client/listers/operators/v1alpha2/expansion_generated.go new file mode 100644 index 0000000000..aaf9e38cc1 --- /dev/null +++ b/pkg/api/client/listers/operators/v1alpha2/expansion_generated.go @@ -0,0 +1,27 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha2 + +// OperatorGroupListerExpansion allows custom methods to be added to +// OperatorGroupLister. +type OperatorGroupListerExpansion interface{} + +// OperatorGroupNamespaceListerExpansion allows custom methods to be added to +// OperatorGroupNamespaceLister. +type OperatorGroupNamespaceListerExpansion interface{} diff --git a/pkg/api/client/listers/operators/v1alpha2/operatorgroup.go b/pkg/api/client/listers/operators/v1alpha2/operatorgroup.go new file mode 100644 index 0000000000..f07276e62d --- /dev/null +++ b/pkg/api/client/listers/operators/v1alpha2/operatorgroup.go @@ -0,0 +1,94 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + v1alpha2 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha2" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// OperatorGroupLister helps list OperatorGroups. +type OperatorGroupLister interface { + // List lists all OperatorGroups in the indexer. + List(selector labels.Selector) (ret []*v1alpha2.OperatorGroup, err error) + // OperatorGroups returns an object that can list and get OperatorGroups. + OperatorGroups(namespace string) OperatorGroupNamespaceLister + OperatorGroupListerExpansion +} + +// operatorGroupLister implements the OperatorGroupLister interface. +type operatorGroupLister struct { + indexer cache.Indexer +} + +// NewOperatorGroupLister returns a new OperatorGroupLister. +func NewOperatorGroupLister(indexer cache.Indexer) OperatorGroupLister { + return &operatorGroupLister{indexer: indexer} +} + +// List lists all OperatorGroups in the indexer. +func (s *operatorGroupLister) List(selector labels.Selector) (ret []*v1alpha2.OperatorGroup, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha2.OperatorGroup)) + }) + return ret, err +} + +// OperatorGroups returns an object that can list and get OperatorGroups. +func (s *operatorGroupLister) OperatorGroups(namespace string) OperatorGroupNamespaceLister { + return operatorGroupNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// OperatorGroupNamespaceLister helps list and get OperatorGroups. +type OperatorGroupNamespaceLister interface { + // List lists all OperatorGroups in the indexer for a given namespace. + List(selector labels.Selector) (ret []*v1alpha2.OperatorGroup, err error) + // Get retrieves the OperatorGroup from the indexer for a given namespace and name. + Get(name string) (*v1alpha2.OperatorGroup, error) + OperatorGroupNamespaceListerExpansion +} + +// operatorGroupNamespaceLister implements the OperatorGroupNamespaceLister +// interface. +type operatorGroupNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all OperatorGroups in the indexer for a given namespace. +func (s operatorGroupNamespaceLister) List(selector labels.Selector) (ret []*v1alpha2.OperatorGroup, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha2.OperatorGroup)) + }) + return ret, err +} + +// Get retrieves the OperatorGroup from the indexer for a given namespace and name. +func (s operatorGroupNamespaceLister) Get(name string) (*v1alpha2.OperatorGroup, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha2.Resource("operatorgroup"), name) + } + return obj.(*v1alpha2.OperatorGroup), nil +} From a043600fefbc46d0ba3da25b6a73ecc8ea88d405 Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Fri, 21 Sep 2018 17:43:45 -0400 Subject: [PATCH 03/33] feat(olm): add CRD definition for OperatorGroup --- .../0000_30_14-operatorgroup.crd.yaml | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 deploy/chart/templates/0000_30_14-operatorgroup.crd.yaml diff --git a/deploy/chart/templates/0000_30_14-operatorgroup.crd.yaml b/deploy/chart/templates/0000_30_14-operatorgroup.crd.yaml new file mode 100644 index 0000000000..b8102198f2 --- /dev/null +++ b/deploy/chart/templates/0000_30_14-operatorgroup.crd.yaml @@ -0,0 +1,49 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: operatorgroups.operators.coreos.com +spec: + group: operators.coreos.com + version: v1alpha2 + versions: + - name: v1alpha2 + served: true + storage: true + names: + plural: operatorgroups + singular: operatorgroup + kind: OperatorGroup + listKind: OperatorGroupList + scope: Namespaced + subresources: + # status enables the status subresource. + status: {} + validation: + openAPIV3Schema: + properties: + spec: + properties: + selector: + type: object + serviceAccountName: + type: string + required: + - selector + type: object + status: + properties: + lastUpdated: + format: date-time + type: string + namespaces: + items: + type: object + type: array + required: + - namespaces + - lastUpdated + type: object + required: + - metadata + - spec + version: v1alpha2 From 2a5a47b8e0710810c6e8a94970fc7ef0adcc72d7 Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Mon, 24 Sep 2018 17:30:57 -0400 Subject: [PATCH 04/33] feat(olm): add operator group control loop So far the loop only looks at the namespace selector and writes the results to the status and an annotation on each deployment. --- pkg/controller/operators/olm/operator.go | 94 ++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/pkg/controller/operators/olm/operator.go b/pkg/controller/operators/olm/operator.go index d39e3a8f57..d89db9e463 100644 --- a/pkg/controller/operators/olm/operator.go +++ b/pkg/controller/operators/olm/operator.go @@ -3,9 +3,11 @@ package olm import ( "errors" "fmt" + "strings" "time" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" + "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha2" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/informers/externalversions" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/annotator" @@ -32,6 +34,9 @@ import ( var ErrRequirementsNotMet = errors.New("requirements were not met") var ErrCRDOwnerConflict = errors.New("CRD owned by another ClusterServiceVersion") +//TODO(jpeeler): copied from catalog/operator.go +var timeNow = func() metav1.Time { return metav1.NewTime(time.Now().UTC()) } + const ( FallbackWakeupInterval = 30 * time.Second ) @@ -207,6 +212,28 @@ func NewOperator(crClient versioned.Interface, opClient operatorclient.ClientInt for _, informer := range depQueueInformers { op.RegisterQueueInformer(informer) } + + // Create an informer for the operator group + operatorGroupInformers := []cache.SharedIndexInformer{} + for _, namespace := range namespaces { + informerFactory := externalversions.NewSharedInformerFactoryWithOptions(crClient, wakeupInterval, externalversions.WithNamespace(namespace)) + operatorGroupInformers = append(operatorGroupInformers, informerFactory.Operators().V1alpha2().OperatorGroups().Informer()) + } + + // Register OperatorGroup informers. + operatorGroupQueue := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "operatorgroups") + operatorGroupQueueInformer := queueinformer.New( + operatorGroupQueue, + operatorGroupInformers, + op.syncOperatorGroups, + nil, + "operatorgroups", + metrics.NewMetricsNil(), + ) + for _, informer := range operatorGroupQueueInformer { + op.RegisterQueueInformer(informer) + } + return op, nil } @@ -318,6 +345,73 @@ func (a *Operator) syncServices(obj interface{}) (syncError error) { return nil } +func (a *Operator) syncOperatorGroups(obj interface{}) error { + op, ok := obj.(*v1alpha2.OperatorGroup) + if !ok { + log.Debugf("wrong type: %#v\n", obj) + return fmt.Errorf("casting OperatorGroup failed") + } + + // write namespace matches to status field + selector, err := metav1.LabelSelectorAsSelector(&op.Spec.Selector) + if err != nil { + return err + } + operatorGroupOpts := metav1.ListOptions{LabelSelector: selector.String()} + // TODO(jpeeler): this needs to use user impersonation with op.Spec.ServiceAccount + namespaceList, err := a.OpClient.KubernetesInterface().CoreV1().Namespaces().List(operatorGroupOpts) + if err != nil { + return err + } + + nsCount := len(namespaceList.Items) + + if len(op.Status.Namespaces) == nsCount { + for i, v := range namespaceList.Items { + if v.Name != op.Status.Namespaces[i].Name { + break + } + } + // status is current with correct namespaces, so no further updates required + return nil + } + op.Status.Namespaces = namespaceList.Items + op.Status.LastUpdated = timeNow() + + // make string list made up of comma separated namespaces + var nsList strings.Builder + for i := 0; i < nsCount-1; i++ { + nsList.WriteString(namespaceList.Items[i].Name + ",") + } + nsList.WriteString(namespaceList.Items[nsCount-1].Name) + + // write namespaces to watch in every deployment + currentNamespace := op.GetNamespace() + csvsInNamespace := a.csvsInNamespace(currentNamespace) + for csvName, csv := range csvsInNamespace { + strategy, err := a.resolver.UnmarshalStrategy(csv.Spec.InstallStrategy) + if err != nil { + return fmt.Errorf("error unmarshaling strategy from ClusterServiceVersion '%s' with error: %s", csvName, err) + } + + strategyDetailsDeployment, ok := strategy.(*install.StrategyDetailsDeployment) + if !ok { + return fmt.Errorf("could not assert strategy implementation as deployment for CSV %s", csvName) + } + + for _, deploy := range strategyDetailsDeployment.DeploymentSpecs { + deploy.Spec.Template.Annotations["olm.targetNamespaces"] = nsList.String() + _, err := a.client.Operators().ClusterServiceVersions(currentNamespace).Update(csv) + if err != nil { + return fmt.Errorf("CSV update for '%v' failed: %v\n", csvName, err) + } + a.requeueCSV(csvName, currentNamespace) + } + } + + return nil +} + // syncClusterServiceVersion is the method that gets called when we see a CSV event in the cluster func (a *Operator) syncClusterServiceVersion(obj interface{}) (syncError error) { clusterServiceVersion, ok := obj.(*v1alpha1.ClusterServiceVersion) From 8ab04c2385c5e523c0eb8804231213c29a70f006 Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Wed, 26 Sep 2018 17:21:41 -0400 Subject: [PATCH 05/33] chore(olm): rename annotateNamespace to syncNamespace Make this sync loop naming consistent with the rest. --- pkg/controller/operators/olm/operator.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/controller/operators/olm/operator.go b/pkg/controller/operators/olm/operator.go index d89db9e463..cb638267c8 100644 --- a/pkg/controller/operators/olm/operator.go +++ b/pkg/controller/operators/olm/operator.go @@ -88,8 +88,8 @@ func NewOperator(crClient versioned.Interface, opClient operatorclient.ClientInt namespaceInformer := informers.NewSharedInformerFactory(queueOperator.OpClient.KubernetesInterface(), wakeupInterval).Core().V1().Namespaces() queueInformer := queueinformer.NewInformer( workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "namespaces"), - namespaceInformer.Informer(), - op.annotateNamespace, + namespaceInformer, + op.syncNamespace, nil, "namespace", metrics.NewMetricsNil(), @@ -719,8 +719,8 @@ func (a *Operator) crdOwnerConflicts(in *v1alpha1.ClusterServiceVersion, csvsInN return nil } -// annotateNamespace is the method that gets called when we see a namespace event in the cluster -func (a *Operator) annotateNamespace(obj interface{}) (syncError error) { +// syncNamespace is the method that gets called when we see a namespace event in the cluster +func (a *Operator) syncNamespace(obj interface{}) (syncError error) { namespace, ok := obj.(*corev1.Namespace) if !ok { log.Debugf("wrong type: %#v", obj) From 92652892e8aa349ac454251c436eee304dde5b64 Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Thu, 27 Sep 2018 12:49:30 -0400 Subject: [PATCH 06/33] feat(olm): make namespace updates trigger annotation updates The namespace sync loop modification is an optimization for faster updates when a namespace is added or removed. Small enhancements: - query informers instead of using client for operator group list - send a patch update for modified CSV - ensure operator group status namespace list is up to date by comparing to a map, rather than a potentially unordered list --- pkg/controller/operators/olm/operator.go | 132 ++++++++++++++++++----- 1 file changed, 108 insertions(+), 24 deletions(-) diff --git a/pkg/controller/operators/olm/operator.go b/pkg/controller/operators/olm/operator.go index cb638267c8..bffbd5171c 100644 --- a/pkg/controller/operators/olm/operator.go +++ b/pkg/controller/operators/olm/operator.go @@ -1,6 +1,7 @@ package olm import ( + "encoding/json" "errors" "fmt" "strings" @@ -10,6 +11,7 @@ import ( "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha2" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/informers/externalversions" + operatorgrouplister "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha2" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/annotator" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/certs" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install" @@ -24,7 +26,11 @@ import ( corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/client-go/informers" + crbacv1 "k8s.io/client-go/listers/rbac/v1" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" @@ -43,13 +49,18 @@ const ( type Operator struct { *queueinformer.Operator - csvQueue workqueue.RateLimitingInterface - client versioned.Interface - resolver install.StrategyResolverInterface - lister operatorlister.OperatorLister - annotator *annotator.Annotator - recorder record.EventRecorder - cleanupFunc func() + csvQueue workqueue.RateLimitingInterface + client versioned.Interface + resolver install.StrategyResolverInterface + lister operatorlister.OperatorLister + roleLister crbacv1.RoleLister + roleBindingLister crbacv1.RoleBindingLister + clusterRoleLister crbacv1.ClusterRoleLister + clusterRoleBindingLister crbacv1.ClusterRoleBindingLister + operatorGroupLister map[string]operatorgrouplister.OperatorGroupLister + annotator *annotator.Annotator + recorder record.EventRecorder + cleanupFunc func() } func NewOperator(crClient versioned.Interface, opClient operatorclient.ClientInterface, resolver install.StrategyResolverInterface, wakeupInterval time.Duration, annotations map[string]string, namespaces []string) (*Operator, error) { @@ -88,7 +99,7 @@ func NewOperator(crClient versioned.Interface, opClient operatorclient.ClientInt namespaceInformer := informers.NewSharedInformerFactory(queueOperator.OpClient.KubernetesInterface(), wakeupInterval).Core().V1().Namespaces() queueInformer := queueinformer.NewInformer( workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "namespaces"), - namespaceInformer, + namespaceInformer.Informer(), op.syncNamespace, nil, "namespace", @@ -215,9 +226,12 @@ func NewOperator(crClient versioned.Interface, opClient operatorclient.ClientInt // Create an informer for the operator group operatorGroupInformers := []cache.SharedIndexInformer{} + op.operatorGroupLister = make(map[string]operatorgrouplister.OperatorGroupLister, len(namespaces)) for _, namespace := range namespaces { informerFactory := externalversions.NewSharedInformerFactoryWithOptions(crClient, wakeupInterval, externalversions.WithNamespace(namespace)) - operatorGroupInformers = append(operatorGroupInformers, informerFactory.Operators().V1alpha2().OperatorGroups().Informer()) + informer := informerFactory.Operators().V1alpha2().OperatorGroups() + operatorGroupInformers = append(operatorGroupInformers, informer.Informer()) + op.operatorGroupLister[namespace] = informer.Lister() } // Register OperatorGroup informers. @@ -345,14 +359,10 @@ func (a *Operator) syncServices(obj interface{}) (syncError error) { return nil } -func (a *Operator) syncOperatorGroups(obj interface{}) error { - op, ok := obj.(*v1alpha2.OperatorGroup) - if !ok { - log.Debugf("wrong type: %#v\n", obj) - return fmt.Errorf("casting OperatorGroup failed") - } +func (a *Operator) updateDeploymentAnnotation(op *v1alpha2.OperatorGroup) error { + // NOTE: if a CSV modification is required in the future, copy the original + // data as done in the bottom of this method first. - // write namespace matches to status field selector, err := metav1.LabelSelectorAsSelector(&op.Spec.Selector) if err != nil { return err @@ -367,13 +377,21 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error { nsCount := len(namespaceList.Items) if len(op.Status.Namespaces) == nsCount { - for i, v := range namespaceList.Items { - if v.Name != op.Status.Namespaces[i].Name { + nsMap := map[string]struct{}{} + for _, v := range namespaceList.Items { + nsMap[v.Name] = struct{}{} + } + equalityCheck := true + for _, v := range op.Status.Namespaces { + if _, ok := nsMap[v.Name]; !ok { + equalityCheck = false break } } - // status is current with correct namespaces, so no further updates required - return nil + if equalityCheck { + // status is current with correct namespaces, so no further updates required + return nil + } } op.Status.Namespaces = namespaceList.Items op.Status.LastUpdated = timeNow() @@ -400,18 +418,45 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error { } for _, deploy := range strategyDetailsDeployment.DeploymentSpecs { + originalData, err := json.Marshal(csv) + if err != nil { + return err + } deploy.Spec.Template.Annotations["olm.targetNamespaces"] = nsList.String() - _, err := a.client.Operators().ClusterServiceVersions(currentNamespace).Update(csv) + modifiedData, err := json.Marshal(csv) + if err != nil { + return err + } + patchBytes, err := strategicpatch.CreateTwoWayMergePatch(originalData, modifiedData, v1alpha1.ClusterServiceVersion{}) + if err != nil { + return err + } + + _, err = a.client.Operators().ClusterServiceVersions(currentNamespace).Patch(csvName, types.StrategicMergePatchType, patchBytes) if err != nil { return fmt.Errorf("CSV update for '%v' failed: %v\n", csvName, err) } - a.requeueCSV(csvName, currentNamespace) + //a.requeueCSV(csvName, currentNamespace) } } return nil } +func (a *Operator) syncOperatorGroups(obj interface{}) error { + op, ok := obj.(*v1alpha2.OperatorGroup) + if !ok { + log.Debugf("wrong type: %#v\n", obj) + return fmt.Errorf("casting OperatorGroup failed") + } + + if err := a.updateDeploymentAnnotation(op); err != nil { + return err + } + + return nil +} + // syncClusterServiceVersion is the method that gets called when we see a CSV event in the cluster func (a *Operator) syncClusterServiceVersion(obj interface{}) (syncError error) { clusterServiceVersion, ok := obj.(*v1alpha1.ClusterServiceVersion) @@ -722,16 +767,55 @@ func (a *Operator) crdOwnerConflicts(in *v1alpha1.ClusterServiceVersion, csvsInN // syncNamespace is the method that gets called when we see a namespace event in the cluster func (a *Operator) syncNamespace(obj interface{}) (syncError error) { namespace, ok := obj.(*corev1.Namespace) + namespaceName := namespace.GetName() if !ok { log.Debugf("wrong type: %#v", obj) return fmt.Errorf("casting Namespace failed") } - log.Infof("syncing Namespace: %s", namespace.GetName()) + log.Infof("syncing Namespace: %s", namespaceName) if err := a.annotator.AnnotateNamespace(namespace); err != nil { - log.Infof("error annotating namespace '%s'", namespace.GetName()) + log.Infof("error annotating namespace '%s'", namespaceName) return err } + + opGroupList, err := a.operatorGroupLister[namespaceName].List(labels.Everything()) + if err != nil { + return err + } + + opGroupUpdate := map[*v1alpha2.OperatorGroup]struct{}{} + for _, op := range opGroupList { + selector, err := metav1.LabelSelectorAsSelector(&op.Spec.Selector) + if err != nil { + return err + } + + if selector.Matches(labels.Set(namespace.GetLabels())) { + namespaceMatch := false + for _, seenNamespace := range op.Status.Namespaces { + if seenNamespace.Name == namespaceName { + namespaceMatch = true + break + } + } + if namespaceMatch == false { + opGroupUpdate[op] = struct{}{} + } + } else { + for _, seenNamespace := range op.Status.Namespaces { + if seenNamespace.Name == namespaceName { + opGroupUpdate[op] = struct{}{} + } + } + } + } + + for op := range opGroupUpdate { + if err := a.updateDeploymentAnnotation(op); err != nil { + return err + } + } return nil } From ed88e91c9c8d81dc9319416c59d51f5bb2bc5b45 Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Fri, 28 Sep 2018 15:47:50 -0400 Subject: [PATCH 07/33] feat(olm): handle required RBAC permissions --- pkg/controller/operators/olm/operator.go | 107 +++++++++++++++++------ 1 file changed, 79 insertions(+), 28 deletions(-) diff --git a/pkg/controller/operators/olm/operator.go b/pkg/controller/operators/olm/operator.go index bffbd5171c..2f8edbb5ac 100644 --- a/pkg/controller/operators/olm/operator.go +++ b/pkg/controller/operators/olm/operator.go @@ -359,6 +359,25 @@ func (a *Operator) syncServices(obj interface{}) (syncError error) { return nil } +func namespacesChanged(clusterNamespaces []corev1.Namespace, statusNamespaces []corev1.Namespace) bool { + nsCount := len(clusterNamespaces) + + if len(statusNamespaces) != nsCount { + return true + } + + nsMap := map[string]struct{}{} + for _, v := range clusterNamespaces { + nsMap[v.Name] = struct{}{} + } + for _, v := range statusNamespaces { + if _, ok := nsMap[v.Name]; !ok { + return true + } + } + return false +} + func (a *Operator) updateDeploymentAnnotation(op *v1alpha2.OperatorGroup) error { // NOTE: if a CSV modification is required in the future, copy the original // data as done in the bottom of this method first. @@ -368,42 +387,18 @@ func (a *Operator) updateDeploymentAnnotation(op *v1alpha2.OperatorGroup) error return err } operatorGroupOpts := metav1.ListOptions{LabelSelector: selector.String()} - // TODO(jpeeler): this needs to use user impersonation with op.Spec.ServiceAccount namespaceList, err := a.OpClient.KubernetesInterface().CoreV1().Namespaces().List(operatorGroupOpts) if err != nil { return err } - nsCount := len(namespaceList.Items) - - if len(op.Status.Namespaces) == nsCount { - nsMap := map[string]struct{}{} - for _, v := range namespaceList.Items { - nsMap[v.Name] = struct{}{} - } - equalityCheck := true - for _, v := range op.Status.Namespaces { - if _, ok := nsMap[v.Name]; !ok { - equalityCheck = false - break - } - } - if equalityCheck { - // status is current with correct namespaces, so no further updates required - return nil - } + if !namespacesChanged(namespaceList.Items, op.Status.Namespaces) { + // status is current with correct namespaces, so no further updates required + return nil } op.Status.Namespaces = namespaceList.Items op.Status.LastUpdated = timeNow() - // make string list made up of comma separated namespaces - var nsList strings.Builder - for i := 0; i < nsCount-1; i++ { - nsList.WriteString(namespaceList.Items[i].Name + ",") - } - nsList.WriteString(namespaceList.Items[nsCount-1].Name) - - // write namespaces to watch in every deployment currentNamespace := op.GetNamespace() csvsInNamespace := a.csvsInNamespace(currentNamespace) for csvName, csv := range csvsInNamespace { @@ -417,12 +412,68 @@ func (a *Operator) updateDeploymentAnnotation(op *v1alpha2.OperatorGroup) error return fmt.Errorf("could not assert strategy implementation as deployment for CSV %s", csvName) } + managerPolicyRules := []rbacv1.PolicyRule{} + apiEditPolicyRules := []rbacv1.PolicyRule{} + apiViewPolicyRules := []rbacv1.PolicyRule{} + for _, owned := range csv.Spec.CustomResourceDefinitions.Owned { + resourceNames := []string{} + for _, resource := range owned.Resources { + resourceNames = append(resourceNames, resource.Name) + } + managerPolicyRules = append(managerPolicyRules, rbacv1.PolicyRule{Verbs: []string{"*"}, APIGroups: []string{owned.Name}, Resources: resourceNames}) + apiEditPolicyRules = append(apiEditPolicyRules, rbacv1.PolicyRule{Verbs: []string{"create", "update", "patch", "delete"}, APIGroups: []string{owned.Name}, Resources: []string{owned.Kind}}) + apiViewPolicyRules = append(apiViewPolicyRules, rbacv1.PolicyRule{Verbs: []string{"get", "list", "watch"}, APIGroups: []string{owned.Name}, Resources: []string{owned.Kind}}) + } + for _, owned := range csv.Spec.APIServiceDefinitions.Owned { + resourceNames := []string{} + for _, resource := range owned.Resources { + resourceNames = append(resourceNames, resource.Name) + } + managerPolicyRules = append(managerPolicyRules, rbacv1.PolicyRule{Verbs: []string{"*"}, APIGroups: []string{owned.Name}, Resources: resourceNames}) + } + clusterRole := &rbacv1.ClusterRole{ + Rules: managerPolicyRules, + } + ownerutil.AddNonBlockingOwner(clusterRole, csv) + clusterRole.SetGenerateName(fmt.Sprintf("owned-crd-manager-%s-", csv.Spec.DisplayName)) + _, err = a.OpClient.KubernetesInterface().RbacV1().ClusterRoles().Create(clusterRole) + if err != nil { + return err + } + + // operator group specific roles + operatorGroupEditClusterRole := &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-edit", op.Name), + }, + Rules: apiEditPolicyRules, + } + _, err = a.OpClient.KubernetesInterface().RbacV1().ClusterRoles().Create(operatorGroupEditClusterRole) + if err != nil { + return err + } + operatorGroupViewClusterRole := &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-view", op.Name), + }, + Rules: apiViewPolicyRules, + } + _, err = a.OpClient.KubernetesInterface().RbacV1().ClusterRoles().Create(operatorGroupViewClusterRole) + if err != nil { + return err + } + + var nsList []string + for ix := range namespaceList.Items { + nsList = append(nsList, namespaceList.Items[ix].Name) + } + // write namespaces to watch in every deployment for _, deploy := range strategyDetailsDeployment.DeploymentSpecs { originalData, err := json.Marshal(csv) if err != nil { return err } - deploy.Spec.Template.Annotations["olm.targetNamespaces"] = nsList.String() + deploy.Spec.Template.Annotations["olm.targetNamespaces"] = strings.Join(nsList, ",") modifiedData, err := json.Marshal(csv) if err != nil { return err From de50de0f0a0cbcdb7794ce1e53810f0af178aa07 Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Tue, 2 Oct 2018 15:18:53 -0400 Subject: [PATCH 08/33] feat(olm): copy "dummy" CSVs to target namespaces --- .../v1alpha1/clusterserviceversion_types.go | 1 + pkg/controller/operators/olm/operator.go | 55 +++++++++++++------ 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/pkg/api/apis/operators/v1alpha1/clusterserviceversion_types.go b/pkg/api/apis/operators/v1alpha1/clusterserviceversion_types.go index 9351661b76..1d121ff684 100644 --- a/pkg/api/apis/operators/v1alpha1/clusterserviceversion_types.go +++ b/pkg/api/apis/operators/v1alpha1/clusterserviceversion_types.go @@ -195,6 +195,7 @@ const ( CSVReasonReplaced ConditionReason = "Replaced" CSVReasonNeedCertRotation ConditionReason = "NeedCertRotation" CSVReasonAPIServiceResourceIssue ConditionReason = "APIServiceResourceIssue" + CSVReasonCopied ConditionReason = "Copied" ) // Conditions appear in the status as a record of state transitions on the ClusterServiceVersion diff --git a/pkg/controller/operators/olm/operator.go b/pkg/controller/operators/olm/operator.go index 2f8edbb5ac..6b24486aae 100644 --- a/pkg/controller/operators/olm/operator.go +++ b/pkg/controller/operators/olm/operator.go @@ -378,23 +378,23 @@ func namespacesChanged(clusterNamespaces []corev1.Namespace, statusNamespaces [] return false } -func (a *Operator) updateDeploymentAnnotation(op *v1alpha2.OperatorGroup) error { +func (a *Operator) updateDeploymentAnnotation(op *v1alpha2.OperatorGroup) (error, []corev1.Namespace) { // NOTE: if a CSV modification is required in the future, copy the original // data as done in the bottom of this method first. selector, err := metav1.LabelSelectorAsSelector(&op.Spec.Selector) if err != nil { - return err + return err, nil } operatorGroupOpts := metav1.ListOptions{LabelSelector: selector.String()} namespaceList, err := a.OpClient.KubernetesInterface().CoreV1().Namespaces().List(operatorGroupOpts) if err != nil { - return err + return err, nil } if !namespacesChanged(namespaceList.Items, op.Status.Namespaces) { // status is current with correct namespaces, so no further updates required - return nil + return nil, namespaceList.Items } op.Status.Namespaces = namespaceList.Items op.Status.LastUpdated = timeNow() @@ -404,12 +404,12 @@ func (a *Operator) updateDeploymentAnnotation(op *v1alpha2.OperatorGroup) error for csvName, csv := range csvsInNamespace { strategy, err := a.resolver.UnmarshalStrategy(csv.Spec.InstallStrategy) if err != nil { - return fmt.Errorf("error unmarshaling strategy from ClusterServiceVersion '%s' with error: %s", csvName, err) + return fmt.Errorf("error unmarshaling strategy from ClusterServiceVersion '%s' with error: %s", csvName, err), namespaceList.Items } strategyDetailsDeployment, ok := strategy.(*install.StrategyDetailsDeployment) if !ok { - return fmt.Errorf("could not assert strategy implementation as deployment for CSV %s", csvName) + return fmt.Errorf("could not assert strategy implementation as deployment for CSV %s", csvName), namespaceList.Items } managerPolicyRules := []rbacv1.PolicyRule{} @@ -438,7 +438,7 @@ func (a *Operator) updateDeploymentAnnotation(op *v1alpha2.OperatorGroup) error clusterRole.SetGenerateName(fmt.Sprintf("owned-crd-manager-%s-", csv.Spec.DisplayName)) _, err = a.OpClient.KubernetesInterface().RbacV1().ClusterRoles().Create(clusterRole) if err != nil { - return err + return err, namespaceList.Items } // operator group specific roles @@ -450,7 +450,7 @@ func (a *Operator) updateDeploymentAnnotation(op *v1alpha2.OperatorGroup) error } _, err = a.OpClient.KubernetesInterface().RbacV1().ClusterRoles().Create(operatorGroupEditClusterRole) if err != nil { - return err + return err, namespaceList.Items } operatorGroupViewClusterRole := &rbacv1.ClusterRole{ ObjectMeta: metav1.ObjectMeta{ @@ -460,7 +460,7 @@ func (a *Operator) updateDeploymentAnnotation(op *v1alpha2.OperatorGroup) error } _, err = a.OpClient.KubernetesInterface().RbacV1().ClusterRoles().Create(operatorGroupViewClusterRole) if err != nil { - return err + return err, namespaceList.Items } var nsList []string @@ -471,27 +471,27 @@ func (a *Operator) updateDeploymentAnnotation(op *v1alpha2.OperatorGroup) error for _, deploy := range strategyDetailsDeployment.DeploymentSpecs { originalData, err := json.Marshal(csv) if err != nil { - return err + return err, namespaceList.Items } deploy.Spec.Template.Annotations["olm.targetNamespaces"] = strings.Join(nsList, ",") modifiedData, err := json.Marshal(csv) if err != nil { - return err + return err, namespaceList.Items } patchBytes, err := strategicpatch.CreateTwoWayMergePatch(originalData, modifiedData, v1alpha1.ClusterServiceVersion{}) if err != nil { - return err + return err, namespaceList.Items } _, err = a.client.Operators().ClusterServiceVersions(currentNamespace).Patch(csvName, types.StrategicMergePatchType, patchBytes) if err != nil { - return fmt.Errorf("CSV update for '%v' failed: %v\n", csvName, err) + return fmt.Errorf("CSV update for '%v' failed: %v\n", csvName, err), namespaceList.Items } //a.requeueCSV(csvName, currentNamespace) } } - return nil + return nil, namespaceList.Items } func (a *Operator) syncOperatorGroups(obj interface{}) error { @@ -501,10 +501,33 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error { return fmt.Errorf("casting OperatorGroup failed") } - if err := a.updateDeploymentAnnotation(op); err != nil { + err, targetedNamespaces := a.updateDeploymentAnnotation(op) + if err != nil { return err } + for _, ns := range targetedNamespaces { + csvsInNamespace := a.csvsInNamespace(ns.Name) + for _, csv := range csvsInNamespace { + if csv.Status.Phase == v1alpha1.CSVPhaseSucceeded { + newCSV := csv.DeepCopy() + newCSV.Status = v1alpha1.ClusterServiceVersionStatus{ + Message: "CSV copied to target namespace", + Reason: v1alpha1.CSVReasonCopied, + LastUpdateTime: timeNow(), + } + newCSV.Annotations["OriginalCSV"] = fmt.Sprintf("Namespace:%v, ResourceVersion:%v", csv.GetNamespace(), csv.GetResourceVersion()) + ownerutil.AddNonBlockingOwner(newCSV, csv) + if newCSV.GetNamespace() != ns.Name { + _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(newCSV.GetNamespace()).Create(newCSV) + if err != nil { + return err + } + } + } + } + } + return nil } @@ -863,7 +886,7 @@ func (a *Operator) syncNamespace(obj interface{}) (syncError error) { } for op := range opGroupUpdate { - if err := a.updateDeploymentAnnotation(op); err != nil { + if err, _ := a.updateDeploymentAnnotation(op); err != nil { return err } } From 1de901775953000fa9090f10dff44e9c51f2f298 Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Wed, 3 Oct 2018 11:31:35 -0400 Subject: [PATCH 09/33] fix(olm): change annotations to be allocation safe Also change CSV annotation naming to be "olm." prefixed. --- pkg/controller/operators/olm/operator.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/controller/operators/olm/operator.go b/pkg/controller/operators/olm/operator.go index 6b24486aae..5e6c503bd4 100644 --- a/pkg/controller/operators/olm/operator.go +++ b/pkg/controller/operators/olm/operator.go @@ -394,6 +394,7 @@ func (a *Operator) updateDeploymentAnnotation(op *v1alpha2.OperatorGroup) (error if !namespacesChanged(namespaceList.Items, op.Status.Namespaces) { // status is current with correct namespaces, so no further updates required + log.Debugf("No namespace changes detected, found: %v", namespaceList.Items) return nil, namespaceList.Items } op.Status.Namespaces = namespaceList.Items @@ -473,7 +474,7 @@ func (a *Operator) updateDeploymentAnnotation(op *v1alpha2.OperatorGroup) (error if err != nil { return err, namespaceList.Items } - deploy.Spec.Template.Annotations["olm.targetNamespaces"] = strings.Join(nsList, ",") + metav1.SetMetaDataAnnotation(&deploy.Spec.Template.ObjectMeta, "olm.targetNamespaces", strings.Join(nsList, ",")) modifiedData, err := json.Marshal(csv) if err != nil { return err, namespaceList.Items @@ -502,6 +503,7 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error { } err, targetedNamespaces := a.updateDeploymentAnnotation(op) + log.Debugf("Got targetedNamespaces: '%v'", targetedNamespaces) if err != nil { return err } @@ -516,7 +518,7 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error { Reason: v1alpha1.CSVReasonCopied, LastUpdateTime: timeNow(), } - newCSV.Annotations["OriginalCSV"] = fmt.Sprintf("Namespace:%v, ResourceVersion:%v", csv.GetNamespace(), csv.GetResourceVersion()) + metav1.SetMetaDataAnnotation(&newCSV.ObjectMeta, "olm.originalCSV", fmt.Sprintf("%v/%v", csv.GetNamespace(), csv.GetName())) ownerutil.AddNonBlockingOwner(newCSV, csv) if newCSV.GetNamespace() != ns.Name { _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(newCSV.GetNamespace()).Create(newCSV) From 45d9e1ead19109e1bc731e23c6bc70db87a26fed Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Fri, 5 Oct 2018 15:31:44 -0400 Subject: [PATCH 10/33] test(unit): add unit tests for operator groups also change operator loops to use indexing rather than by value --- pkg/controller/operators/olm/operator.go | 55 ++-- pkg/controller/operators/olm/operator_test.go | 276 ++++++++++++++++-- 2 files changed, 280 insertions(+), 51 deletions(-) diff --git a/pkg/controller/operators/olm/operator.go b/pkg/controller/operators/olm/operator.go index 5e6c503bd4..2e523ead62 100644 --- a/pkg/controller/operators/olm/operator.go +++ b/pkg/controller/operators/olm/operator.go @@ -27,8 +27,6 @@ import ( rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/client-go/informers" crbacv1 "k8s.io/client-go/listers/rbac/v1" "k8s.io/client-go/tools/cache" @@ -430,7 +428,7 @@ func (a *Operator) updateDeploymentAnnotation(op *v1alpha2.OperatorGroup) (error for _, resource := range owned.Resources { resourceNames = append(resourceNames, resource.Name) } - managerPolicyRules = append(managerPolicyRules, rbacv1.PolicyRule{Verbs: []string{"*"}, APIGroups: []string{owned.Name}, Resources: resourceNames}) + managerPolicyRules = append(managerPolicyRules, rbacv1.PolicyRule{Verbs: []string{"*"}, APIGroups: []string{owned.Group}, Resources: resourceNames}) } clusterRole := &rbacv1.ClusterRole{ Rules: managerPolicyRules, @@ -469,26 +467,39 @@ func (a *Operator) updateDeploymentAnnotation(op *v1alpha2.OperatorGroup) (error nsList = append(nsList, namespaceList.Items[ix].Name) } // write namespaces to watch in every deployment - for _, deploy := range strategyDetailsDeployment.DeploymentSpecs { - originalData, err := json.Marshal(csv) - if err != nil { - return err, namespaceList.Items - } - metav1.SetMetaDataAnnotation(&deploy.Spec.Template.ObjectMeta, "olm.targetNamespaces", strings.Join(nsList, ",")) - modifiedData, err := json.Marshal(csv) - if err != nil { - return err, namespaceList.Items - } - patchBytes, err := strategicpatch.CreateTwoWayMergePatch(originalData, modifiedData, v1alpha1.ClusterServiceVersion{}) - if err != nil { - return err, namespaceList.Items - } + // originalData, err := json.Marshal(csv) + // if err != nil { + // return err, namespaceList.Items + // } - _, err = a.client.Operators().ClusterServiceVersions(currentNamespace).Patch(csvName, types.StrategicMergePatchType, patchBytes) - if err != nil { - return fmt.Errorf("CSV update for '%v' failed: %v\n", csvName, err), namespaceList.Items - } - //a.requeueCSV(csvName, currentNamespace) + for i, _ := range strategyDetailsDeployment.DeploymentSpecs { + deploy := &strategyDetailsDeployment.DeploymentSpecs[i] + metav1.SetMetaDataAnnotation(&deploy.Spec.Template.ObjectMeta, "olm.targetNamespaces", strings.Join(nsList, ",")) + log.Debugf("Wrote annotation '%v' on %v. Check: %#v\n", nsList, deploy.Name, deploy) + } + strategyRaw, err := json.Marshal(strategyDetailsDeployment) + if err != nil { + return err, namespaceList.Items + } + csv.Spec.InstallStrategy.StrategySpecRaw = strategyRaw + + // JPEELER - this breaks things + // modifiedData, err := json.Marshal(csv) + // if err != nil { + // return err, namespaceList.Items + // } + // patchBytes, err := strategicpatch.CreateTwoWayMergePatch(originalData, modifiedData, csv) + // if err != nil { + // return err, namespaceList.Items + // } + + // _, err = a.client.Operators().ClusterServiceVersions(currentNamespace).Patch(csvName, types.StrategicMergePatchType, patchBytes) + // if err != nil { + // return fmt.Errorf("CSV update for '%v' failed: %v\n", csvName, err), namespaceList.Items + // } + _, err = a.client.Operators().ClusterServiceVersions(currentNamespace).Update(csv) + if err != nil { + return fmt.Errorf("CSV update for '%v' failed: %v\n", csvName, err), namespaceList.Items } } diff --git a/pkg/controller/operators/olm/operator_test.go b/pkg/controller/operators/olm/operator_test.go index e9507a4467..e8ed1677a8 100644 --- a/pkg/controller/operators/olm/operator_test.go +++ b/pkg/controller/operators/olm/operator_test.go @@ -23,6 +23,7 @@ import ( apiregistrationfake "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" + "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha2" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned/fake" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install" @@ -99,17 +100,24 @@ func apiResourcesForObjects(objs []runtime.Object) []*metav1.APIResourceList { return apis } -func NewFakeOperator(clientObjs []runtime.Object, k8sObjs []runtime.Object, extObjs []runtime.Object, regObjs []runtime.Object, resolver install.StrategyResolverInterface, namespace string) (*Operator, error) { +func NewFakeOperator(clientObjs []runtime.Object, k8sObjs []runtime.Object, extObjs []runtime.Object, regObjs []runtime.Object, resolver install.StrategyResolverInterface, namespaces []v1.Namespace) (*Operator, error) { clientFake := fake.NewSimpleClientset(clientObjs...) k8sClientFake := k8sfake.NewSimpleClientset(k8sObjs...) k8sClientFake.Resources = apiResourcesForObjects(append(extObjs, regObjs...)) opClientFake := operatorclient.NewClient(k8sClientFake, apiextensionsfake.NewSimpleClientset(extObjs...), apiregistrationfake.NewSimpleClientset(regObjs...)) annotations := map[string]string{"test": "annotation"} - _, err := opClientFake.KubernetesInterface().CoreV1().Namespaces().Create(&v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}) - if err != nil { - return nil, err + for _, ns := range namespaces { + _, err := opClientFake.KubernetesInterface().CoreV1().Namespaces().Create(&ns) + if err != nil { + return nil, err + } + } + + var nsList []string + for ix := range namespaces { + nsList = append(nsList, namespaces[ix].Name) } - return NewOperator(clientFake, opClientFake, resolver, 5*time.Second, annotations, []string{namespace}) + return NewOperator(clientFake, opClientFake, resolver, 5*time.Second, annotations, nsList) } func (o *Operator) GetClient() versioned.Interface { @@ -164,32 +172,30 @@ func deployment(deploymentName, namespace string) *appsv1.Deployment { func installStrategy(deploymentName string, permissions []install.StrategyDeploymentPermissions, clusterPermissions []install.StrategyDeploymentPermissions) v1alpha1.NamedInstallStrategy { var singleInstance = int32(1) - strategy := install.StrategyDetailsDeployment{ - DeploymentSpecs: []install.StrategyDeploymentSpec{ - { - Name: deploymentName, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ + specs := []install.StrategyDeploymentSpec{ + install.StrategyDeploymentSpec{ + Name: deploymentName, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": deploymentName, + }, + }, + Replicas: &singleInstance, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ "app": deploymentName, }, }, - Replicas: &singleInstance, - Template: v1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "app": deploymentName, - }, - }, - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Name: deploymentName + "-c1", - Image: "nginx:1.7.9", - Ports: []v1.ContainerPort{ - { - ContainerPort: 80, - }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: deploymentName + "-c1", + Image: "nginx:1.7.9", + Ports: []v1.ContainerPort{ + { + ContainerPort: 80, }, }, }, @@ -201,6 +207,52 @@ func installStrategy(deploymentName string, permissions []install.StrategyDeploy Permissions: permissions, ClusterPermissions: clusterPermissions, } + + return specs +} + +func getModifiedInstallStrategy(deploymentName string, deploymentFn func(string) []install.StrategyDeploymentSpec, permissionsFn func() []install.StrategyDeploymentPermissions, clusterPermissionsFn func() []install.StrategyDeploymentPermissions) v1alpha1.NamedInstallStrategy { + + var deploySpecs []install.StrategyDeploymentSpec + var permissions []install.StrategyDeploymentPermissions + var clusterPermissions []install.StrategyDeploymentPermissions + if deploymentFn != nil { + deploySpecs = deploymentFn(deploymentName) + } else { + deploySpecs = getStrategyDeploymentSpecs(deploymentName) + } + if permissionsFn != nil { + permissions = permissionsFn() + } else { + permissions = []install.StrategyDeploymentPermissions{} + } + if clusterPermissionsFn != nil { + clusterPermissions = clusterPermissionsFn() + } else { + clusterPermissions = []install.StrategyDeploymentPermissions{} + } + + strategy := install.StrategyDetailsDeployment{ + DeploymentSpecs: deploySpecs, + Permissions: permissions, + ClusterPermissions: clusterPermissions, + } + + strategyRaw, err := json.Marshal(strategy) + if err != nil { + panic(err) + } + + return v1alpha1.NamedInstallStrategy{ + StrategyName: install.InstallStrategyNameDeployment, + StrategySpecRaw: strategyRaw, + } +} + +func installStrategy(deploymentName string) v1alpha1.NamedInstallStrategy { + strategy := install.StrategyDetailsDeployment{ + DeploymentSpecs: getStrategyDeploymentSpecs(deploymentName), + } strategyRaw, err := json.Marshal(strategy) if err != nil { panic(err) @@ -1041,9 +1093,10 @@ func TestTransitionCSV(t *testing.T) { }, } for _, tt := range tests { + namespaceObj := v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} t.Run(tt.name, func(t *testing.T) { // configure cluster state - op, err := NewFakeOperator(tt.initial.csvs, tt.initial.objs, tt.initial.crds, tt.initial.apis, &install.StrategyResolver{}, namespace) + op, err := NewFakeOperator(tt.initial.csvs, tt.initial.objs, tt.initial.crds, tt.initial.apis, &install.StrategyResolver{}, []v1.Namespace{namespaceObj}) require.NoError(t, err) // run csv sync for each CSV @@ -1073,6 +1126,171 @@ func TestTransitionCSV(t *testing.T) { } } +func TestSyncOperatorGroups(t *testing.T) { + log.SetLevel(log.DebugLevel) + + nowTime := metav1.Date(2006, time.January, 2, 15, 4, 5, 0, time.FixedZone("MST", -7*3600)) + timeNow = func() metav1.Time { return nowTime } + + testNS := "test-ns" + aLabel := map[string]string{"app": "matchLabel"} + newStrategySpecWithAnnotation := func(deploymentName string, namespace string) v1alpha1.NamedInstallStrategy { + deploymentFn := func(deploymentName string) []install.StrategyDeploymentSpec { + deploymentSpecs := getStrategyDeploymentSpecs(deploymentName) + for i := range deploymentSpecs { + metav1.SetMetaDataAnnotation(&deploymentSpecs[i].Spec.Template.ObjectMeta, "olm.targetNamespaces", namespace) + } + return deploymentSpecs + } + return getModifiedInstallStrategy(deploymentName, deploymentFn, nil, nil) + } + + tests := []struct { + name string + initialCsvs []runtime.Object + initialCrds []runtime.Object + initialObjs []runtime.Object + initialApis []runtime.Object + namespaces []v1.Namespace + inputGroup v1alpha2.OperatorGroup + expectedStatus v1alpha2.OperatorGroupStatus + expectedCSVs []v1alpha1.ClusterServiceVersion + }{ + { + name: "operator group with no matching namespace, no CSVs", + namespaces: []v1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: testNS, + }, + }, + }, + inputGroup: v1alpha2.OperatorGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "operator-group-1", + Namespace: testNS, + Labels: map[string]string{"app": "matchLabel"}, + }, + Spec: v1alpha2.OperatorGroupSpec{ + Selector: metav1.LabelSelector{ + MatchLabels: aLabel, + }, + }, + }, + expectedStatus: v1alpha2.OperatorGroupStatus{}, + }, + { + name: "operator group with matching namespace, no CSVs", + namespaces: []v1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: testNS, + Labels: aLabel, + }, + }, + }, + inputGroup: v1alpha2.OperatorGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "operator-group-1", + Namespace: testNS, + }, + Spec: v1alpha2.OperatorGroupSpec{ + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "matchLabel", + }, + }, + }, + }, + expectedStatus: v1alpha2.OperatorGroupStatus{ + Namespaces: []v1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: testNS, + Labels: aLabel, + Annotations: map[string]string{"test": "annotation"}, + }, + }, + }, + LastUpdated: timeNow(), + }, + }, + { + name: "operator group with matching namespace, CSV present", + namespaces: []v1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: testNS, + Labels: aLabel, + }, + }, + }, + initialCsvs: []runtime.Object{ + csv("csv1", + testNS, + "", + installStrategy("csv1-dep1"), + []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{}, + v1alpha1.CSVPhaseSucceeded, + ), + }, + inputGroup: v1alpha2.OperatorGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "operator-group-1", + Namespace: testNS, + Labels: aLabel, + }, + Spec: v1alpha2.OperatorGroupSpec{ + Selector: metav1.LabelSelector{ + MatchLabels: aLabel, + }, + }, + }, + expectedStatus: v1alpha2.OperatorGroupStatus{ + Namespaces: []v1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: testNS, + Labels: aLabel, + Annotations: map[string]string{"test": "annotation"}, + }, + }, + }, + LastUpdated: timeNow(), + }, + expectedCSVs: []v1alpha1.ClusterServiceVersion{ + *csv("csv1", + testNS, + "", + newStrategySpecWithAnnotation("csv1-dep1", testNS), + []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, + []*v1beta1.CustomResourceDefinition{}, + v1alpha1.CSVPhaseSucceeded, + ), + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + op, err := NewFakeOperator(tc.initialCsvs, tc.initialObjs, tc.initialCrds, tc.initialApis, &install.StrategyResolver{}, tc.namespaces) + require.NoError(t, err) + + err = op.syncOperatorGroups(&tc.inputGroup) + require.NoError(t, err) + assert.Equal(t, tc.expectedStatus, tc.inputGroup.Status) + + outCSVs, err := op.GetClient().OperatorsV1alpha1().ClusterServiceVersions(testNS).List(metav1.ListOptions{}) + require.NoError(t, err) + + if tc.initialCsvs != nil { + assert.Equal(t, tc.expectedCSVs, outCSVs.Items) + } + }) + } +} + func TestIsReplacing(t *testing.T) { log.SetLevel(log.DebugLevel) namespace := "ns" From 17cfe6f8b89d201f8eb6857d24750ca438bfc2f0 Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Mon, 8 Oct 2018 11:12:27 -0400 Subject: [PATCH 11/33] fix(olm): make status updates on deployments, not CSVs This commit should not be looked at too closely... some of it was written to handle rebasing against changes in master. Additional changes included fixing the tests and changing deployments to use informers. --- pkg/controller/operators/olm/operator.go | 92 ++++--- pkg/controller/operators/olm/operator_test.go | 224 ++++++++++-------- .../operators/olm/requirements_test.go | 13 +- 3 files changed, 176 insertions(+), 153 deletions(-) diff --git a/pkg/controller/operators/olm/operator.go b/pkg/controller/operators/olm/operator.go index 2e523ead62..1e7e61d1c0 100644 --- a/pkg/controller/operators/olm/operator.go +++ b/pkg/controller/operators/olm/operator.go @@ -27,7 +27,10 @@ import ( rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/client-go/informers" + cappsv1 "k8s.io/client-go/listers/apps/v1" crbacv1 "k8s.io/client-go/listers/rbac/v1" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" @@ -56,6 +59,7 @@ type Operator struct { clusterRoleLister crbacv1.ClusterRoleLister clusterRoleBindingLister crbacv1.ClusterRoleBindingLister operatorGroupLister map[string]operatorgrouplister.OperatorGroupLister + deploymentLister map[string]cappsv1.DeploymentLister annotator *annotator.Annotator recorder record.EventRecorder cleanupFunc func() @@ -203,10 +207,13 @@ func NewOperator(crClient versioned.Interface, opClient operatorclient.ClientInt // set up watch on deployments depInformers := []cache.SharedIndexInformer{} + op.deploymentLister = make(map[string]cappsv1.DeploymentLister, len(namespaces)) for _, namespace := range namespaces { log.Debugf("watching deployments in namespace %s", namespace) - informer := informers.NewSharedInformerFactoryWithOptions(opClient.KubernetesInterface(), wakeupInterval, informers.WithNamespace(namespace)).Apps().V1().Deployments().Informer() - depInformers = append(depInformers, informer) + informerFactory := informers.NewSharedInformerFactoryWithOptions(opClient.KubernetesInterface(), wakeupInterval, informers.WithNamespace(namespace)) + informer := informerFactory.Apps().V1().Deployments() + depInformers = append(depInformers, informer.Informer()) + op.deploymentLister[namespace] = informer.Lister() } depQueue := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "csv-deployments") @@ -400,17 +407,7 @@ func (a *Operator) updateDeploymentAnnotation(op *v1alpha2.OperatorGroup) (error currentNamespace := op.GetNamespace() csvsInNamespace := a.csvsInNamespace(currentNamespace) - for csvName, csv := range csvsInNamespace { - strategy, err := a.resolver.UnmarshalStrategy(csv.Spec.InstallStrategy) - if err != nil { - return fmt.Errorf("error unmarshaling strategy from ClusterServiceVersion '%s' with error: %s", csvName, err), namespaceList.Items - } - - strategyDetailsDeployment, ok := strategy.(*install.StrategyDetailsDeployment) - if !ok { - return fmt.Errorf("could not assert strategy implementation as deployment for CSV %s", csvName), namespaceList.Items - } - + for _, csv := range csvsInNamespace { managerPolicyRules := []rbacv1.PolicyRule{} apiEditPolicyRules := []rbacv1.PolicyRule{} apiViewPolicyRules := []rbacv1.PolicyRule{} @@ -462,44 +459,45 @@ func (a *Operator) updateDeploymentAnnotation(op *v1alpha2.OperatorGroup) (error return err, namespaceList.Items } - var nsList []string - for ix := range namespaceList.Items { - nsList = append(nsList, namespaceList.Items[ix].Name) - } - // write namespaces to watch in every deployment - // originalData, err := json.Marshal(csv) - // if err != nil { - // return err, namespaceList.Items - // } + } - for i, _ := range strategyDetailsDeployment.DeploymentSpecs { - deploy := &strategyDetailsDeployment.DeploymentSpecs[i] - metav1.SetMetaDataAnnotation(&deploy.Spec.Template.ObjectMeta, "olm.targetNamespaces", strings.Join(nsList, ",")) - log.Debugf("Wrote annotation '%v' on %v. Check: %#v\n", nsList, deploy.Name, deploy) - } - strategyRaw, err := json.Marshal(strategyDetailsDeployment) + var nsList []string + for ix := range namespaceList.Items { + nsList = append(nsList, namespaceList.Items[ix].Name) + } + + // write above namespaces to watch in every deployment + for _, ns := range nsList { + //deploymentList, err := a.OpClient.KubernetesInterface().AppsV1().Deployments(ns).List(metav1.ListOptions{}) + deploymentList, err := a.deploymentLister[ns].List(labels.Everything()) + log.Debugf("JPEELER: looking at ns %v deployments:%v\n", ns, deploymentList) if err != nil { return err, namespaceList.Items } - csv.Spec.InstallStrategy.StrategySpecRaw = strategyRaw - - // JPEELER - this breaks things - // modifiedData, err := json.Marshal(csv) - // if err != nil { - // return err, namespaceList.Items - // } - // patchBytes, err := strategicpatch.CreateTwoWayMergePatch(originalData, modifiedData, csv) - // if err != nil { - // return err, namespaceList.Items - // } - - // _, err = a.client.Operators().ClusterServiceVersions(currentNamespace).Patch(csvName, types.StrategicMergePatchType, patchBytes) - // if err != nil { - // return fmt.Errorf("CSV update for '%v' failed: %v\n", csvName, err), namespaceList.Items - // } - _, err = a.client.Operators().ClusterServiceVersions(currentNamespace).Update(csv) - if err != nil { - return fmt.Errorf("CSV update for '%v' failed: %v\n", csvName, err), namespaceList.Items + + for _, deploy := range deploymentList { + originalData, err := json.Marshal(deploy) + if err != nil { + return err, namespaceList.Items + } + + metav1.SetMetaDataAnnotation(&deploy.Spec.Template.ObjectMeta, "olm.targetNamespaces", strings.Join(nsList, ",")) + log.Debugf("Wrote annotation '%v' on %v. Check: %#v\n", nsList, deploy.Name, deploy) + + modifiedData, err := json.Marshal(deploy) + if err != nil { + return err, namespaceList.Items + } + + patchBytes, err := strategicpatch.CreateTwoWayMergePatch(originalData, modifiedData, deploy) + if err != nil { + return err, namespaceList.Items + } + + _, err = a.OpClient.KubernetesInterface().AppsV1().Deployments(ns).Patch(deploy.Name, types.StrategicMergePatchType, patchBytes) + if err != nil { + return fmt.Errorf("Deployment update for '%v' failed: %v\n", deploy.Name, err), namespaceList.Items + } } } diff --git a/pkg/controller/operators/olm/operator_test.go b/pkg/controller/operators/olm/operator_test.go index e8ed1677a8..24d407d137 100644 --- a/pkg/controller/operators/olm/operator_test.go +++ b/pkg/controller/operators/olm/operator_test.go @@ -17,8 +17,11 @@ import ( "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apiextensionsfake "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/informers" k8sfake "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/tools/cache" apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" apiregistrationfake "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake" @@ -172,30 +175,32 @@ func deployment(deploymentName, namespace string) *appsv1.Deployment { func installStrategy(deploymentName string, permissions []install.StrategyDeploymentPermissions, clusterPermissions []install.StrategyDeploymentPermissions) v1alpha1.NamedInstallStrategy { var singleInstance = int32(1) - specs := []install.StrategyDeploymentSpec{ - install.StrategyDeploymentSpec{ - Name: deploymentName, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "app": deploymentName, - }, - }, - Replicas: &singleInstance, - Template: v1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ + strategy := install.StrategyDetailsDeployment{ + DeploymentSpecs: []install.StrategyDeploymentSpec{ + { + Name: deploymentName, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ "app": deploymentName, }, }, - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Name: deploymentName + "-c1", - Image: "nginx:1.7.9", - Ports: []v1.ContainerPort{ - { - ContainerPort: 80, + Replicas: &singleInstance, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "app": deploymentName, + }, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: deploymentName + "-c1", + Image: "nginx:1.7.9", + Ports: []v1.ContainerPort{ + { + ContainerPort: 80, + }, }, }, }, @@ -207,52 +212,6 @@ func installStrategy(deploymentName string, permissions []install.StrategyDeploy Permissions: permissions, ClusterPermissions: clusterPermissions, } - - return specs -} - -func getModifiedInstallStrategy(deploymentName string, deploymentFn func(string) []install.StrategyDeploymentSpec, permissionsFn func() []install.StrategyDeploymentPermissions, clusterPermissionsFn func() []install.StrategyDeploymentPermissions) v1alpha1.NamedInstallStrategy { - - var deploySpecs []install.StrategyDeploymentSpec - var permissions []install.StrategyDeploymentPermissions - var clusterPermissions []install.StrategyDeploymentPermissions - if deploymentFn != nil { - deploySpecs = deploymentFn(deploymentName) - } else { - deploySpecs = getStrategyDeploymentSpecs(deploymentName) - } - if permissionsFn != nil { - permissions = permissionsFn() - } else { - permissions = []install.StrategyDeploymentPermissions{} - } - if clusterPermissionsFn != nil { - clusterPermissions = clusterPermissionsFn() - } else { - clusterPermissions = []install.StrategyDeploymentPermissions{} - } - - strategy := install.StrategyDetailsDeployment{ - DeploymentSpecs: deploySpecs, - Permissions: permissions, - ClusterPermissions: clusterPermissions, - } - - strategyRaw, err := json.Marshal(strategy) - if err != nil { - panic(err) - } - - return v1alpha1.NamedInstallStrategy{ - StrategyName: install.InstallStrategyNameDeployment, - StrategySpecRaw: strategyRaw, - } -} - -func installStrategy(deploymentName string) v1alpha1.NamedInstallStrategy { - strategy := install.StrategyDetailsDeployment{ - DeploymentSpecs: getStrategyDeploymentSpecs(deploymentName), - } strategyRaw, err := json.Marshal(strategy) if err != nil { panic(err) @@ -1134,30 +1093,22 @@ func TestSyncOperatorGroups(t *testing.T) { testNS := "test-ns" aLabel := map[string]string{"app": "matchLabel"} - newStrategySpecWithAnnotation := func(deploymentName string, namespace string) v1alpha1.NamedInstallStrategy { - deploymentFn := func(deploymentName string) []install.StrategyDeploymentSpec { - deploymentSpecs := getStrategyDeploymentSpecs(deploymentName) - for i := range deploymentSpecs { - metav1.SetMetaDataAnnotation(&deploymentSpecs[i].Spec.Template.ObjectMeta, "olm.targetNamespaces", namespace) - } - return deploymentSpecs - } - return getModifiedInstallStrategy(deploymentName, deploymentFn, nil, nil) - } tests := []struct { - name string - initialCsvs []runtime.Object - initialCrds []runtime.Object - initialObjs []runtime.Object - initialApis []runtime.Object - namespaces []v1.Namespace - inputGroup v1alpha2.OperatorGroup - expectedStatus v1alpha2.OperatorGroupStatus - expectedCSVs []v1alpha1.ClusterServiceVersion + name string + expectedEqual bool + initialCsvs []runtime.Object + initialCrds []runtime.Object + initialObjs []runtime.Object + initialApis []runtime.Object + namespaces []v1.Namespace + inputGroup v1alpha2.OperatorGroup + expectedStatus v1alpha2.OperatorGroupStatus + expectedAnnotation map[string]string }{ { - name: "operator group with no matching namespace, no CSVs", + name: "operator group with no matching namespace, no CSVs", + expectedEqual: true, namespaces: []v1.Namespace{ { ObjectMeta: metav1.ObjectMeta{ @@ -1180,12 +1131,14 @@ func TestSyncOperatorGroups(t *testing.T) { expectedStatus: v1alpha2.OperatorGroupStatus{}, }, { - name: "operator group with matching namespace, no CSVs", + name: "operator group with matching namespace, no CSVs", + expectedEqual: true, namespaces: []v1.Namespace{ { ObjectMeta: metav1.ObjectMeta{ - Name: testNS, - Labels: aLabel, + Name: testNS, + Labels: aLabel, + Annotations: map[string]string{"test": "annotation"}, }, }, }, @@ -1216,12 +1169,14 @@ func TestSyncOperatorGroups(t *testing.T) { }, }, { - name: "operator group with matching namespace, CSV present", + name: "operator group with matching namespace, CSV present", + expectedEqual: false, namespaces: []v1.Namespace{ { ObjectMeta: metav1.ObjectMeta{ - Name: testNS, - Labels: aLabel, + Name: testNS, + Labels: aLabel, + Annotations: map[string]string{"test": "annotation"}, }, }, }, @@ -1229,12 +1184,15 @@ func TestSyncOperatorGroups(t *testing.T) { csv("csv1", testNS, "", - installStrategy("csv1-dep1"), + installStrategy("csv1-dep1", nil, nil), []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseSucceeded, ), }, + initialObjs: []runtime.Object{ + deployment("csv1-dep1", testNS), + }, inputGroup: v1alpha2.OperatorGroup{ ObjectMeta: metav1.ObjectMeta{ Name: "operator-group-1", @@ -1259,16 +1217,58 @@ func TestSyncOperatorGroups(t *testing.T) { }, LastUpdated: timeNow(), }, - expectedCSVs: []v1alpha1.ClusterServiceVersion{ - *csv("csv1", + expectedAnnotation: map[string]string{"NOTFOUND": testNS}, + }, + { + name: "operator group with matching namespace, CSV present", + expectedEqual: true, + namespaces: []v1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: testNS, + Labels: aLabel, + Annotations: map[string]string{"test": "annotation"}, + }, + }, + }, + initialCsvs: []runtime.Object{ + csv("csv1", testNS, "", - newStrategySpecWithAnnotation("csv1-dep1", testNS), + installStrategy("csv1-dep1", nil, nil), []*v1beta1.CustomResourceDefinition{crd("c1", "v1")}, []*v1beta1.CustomResourceDefinition{}, v1alpha1.CSVPhaseSucceeded, ), }, + initialObjs: []runtime.Object{ + deployment("csv1-dep1", testNS), + }, + inputGroup: v1alpha2.OperatorGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "operator-group-1", + Namespace: testNS, + Labels: aLabel, + }, + Spec: v1alpha2.OperatorGroupSpec{ + Selector: metav1.LabelSelector{ + MatchLabels: aLabel, + }, + }, + }, + expectedStatus: v1alpha2.OperatorGroupStatus{ + Namespaces: []v1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: testNS, + Labels: aLabel, + Annotations: map[string]string{"test": "annotation"}, + }, + }, + }, + LastUpdated: timeNow(), + }, + expectedAnnotation: map[string]string{"olm.targetNamespaces": testNS}, }, } @@ -1277,15 +1277,35 @@ func TestSyncOperatorGroups(t *testing.T) { op, err := NewFakeOperator(tc.initialCsvs, tc.initialObjs, tc.initialCrds, tc.initialApis, &install.StrategyResolver{}, tc.namespaces) require.NoError(t, err) + stopCh := make(chan struct{}) + informerFactory := informers.NewSharedInformerFactory(op.OpClient.KubernetesInterface(), 1*time.Second) + deployInformer := informerFactory.Apps().V1().Deployments() + for _, informer := range []cache.SharedIndexInformer{deployInformer.Informer()} { + go informer.Run(stopCh) + } + op.deploymentLister[testNS] = deployInformer.Lister() + informerFactory.Start(stopCh) + informerFactory.WaitForCacheSync(stopCh) + err = op.syncOperatorGroups(&tc.inputGroup) require.NoError(t, err) assert.Equal(t, tc.expectedStatus, tc.inputGroup.Status) - outCSVs, err := op.GetClient().OperatorsV1alpha1().ClusterServiceVersions(testNS).List(metav1.ListOptions{}) - require.NoError(t, err) - - if tc.initialCsvs != nil { - assert.Equal(t, tc.expectedCSVs, outCSVs.Items) + if tc.expectedAnnotation != nil { + // assuming CSVs are in correct namespace + for _, ns := range tc.namespaces { + deployments, err := op.deploymentLister[ns.Name].List(labels.Everything()) + if err != nil { + t.Fatal(err) + } + for _, deploy := range deployments { + if tc.expectedEqual { + assert.Equal(t, tc.expectedAnnotation, deploy.Spec.Template.Annotations) + } else { + assert.NotEqual(t, tc.expectedAnnotation, deploy.Spec.Template.Annotations) + } + } + } } }) } diff --git a/pkg/controller/operators/olm/requirements_test.go b/pkg/controller/operators/olm/requirements_test.go index 080cba6374..39f3214a92 100644 --- a/pkg/controller/operators/olm/requirements_test.go +++ b/pkg/controller/operators/olm/requirements_test.go @@ -44,9 +44,9 @@ func TestRequirementAndPermissionStatus(t *testing.T) { nil, v1alpha1.CSVPhasePending, ), - existingObjs: nil, - existingExtObjs: nil, - met: false, + existingObjs: nil, + existingExtObjs: nil, + met: false, expectedRequirementStatuses: nil, expectedError: fmt.Errorf("unexpected end of JSON input"), }, @@ -457,7 +457,12 @@ func TestRequirementAndPermissionStatus(t *testing.T) { for _, test := range tests { t.Run(test.description, func(t *testing.T) { - op, err := NewFakeOperator(nil, test.existingObjs, test.existingExtObjs, nil, &install.StrategyResolver{}, namespace) + namespaceObj := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + } + op, err := NewFakeOperator(nil, test.existingObjs, test.existingExtObjs, nil, &install.StrategyResolver{}, []corev1.Namespace{namespaceObj}) require.NoError(t, err) stopCh := make(chan struct{}) From a15ba1275c6809e8ece3e8906f72fee6515cdfe1 Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Fri, 12 Oct 2018 14:25:51 -0400 Subject: [PATCH 12/33] test(e2e): add e2e test for operator groups and include fixes for bugs discovered --- pkg/controller/operators/olm/operator.go | 61 +++++++----- pkg/controller/operators/olm/operator_test.go | 6 +- test/e2e/operator_groups_e2e_test.go | 97 +++++++++++++++++++ 3 files changed, 138 insertions(+), 26 deletions(-) create mode 100644 test/e2e/operator_groups_e2e_test.go diff --git a/pkg/controller/operators/olm/operator.go b/pkg/controller/operators/olm/operator.go index 1e7e61d1c0..35592ad11e 100644 --- a/pkg/controller/operators/olm/operator.go +++ b/pkg/controller/operators/olm/operator.go @@ -365,9 +365,7 @@ func (a *Operator) syncServices(obj interface{}) (syncError error) { } func namespacesChanged(clusterNamespaces []corev1.Namespace, statusNamespaces []corev1.Namespace) bool { - nsCount := len(clusterNamespaces) - - if len(statusNamespaces) != nsCount { + if len(clusterNamespaces) != len(statusNamespaces) { return true } @@ -399,11 +397,16 @@ func (a *Operator) updateDeploymentAnnotation(op *v1alpha2.OperatorGroup) (error if !namespacesChanged(namespaceList.Items, op.Status.Namespaces) { // status is current with correct namespaces, so no further updates required - log.Debugf("No namespace changes detected, found: %v", namespaceList.Items) return nil, namespaceList.Items } - op.Status.Namespaces = namespaceList.Items + log.Debugf("Namespace change detected, found: %v", namespaceList.Items) + op.Status.Namespaces = make([]corev1.Namespace, len(namespaceList.Items)) + copy(op.Status.Namespaces, namespaceList.Items) op.Status.LastUpdated = timeNow() + _, err = a.client.OperatorsV1alpha2().OperatorGroups(op.Namespace).UpdateStatus(op) + if err != nil { + return err, namespaceList.Items + } currentNamespace := op.GetNamespace() csvsInNamespace := a.csvsInNamespace(currentNamespace) @@ -468,9 +471,7 @@ func (a *Operator) updateDeploymentAnnotation(op *v1alpha2.OperatorGroup) (error // write above namespaces to watch in every deployment for _, ns := range nsList { - //deploymentList, err := a.OpClient.KubernetesInterface().AppsV1().Deployments(ns).List(metav1.ListOptions{}) deploymentList, err := a.deploymentLister[ns].List(labels.Everything()) - log.Debugf("JPEELER: looking at ns %v deployments:%v\n", ns, deploymentList) if err != nil { return err, namespaceList.Items } @@ -517,23 +518,35 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error { return err } - for _, ns := range targetedNamespaces { - csvsInNamespace := a.csvsInNamespace(ns.Name) - for _, csv := range csvsInNamespace { - if csv.Status.Phase == v1alpha1.CSVPhaseSucceeded { - newCSV := csv.DeepCopy() - newCSV.Status = v1alpha1.ClusterServiceVersionStatus{ - Message: "CSV copied to target namespace", - Reason: v1alpha1.CSVReasonCopied, - LastUpdateTime: timeNow(), - } - metav1.SetMetaDataAnnotation(&newCSV.ObjectMeta, "olm.originalCSV", fmt.Sprintf("%v/%v", csv.GetNamespace(), csv.GetName())) - ownerutil.AddNonBlockingOwner(newCSV, csv) - if newCSV.GetNamespace() != ns.Name { - _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(newCSV.GetNamespace()).Create(newCSV) - if err != nil { - return err - } + csvsInNamespace := a.csvsInNamespace(op.Namespace) + for _, csv := range csvsInNamespace { + // TODO: handle CSV copying in a different place + // if csv.Status.Phase != v1alpha1.CSVPhaseSucceeded { + // log.Debugf("JPEELER: continuing on, skipping CSV %v\n", csv.Name) + // continue + // } + + // create new CSV instead of DeepCopy as namespace and resource version (and status) will be different + newCSV := v1alpha1.ClusterServiceVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: csv.Name, + }, + Spec: *csv.Spec.DeepCopy(), + Status: v1alpha1.ClusterServiceVersionStatus{ + Message: "CSV copied to target namespace", + Reason: v1alpha1.CSVReasonCopied, + LastUpdateTime: timeNow(), + }, + } + + metav1.SetMetaDataAnnotation(&newCSV.ObjectMeta, "olm.originalCSV", fmt.Sprintf("%v/%v", csv.GetNamespace(), csv.GetName())) + ownerutil.AddNonBlockingOwner(&newCSV, csv) + for _, ns := range targetedNamespaces { + newCSV.SetNamespace(ns.Name) + if ns.Name != op.Namespace { + _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(newCSV.GetNamespace()).Create(&newCSV) + if err != nil { + return err } } } diff --git a/pkg/controller/operators/olm/operator_test.go b/pkg/controller/operators/olm/operator_test.go index 24d407d137..11381675ed 100644 --- a/pkg/controller/operators/olm/operator_test.go +++ b/pkg/controller/operators/olm/operator_test.go @@ -1086,7 +1086,7 @@ func TestTransitionCSV(t *testing.T) { } func TestSyncOperatorGroups(t *testing.T) { - log.SetLevel(log.DebugLevel) + //log.SetLevel(log.DebugLevel) nowTime := metav1.Date(2006, time.January, 2, 15, 4, 5, 0, time.FixedZone("MST", -7*3600)) timeNow = func() metav1.Time { return nowTime } @@ -1120,7 +1120,6 @@ func TestSyncOperatorGroups(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "operator-group-1", Namespace: testNS, - Labels: map[string]string{"app": "matchLabel"}, }, Spec: v1alpha2.OperatorGroupSpec{ Selector: metav1.LabelSelector{ @@ -1287,6 +1286,9 @@ func TestSyncOperatorGroups(t *testing.T) { informerFactory.Start(stopCh) informerFactory.WaitForCacheSync(stopCh) + // Could not put this in initialObjs - got "no kind is registered for the type v1alpha2.OperatorGroup" + op.client.OperatorsV1alpha2().OperatorGroups(tc.inputGroup.Namespace).Create(&tc.inputGroup) + err = op.syncOperatorGroups(&tc.inputGroup) require.NoError(t, err) assert.Equal(t, tc.expectedStatus, tc.inputGroup.Status) diff --git a/test/e2e/operator_groups_e2e_test.go b/test/e2e/operator_groups_e2e_test.go new file mode 100644 index 0000000000..e8df796e3f --- /dev/null +++ b/test/e2e/operator_groups_e2e_test.go @@ -0,0 +1,97 @@ +package e2e + +import ( + "testing" + + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/util/wait" + + "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" + "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha2" + + "github.com/coreos/go-semver/semver" + log "github.com/sirupsen/logrus" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// TODO: make tests for: +// RBAC +// Deployment annotation verification + +// func NoTestCreateOperatorGroupWithMatchingNamespace(t *testing.T) { +// // Create namespace with specific label +// // Create deployment in namespace +// // Create operator group that watches namespace and uses specific label +// // Verify operator group status contains correct status +// // Verify deployments have correct namespace annotation + +// // c := newKubeClient(t) + +// // deployment := appsv1. + +// // c.CreateDeployment() + +// } + +func TestCreateOperatorCSVCopy(t *testing.T) { + // create operator namespace + // create operator group in OLM namespace + // create CSV in OLM namespace + // verify CSV is copied to operator namespace + + log.SetLevel(log.DebugLevel) + c := newKubeClient(t) + crc := newCRClient(t) + operatorNamespaceName := testNamespace + "-operator" + csvName := "acsv-that-is-unique" // must be lowercase for DNS-1123 validation + matchingLabel := map[string]string{"app": "matchLabel"} + + operatorNamespace := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: operatorNamespaceName, + Labels: matchingLabel, + }, + } + _, err := c.KubernetesInterface().CoreV1().Namespaces().Create(&operatorNamespace) + require.NoError(t, err) + + operatorGroup := v1alpha2.OperatorGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "e2e-operator-group", + Namespace: testNamespace, + }, + Spec: v1alpha2.OperatorGroupSpec{ + Selector: metav1.LabelSelector{ + MatchLabels: matchingLabel, + }, + }, + } + _, err = crc.OperatorsV1alpha2().OperatorGroups(testNamespace).Create(&operatorGroup) + require.NoError(t, err) + + aCSV := newCSV(csvName, testNamespace, "", *semver.New("0.0.0"), nil, nil, newNginxInstallStrategy("aspec", nil, nil)) + createdCSV, err := crc.OperatorsV1alpha1().ClusterServiceVersions(testNamespace).Create(&aCSV) + require.NoError(t, err) + + var csvCopy *v1alpha1.ClusterServiceVersion + err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { + csvCopy, err = crc.OperatorsV1alpha1().ClusterServiceVersions(operatorNamespaceName).Get(csvName, metav1.GetOptions{}) + if err != nil { + if errors.IsNotFound(err) { + return false, nil + } + return false, err + } + return true, nil + }) + require.Equal(t, createdCSV.Name, csvCopy.Name) + require.Equal(t, createdCSV.Spec, csvCopy.Spec) + + // clean up + err = c.KubernetesInterface().CoreV1().Namespaces().Delete(operatorNamespaceName, &metav1.DeleteOptions{}) + if err != nil { + t.Errorf("Operator namespace cleanup failed: %v\n", err) + } +} From c93a4335f86a296b672e32ce9683042b73c3ea34 Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Fri, 12 Oct 2018 17:50:36 -0400 Subject: [PATCH 13/33] fix(unit): call Run on operator directly This is cleaner than manually setting up informers. --- pkg/controller/operators/olm/operator_test.go | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/pkg/controller/operators/olm/operator_test.go b/pkg/controller/operators/olm/operator_test.go index 11381675ed..a05687869a 100644 --- a/pkg/controller/operators/olm/operator_test.go +++ b/pkg/controller/operators/olm/operator_test.go @@ -19,9 +19,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/informers" k8sfake "k8s.io/client-go/kubernetes/fake" - "k8s.io/client-go/tools/cache" apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" apiregistrationfake "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake" @@ -1277,14 +1275,9 @@ func TestSyncOperatorGroups(t *testing.T) { require.NoError(t, err) stopCh := make(chan struct{}) - informerFactory := informers.NewSharedInformerFactory(op.OpClient.KubernetesInterface(), 1*time.Second) - deployInformer := informerFactory.Apps().V1().Deployments() - for _, informer := range []cache.SharedIndexInformer{deployInformer.Informer()} { - go informer.Run(stopCh) - } - op.deploymentLister[testNS] = deployInformer.Lister() - informerFactory.Start(stopCh) - informerFactory.WaitForCacheSync(stopCh) + defer func() { stopCh <- struct{}{} }() + ready, _ := op.Run(stopCh) + <-ready // Could not put this in initialObjs - got "no kind is registered for the type v1alpha2.OperatorGroup" op.client.OperatorsV1alpha2().OperatorGroups(tc.inputGroup.Namespace).Create(&tc.inputGroup) From 22481273a3d14dd9a63f1212bde3e3e61381271d Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Thu, 18 Oct 2018 11:38:47 -0400 Subject: [PATCH 14/33] test(e2e): add second e2e for operator groups This verifies that deployments and the operator group statuses are correct. Also, add a few missing annotations on the copied CSVs. --- pkg/controller/operators/olm/operator.go | 18 ++- test/e2e/operator_groups_e2e_test.go | 150 +++++++++++++++++++++-- 2 files changed, 153 insertions(+), 15 deletions(-) diff --git a/pkg/controller/operators/olm/operator.go b/pkg/controller/operators/olm/operator.go index 35592ad11e..42c9fb2cf3 100644 --- a/pkg/controller/operators/olm/operator.go +++ b/pkg/controller/operators/olm/operator.go @@ -468,6 +468,7 @@ func (a *Operator) updateDeploymentAnnotation(op *v1alpha2.OperatorGroup) (error for ix := range namespaceList.Items { nsList = append(nsList, namespaceList.Items[ix].Name) } + nsListJoined := strings.Join(nsList, ",") // write above namespaces to watch in every deployment for _, ns := range nsList { @@ -477,12 +478,17 @@ func (a *Operator) updateDeploymentAnnotation(op *v1alpha2.OperatorGroup) (error } for _, deploy := range deploymentList { + if lastAnnotation, ok := deploy.Spec.Template.Annotations["olm.targetNamespaces"]; ok { + if lastAnnotation == nsListJoined { + continue + } + } originalData, err := json.Marshal(deploy) if err != nil { return err, namespaceList.Items } - metav1.SetMetaDataAnnotation(&deploy.Spec.Template.ObjectMeta, "olm.targetNamespaces", strings.Join(nsList, ",")) + metav1.SetMetaDataAnnotation(&deploy.Spec.Template.ObjectMeta, "olm.targetNamespaces", nsListJoined) log.Debugf("Wrote annotation '%v' on %v. Check: %#v\n", nsList, deploy.Name, deploy) modifiedData, err := json.Marshal(deploy) @@ -517,12 +523,16 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error { if err != nil { return err } + var nsList []string + for ix := range targetedNamespaces { + nsList = append(nsList, targetedNamespaces[ix].Name) + } csvsInNamespace := a.csvsInNamespace(op.Namespace) for _, csv := range csvsInNamespace { // TODO: handle CSV copying in a different place // if csv.Status.Phase != v1alpha1.CSVPhaseSucceeded { - // log.Debugf("JPEELER: continuing on, skipping CSV %v\n", csv.Name) + // log.Debugf("continuing on, skipping CSV %v\n", csv.Name) // continue // } @@ -540,6 +550,10 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error { } metav1.SetMetaDataAnnotation(&newCSV.ObjectMeta, "olm.originalCSV", fmt.Sprintf("%v/%v", csv.GetNamespace(), csv.GetName())) + metav1.SetMetaDataAnnotation(&newCSV.ObjectMeta, "olm.targetNamespaces", strings.Join(nsList, ",")) + metav1.SetMetaDataAnnotation(&newCSV.ObjectMeta, "olm.operatorNamespace", a.annotator.Annotations["ALMManagedAnnotationKey"]) + metav1.SetMetaDataAnnotation(&newCSV.ObjectMeta, "olm.operatorGroup", fmt.Sprintf("%v/%v", op.GetNamespace(), op.GetName())) + ownerutil.AddNonBlockingOwner(&newCSV, csv) for _, ns := range targetedNamespaces { newCSV.SetNamespace(ns.Name) diff --git a/test/e2e/operator_groups_e2e_test.go b/test/e2e/operator_groups_e2e_test.go index e8df796e3f..696c9d1dd7 100644 --- a/test/e2e/operator_groups_e2e_test.go +++ b/test/e2e/operator_groups_e2e_test.go @@ -1,13 +1,17 @@ package e2e import ( + "regexp" + "strings" "testing" + appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/util/wait" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha2" + "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient" "github.com/coreos/go-semver/semver" log "github.com/sirupsen/logrus" @@ -16,24 +20,144 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// TODO: make tests for: -// RBAC -// Deployment annotation verification +func DeploymentComplete(deployment *appsv1.Deployment, newStatus *appsv1.DeploymentStatus) bool { + return newStatus.UpdatedReplicas == *(deployment.Spec.Replicas) && + newStatus.Replicas == *(deployment.Spec.Replicas) && + newStatus.AvailableReplicas == *(deployment.Spec.Replicas) && + newStatus.ObservedGeneration >= deployment.Generation +} + +// Currently this function only modifies the watchedNamespace in the container command +func patchOlmDeployment(t *testing.T, c operatorclient.ClientInterface, newNamespace string) { + runningDeploy, err := c.GetDeployment(testNamespace, "olm-operator") + require.NoError(t, err) + + command := runningDeploy.Spec.Template.Spec.Containers[0].Command + re, err := regexp.Compile(`-watchedNamespaces\W(\S+)`) + require.NoError(t, err) + newCommand := re.ReplaceAllString(strings.Join(command, " "), "$0"+","+newNamespace) + log.Debugf("original=%#v newCommand=%#v", command, newCommand) + finalNewCommand := strings.Split(newCommand, " ") + runningDeploy.Spec.Template.Spec.Containers[0].Command = make([]string, len(finalNewCommand)) + copy(runningDeploy.Spec.Template.Spec.Containers[0].Command, finalNewCommand) + + newDeployment, updated, err := c.UpdateDeployment(runningDeploy) + if err != nil || updated == false { + t.Fatalf("Deployment update failed: (updated %v) %v\n", updated, err) + } + require.NoError(t, err) + + err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { + fetchedDeployment, err := c.GetDeployment(newDeployment.Namespace, newDeployment.Name) + if err != nil { + return false, err + } + if DeploymentComplete(newDeployment, &fetchedDeployment.Status) { + return true, nil + } + return false, nil + }) + + require.NoError(t, err) +} -// func NoTestCreateOperatorGroupWithMatchingNamespace(t *testing.T) { -// // Create namespace with specific label -// // Create deployment in namespace -// // Create operator group that watches namespace and uses specific label -// // Verify operator group status contains correct status -// // Verify deployments have correct namespace annotation +func TestCreateOperatorGroupWithMatchingNamespace(t *testing.T) { + // Create namespace with specific label + // Create deployment in namespace + // Create operator group that watches namespace and uses specific label + // Verify operator group status contains correct status + // Verify deployments have correct namespace annotation + + log.SetLevel(log.DebugLevel) + c := newKubeClient(t) + crc := newCRClient(t) + + matchingLabel := map[string]string{"app": "matchLabel"} + otherNamespaceName := testNamespace + "-namespace-two" + + otherNamespace := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: otherNamespaceName, + Labels: matchingLabel, + }, + } + createdOtherNamespace, err := c.KubernetesInterface().CoreV1().Namespaces().Create(&otherNamespace) + require.NoError(t, err) + + patchOlmDeployment(t, c, otherNamespaceName) + + var one = int32(1) + deployment := appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "deployment", + Namespace: otherNamespaceName, + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: matchingLabel, + }, + Replicas: &one, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: matchingLabel, + }, + Spec: corev1.PodSpec{Containers: []corev1.Container{ + { + Name: genName("nginx"), + Image: "nginx:1.7.9", + Ports: []corev1.ContainerPort{{ContainerPort: 80}}, + }, + }}, + }, + }, + } -// // c := newKubeClient(t) + createdDeployment, err := c.CreateDeployment(&deployment) + require.NoError(t, err) -// // deployment := appsv1. + operatorGroup := v1alpha2.OperatorGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "e2e-operator-group", + Namespace: testNamespace, + }, + Spec: v1alpha2.OperatorGroupSpec{ + Selector: metav1.LabelSelector{ + MatchLabels: matchingLabel, + }, + }, + } + createdOperatorGroup, err := crc.OperatorsV1alpha2().OperatorGroups(testNamespace).Create(&operatorGroup) + require.NoError(t, err) + expectedOperatorGroupStatus := v1alpha2.OperatorGroupStatus{ + Namespaces: []corev1.Namespace{*createdOtherNamespace}, + } -// // c.CreateDeployment() + err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { + createdOperatorGroup, err = crc.OperatorsV1alpha2().OperatorGroups(testNamespace).Get(operatorGroup.Name, metav1.GetOptions{}) + if err != nil { + return false, err + } + if len(createdOperatorGroup.Status.Namespaces) > 0 { + require.Equal(t, expectedOperatorGroupStatus.Namespaces[0].Name, createdOperatorGroup.Status.Namespaces[0].Name) + return true, nil + } + return false, nil + }) -// } + err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { + createdDeployment, err = c.GetDeployment(otherNamespaceName, "deployment") + if err != nil { + if errors.IsNotFound(err) { + return false, nil + } + return false, err + } + if createdDeployment.Spec.Template.Annotations["olm.targetNamespaces"] == otherNamespaceName { + return true, nil + } + return false, nil + }) +} func TestCreateOperatorCSVCopy(t *testing.T) { // create operator namespace From 4d6c59efde6ea8332d0646478359bd3a215bcb3f Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Fri, 19 Oct 2018 10:19:57 -0400 Subject: [PATCH 15/33] fix(olm): make RBAC creation resilient If by chance somehow existing RBAC rules exist, update them rather than failing on creation attempt. --- pkg/controller/operators/olm/operator.go | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/pkg/controller/operators/olm/operator.go b/pkg/controller/operators/olm/operator.go index 42c9fb2cf3..aa4193a009 100644 --- a/pkg/controller/operators/olm/operator.go +++ b/pkg/controller/operators/olm/operator.go @@ -25,6 +25,7 @@ import ( "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" @@ -436,7 +437,11 @@ func (a *Operator) updateDeploymentAnnotation(op *v1alpha2.OperatorGroup) (error ownerutil.AddNonBlockingOwner(clusterRole, csv) clusterRole.SetGenerateName(fmt.Sprintf("owned-crd-manager-%s-", csv.Spec.DisplayName)) _, err = a.OpClient.KubernetesInterface().RbacV1().ClusterRoles().Create(clusterRole) - if err != nil { + if k8serrors.IsAlreadyExists(err) { + if _, err = a.OpClient.UpdateClusterRole(clusterRole); err != nil { + return err, namespaceList.Items + } + } else if err != nil { return err, namespaceList.Items } @@ -448,7 +453,11 @@ func (a *Operator) updateDeploymentAnnotation(op *v1alpha2.OperatorGroup) (error Rules: apiEditPolicyRules, } _, err = a.OpClient.KubernetesInterface().RbacV1().ClusterRoles().Create(operatorGroupEditClusterRole) - if err != nil { + if k8serrors.IsAlreadyExists(err) { + if _, err = a.OpClient.UpdateClusterRole(operatorGroupEditClusterRole); err != nil { + return err, namespaceList.Items + } + } else if err != nil { return err, namespaceList.Items } operatorGroupViewClusterRole := &rbacv1.ClusterRole{ @@ -458,7 +467,11 @@ func (a *Operator) updateDeploymentAnnotation(op *v1alpha2.OperatorGroup) (error Rules: apiViewPolicyRules, } _, err = a.OpClient.KubernetesInterface().RbacV1().ClusterRoles().Create(operatorGroupViewClusterRole) - if err != nil { + if k8serrors.IsAlreadyExists(err) { + if _, err = a.OpClient.UpdateClusterRole(operatorGroupViewClusterRole); err != nil { + return err, namespaceList.Items + } + } else if err != nil { return err, namespaceList.Items } From 42dfe7753f335908a3b8cee5c35978cba3cac03e Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Fri, 19 Oct 2018 10:21:03 -0400 Subject: [PATCH 16/33] fix(e2e): ensure created objects are deleted also rename a test and "unpatch" olm deployment --- test/e2e/operator_groups_e2e_test.go | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/test/e2e/operator_groups_e2e_test.go b/test/e2e/operator_groups_e2e_test.go index 696c9d1dd7..5f488b1043 100644 --- a/test/e2e/operator_groups_e2e_test.go +++ b/test/e2e/operator_groups_e2e_test.go @@ -28,7 +28,7 @@ func DeploymentComplete(deployment *appsv1.Deployment, newStatus *appsv1.Deploym } // Currently this function only modifies the watchedNamespace in the container command -func patchOlmDeployment(t *testing.T, c operatorclient.ClientInterface, newNamespace string) { +func patchOlmDeployment(t *testing.T, c operatorclient.ClientInterface, newNamespace string) []string { runningDeploy, err := c.GetDeployment(testNamespace, "olm-operator") require.NoError(t, err) @@ -59,6 +59,7 @@ func patchOlmDeployment(t *testing.T, c operatorclient.ClientInterface, newNames }) require.NoError(t, err) + return command } func TestCreateOperatorGroupWithMatchingNamespace(t *testing.T) { @@ -84,7 +85,7 @@ func TestCreateOperatorGroupWithMatchingNamespace(t *testing.T) { createdOtherNamespace, err := c.KubernetesInterface().CoreV1().Namespaces().Create(&otherNamespace) require.NoError(t, err) - patchOlmDeployment(t, c, otherNamespaceName) + oldCommand := patchOlmDeployment(t, c, otherNamespaceName) var one = int32(1) deployment := appsv1.Deployment{ @@ -157,9 +158,24 @@ func TestCreateOperatorGroupWithMatchingNamespace(t *testing.T) { } return false, nil }) + + // clean up + runningDeploy, err := c.GetDeployment(testNamespace, "olm-operator") + require.NoError(t, err) + runningDeploy.Spec.Template.Spec.Containers[0].Command = oldCommand + _, updated, err := c.UpdateDeployment(runningDeploy) + if err != nil || updated == false { + t.Fatalf("Deployment update failed: (updated %v) %v\n", updated, err) + } + require.NoError(t, err) + + err = c.KubernetesInterface().CoreV1().Namespaces().Delete(otherNamespaceName, &metav1.DeleteOptions{}) + require.NoError(t, err) + err = crc.OperatorsV1alpha2().OperatorGroups(testNamespace).Delete(operatorGroup.Name, &metav1.DeleteOptions{}) + require.NoError(t, err) } -func TestCreateOperatorCSVCopy(t *testing.T) { +func TestCreateOperatorGroupCSVCopy(t *testing.T) { // create operator namespace // create operator group in OLM namespace // create CSV in OLM namespace @@ -215,7 +231,7 @@ func TestCreateOperatorCSVCopy(t *testing.T) { // clean up err = c.KubernetesInterface().CoreV1().Namespaces().Delete(operatorNamespaceName, &metav1.DeleteOptions{}) - if err != nil { - t.Errorf("Operator namespace cleanup failed: %v\n", err) - } + require.NoError(t, err) + err = crc.OperatorsV1alpha2().OperatorGroups(testNamespace).Delete(operatorGroup.Name, &metav1.DeleteOptions{}) + require.NoError(t, err) } From 522c0b620f0d78d3ec61be9fe7f98afa4b050279 Mon Sep 17 00:00:00 2001 From: Evan Cordell Date: Fri, 19 Oct 2018 16:18:51 -0400 Subject: [PATCH 17/33] refactor(operatorgroup): split apart the larger sync functions also fixed an issue where all deployments in a namespace were being annotated, not just those owned by CSVs --- .../operators/v1alpha2/operatorgroup_types.go | 2 +- .../v1alpha2/zz_generated.deepcopy.go | 9 +- pkg/controller/operators/olm/operator.go | 256 ++---------------- pkg/controller/operators/olm/operator_test.go | 6 +- pkg/controller/operators/olm/operatorgroup.go | 229 ++++++++++++++++ 5 files changed, 257 insertions(+), 245 deletions(-) create mode 100644 pkg/controller/operators/olm/operatorgroup.go diff --git a/pkg/api/apis/operators/v1alpha2/operatorgroup_types.go b/pkg/api/apis/operators/v1alpha2/operatorgroup_types.go index 84133e5d8c..0f91c3c5f9 100644 --- a/pkg/api/apis/operators/v1alpha2/operatorgroup_types.go +++ b/pkg/api/apis/operators/v1alpha2/operatorgroup_types.go @@ -11,7 +11,7 @@ type OperatorGroupSpec struct { } type OperatorGroupStatus struct { - Namespaces []corev1.Namespace `json:"namespaces"` + Namespaces []*corev1.Namespace `json:"namespaces"` LastUpdated metav1.Time `json:"lastUpdated"` } diff --git a/pkg/api/apis/operators/v1alpha2/zz_generated.deepcopy.go b/pkg/api/apis/operators/v1alpha2/zz_generated.deepcopy.go index 3e5837f6f0..e3d6bb2aa5 100644 --- a/pkg/api/apis/operators/v1alpha2/zz_generated.deepcopy.go +++ b/pkg/api/apis/operators/v1alpha2/zz_generated.deepcopy.go @@ -109,9 +109,14 @@ func (in *OperatorGroupStatus) DeepCopyInto(out *OperatorGroupStatus) { *out = *in if in.Namespaces != nil { in, out := &in.Namespaces, &out.Namespaces - *out = make([]v1.Namespace, len(*in)) + *out = make([]*v1.Namespace, len(*in)) for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) + if (*in)[i] == nil { + (*out)[i] = nil + } else { + (*out)[i] = new(v1.Namespace) + (*in)[i].DeepCopyInto((*out)[i]) + } } } in.LastUpdated.DeepCopyInto(&out.LastUpdated) diff --git a/pkg/controller/operators/olm/operator.go b/pkg/controller/operators/olm/operator.go index aa4193a009..f995bd1b51 100644 --- a/pkg/controller/operators/olm/operator.go +++ b/pkg/controller/operators/olm/operator.go @@ -1,10 +1,8 @@ package olm import ( - "encoding/json" "errors" "fmt" - "strings" "time" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" @@ -25,13 +23,11 @@ import ( "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/client-go/informers" cappsv1 "k8s.io/client-go/listers/apps/v1" + cv1 "k8s.io/client-go/listers/core/v1" crbacv1 "k8s.io/client-go/listers/rbac/v1" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" @@ -61,6 +57,7 @@ type Operator struct { clusterRoleBindingLister crbacv1.ClusterRoleBindingLister operatorGroupLister map[string]operatorgrouplister.OperatorGroupLister deploymentLister map[string]cappsv1.DeploymentLister + namespaceLister cv1.NamespaceLister annotator *annotator.Annotator recorder record.EventRecorder cleanupFunc func() @@ -96,28 +93,25 @@ func NewOperator(crClient versioned.Interface, opClient operatorclient.ClientInt }, } - // If watching all namespaces, set up a watch to annotate new namespaces - if len(namespaces) == 1 && namespaces[0] == metav1.NamespaceAll { - log.Debug("watching all namespaces, setting up queue") - namespaceInformer := informers.NewSharedInformerFactory(queueOperator.OpClient.KubernetesInterface(), wakeupInterval).Core().V1().Namespaces() - queueInformer := queueinformer.NewInformer( - workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "namespaces"), - namespaceInformer.Informer(), - op.syncNamespace, - nil, - "namespace", - metrics.NewMetricsNil(), - ) - op.RegisterQueueInformer(queueInformer) - op.lister.CoreV1().RegisterNamespaceLister(namespaceInformer.Lister()) - } - // Set up RBAC informers informerFactory := informers.NewSharedInformerFactory(opClient.KubernetesInterface(), wakeupInterval) roleInformer := informerFactory.Rbac().V1().Roles() roleBindingInformer := informerFactory.Rbac().V1().RoleBindings() clusterRoleInformer := informerFactory.Rbac().V1().ClusterRoles() clusterRoleBindingInformer := informerFactory.Rbac().V1().ClusterRoleBindings() + namespaceInformer := informerFactory.Core().V1().Namespaces() + + // register namespace queueinformer + queueInformer := queueinformer.NewInformer( + workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "namespaces"), + namespaceInformer.Informer(), + op.syncNamespace, + nil, + "namespace", + metrics.NewMetricsNil(), + ) + op.RegisterQueueInformer(queueInformer) + op.lister.CoreV1().RegisterNamespaceLister(namespaceInformer.Lister()) // Register RBAC QueueInformers rbacInformers := []cache.SharedIndexInformer{ @@ -365,223 +359,6 @@ func (a *Operator) syncServices(obj interface{}) (syncError error) { return nil } -func namespacesChanged(clusterNamespaces []corev1.Namespace, statusNamespaces []corev1.Namespace) bool { - if len(clusterNamespaces) != len(statusNamespaces) { - return true - } - - nsMap := map[string]struct{}{} - for _, v := range clusterNamespaces { - nsMap[v.Name] = struct{}{} - } - for _, v := range statusNamespaces { - if _, ok := nsMap[v.Name]; !ok { - return true - } - } - return false -} - -func (a *Operator) updateDeploymentAnnotation(op *v1alpha2.OperatorGroup) (error, []corev1.Namespace) { - // NOTE: if a CSV modification is required in the future, copy the original - // data as done in the bottom of this method first. - - selector, err := metav1.LabelSelectorAsSelector(&op.Spec.Selector) - if err != nil { - return err, nil - } - operatorGroupOpts := metav1.ListOptions{LabelSelector: selector.String()} - namespaceList, err := a.OpClient.KubernetesInterface().CoreV1().Namespaces().List(operatorGroupOpts) - if err != nil { - return err, nil - } - - if !namespacesChanged(namespaceList.Items, op.Status.Namespaces) { - // status is current with correct namespaces, so no further updates required - return nil, namespaceList.Items - } - log.Debugf("Namespace change detected, found: %v", namespaceList.Items) - op.Status.Namespaces = make([]corev1.Namespace, len(namespaceList.Items)) - copy(op.Status.Namespaces, namespaceList.Items) - op.Status.LastUpdated = timeNow() - _, err = a.client.OperatorsV1alpha2().OperatorGroups(op.Namespace).UpdateStatus(op) - if err != nil { - return err, namespaceList.Items - } - - currentNamespace := op.GetNamespace() - csvsInNamespace := a.csvsInNamespace(currentNamespace) - for _, csv := range csvsInNamespace { - managerPolicyRules := []rbacv1.PolicyRule{} - apiEditPolicyRules := []rbacv1.PolicyRule{} - apiViewPolicyRules := []rbacv1.PolicyRule{} - for _, owned := range csv.Spec.CustomResourceDefinitions.Owned { - resourceNames := []string{} - for _, resource := range owned.Resources { - resourceNames = append(resourceNames, resource.Name) - } - managerPolicyRules = append(managerPolicyRules, rbacv1.PolicyRule{Verbs: []string{"*"}, APIGroups: []string{owned.Name}, Resources: resourceNames}) - apiEditPolicyRules = append(apiEditPolicyRules, rbacv1.PolicyRule{Verbs: []string{"create", "update", "patch", "delete"}, APIGroups: []string{owned.Name}, Resources: []string{owned.Kind}}) - apiViewPolicyRules = append(apiViewPolicyRules, rbacv1.PolicyRule{Verbs: []string{"get", "list", "watch"}, APIGroups: []string{owned.Name}, Resources: []string{owned.Kind}}) - } - for _, owned := range csv.Spec.APIServiceDefinitions.Owned { - resourceNames := []string{} - for _, resource := range owned.Resources { - resourceNames = append(resourceNames, resource.Name) - } - managerPolicyRules = append(managerPolicyRules, rbacv1.PolicyRule{Verbs: []string{"*"}, APIGroups: []string{owned.Group}, Resources: resourceNames}) - } - clusterRole := &rbacv1.ClusterRole{ - Rules: managerPolicyRules, - } - ownerutil.AddNonBlockingOwner(clusterRole, csv) - clusterRole.SetGenerateName(fmt.Sprintf("owned-crd-manager-%s-", csv.Spec.DisplayName)) - _, err = a.OpClient.KubernetesInterface().RbacV1().ClusterRoles().Create(clusterRole) - if k8serrors.IsAlreadyExists(err) { - if _, err = a.OpClient.UpdateClusterRole(clusterRole); err != nil { - return err, namespaceList.Items - } - } else if err != nil { - return err, namespaceList.Items - } - - // operator group specific roles - operatorGroupEditClusterRole := &rbacv1.ClusterRole{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-edit", op.Name), - }, - Rules: apiEditPolicyRules, - } - _, err = a.OpClient.KubernetesInterface().RbacV1().ClusterRoles().Create(operatorGroupEditClusterRole) - if k8serrors.IsAlreadyExists(err) { - if _, err = a.OpClient.UpdateClusterRole(operatorGroupEditClusterRole); err != nil { - return err, namespaceList.Items - } - } else if err != nil { - return err, namespaceList.Items - } - operatorGroupViewClusterRole := &rbacv1.ClusterRole{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-view", op.Name), - }, - Rules: apiViewPolicyRules, - } - _, err = a.OpClient.KubernetesInterface().RbacV1().ClusterRoles().Create(operatorGroupViewClusterRole) - if k8serrors.IsAlreadyExists(err) { - if _, err = a.OpClient.UpdateClusterRole(operatorGroupViewClusterRole); err != nil { - return err, namespaceList.Items - } - } else if err != nil { - return err, namespaceList.Items - } - - } - - var nsList []string - for ix := range namespaceList.Items { - nsList = append(nsList, namespaceList.Items[ix].Name) - } - nsListJoined := strings.Join(nsList, ",") - - // write above namespaces to watch in every deployment - for _, ns := range nsList { - deploymentList, err := a.deploymentLister[ns].List(labels.Everything()) - if err != nil { - return err, namespaceList.Items - } - - for _, deploy := range deploymentList { - if lastAnnotation, ok := deploy.Spec.Template.Annotations["olm.targetNamespaces"]; ok { - if lastAnnotation == nsListJoined { - continue - } - } - originalData, err := json.Marshal(deploy) - if err != nil { - return err, namespaceList.Items - } - - metav1.SetMetaDataAnnotation(&deploy.Spec.Template.ObjectMeta, "olm.targetNamespaces", nsListJoined) - log.Debugf("Wrote annotation '%v' on %v. Check: %#v\n", nsList, deploy.Name, deploy) - - modifiedData, err := json.Marshal(deploy) - if err != nil { - return err, namespaceList.Items - } - - patchBytes, err := strategicpatch.CreateTwoWayMergePatch(originalData, modifiedData, deploy) - if err != nil { - return err, namespaceList.Items - } - - _, err = a.OpClient.KubernetesInterface().AppsV1().Deployments(ns).Patch(deploy.Name, types.StrategicMergePatchType, patchBytes) - if err != nil { - return fmt.Errorf("Deployment update for '%v' failed: %v\n", deploy.Name, err), namespaceList.Items - } - } - } - - return nil, namespaceList.Items -} - -func (a *Operator) syncOperatorGroups(obj interface{}) error { - op, ok := obj.(*v1alpha2.OperatorGroup) - if !ok { - log.Debugf("wrong type: %#v\n", obj) - return fmt.Errorf("casting OperatorGroup failed") - } - - err, targetedNamespaces := a.updateDeploymentAnnotation(op) - log.Debugf("Got targetedNamespaces: '%v'", targetedNamespaces) - if err != nil { - return err - } - var nsList []string - for ix := range targetedNamespaces { - nsList = append(nsList, targetedNamespaces[ix].Name) - } - - csvsInNamespace := a.csvsInNamespace(op.Namespace) - for _, csv := range csvsInNamespace { - // TODO: handle CSV copying in a different place - // if csv.Status.Phase != v1alpha1.CSVPhaseSucceeded { - // log.Debugf("continuing on, skipping CSV %v\n", csv.Name) - // continue - // } - - // create new CSV instead of DeepCopy as namespace and resource version (and status) will be different - newCSV := v1alpha1.ClusterServiceVersion{ - ObjectMeta: metav1.ObjectMeta{ - Name: csv.Name, - }, - Spec: *csv.Spec.DeepCopy(), - Status: v1alpha1.ClusterServiceVersionStatus{ - Message: "CSV copied to target namespace", - Reason: v1alpha1.CSVReasonCopied, - LastUpdateTime: timeNow(), - }, - } - - metav1.SetMetaDataAnnotation(&newCSV.ObjectMeta, "olm.originalCSV", fmt.Sprintf("%v/%v", csv.GetNamespace(), csv.GetName())) - metav1.SetMetaDataAnnotation(&newCSV.ObjectMeta, "olm.targetNamespaces", strings.Join(nsList, ",")) - metav1.SetMetaDataAnnotation(&newCSV.ObjectMeta, "olm.operatorNamespace", a.annotator.Annotations["ALMManagedAnnotationKey"]) - metav1.SetMetaDataAnnotation(&newCSV.ObjectMeta, "olm.operatorGroup", fmt.Sprintf("%v/%v", op.GetNamespace(), op.GetName())) - - ownerutil.AddNonBlockingOwner(&newCSV, csv) - for _, ns := range targetedNamespaces { - newCSV.SetNamespace(ns.Name) - if ns.Name != op.Namespace { - _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(newCSV.GetNamespace()).Create(&newCSV) - if err != nil { - return err - } - } - } - } - - return nil -} - // syncClusterServiceVersion is the method that gets called when we see a CSV event in the cluster func (a *Operator) syncClusterServiceVersion(obj interface{}) (syncError error) { clusterServiceVersion, ok := obj.(*v1alpha1.ClusterServiceVersion) @@ -909,6 +686,7 @@ func (a *Operator) syncNamespace(obj interface{}) (syncError error) { return err } + // TODO: do we need this? opGroupUpdate := map[*v1alpha2.OperatorGroup]struct{}{} for _, op := range opGroupList { selector, err := metav1.LabelSelectorAsSelector(&op.Spec.Selector) @@ -937,7 +715,7 @@ func (a *Operator) syncNamespace(obj interface{}) (syncError error) { } for op := range opGroupUpdate { - if err, _ := a.updateDeploymentAnnotation(op); err != nil { + if err, _ := a.updateNamespaceList(op); err != nil { return err } } diff --git a/pkg/controller/operators/olm/operator_test.go b/pkg/controller/operators/olm/operator_test.go index a05687869a..6c67236089 100644 --- a/pkg/controller/operators/olm/operator_test.go +++ b/pkg/controller/operators/olm/operator_test.go @@ -1153,7 +1153,7 @@ func TestSyncOperatorGroups(t *testing.T) { }, }, expectedStatus: v1alpha2.OperatorGroupStatus{ - Namespaces: []v1.Namespace{ + Namespaces: []*v1.Namespace{ { ObjectMeta: metav1.ObjectMeta{ Name: testNS, @@ -1203,7 +1203,7 @@ func TestSyncOperatorGroups(t *testing.T) { }, }, expectedStatus: v1alpha2.OperatorGroupStatus{ - Namespaces: []v1.Namespace{ + Namespaces: []*v1.Namespace{ { ObjectMeta: metav1.ObjectMeta{ Name: testNS, @@ -1254,7 +1254,7 @@ func TestSyncOperatorGroups(t *testing.T) { }, }, expectedStatus: v1alpha2.OperatorGroupStatus{ - Namespaces: []v1.Namespace{ + Namespaces: []*v1.Namespace{ { ObjectMeta: metav1.ObjectMeta{ Name: testNS, diff --git a/pkg/controller/operators/olm/operatorgroup.go b/pkg/controller/operators/olm/operatorgroup.go new file mode 100644 index 0000000000..14caa34b9a --- /dev/null +++ b/pkg/controller/operators/olm/operatorgroup.go @@ -0,0 +1,229 @@ +package olm + +import ( + "fmt" + "strings" + + "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" + "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha2" + "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil" + log "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" +) + +func (a *Operator) syncOperatorGroups(obj interface{}) error { + op, ok := obj.(*v1alpha2.OperatorGroup) + if !ok { + log.Debugf("wrong type: %#v\n", obj) + return fmt.Errorf("casting OperatorGroup failed") + } + + err, targetedNamespaces := a.updateNamespaceList(op) + log.Debugf("Got targetedNamespaces: '%v'", targetedNamespaces) + if err != nil { + return err + } + + if err := a.ensureClusterRoles(op); err != nil { + return err + } + + var nsList []string + for ix := range targetedNamespaces { + nsList = append(nsList, targetedNamespaces[ix].GetName()) + } + nsListJoined := strings.Join(nsList, ",") + + if err := a.annotateDeployments(nsList, nsListJoined); err != nil { + return err + } + + // annotate csvs + csvsInNamespace := a.csvsInNamespace(op.Namespace) + for _, csv := range csvsInNamespace { + // create new CSV instead of DeepCopy as namespace and resource version (and status) will be different + newCSV := v1alpha1.ClusterServiceVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: csv.Name, + }, + Spec: *csv.Spec.DeepCopy(), + Status: v1alpha1.ClusterServiceVersionStatus{ + Message: "CSV copied to target namespace", + Reason: v1alpha1.CSVReasonCopied, + LastUpdateTime: timeNow(), + }, + } + + //TODO: listen to delete events on CSVS, delete all "target CSVs" + metav1.SetMetaDataAnnotation(&newCSV.ObjectMeta, "olm.targetNamespaces", strings.Join(nsList, ",")) + metav1.SetMetaDataAnnotation(&newCSV.ObjectMeta, "olm.operatorNamespace", op.GetNamespace()) + metav1.SetMetaDataAnnotation(&newCSV.ObjectMeta, "olm.operatorGroup", op.GetName()) + + for _, ns := range targetedNamespaces { + newCSV.SetNamespace(ns.Name) + if ns.Name != op.Namespace { + _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(newCSV.GetNamespace()).Create(&newCSV) + if err != nil { + return err + } + } + } + } + + //TODO: ensure RBAC on operator serviceaccount + + return nil +} + +func namespacesChanged(clusterNamespaces []*corev1.Namespace, statusNamespaces []*corev1.Namespace) bool { + if len(clusterNamespaces) != len(statusNamespaces) { + return true + } + + nsMap := map[string]struct{}{} + for _, v := range clusterNamespaces { + nsMap[v.Name] = struct{}{} + } + for _, v := range statusNamespaces { + if _, ok := nsMap[v.Name]; !ok { + return true + } + } + return false +} + +func (a *Operator) updateNamespaceList(op *v1alpha2.OperatorGroup) (error, []*corev1.Namespace) { + selector, err := metav1.LabelSelectorAsSelector(&op.Spec.Selector) + if err != nil { + return err, nil + } + + namespaceList, err := a.lister.CoreV1().NamespaceLister().List(selector) + if err != nil { + return err, nil + } + + if !namespacesChanged(namespaceList, op.Status.Namespaces) { + // status is current with correct namespaces, so no further updates required + return nil, namespaceList + } + log.Debugf("Namespace change detected, found: %v", namespaceList) + op.Status.Namespaces = make([]*corev1.Namespace, len(namespaceList)) + copy(op.Status.Namespaces, namespaceList) + op.Status.LastUpdated = timeNow() + _, err = a.client.OperatorsV1alpha2().OperatorGroups(op.Namespace).UpdateStatus(op) + if err != nil { + return err, namespaceList + } + return nil, namespaceList +} + +func (a *Operator) ensureClusterRoles(op *v1alpha2.OperatorGroup) error { + currentNamespace := op.GetNamespace() + csvsInNamespace := a.csvsInNamespace(currentNamespace) + for _, csv := range csvsInNamespace { + managerPolicyRules := []rbacv1.PolicyRule{} + apiEditPolicyRules := []rbacv1.PolicyRule{} + apiViewPolicyRules := []rbacv1.PolicyRule{} + for _, owned := range csv.Spec.CustomResourceDefinitions.Owned { + resourceNames := []string{} + for _, resource := range owned.Resources { + resourceNames = append(resourceNames, resource.Name) + } + managerPolicyRules = append(managerPolicyRules, rbacv1.PolicyRule{Verbs: []string{"*"}, APIGroups: []string{owned.Name}, Resources: resourceNames}) + apiEditPolicyRules = append(apiEditPolicyRules, rbacv1.PolicyRule{Verbs: []string{"create", "update", "patch", "delete"}, APIGroups: []string{owned.Name}, Resources: []string{owned.Kind}}) + apiViewPolicyRules = append(apiViewPolicyRules, rbacv1.PolicyRule{Verbs: []string{"get", "list", "watch"}, APIGroups: []string{owned.Name}, Resources: []string{owned.Kind}}) + } + for _, owned := range csv.Spec.APIServiceDefinitions.Owned { + resourceNames := []string{} + for _, resource := range owned.Resources { + resourceNames = append(resourceNames, resource.Name) + } + managerPolicyRules = append(managerPolicyRules, rbacv1.PolicyRule{Verbs: []string{"*"}, APIGroups: []string{owned.Group}, Resources: resourceNames}) + apiEditPolicyRules = append(apiEditPolicyRules, rbacv1.PolicyRule{Verbs: []string{"create", "update", "patch", "delete"}, APIGroups: []string{owned.Group}, Resources: []string{owned.Kind}}) + apiViewPolicyRules = append(apiViewPolicyRules, rbacv1.PolicyRule{Verbs: []string{"get", "list", "watch"}, APIGroups: []string{owned.Group}, Resources: []string{owned.Kind}}) + } + + clusterRole := &rbacv1.ClusterRole{ + Rules: managerPolicyRules, + } + ownerutil.AddNonBlockingOwner(clusterRole, csv) + clusterRole.SetGenerateName(fmt.Sprintf("owned-crd-manager-%s-", csv.Spec.DisplayName)) + _, err := a.OpClient.KubernetesInterface().RbacV1().ClusterRoles().Create(clusterRole) + if k8serrors.IsAlreadyExists(err) { + if _, err = a.OpClient.UpdateClusterRole(clusterRole); err != nil { + return err + } + } else if err != nil { + return err + } + + // operator group specific roles + operatorGroupEditClusterRole := &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-edit", op.Name), + }, + Rules: apiEditPolicyRules, + } + _, err = a.OpClient.KubernetesInterface().RbacV1().ClusterRoles().Create(operatorGroupEditClusterRole) + if k8serrors.IsAlreadyExists(err) { + if _, err = a.OpClient.UpdateClusterRole(operatorGroupEditClusterRole); err != nil { + return err + } + } else if err != nil { + return err + } + operatorGroupViewClusterRole := &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-view", op.GetName()), + }, + Rules: apiViewPolicyRules, + } + _, err = a.OpClient.KubernetesInterface().RbacV1().ClusterRoles().Create(operatorGroupViewClusterRole) + if k8serrors.IsAlreadyExists(err) { + if _, err = a.OpClient.UpdateClusterRole(operatorGroupViewClusterRole); err != nil { + return err + } + } else if err != nil { + return err + } + } + return nil +} + +func (a *Operator) annotateDeployments(targetNamespaces []string, targetNamespaceString string) error { + // write above namespaces to watch in every deployment + for _, ns := range targetNamespaces { + deploymentList, err := a.deploymentLister[ns].List(labels.Everything()) + if err != nil { + return err + } + + for _, deploy := range deploymentList { + // TODO: this will be incorrect if two operatorgroups own the same namespace + // also - will be incorrect if a CSV is manually installed into a namespace + if !ownerutil.IsOwnedByKind(deploy, "ClusterServiceVersion") { + continue + } + + if lastAnnotation, ok := deploy.Spec.Template.Annotations["olm.targetNamespaces"]; ok { + if lastAnnotation == targetNamespaceString { + continue + } + } + + originalDeploy := deploy.DeepCopy() + metav1.SetMetaDataAnnotation(&deploy.Spec.Template.ObjectMeta, "olm.targetNamespaces", targetNamespaceString) + if _, _, err := a.OpClient.PatchDeployment(originalDeploy, deploy); err != nil { + return err + } + + } + } + + return nil +} From fccd2bca2f83fae8aa99a08142b5f0d9058e17ba Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Mon, 22 Oct 2018 13:24:51 -0400 Subject: [PATCH 18/33] fix(unit): set expected owner refs for deployments --- pkg/controller/operators/olm/operator_test.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/pkg/controller/operators/olm/operator_test.go b/pkg/controller/operators/olm/operator_test.go index 6c67236089..35543425f7 100644 --- a/pkg/controller/operators/olm/operator_test.go +++ b/pkg/controller/operators/olm/operator_test.go @@ -1092,6 +1092,14 @@ func TestSyncOperatorGroups(t *testing.T) { testNS := "test-ns" aLabel := map[string]string{"app": "matchLabel"} + ownedDeployment := deployment("csv1-dep1", testNS) + ownedDeployment.SetOwnerReferences([]metav1.OwnerReference{ + { + Kind: "ClusterServiceVersion", + Name: "csv1-dep1", + }, + }) + tests := []struct { name string expectedEqual bool @@ -1239,7 +1247,7 @@ func TestSyncOperatorGroups(t *testing.T) { ), }, initialObjs: []runtime.Object{ - deployment("csv1-dep1", testNS), + ownedDeployment, }, inputGroup: v1alpha2.OperatorGroup{ ObjectMeta: metav1.ObjectMeta{ @@ -1280,7 +1288,8 @@ func TestSyncOperatorGroups(t *testing.T) { <-ready // Could not put this in initialObjs - got "no kind is registered for the type v1alpha2.OperatorGroup" - op.client.OperatorsV1alpha2().OperatorGroups(tc.inputGroup.Namespace).Create(&tc.inputGroup) + _, err = op.client.OperatorsV1alpha2().OperatorGroups(tc.inputGroup.Namespace).Create(&tc.inputGroup) + require.NoError(t, err) err = op.syncOperatorGroups(&tc.inputGroup) require.NoError(t, err) From 857676bdba58f7d70d45e4d40f91c82066554995 Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Mon, 22 Oct 2018 16:50:04 -0400 Subject: [PATCH 19/33] fix(unit): fix race condition The operator group sync loop and namespace sync loops both were operating on the same namespaces at the same time. Since AnnotateNamespace is operating only one a single namespace at a time a DeepCopy solved the race issue. --- pkg/controller/annotator/annotator.go | 4 ++- pkg/controller/operators/olm/operator.go | 40 ------------------------ 2 files changed, 3 insertions(+), 41 deletions(-) diff --git a/pkg/controller/annotator/annotator.go b/pkg/controller/annotator/annotator.go index 2100e084a2..538c24323a 100644 --- a/pkg/controller/annotator/annotator.go +++ b/pkg/controller/annotator/annotator.go @@ -85,7 +85,9 @@ func (a *Annotator) getNamespaces(namespaceNames []string) (namespaces []corev1. return namespaces, nil } -func (a *Annotator) AnnotateNamespace(namespace *corev1.Namespace) error { +func (a *Annotator) AnnotateNamespace(origNamespace *corev1.Namespace) error { + namespace := origNamespace.DeepCopy() + originalName := namespace.GetName() originalData, err := json.Marshal(namespace) if err != nil { diff --git a/pkg/controller/operators/olm/operator.go b/pkg/controller/operators/olm/operator.go index f995bd1b51..3367271e77 100644 --- a/pkg/controller/operators/olm/operator.go +++ b/pkg/controller/operators/olm/operator.go @@ -6,7 +6,6 @@ import ( "time" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" - "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha2" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/informers/externalversions" operatorgrouplister "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha2" @@ -24,7 +23,6 @@ import ( corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/informers" cappsv1 "k8s.io/client-go/listers/apps/v1" cv1 "k8s.io/client-go/listers/core/v1" @@ -681,44 +679,6 @@ func (a *Operator) syncNamespace(obj interface{}) (syncError error) { return err } - opGroupList, err := a.operatorGroupLister[namespaceName].List(labels.Everything()) - if err != nil { - return err - } - - // TODO: do we need this? - opGroupUpdate := map[*v1alpha2.OperatorGroup]struct{}{} - for _, op := range opGroupList { - selector, err := metav1.LabelSelectorAsSelector(&op.Spec.Selector) - if err != nil { - return err - } - - if selector.Matches(labels.Set(namespace.GetLabels())) { - namespaceMatch := false - for _, seenNamespace := range op.Status.Namespaces { - if seenNamespace.Name == namespaceName { - namespaceMatch = true - break - } - } - if namespaceMatch == false { - opGroupUpdate[op] = struct{}{} - } - } else { - for _, seenNamespace := range op.Status.Namespaces { - if seenNamespace.Name == namespaceName { - opGroupUpdate[op] = struct{}{} - } - } - } - } - - for op := range opGroupUpdate { - if err, _ := a.updateNamespaceList(op); err != nil { - return err - } - } return nil } From 5298ddadbe192cf05038fbf81100430b427bca2d Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Mon, 22 Oct 2018 16:59:34 -0400 Subject: [PATCH 20/33] fix(e2e): update e2e for refactoring also fix minor crash issue due to the watched namespaces not being correct. use a unique label for namespaces so that previous test runs can't possibly conflict. --- test/e2e/operator_groups_e2e_test.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/test/e2e/operator_groups_e2e_test.go b/test/e2e/operator_groups_e2e_test.go index 5f488b1043..769eb6e023 100644 --- a/test/e2e/operator_groups_e2e_test.go +++ b/test/e2e/operator_groups_e2e_test.go @@ -73,7 +73,7 @@ func TestCreateOperatorGroupWithMatchingNamespace(t *testing.T) { c := newKubeClient(t) crc := newCRClient(t) - matchingLabel := map[string]string{"app": "matchLabel"} + matchingLabel := map[string]string{"matchLabel": testNamespace} otherNamespaceName := testNamespace + "-namespace-two" otherNamespace := corev1.Namespace{ @@ -130,7 +130,7 @@ func TestCreateOperatorGroupWithMatchingNamespace(t *testing.T) { createdOperatorGroup, err := crc.OperatorsV1alpha2().OperatorGroups(testNamespace).Create(&operatorGroup) require.NoError(t, err) expectedOperatorGroupStatus := v1alpha2.OperatorGroupStatus{ - Namespaces: []corev1.Namespace{*createdOtherNamespace}, + Namespaces: []*corev1.Namespace{createdOtherNamespace}, } err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { @@ -186,7 +186,7 @@ func TestCreateOperatorGroupCSVCopy(t *testing.T) { crc := newCRClient(t) operatorNamespaceName := testNamespace + "-operator" csvName := "acsv-that-is-unique" // must be lowercase for DNS-1123 validation - matchingLabel := map[string]string{"app": "matchLabel"} + matchingLabel := map[string]string{"matchLabel": testNamespace} operatorNamespace := corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ @@ -197,6 +197,8 @@ func TestCreateOperatorGroupCSVCopy(t *testing.T) { _, err := c.KubernetesInterface().CoreV1().Namespaces().Create(&operatorNamespace) require.NoError(t, err) + oldCommand := patchOlmDeployment(t, c, operatorNamespaceName) + operatorGroup := v1alpha2.OperatorGroup{ ObjectMeta: metav1.ObjectMeta{ Name: "e2e-operator-group", @@ -230,6 +232,15 @@ func TestCreateOperatorGroupCSVCopy(t *testing.T) { require.Equal(t, createdCSV.Spec, csvCopy.Spec) // clean up + runningDeploy, err := c.GetDeployment(testNamespace, "olm-operator") + require.NoError(t, err) + runningDeploy.Spec.Template.Spec.Containers[0].Command = oldCommand + _, updated, err := c.UpdateDeployment(runningDeploy) + if err != nil || updated == false { + t.Fatalf("Deployment update failed: (updated %v) %v\n", updated, err) + } + require.NoError(t, err) + err = c.KubernetesInterface().CoreV1().Namespaces().Delete(operatorNamespaceName, &metav1.DeleteOptions{}) require.NoError(t, err) err = crc.OperatorsV1alpha2().OperatorGroups(testNamespace).Delete(operatorGroup.Name, &metav1.DeleteOptions{}) From 59fab56afc446074b04d48188553ecb45b56904c Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Thu, 25 Oct 2018 14:58:02 -0400 Subject: [PATCH 21/33] fix(olm): handle copied CSVs when deletion occurs --- pkg/controller/operators/olm/operator.go | 49 +++++++++++- pkg/controller/operators/olm/operatorgroup.go | 17 +++- pkg/lib/queueinformer/queueinformer.go | 80 ++++++++++++------- test/e2e/operator_groups_e2e_test.go | 32 ++++++-- 4 files changed, 138 insertions(+), 40 deletions(-) diff --git a/pkg/controller/operators/olm/operator.go b/pkg/controller/operators/olm/operator.go index 3367271e77..129cb1ebbd 100644 --- a/pkg/controller/operators/olm/operator.go +++ b/pkg/controller/operators/olm/operator.go @@ -3,6 +3,7 @@ package olm import ( "errors" "fmt" + "strings" "time" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" @@ -185,11 +186,14 @@ func NewOperator(crClient versioned.Interface, opClient operatorclient.ClientInt // csvInformers for each namespace all use the same backing queue // queue keys are namespaced csvQueue := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "clusterserviceversions") + csvHandlers := cache.ResourceEventHandlerFuncs{ + DeleteFunc: op.deleteClusterServiceVersion, + } queueInformers := queueinformer.New( csvQueue, csvInformers, op.syncClusterServiceVersion, - nil, + &csvHandlers, "csv", metrics.NewMetricsCSV(op.client), ) @@ -357,6 +361,30 @@ func (a *Operator) syncServices(obj interface{}) (syncError error) { return nil } +func (a *Operator) deleteClusterServiceVersion(obj interface{}) { + clusterServiceVersion, ok := obj.(*v1alpha1.ClusterServiceVersion) + if !ok { + log.Debugf("wrong type: %#v", obj) + return + } + + targetNamespaces, ok := clusterServiceVersion.Annotations["olm.targetNamespaces"] + if ok { + operatorNamespace, ok := clusterServiceVersion.Annotations["olm.operatorNamespace"] + if !ok { + log.Debugf("missing operator namespace annotation on CSV %v", clusterServiceVersion) + } + for _, namespace := range strings.Split(targetNamespaces, ",") { + if namespace != operatorNamespace { + a.client.OperatorsV1alpha1().ClusterServiceVersions(namespace).Delete(clusterServiceVersion.GetName(), &metav1.DeleteOptions{}) + } + } + } else { + log.Debugf("Ignoring CSV '%v' with no annotation", clusterServiceVersion) + } + +} + // syncClusterServiceVersion is the method that gets called when we see a CSV event in the cluster func (a *Operator) syncClusterServiceVersion(obj interface{}) (syncError error) { clusterServiceVersion, ok := obj.(*v1alpha1.ClusterServiceVersion) @@ -373,6 +401,25 @@ func (a *Operator) syncClusterServiceVersion(obj interface{}) (syncError error) outCSV, syncError := a.transitionCSVState(*clusterServiceVersion) + opNamespace, ok := clusterServiceVersion.Annotations["olm.operatorNamespace"] + if ok { + // ensure "parent" CSV has not been deleted + csvs := a.csvsInNamespace(opNamespace) + csv, found := csvs[clusterServiceVersion.Name] + if !found { + targetNamespaces, ok := clusterServiceVersion.Annotations["olm.targetNamespaces"] + if !ok { + return fmt.Errorf("Did not find targetNamespaces annotation on %v", csv) + } + for _, ns := range strings.Split(targetNamespaces, ",") { + if err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns).Delete(csv.Name, &metav1.DeleteOptions{}); err != nil { + return err + } + } + return nil + } + } + // no changes in status, don't update if outCSV.Status.Phase == clusterServiceVersion.Status.Phase && outCSV.Status.Reason == clusterServiceVersion.Status.Reason && outCSV.Status.Message == clusterServiceVersion.Status.Message { return diff --git a/pkg/controller/operators/olm/operatorgroup.go b/pkg/controller/operators/olm/operatorgroup.go index 14caa34b9a..5812d10ba6 100644 --- a/pkg/controller/operators/olm/operatorgroup.go +++ b/pkg/controller/operators/olm/operatorgroup.go @@ -58,7 +58,6 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error { }, } - //TODO: listen to delete events on CSVS, delete all "target CSVs" metav1.SetMetaDataAnnotation(&newCSV.ObjectMeta, "olm.targetNamespaces", strings.Join(nsList, ",")) metav1.SetMetaDataAnnotation(&newCSV.ObjectMeta, "olm.operatorNamespace", op.GetNamespace()) metav1.SetMetaDataAnnotation(&newCSV.ObjectMeta, "olm.operatorGroup", op.GetName()) @@ -66,8 +65,20 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error { for _, ns := range targetedNamespaces { newCSV.SetNamespace(ns.Name) if ns.Name != op.Namespace { - _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(newCSV.GetNamespace()).Create(&newCSV) - if err != nil { + log.Debugf("Copying CSV %v to namespace %v", csv.GetName(), ns.GetName()) + newCSVsynced, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.GetName()).Create(&newCSV) + if k8serrors.IsAlreadyExists(err) { + if _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.GetName()).Update(newCSVsynced); err != nil { + return err + } + } else if err != nil { + return err + } + } else { + metav1.SetMetaDataAnnotation(&csv.ObjectMeta, "olm.targetNamespaces", strings.Join(nsList, ",")) + metav1.SetMetaDataAnnotation(&csv.ObjectMeta, "olm.operatorNamespace", op.GetNamespace()) + metav1.SetMetaDataAnnotation(&csv.ObjectMeta, "olm.operatorGroup", op.GetName()) + if _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.GetName()).Update(csv); err != nil { return err } } diff --git a/pkg/lib/queueinformer/queueinformer.go b/pkg/lib/queueinformer/queueinformer.go index 6b17be906a..906b09f60f 100644 --- a/pkg/lib/queueinformer/queueinformer.go +++ b/pkg/lib/queueinformer/queueinformer.go @@ -51,37 +51,43 @@ func (q *QueueInformer) keyFunc(obj interface{}) (string, bool) { return k, true } +func (q *QueueInformer) defaultAddFunc(obj interface{}) { + key, ok := q.keyFunc(obj) + if !ok { + return + } + + log.Infof("%s added", key) + q.enqueue(key) +} + +func (q *QueueInformer) defaultDeleteFunc(obj interface{}) { + key, ok := q.keyFunc(obj) + if !ok { + return + } + + log.Infof("%s deleted", key) + q.queue.Forget(key) +} + +func (q *QueueInformer) defaultUpdateFunc(oldObj, newObj interface{}) { + key, ok := q.keyFunc(newObj) + if !ok { + return + } + + log.Infof("%s updated", key) + q.enqueue(key) +} + // defaultResourceEventhandlerFuncs provides the default implementation for responding to events // these simply log the event and add the object's key to the queue for later processing func (q *QueueInformer) defaultResourceEventHandlerFuncs() *cache.ResourceEventHandlerFuncs { return &cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { - key, ok := q.keyFunc(obj) - if !ok { - return - } - - log.Infof("%s added", key) - q.enqueue(key) - }, - DeleteFunc: func(obj interface{}) { - key, ok := q.keyFunc(obj) - if !ok { - return - } - - log.Infof("%s deleted", key) - q.queue.Forget(key) - }, - UpdateFunc: func(oldObj, newObj interface{}) { - key, ok := q.keyFunc(newObj) - if !ok { - return - } - - log.Infof("%s updated", key) - q.enqueue(key) - }, + AddFunc: q.defaultAddFunc, + DeleteFunc: q.defaultDeleteFunc, + UpdateFunc: q.defaultUpdateFunc, } } @@ -108,7 +114,25 @@ func NewInformer(queue workqueue.RateLimitingInterface, informer cache.SharedInd if funcs == nil { queueInformer.resourceEventHandlerFuncs = queueInformer.defaultResourceEventHandlerFuncs() } else { - queueInformer.resourceEventHandlerFuncs = funcs + queueInformer.resourceEventHandlerFuncs = &cache.ResourceEventHandlerFuncs{} + if funcs.AddFunc != nil { + queueInformer.resourceEventHandlerFuncs.AddFunc = func(obj interface{}) { + funcs.AddFunc(obj) + queueInformer.defaultAddFunc(obj) + } + } + if funcs.DeleteFunc != nil { + queueInformer.resourceEventHandlerFuncs.DeleteFunc = func(obj interface{}) { + funcs.DeleteFunc(obj) + queueInformer.defaultDeleteFunc(obj) + } + } + if funcs.UpdateFunc != nil { + queueInformer.resourceEventHandlerFuncs.UpdateFunc = func(oldObj, newObj interface{}) { + funcs.UpdateFunc(oldObj, newObj) + queueInformer.defaultUpdateFunc(oldObj, newObj) + } + } } queueInformer.informer.AddEventHandler(queueInformer.resourceEventHandlerFuncs) return queueInformer diff --git a/test/e2e/operator_groups_e2e_test.go b/test/e2e/operator_groups_e2e_test.go index 769eb6e023..43613ea3cb 100644 --- a/test/e2e/operator_groups_e2e_test.go +++ b/test/e2e/operator_groups_e2e_test.go @@ -176,28 +176,28 @@ func TestCreateOperatorGroupWithMatchingNamespace(t *testing.T) { } func TestCreateOperatorGroupCSVCopy(t *testing.T) { - // create operator namespace - // create operator group in OLM namespace + // create target namespace + // create operator group in OLM namespace, which serves as operator namespace // create CSV in OLM namespace - // verify CSV is copied to operator namespace + // verify CSV is copied to target namespace log.SetLevel(log.DebugLevel) c := newKubeClient(t) crc := newCRClient(t) - operatorNamespaceName := testNamespace + "-operator" + targetNamespaceName := testNamespace + "-target" csvName := "acsv-that-is-unique" // must be lowercase for DNS-1123 validation matchingLabel := map[string]string{"matchLabel": testNamespace} operatorNamespace := corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ - Name: operatorNamespaceName, + Name: targetNamespaceName, Labels: matchingLabel, }, } _, err := c.KubernetesInterface().CoreV1().Namespaces().Create(&operatorNamespace) require.NoError(t, err) - oldCommand := patchOlmDeployment(t, c, operatorNamespaceName) + oldCommand := patchOlmDeployment(t, c, targetNamespaceName) operatorGroup := v1alpha2.OperatorGroup{ ObjectMeta: metav1.ObjectMeta{ @@ -219,7 +219,7 @@ func TestCreateOperatorGroupCSVCopy(t *testing.T) { var csvCopy *v1alpha1.ClusterServiceVersion err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { - csvCopy, err = crc.OperatorsV1alpha1().ClusterServiceVersions(operatorNamespaceName).Get(csvName, metav1.GetOptions{}) + csvCopy, err = crc.OperatorsV1alpha1().ClusterServiceVersions(targetNamespaceName).Get(csvName, metav1.GetOptions{}) if err != nil { if errors.IsNotFound(err) { return false, nil @@ -231,6 +231,22 @@ func TestCreateOperatorGroupCSVCopy(t *testing.T) { require.Equal(t, createdCSV.Name, csvCopy.Name) require.Equal(t, createdCSV.Spec, csvCopy.Spec) + // part 2 - ensure deletion cleans up copied CSV + err = crc.OperatorsV1alpha1().ClusterServiceVersions(testNamespace).Delete(csvName, &metav1.DeleteOptions{}) + require.NoError(t, err) + + err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { + csvCopy, err = crc.OperatorsV1alpha1().ClusterServiceVersions(targetNamespaceName).Get(csvName, metav1.GetOptions{}) + if err != nil { + if errors.IsNotFound(err) { + return true, nil + } + return false, err + } + return false, nil + }) + require.NoError(t, err) + // clean up runningDeploy, err := c.GetDeployment(testNamespace, "olm-operator") require.NoError(t, err) @@ -241,7 +257,7 @@ func TestCreateOperatorGroupCSVCopy(t *testing.T) { } require.NoError(t, err) - err = c.KubernetesInterface().CoreV1().Namespaces().Delete(operatorNamespaceName, &metav1.DeleteOptions{}) + err = c.KubernetesInterface().CoreV1().Namespaces().Delete(targetNamespaceName, &metav1.DeleteOptions{}) require.NoError(t, err) err = crc.OperatorsV1alpha2().OperatorGroups(testNamespace).Delete(operatorGroup.Name, &metav1.DeleteOptions{}) require.NoError(t, err) From 6a00fb95ff6f27a1843230ae09d6f5d3131a2955 Mon Sep 17 00:00:00 2001 From: Evan Cordell Date: Thu, 25 Oct 2018 16:27:56 -0400 Subject: [PATCH 22/33] feat(olm): detect dangling child csvs in olm operator in case deleting top-down fails, we collect dangling children and kill them as they're found in normal CSV processing --- pkg/controller/operators/olm/operator.go | 70 ++++++++++++++++++++---- test/e2e/operator_groups_e2e_test.go | 14 +++-- 2 files changed, 66 insertions(+), 18 deletions(-) diff --git a/pkg/controller/operators/olm/operator.go b/pkg/controller/operators/olm/operator.go index 129cb1ebbd..75e9732140 100644 --- a/pkg/controller/operators/olm/operator.go +++ b/pkg/controller/operators/olm/operator.go @@ -9,6 +9,7 @@ import ( "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/informers/externalversions" + csvlister "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1" operatorgrouplister "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha2" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/annotator" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/certs" @@ -23,6 +24,7 @@ import ( "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/informers" cappsv1 "k8s.io/client-go/listers/apps/v1" @@ -54,6 +56,7 @@ type Operator struct { roleBindingLister crbacv1.RoleBindingLister clusterRoleLister crbacv1.ClusterRoleLister clusterRoleBindingLister crbacv1.ClusterRoleBindingLister + csvLister map[string]csvlister.ClusterServiceVersionLister operatorGroupLister map[string]operatorgrouplister.OperatorGroupLister deploymentLister map[string]cappsv1.DeploymentLister namespaceLister cv1.NamespaceLister @@ -176,12 +179,15 @@ func NewOperator(crClient versioned.Interface, opClient operatorclient.ClientInt // Set up watch on CSVs csvInformers := []cache.SharedIndexInformer{} + csvLister := map[string]csvlister.ClusterServiceVersionLister{} for _, namespace := range namespaces { log.Debugf("watching for CSVs in namespace %s", namespace) sharedInformerFactory := externalversions.NewSharedInformerFactoryWithOptions(crClient, wakeupInterval, externalversions.WithNamespace(namespace)) - informer := sharedInformerFactory.Operators().V1alpha1().ClusterServiceVersions().Informer() - csvInformers = append(csvInformers, informer) + informer := sharedInformerFactory.Operators().V1alpha1().ClusterServiceVersions() + csvInformers = append(csvInformers, informer.Informer()) + csvLister[namespace] = informer.Lister() } + op.csvLister = csvLister // csvInformers for each namespace all use the same backing queue // queue keys are namespaced @@ -368,21 +374,55 @@ func (a *Operator) deleteClusterServiceVersion(obj interface{}) { return } + logger := log.WithFields(log.Fields{ + "csv": clusterServiceVersion.GetName(), + "namespace": clusterServiceVersion.GetNamespace(), + "phase": clusterServiceVersion.Status.Phase, + }) + targetNamespaces, ok := clusterServiceVersion.Annotations["olm.targetNamespaces"] - if ok { - operatorNamespace, ok := clusterServiceVersion.Annotations["olm.operatorNamespace"] - if !ok { - log.Debugf("missing operator namespace annotation on CSV %v", clusterServiceVersion) - } - for _, namespace := range strings.Split(targetNamespaces, ",") { - if namespace != operatorNamespace { - a.client.OperatorsV1alpha1().ClusterServiceVersions(namespace).Delete(clusterServiceVersion.GetName(), &metav1.DeleteOptions{}) + if !ok { + logger.Debugf("Ignoring CSV with no annotation") + } + + operatorNamespace, ok := clusterServiceVersion.Annotations["olm.operatorNamespace"] + if !ok { + logger.Debugf("missing operator namespace annotation on CSV") + } + + if clusterServiceVersion.Status.Reason == v1alpha1.CSVReasonCopied { + return + } + logger.Info("parent CSV deleted, GC children") + for _, namespace := range strings.Split(targetNamespaces, ",") { + if namespace != operatorNamespace { + if err := a.client.OperatorsV1alpha1().ClusterServiceVersions(namespace).Delete(clusterServiceVersion.GetName(), &metav1.DeleteOptions{}); err != nil { + logger.WithError(err).Debug("error deleting child CSV") } } - } else { - log.Debugf("Ignoring CSV '%v' with no annotation", clusterServiceVersion) + } +} + +func (a *Operator) removeDanglingChildCSVs(csv *v1alpha1.ClusterServiceVersion) error { + logger := log.WithFields(log.Fields{ + "csv": csv.GetName(), + "namespace": csv.GetNamespace(), + "phase": csv.Status.Phase, + }) + + operatorNamespace, ok := csv.Annotations["olm.operatorNamespace"] + if !ok { + logger.Debugf("missing operator namespace annotation on copied CSV") + return fmt.Errorf("missing operator namespace annotation on copied CSV") } + _, err := a.csvLister[operatorNamespace].ClusterServiceVersions(operatorNamespace).Get(csv.GetName()) + if k8serrors.IsNotFound(err) || k8serrors.IsGone(err) { + if err := a.client.OperatorsV1alpha1().ClusterServiceVersions(csv.GetNamespace()).Delete(csv.GetName(), &metav1.DeleteOptions{}); err != nil { + return err + } + } + return nil } // syncClusterServiceVersion is the method that gets called when we see a CSV event in the cluster @@ -397,6 +437,12 @@ func (a *Operator) syncClusterServiceVersion(obj interface{}) (syncError error) "namespace": clusterServiceVersion.GetNamespace(), "phase": clusterServiceVersion.Status.Phase, }) + + if clusterServiceVersion.Status.Reason == v1alpha1.CSVReasonCopied { + logger.Info("skip sync of dummy CSV") + return a.removeDanglingChildCSVs(clusterServiceVersion) + } + logger.Info("syncing") outCSV, syncError := a.transitionCSVState(*clusterServiceVersion) diff --git a/test/e2e/operator_groups_e2e_test.go b/test/e2e/operator_groups_e2e_test.go index 43613ea3cb..834fca6636 100644 --- a/test/e2e/operator_groups_e2e_test.go +++ b/test/e2e/operator_groups_e2e_test.go @@ -1,6 +1,7 @@ package e2e import ( + "fmt" "regexp" "strings" "testing" @@ -127,19 +128,20 @@ func TestCreateOperatorGroupWithMatchingNamespace(t *testing.T) { }, }, } - createdOperatorGroup, err := crc.OperatorsV1alpha2().OperatorGroups(testNamespace).Create(&operatorGroup) + _, err = crc.OperatorsV1alpha2().OperatorGroups(testNamespace).Create(&operatorGroup) require.NoError(t, err) expectedOperatorGroupStatus := v1alpha2.OperatorGroupStatus{ Namespaces: []*corev1.Namespace{createdOtherNamespace}, } err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { - createdOperatorGroup, err = crc.OperatorsV1alpha2().OperatorGroups(testNamespace).Get(operatorGroup.Name, metav1.GetOptions{}) - if err != nil { - return false, err + fetched, fetchErr := crc.OperatorsV1alpha2().OperatorGroups(testNamespace).Get(operatorGroup.Name, metav1.GetOptions{}) + if fetchErr != nil { + fmt.Println(fetchErr) + return false, fetchErr } - if len(createdOperatorGroup.Status.Namespaces) > 0 { - require.Equal(t, expectedOperatorGroupStatus.Namespaces[0].Name, createdOperatorGroup.Status.Namespaces[0].Name) + if len(fetched.Status.Namespaces) > 0 { + require.Equal(t, expectedOperatorGroupStatus.Namespaces[0].Name, fetched.Status.Namespaces[0].Name) return true, nil } return false, nil From 6486f52adf39426316da1c065b692aa5bc23334e Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Fri, 26 Oct 2018 15:24:16 -0400 Subject: [PATCH 23/33] fix(olm): adds debug logging for operator groups also fixes e2e to not create deployment manually, but rather have it automatically from the created CSV --- pkg/controller/operators/olm/operator.go | 5 +- pkg/controller/operators/olm/operatorgroup.go | 83 +++++++++++------- test/e2e/operator_groups_e2e_test.go | 84 ++++++++++++------- 3 files changed, 107 insertions(+), 65 deletions(-) diff --git a/pkg/controller/operators/olm/operator.go b/pkg/controller/operators/olm/operator.go index 75e9732140..33b52e360b 100644 --- a/pkg/controller/operators/olm/operator.go +++ b/pkg/controller/operators/olm/operator.go @@ -412,12 +412,13 @@ func (a *Operator) removeDanglingChildCSVs(csv *v1alpha1.ClusterServiceVersion) operatorNamespace, ok := csv.Annotations["olm.operatorNamespace"] if !ok { - logger.Debugf("missing operator namespace annotation on copied CSV") + logger.Debug("missing operator namespace annotation on copied CSV") return fmt.Errorf("missing operator namespace annotation on copied CSV") } _, err := a.csvLister[operatorNamespace].ClusterServiceVersions(operatorNamespace).Get(csv.GetName()) if k8serrors.IsNotFound(err) || k8serrors.IsGone(err) { + logger.Debug("deleting CSV since parent is missing") if err := a.client.OperatorsV1alpha1().ClusterServiceVersions(csv.GetNamespace()).Delete(csv.GetName(), &metav1.DeleteOptions{}); err != nil { return err } @@ -443,7 +444,7 @@ func (a *Operator) syncClusterServiceVersion(obj interface{}) (syncError error) return a.removeDanglingChildCSVs(clusterServiceVersion) } - logger.Info("syncing") + logger.Info("syncing CSV") outCSV, syncError := a.transitionCSVState(*clusterServiceVersion) diff --git a/pkg/controller/operators/olm/operatorgroup.go b/pkg/controller/operators/olm/operatorgroup.go index 5812d10ba6..356e9f38f1 100644 --- a/pkg/controller/operators/olm/operatorgroup.go +++ b/pkg/controller/operators/olm/operatorgroup.go @@ -25,12 +25,15 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error { err, targetedNamespaces := a.updateNamespaceList(op) log.Debugf("Got targetedNamespaces: '%v'", targetedNamespaces) if err != nil { + log.Errorf("updateNamespaceList error: %v", err) return err } if err := a.ensureClusterRoles(op); err != nil { + log.Errorf("ensureClusterRoles error: %v", err) return err } + log.Debug("Cluster roles completed") var nsList []string for ix := range targetedNamespaces { @@ -38,9 +41,11 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error { } nsListJoined := strings.Join(nsList, ",") - if err := a.annotateDeployments(nsList, nsListJoined); err != nil { + if err := a.annotateDeployments(op.GetNamespace(), nsListJoined); err != nil { + log.Errorf("annotateDeployments error: %v", err) return err } + log.Debug("Deployment annotation completed") // annotate csvs csvsInNamespace := a.csvsInNamespace(op.Namespace) @@ -58,38 +63,44 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error { }, } - metav1.SetMetaDataAnnotation(&newCSV.ObjectMeta, "olm.targetNamespaces", strings.Join(nsList, ",")) - metav1.SetMetaDataAnnotation(&newCSV.ObjectMeta, "olm.operatorNamespace", op.GetNamespace()) - metav1.SetMetaDataAnnotation(&newCSV.ObjectMeta, "olm.operatorGroup", op.GetName()) + a.addAnnotationsToCSV(&newCSV, op, nsListJoined) for _, ns := range targetedNamespaces { newCSV.SetNamespace(ns.Name) if ns.Name != op.Namespace { log.Debugf("Copying CSV %v to namespace %v", csv.GetName(), ns.GetName()) - newCSVsynced, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.GetName()).Create(&newCSV) + _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.GetName()).Create(&newCSV) if k8serrors.IsAlreadyExists(err) { - if _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.GetName()).Update(newCSVsynced); err != nil { + a.addAnnotationsToCSV(csv, op, nsListJoined) + if _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.GetName()).Update(csv); err != nil { + log.Errorf("Update CSV in target namespace failed: %v", err) return err } } else if err != nil { + log.Errorf("Create for new CSV failed: %v", err) return err } } else { - metav1.SetMetaDataAnnotation(&csv.ObjectMeta, "olm.targetNamespaces", strings.Join(nsList, ",")) - metav1.SetMetaDataAnnotation(&csv.ObjectMeta, "olm.operatorNamespace", op.GetNamespace()) - metav1.SetMetaDataAnnotation(&csv.ObjectMeta, "olm.operatorGroup", op.GetName()) + a.addAnnotationsToCSV(csv, op, nsListJoined) if _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.GetName()).Update(csv); err != nil { + log.Errorf("Update for existing CSV failed: %v", err) return err } } } } - + log.Debug("CSV annotation completed") //TODO: ensure RBAC on operator serviceaccount return nil } +func (a *Operator) addAnnotationsToCSV(csv *v1alpha1.ClusterServiceVersion, op *v1alpha2.OperatorGroup, targetNamespaces string) { + metav1.SetMetaDataAnnotation(&csv.ObjectMeta, "olm.targetNamespaces", targetNamespaces) + metav1.SetMetaDataAnnotation(&csv.ObjectMeta, "olm.operatorNamespace", op.GetNamespace()) + metav1.SetMetaDataAnnotation(&csv.ObjectMeta, "olm.operatorGroup", op.GetName()) +} + func namespacesChanged(clusterNamespaces []*corev1.Namespace, statusNamespaces []*corev1.Namespace) bool { if len(clusterNamespaces) != len(statusNamespaces) { return true @@ -167,9 +178,11 @@ func (a *Operator) ensureClusterRoles(op *v1alpha2.OperatorGroup) error { _, err := a.OpClient.KubernetesInterface().RbacV1().ClusterRoles().Create(clusterRole) if k8serrors.IsAlreadyExists(err) { if _, err = a.OpClient.UpdateClusterRole(clusterRole); err != nil { + log.Errorf("Update CRD existing cluster role failed: %v", err) return err } } else if err != nil { + log.Errorf("Update CRD cluster role failed: %v", err) return err } @@ -183,9 +196,11 @@ func (a *Operator) ensureClusterRoles(op *v1alpha2.OperatorGroup) error { _, err = a.OpClient.KubernetesInterface().RbacV1().ClusterRoles().Create(operatorGroupEditClusterRole) if k8serrors.IsAlreadyExists(err) { if _, err = a.OpClient.UpdateClusterRole(operatorGroupEditClusterRole); err != nil { + log.Errorf("Update existing edit cluster role failed: %v", err) return err } } else if err != nil { + log.Errorf("Update edit cluster role failed: %v", err) return err } operatorGroupViewClusterRole := &rbacv1.ClusterRole{ @@ -197,43 +212,47 @@ func (a *Operator) ensureClusterRoles(op *v1alpha2.OperatorGroup) error { _, err = a.OpClient.KubernetesInterface().RbacV1().ClusterRoles().Create(operatorGroupViewClusterRole) if k8serrors.IsAlreadyExists(err) { if _, err = a.OpClient.UpdateClusterRole(operatorGroupViewClusterRole); err != nil { + log.Errorf("Update existing view cluster role failed: %v", err) return err } } else if err != nil { + log.Errorf("Update view cluster role failed: %v", err) return err } } return nil } -func (a *Operator) annotateDeployments(targetNamespaces []string, targetNamespaceString string) error { - // write above namespaces to watch in every deployment - for _, ns := range targetNamespaces { - deploymentList, err := a.deploymentLister[ns].List(labels.Everything()) - if err != nil { - return err +func (a *Operator) annotateDeployments(operatorNamespace string, targetNamespaceString string) error { + // write above namespaces to watch in every deployment in operator namespace + deploymentList, err := a.deploymentLister[operatorNamespace].List(labels.Everything()) + if err != nil { + log.Errorf("deployment list failed: %v\n", err) + return err + } + + for _, deploy := range deploymentList { + // TODO: this will be incorrect if two operatorgroups own the same namespace + // also - will be incorrect if a CSV is manually installed into a namespace + if !ownerutil.IsOwnedByKind(deploy, "ClusterServiceVersion") { + log.Debugf("deployment '%v' not owned by CSV, skipping", deploy.GetName()) + continue } - for _, deploy := range deploymentList { - // TODO: this will be incorrect if two operatorgroups own the same namespace - // also - will be incorrect if a CSV is manually installed into a namespace - if !ownerutil.IsOwnedByKind(deploy, "ClusterServiceVersion") { + if lastAnnotation, ok := deploy.Spec.Template.Annotations["olm.targetNamespaces"]; ok { + if lastAnnotation == targetNamespaceString { + log.Debugf("deployment '%v' already has annotation, skipping", deploy) continue } + } - if lastAnnotation, ok := deploy.Spec.Template.Annotations["olm.targetNamespaces"]; ok { - if lastAnnotation == targetNamespaceString { - continue - } - } - - originalDeploy := deploy.DeepCopy() - metav1.SetMetaDataAnnotation(&deploy.Spec.Template.ObjectMeta, "olm.targetNamespaces", targetNamespaceString) - if _, _, err := a.OpClient.PatchDeployment(originalDeploy, deploy); err != nil { - return err - } - + originalDeploy := deploy.DeepCopy() + metav1.SetMetaDataAnnotation(&deploy.Spec.Template.ObjectMeta, "olm.targetNamespaces", targetNamespaceString) + if _, _, err := a.OpClient.PatchDeployment(originalDeploy, deploy); err != nil { + log.Errorf("patch deployment failed: %v\n", err) + return err } + } return nil diff --git a/test/e2e/operator_groups_e2e_test.go b/test/e2e/operator_groups_e2e_test.go index 834fca6636..d6e5bcb48a 100644 --- a/test/e2e/operator_groups_e2e_test.go +++ b/test/e2e/operator_groups_e2e_test.go @@ -65,7 +65,7 @@ func patchOlmDeployment(t *testing.T, c operatorclient.ClientInterface, newNames func TestCreateOperatorGroupWithMatchingNamespace(t *testing.T) { // Create namespace with specific label - // Create deployment in namespace + // Create CSV in namespace // Create operator group that watches namespace and uses specific label // Verify operator group status contains correct status // Verify deployments have correct namespace annotation @@ -73,6 +73,7 @@ func TestCreateOperatorGroupWithMatchingNamespace(t *testing.T) { log.SetLevel(log.DebugLevel) c := newKubeClient(t) crc := newCRClient(t) + csvName := "another-csv" matchingLabel := map[string]string{"matchLabel": testNamespace} otherNamespaceName := testNamespace + "-namespace-two" @@ -88,35 +89,48 @@ func TestCreateOperatorGroupWithMatchingNamespace(t *testing.T) { oldCommand := patchOlmDeployment(t, c, otherNamespaceName) - var one = int32(1) - deployment := appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "deployment", - Namespace: otherNamespaceName, - }, - Spec: appsv1.DeploymentSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: matchingLabel, - }, - Replicas: &one, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: matchingLabel, - }, - Spec: corev1.PodSpec{Containers: []corev1.Container{ - { - Name: genName("nginx"), - Image: "nginx:1.7.9", - Ports: []corev1.ContainerPort{{ContainerPort: 80}}, - }, - }}, - }, - }, - } - - createdDeployment, err := c.CreateDeployment(&deployment) + log.Debug("Creating CSV") + aCSV := newCSV(csvName, testNamespace, "", *semver.New("0.0.0"), nil, nil, newNginxInstallStrategy("operator-deployment", nil, nil)) + _, err = crc.OperatorsV1alpha1().ClusterServiceVersions(testNamespace).Create(&aCSV) require.NoError(t, err) + // var one = int32(1) + // deployment := appsv1.Deployment{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: "operator-deployment", + // Namespace: testNamespace, + // }, + // Spec: appsv1.DeploymentSpec{ + // Selector: &metav1.LabelSelector{ + // MatchLabels: matchingLabel, + // }, + // Replicas: &one, + // Template: corev1.PodTemplateSpec{ + // ObjectMeta: metav1.ObjectMeta{ + // Labels: matchingLabel, + // }, + // Spec: corev1.PodSpec{Containers: []corev1.Container{ + // { + // Name: genName("nginx"), + // Image: "nginx:1.7.9", + // Ports: []corev1.ContainerPort{{ContainerPort: 80}}, + // }, + // }}, + // }, + // }, + // } + // deployment.SetOwnerReferences([]metav1.OwnerReference{ + // { + // Kind: "ClusterServiceVersion", + // Name: "fake-csv", + // }, + // }) + + // log.Debug("Creating deployment") + // createdDeployment, err := c.CreateDeployment(&deployment) + // require.NoError(t, err) + + log.Debug("Creating operator group") operatorGroup := v1alpha2.OperatorGroup{ ObjectMeta: metav1.ObjectMeta{ Name: "e2e-operator-group", @@ -134,6 +148,7 @@ func TestCreateOperatorGroupWithMatchingNamespace(t *testing.T) { Namespaces: []*corev1.Namespace{createdOtherNamespace}, } + log.Debug("Waiting on operator group to have correct status") err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { fetched, fetchErr := crc.OperatorsV1alpha2().OperatorGroups(testNamespace).Get(operatorGroup.Name, metav1.GetOptions{}) if fetchErr != nil { @@ -147,8 +162,9 @@ func TestCreateOperatorGroupWithMatchingNamespace(t *testing.T) { return false, nil }) + log.Debug("Waiting on deployment to have correct annotation") err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { - createdDeployment, err = c.GetDeployment(otherNamespaceName, "deployment") + createdDeployment, err := c.GetDeployment(testNamespace, "operator-deployment") if err != nil { if errors.IsNotFound(err) { return false, nil @@ -190,17 +206,19 @@ func TestCreateOperatorGroupCSVCopy(t *testing.T) { csvName := "acsv-that-is-unique" // must be lowercase for DNS-1123 validation matchingLabel := map[string]string{"matchLabel": testNamespace} - operatorNamespace := corev1.Namespace{ + log.Debug("Creating operator namespace") + targetNamespace := corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: targetNamespaceName, Labels: matchingLabel, }, } - _, err := c.KubernetesInterface().CoreV1().Namespaces().Create(&operatorNamespace) + _, err := c.KubernetesInterface().CoreV1().Namespaces().Create(&targetNamespace) require.NoError(t, err) oldCommand := patchOlmDeployment(t, c, targetNamespaceName) + log.Debug("Creating operator group") operatorGroup := v1alpha2.OperatorGroup{ ObjectMeta: metav1.ObjectMeta{ Name: "e2e-operator-group", @@ -215,10 +233,12 @@ func TestCreateOperatorGroupCSVCopy(t *testing.T) { _, err = crc.OperatorsV1alpha2().OperatorGroups(testNamespace).Create(&operatorGroup) require.NoError(t, err) + log.Debug("Creating CSV") aCSV := newCSV(csvName, testNamespace, "", *semver.New("0.0.0"), nil, nil, newNginxInstallStrategy("aspec", nil, nil)) createdCSV, err := crc.OperatorsV1alpha1().ClusterServiceVersions(testNamespace).Create(&aCSV) require.NoError(t, err) + log.Debug("Waiting for CSV copy to have correct properties") var csvCopy *v1alpha1.ClusterServiceVersion err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { csvCopy, err = crc.OperatorsV1alpha1().ClusterServiceVersions(targetNamespaceName).Get(csvName, metav1.GetOptions{}) @@ -234,9 +254,11 @@ func TestCreateOperatorGroupCSVCopy(t *testing.T) { require.Equal(t, createdCSV.Spec, csvCopy.Spec) // part 2 - ensure deletion cleans up copied CSV + log.Debug("Deleting CSV") err = crc.OperatorsV1alpha1().ClusterServiceVersions(testNamespace).Delete(csvName, &metav1.DeleteOptions{}) require.NoError(t, err) + log.Debug("Waiting for orphaned CSV to be deleted") err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { csvCopy, err = crc.OperatorsV1alpha1().ClusterServiceVersions(targetNamespaceName).Get(csvName, metav1.GetOptions{}) if err != nil { From 2af56a72732f6937267830264914e3eb86e4b75b Mon Sep 17 00:00:00 2001 From: Evan Cordell Date: Fri, 26 Oct 2018 16:33:53 -0400 Subject: [PATCH 24/33] WIP: cvs in operator namespace are being annotated, csvs in target namespaces should be annotated --- pkg/controller/operators/olm/operatorgroup.go | 118 +++++++++++++----- test/e2e/operator_groups_e2e_test.go | 54 ++++++-- 2 files changed, 131 insertions(+), 41 deletions(-) diff --git a/pkg/controller/operators/olm/operatorgroup.go b/pkg/controller/operators/olm/operatorgroup.go index 356e9f38f1..fcce1903af 100644 --- a/pkg/controller/operators/olm/operatorgroup.go +++ b/pkg/controller/operators/olm/operatorgroup.go @@ -41,16 +41,85 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error { } nsListJoined := strings.Join(nsList, ",") - if err := a.annotateDeployments(op.GetNamespace(), nsListJoined); err != nil { - log.Errorf("annotateDeployments error: %v", err) - return err - } - log.Debug("Deployment annotation completed") + //if err := a.annotateDeployments(op.GetNamespace(), nsListJoined); err != nil { + // log.Errorf("annotateDeployments error: %v", err) + // return err + //} + //log.Debug("Deployment annotation completed") // annotate csvs csvsInNamespace := a.csvsInNamespace(op.Namespace) for _, csv := range csvsInNamespace { - // create new CSV instead of DeepCopy as namespace and resource version (and status) will be different + a.addAnnotationsToCSV(csv, op, nsListJoined) + // TODO: generate a patch + if _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(csv.GetNamespace()).Update(csv); err != nil { + log.Errorf("Update for existing CSV failed: %v", err) + return err + } + + if err := a.copyCsvToTargetNamespace(csv, op, targetedNamespaces); err!=nil { + return err + } + } + + // // create new CSV instead of DeepCopy as namespace and resource version (and status) will be different + // newCSV := v1alpha1.ClusterServiceVersion{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: csv.Name, + // }, + // Spec: *csv.Spec.DeepCopy(), + // Status: v1alpha1.ClusterServiceVersionStatus{ + // Message: "CSV copied to target namespace", + // Reason: v1alpha1.CSVReasonCopied, + // LastUpdateTime: timeNow(), + // }, + // } + // + // a.addAnnotationsToCSV(&newCSV, op, nsListJoined) + // + // + // for _, ns := range targetedNamespaces { + // newCSV.SetNamespace(ns.Name) + // if ns.Name != op.Namespace { + // log.Debugf("Copying CSV %v to namespace %v", csv.GetName(), ns.GetName()) + // _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.GetName()).Create(&newCSV) + // if k8serrors.IsAlreadyExists(err) { + // a.addAnnotationsToCSV(csv, op, nsListJoined) + // if _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.GetName()).Update(csv); err != nil { + // log.Errorf("Update CSV in target namespace failed: %v", err) + // return err + // } + // } else if err != nil { + // log.Errorf("Create for new CSV failed: %v", err) + // return err + // } + // } else { + // a.addAnnotationsToCSV(csv, op, nsListJoined) + // if _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.GetName()).Update(csv); err != nil { + // log.Errorf("Update for existing CSV failed: %v", err) + // return err + // } + // } + // } + //} + log.Debug("CSV annotation completed") + //TODO: ensure RBAC on operator serviceaccount + + return nil +} + +func (a *Operator) copyCsvToTargetNamespace(csv *v1alpha1.ClusterServiceVersion, operatorGroup *v1alpha2.OperatorGroup, targetNamespaces []*corev1.Namespace) error { + var nsList []string + for _, ns := range targetNamespaces { + nsList = append(nsList, ns.Name) + } + nsListJoined := strings.Join(nsList, ",") + + + for _, ns := range targetNamespaces { + if ns.Name == operatorGroup.GetNamespace() { + continue + } newCSV := v1alpha1.ClusterServiceVersion{ ObjectMeta: metav1.ObjectMeta{ Name: csv.Name, @@ -62,36 +131,21 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error { LastUpdateTime: timeNow(), }, } + a.addAnnotationsToCSV(&newCSV, operatorGroup, nsListJoined) + newCSV.SetNamespace(ns.Name) - a.addAnnotationsToCSV(&newCSV, op, nsListJoined) - - for _, ns := range targetedNamespaces { - newCSV.SetNamespace(ns.Name) - if ns.Name != op.Namespace { - log.Debugf("Copying CSV %v to namespace %v", csv.GetName(), ns.GetName()) - _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.GetName()).Create(&newCSV) - if k8serrors.IsAlreadyExists(err) { - a.addAnnotationsToCSV(csv, op, nsListJoined) - if _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.GetName()).Update(csv); err != nil { - log.Errorf("Update CSV in target namespace failed: %v", err) - return err - } - } else if err != nil { - log.Errorf("Create for new CSV failed: %v", err) - return err - } - } else { - a.addAnnotationsToCSV(csv, op, nsListJoined) - if _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.GetName()).Update(csv); err != nil { - log.Errorf("Update for existing CSV failed: %v", err) - return err - } + log.Debugf("Copying CSV %v to namespace %v", csv.GetName(), ns.Name) + _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.Name).Create(&newCSV) + if k8serrors.IsAlreadyExists(err) { + if _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.Name).Update(&newCSV); err != nil { + log.Errorf("Update CSV in target namespace failed: %v", err) + return err } + } else if err != nil { + log.Errorf("Create for new CSV failed: %v", err) + return err } } - log.Debug("CSV annotation completed") - //TODO: ensure RBAC on operator serviceaccount - return nil } diff --git a/test/e2e/operator_groups_e2e_test.go b/test/e2e/operator_groups_e2e_test.go index d6e5bcb48a..32a572d99c 100644 --- a/test/e2e/operator_groups_e2e_test.go +++ b/test/e2e/operator_groups_e2e_test.go @@ -65,10 +65,12 @@ func patchOlmDeployment(t *testing.T, c operatorclient.ClientInterface, newNames func TestCreateOperatorGroupWithMatchingNamespace(t *testing.T) { // Create namespace with specific label - // Create CSV in namespace + // Create CSV in opoerator namespace // Create operator group that watches namespace and uses specific label // Verify operator group status contains correct status + // Verify csv in target namespace exists, has copied status, has annotations // Verify deployments have correct namespace annotation + // (Verify that the operator can operate in the target namespace) log.SetLevel(log.DebugLevel) c := newKubeClient(t) @@ -162,22 +164,56 @@ func TestCreateOperatorGroupWithMatchingNamespace(t *testing.T) { return false, nil }) - log.Debug("Waiting on deployment to have correct annotation") + log.Debug("Waiting for operator namespace csv to have annotations") err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { - createdDeployment, err := c.GetDeployment(testNamespace, "operator-deployment") - if err != nil { - if errors.IsNotFound(err) { - return false, nil - } - return false, err + fetchedCSV, fetchErr := crc.OperatorsV1alpha1().ClusterServiceVersions(testNamespace).Get(csvName, metav1.GetOptions{}) + if fetchErr != nil { + fmt.Println(fetchErr) + return false, fetchErr + } + //TODO: check other annotations + if fetchedCSV.Annotations["olm.targetNamespaces"] == otherNamespaceName { + return true, nil } - if createdDeployment.Spec.Template.Annotations["olm.targetNamespaces"] == otherNamespaceName { + return false, nil + }) + + log.Debug("Waiting for target namespace csv to have annotations") + err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { + fetchedCSV, fetchErr := crc.OperatorsV1alpha1().ClusterServiceVersions(otherNamespaceName).Get(csvName, metav1.GetOptions{}) + if fetchErr != nil { + fmt.Println(fetchErr) + return false, fetchErr + } + //TODO: check other annotations + if fetchedCSV.Annotations["olm.targetNamespaces"] == otherNamespaceName { return true, nil } return false, nil }) + + // TODO: verify CSV in target namespace has correct annotations and status + // TODO: verify CSV in operator namespace has correct annotations + + //log.Debug("Waiting on deployment to have correct annotation") + //err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { + // createdDeployment, err := c.GetDeployment(testNamespace, "operator-deployment") + // if err != nil { + // if errors.IsNotFound(err) { + // return false, nil + // } + // return false, err + // } + // // TODO: verify operatorNamespace annotation, operatorGroup annotation + // if createdDeployment.Spec.Template.Annotations["olm.targetNamespaces"] == otherNamespaceName { + // return true, nil + // } + // return false, nil + //}) + // clean up + // TODO: unpatch function runningDeploy, err := c.GetDeployment(testNamespace, "olm-operator") require.NoError(t, err) runningDeploy.Spec.Template.Spec.Containers[0].Command = oldCommand From 22a29b37dd51232d22f9f773b495192ccb9e10e3 Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Fri, 26 Oct 2018 18:50:22 -0400 Subject: [PATCH 25/33] fix(olm): refactor e2e and check annotations --- pkg/controller/operators/olm/operatorgroup.go | 40 +++++++++++-------- test/e2e/operator_groups_e2e_test.go | 32 ++++++++++----- 2 files changed, 46 insertions(+), 26 deletions(-) diff --git a/pkg/controller/operators/olm/operatorgroup.go b/pkg/controller/operators/olm/operatorgroup.go index fcce1903af..40dd4c0e3b 100644 --- a/pkg/controller/operators/olm/operatorgroup.go +++ b/pkg/controller/operators/olm/operatorgroup.go @@ -57,7 +57,7 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error { return err } - if err := a.copyCsvToTargetNamespace(csv, op, targetedNamespaces); err!=nil { + if err := a.copyCsvToTargetNamespace(csv, op, targetedNamespaces); err != nil { return err } } @@ -109,35 +109,41 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error { } func (a *Operator) copyCsvToTargetNamespace(csv *v1alpha1.ClusterServiceVersion, operatorGroup *v1alpha2.OperatorGroup, targetNamespaces []*corev1.Namespace) error { - var nsList []string - for _, ns := range targetNamespaces { - nsList = append(nsList, ns.Name) - } - nsListJoined := strings.Join(nsList, ",") - - for _, ns := range targetNamespaces { if ns.Name == operatorGroup.GetNamespace() { continue } + // create new CSV instead of DeepCopy as namespace and resource version (and status) will be different newCSV := v1alpha1.ClusterServiceVersion{ ObjectMeta: metav1.ObjectMeta{ - Name: csv.Name, + Name: csv.Name, + Annotations: csv.Annotations, }, Spec: *csv.Spec.DeepCopy(), - Status: v1alpha1.ClusterServiceVersionStatus{ + } + newCSV.SetNamespace(ns.Name) + + log.Debugf("Copying/updating CSV %v to/in namespace %v", csv.GetName(), ns.Name) + createdCSV, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.Name).Create(&newCSV) + if err == nil { + createdCSV.Status = v1alpha1.ClusterServiceVersionStatus{ Message: "CSV copied to target namespace", Reason: v1alpha1.CSVReasonCopied, LastUpdateTime: timeNow(), - }, + } + if _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.Name).UpdateStatus(createdCSV); err != nil { + log.Errorf("Status update for CSV failed: %v", err) + return err + } } - a.addAnnotationsToCSV(&newCSV, operatorGroup, nsListJoined) - newCSV.SetNamespace(ns.Name) - - log.Debugf("Copying CSV %v to namespace %v", csv.GetName(), ns.Name) - _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.Name).Create(&newCSV) if k8serrors.IsAlreadyExists(err) { - if _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.Name).Update(&newCSV); err != nil { + fetchedCSV, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.Name).Get(csv.GetName(), metav1.GetOptions{}) + if err != nil { + log.Errorf("Create failed, yet get failed: %v", err) + } + // update the potentially different annotations + fetchedCSV.Annotations = csv.Annotations + if _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.Name).Update(fetchedCSV); err != nil { log.Errorf("Update CSV in target namespace failed: %v", err) return err } diff --git a/test/e2e/operator_groups_e2e_test.go b/test/e2e/operator_groups_e2e_test.go index 32a572d99c..f61068ec82 100644 --- a/test/e2e/operator_groups_e2e_test.go +++ b/test/e2e/operator_groups_e2e_test.go @@ -63,9 +63,23 @@ func patchOlmDeployment(t *testing.T, c operatorclient.ClientInterface, newNames return command } +func checkOperatorGroupAnnotations(obj metav1.Object, op *v1alpha2.OperatorGroup, targetNamespaces string) error { + if annotation, ok := obj.GetAnnotations()["olm.targetNamespaces"]; !ok || annotation != targetNamespaces { + return fmt.Errorf("missing targetNamespaces annotation on %v", obj.GetName()) + } + if annotation, ok := obj.GetAnnotations()["olm.operatorNamespace"]; !ok || annotation != op.GetNamespace() { + return fmt.Errorf("missing operatorNamespace on %v", obj.GetName()) + } + if annotation, ok := obj.GetAnnotations()["olm.operatorGroup"]; !ok || annotation != op.GetName() { + return fmt.Errorf("missing operatorGroup annotation on %v", obj.GetName()) + } + + return nil +} + func TestCreateOperatorGroupWithMatchingNamespace(t *testing.T) { // Create namespace with specific label - // Create CSV in opoerator namespace + // Create CSV in operator namespace // Create operator group that watches namespace and uses specific label // Verify operator group status contains correct status // Verify csv in target namespace exists, has copied status, has annotations @@ -171,8 +185,7 @@ func TestCreateOperatorGroupWithMatchingNamespace(t *testing.T) { fmt.Println(fetchErr) return false, fetchErr } - //TODO: check other annotations - if fetchedCSV.Annotations["olm.targetNamespaces"] == otherNamespaceName { + if checkOperatorGroupAnnotations(fetchedCSV, &operatorGroup, otherNamespaceName) == nil { return true, nil } return false, nil @@ -185,16 +198,17 @@ func TestCreateOperatorGroupWithMatchingNamespace(t *testing.T) { fmt.Println(fetchErr) return false, fetchErr } - //TODO: check other annotations - if fetchedCSV.Annotations["olm.targetNamespaces"] == otherNamespaceName { + if checkOperatorGroupAnnotations(fetchedCSV, &operatorGroup, otherNamespaceName) == nil { return true, nil } + return false, nil }) - - - // TODO: verify CSV in target namespace has correct annotations and status - // TODO: verify CSV in operator namespace has correct annotations + // since annotations are set along with status, no reason to poll for this check as done above + log.Debug("Checking status on CSV in target namespace") + fetchedCSV, err := crc.OperatorsV1alpha1().ClusterServiceVersions(otherNamespaceName).Get(csvName, metav1.GetOptions{}) + require.NoError(t, err) + require.EqualValues(t, v1alpha1.CSVReasonCopied, fetchedCSV.Status.Reason) //log.Debug("Waiting on deployment to have correct annotation") //err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { From ae457b3ae96180fabb55776ba6bc69a5d8660596 Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Mon, 29 Oct 2018 17:28:39 -0400 Subject: [PATCH 26/33] fix(olm): set event handler functions correctly also gets rid of stale code --- pkg/controller/operators/olm/operatorgroup.go | 10 +- pkg/lib/queueinformer/queueinformer.go | 6 +- test/e2e/operator_groups_e2e_test.go | 145 +++--------------- 3 files changed, 25 insertions(+), 136 deletions(-) diff --git a/pkg/controller/operators/olm/operatorgroup.go b/pkg/controller/operators/olm/operatorgroup.go index 40dd4c0e3b..91acc920f1 100644 --- a/pkg/controller/operators/olm/operatorgroup.go +++ b/pkg/controller/operators/olm/operatorgroup.go @@ -41,11 +41,11 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error { } nsListJoined := strings.Join(nsList, ",") - //if err := a.annotateDeployments(op.GetNamespace(), nsListJoined); err != nil { - // log.Errorf("annotateDeployments error: %v", err) - // return err - //} - //log.Debug("Deployment annotation completed") + if err := a.annotateDeployments(op.GetNamespace(), nsListJoined); err != nil { + log.Errorf("annotateDeployments error: %v", err) + return err + } + log.Debug("Deployment annotation completed") // annotate csvs csvsInNamespace := a.csvsInNamespace(op.Namespace) diff --git a/pkg/lib/queueinformer/queueinformer.go b/pkg/lib/queueinformer/queueinformer.go index 906b09f60f..0b36928af6 100644 --- a/pkg/lib/queueinformer/queueinformer.go +++ b/pkg/lib/queueinformer/queueinformer.go @@ -111,10 +111,8 @@ func NewInformer(queue workqueue.RateLimitingInterface, informer cache.SharedInd name: name, MetricsProvider: metrics, } - if funcs == nil { - queueInformer.resourceEventHandlerFuncs = queueInformer.defaultResourceEventHandlerFuncs() - } else { - queueInformer.resourceEventHandlerFuncs = &cache.ResourceEventHandlerFuncs{} + queueInformer.resourceEventHandlerFuncs = queueInformer.defaultResourceEventHandlerFuncs() + if funcs != nil { if funcs.AddFunc != nil { queueInformer.resourceEventHandlerFuncs.AddFunc = func(obj interface{}) { funcs.AddFunc(obj) diff --git a/test/e2e/operator_groups_e2e_test.go b/test/e2e/operator_groups_e2e_test.go index f61068ec82..d9c135fa7a 100644 --- a/test/e2e/operator_groups_e2e_test.go +++ b/test/e2e/operator_groups_e2e_test.go @@ -77,7 +77,7 @@ func checkOperatorGroupAnnotations(obj metav1.Object, op *v1alpha2.OperatorGroup return nil } -func TestCreateOperatorGroupWithMatchingNamespace(t *testing.T) { +func TestOperatorGroup(t *testing.T) { // Create namespace with specific label // Create CSV in operator namespace // Create operator group that watches namespace and uses specific label @@ -89,7 +89,7 @@ func TestCreateOperatorGroupWithMatchingNamespace(t *testing.T) { log.SetLevel(log.DebugLevel) c := newKubeClient(t) crc := newCRClient(t) - csvName := "another-csv" + csvName := "another-csv" // must be lowercase for DNS-1123 validation matchingLabel := map[string]string{"matchLabel": testNamespace} otherNamespaceName := testNamespace + "-namespace-two" @@ -107,45 +107,9 @@ func TestCreateOperatorGroupWithMatchingNamespace(t *testing.T) { log.Debug("Creating CSV") aCSV := newCSV(csvName, testNamespace, "", *semver.New("0.0.0"), nil, nil, newNginxInstallStrategy("operator-deployment", nil, nil)) - _, err = crc.OperatorsV1alpha1().ClusterServiceVersions(testNamespace).Create(&aCSV) + createdCSV, err := crc.OperatorsV1alpha1().ClusterServiceVersions(testNamespace).Create(&aCSV) require.NoError(t, err) - // var one = int32(1) - // deployment := appsv1.Deployment{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: "operator-deployment", - // Namespace: testNamespace, - // }, - // Spec: appsv1.DeploymentSpec{ - // Selector: &metav1.LabelSelector{ - // MatchLabels: matchingLabel, - // }, - // Replicas: &one, - // Template: corev1.PodTemplateSpec{ - // ObjectMeta: metav1.ObjectMeta{ - // Labels: matchingLabel, - // }, - // Spec: corev1.PodSpec{Containers: []corev1.Container{ - // { - // Name: genName("nginx"), - // Image: "nginx:1.7.9", - // Ports: []corev1.ContainerPort{{ContainerPort: 80}}, - // }, - // }}, - // }, - // }, - // } - // deployment.SetOwnerReferences([]metav1.OwnerReference{ - // { - // Kind: "ClusterServiceVersion", - // Name: "fake-csv", - // }, - // }) - - // log.Debug("Creating deployment") - // createdDeployment, err := c.CreateDeployment(&deployment) - // require.NoError(t, err) - log.Debug("Creating operator group") operatorGroup := v1alpha2.OperatorGroup{ ObjectMeta: metav1.ObjectMeta{ @@ -205,112 +169,38 @@ func TestCreateOperatorGroupWithMatchingNamespace(t *testing.T) { return false, nil }) // since annotations are set along with status, no reason to poll for this check as done above - log.Debug("Checking status on CSV in target namespace") + log.Debug("Checking status on csv in target namespace") fetchedCSV, err := crc.OperatorsV1alpha1().ClusterServiceVersions(otherNamespaceName).Get(csvName, metav1.GetOptions{}) require.NoError(t, err) require.EqualValues(t, v1alpha1.CSVReasonCopied, fetchedCSV.Status.Reason) + // also check name and spec + require.Equal(t, createdCSV.Name, fetchedCSV.Name) + require.Equal(t, createdCSV.Spec, fetchedCSV.Spec) - //log.Debug("Waiting on deployment to have correct annotation") - //err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { - // createdDeployment, err := c.GetDeployment(testNamespace, "operator-deployment") - // if err != nil { - // if errors.IsNotFound(err) { - // return false, nil - // } - // return false, err - // } - // // TODO: verify operatorNamespace annotation, operatorGroup annotation - // if createdDeployment.Spec.Template.Annotations["olm.targetNamespaces"] == otherNamespaceName { - // return true, nil - // } - // return false, nil - //}) - - // clean up - // TODO: unpatch function - runningDeploy, err := c.GetDeployment(testNamespace, "olm-operator") - require.NoError(t, err) - runningDeploy.Spec.Template.Spec.Containers[0].Command = oldCommand - _, updated, err := c.UpdateDeployment(runningDeploy) - if err != nil || updated == false { - t.Fatalf("Deployment update failed: (updated %v) %v\n", updated, err) - } - require.NoError(t, err) - - err = c.KubernetesInterface().CoreV1().Namespaces().Delete(otherNamespaceName, &metav1.DeleteOptions{}) - require.NoError(t, err) - err = crc.OperatorsV1alpha2().OperatorGroups(testNamespace).Delete(operatorGroup.Name, &metav1.DeleteOptions{}) - require.NoError(t, err) -} - -func TestCreateOperatorGroupCSVCopy(t *testing.T) { - // create target namespace - // create operator group in OLM namespace, which serves as operator namespace - // create CSV in OLM namespace - // verify CSV is copied to target namespace - - log.SetLevel(log.DebugLevel) - c := newKubeClient(t) - crc := newCRClient(t) - targetNamespaceName := testNamespace + "-target" - csvName := "acsv-that-is-unique" // must be lowercase for DNS-1123 validation - matchingLabel := map[string]string{"matchLabel": testNamespace} - - log.Debug("Creating operator namespace") - targetNamespace := corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: targetNamespaceName, - Labels: matchingLabel, - }, - } - _, err := c.KubernetesInterface().CoreV1().Namespaces().Create(&targetNamespace) - require.NoError(t, err) - - oldCommand := patchOlmDeployment(t, c, targetNamespaceName) - - log.Debug("Creating operator group") - operatorGroup := v1alpha2.OperatorGroup{ - ObjectMeta: metav1.ObjectMeta{ - Name: "e2e-operator-group", - Namespace: testNamespace, - }, - Spec: v1alpha2.OperatorGroupSpec{ - Selector: metav1.LabelSelector{ - MatchLabels: matchingLabel, - }, - }, - } - _, err = crc.OperatorsV1alpha2().OperatorGroups(testNamespace).Create(&operatorGroup) - require.NoError(t, err) - - log.Debug("Creating CSV") - aCSV := newCSV(csvName, testNamespace, "", *semver.New("0.0.0"), nil, nil, newNginxInstallStrategy("aspec", nil, nil)) - createdCSV, err := crc.OperatorsV1alpha1().ClusterServiceVersions(testNamespace).Create(&aCSV) - require.NoError(t, err) - - log.Debug("Waiting for CSV copy to have correct properties") - var csvCopy *v1alpha1.ClusterServiceVersion + log.Debug("Waiting on deployment to have correct annotation") err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { - csvCopy, err = crc.OperatorsV1alpha1().ClusterServiceVersions(targetNamespaceName).Get(csvName, metav1.GetOptions{}) + createdDeployment, err := c.GetDeployment(testNamespace, "operator-deployment") if err != nil { if errors.IsNotFound(err) { return false, nil } return false, err } - return true, nil + // TODO: verify operatorNamespace annotation, operatorGroup annotation + if createdDeployment.Spec.Template.Annotations["olm.targetNamespaces"] == otherNamespaceName { + return true, nil + } + return false, nil }) - require.Equal(t, createdCSV.Name, csvCopy.Name) - require.Equal(t, createdCSV.Spec, csvCopy.Spec) - // part 2 - ensure deletion cleans up copied CSV + // ensure deletion cleans up copied CSV log.Debug("Deleting CSV") err = crc.OperatorsV1alpha1().ClusterServiceVersions(testNamespace).Delete(csvName, &metav1.DeleteOptions{}) require.NoError(t, err) log.Debug("Waiting for orphaned CSV to be deleted") err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { - csvCopy, err = crc.OperatorsV1alpha1().ClusterServiceVersions(targetNamespaceName).Get(csvName, metav1.GetOptions{}) + _, err = crc.OperatorsV1alpha1().ClusterServiceVersions(otherNamespaceName).Get(csvName, metav1.GetOptions{}) if err != nil { if errors.IsNotFound(err) { return true, nil @@ -322,6 +212,7 @@ func TestCreateOperatorGroupCSVCopy(t *testing.T) { require.NoError(t, err) // clean up + // TODO: unpatch function runningDeploy, err := c.GetDeployment(testNamespace, "olm-operator") require.NoError(t, err) runningDeploy.Spec.Template.Spec.Containers[0].Command = oldCommand @@ -331,7 +222,7 @@ func TestCreateOperatorGroupCSVCopy(t *testing.T) { } require.NoError(t, err) - err = c.KubernetesInterface().CoreV1().Namespaces().Delete(targetNamespaceName, &metav1.DeleteOptions{}) + err = c.KubernetesInterface().CoreV1().Namespaces().Delete(otherNamespaceName, &metav1.DeleteOptions{}) require.NoError(t, err) err = crc.OperatorsV1alpha2().OperatorGroups(testNamespace).Delete(operatorGroup.Name, &metav1.DeleteOptions{}) require.NoError(t, err) From d7229f160b2d2e82ef5d58a69122a7bf2586a880 Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Tue, 30 Oct 2018 18:39:22 -0400 Subject: [PATCH 27/33] fix(e2e): slightly improves execution time --- pkg/controller/operators/olm/operatorgroup.go | 1 + test/e2e/operator_groups_e2e_test.go | 15 ++++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/pkg/controller/operators/olm/operatorgroup.go b/pkg/controller/operators/olm/operatorgroup.go index 91acc920f1..d3db4dae4d 100644 --- a/pkg/controller/operators/olm/operatorgroup.go +++ b/pkg/controller/operators/olm/operatorgroup.go @@ -21,6 +21,7 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error { log.Debugf("wrong type: %#v\n", obj) return fmt.Errorf("casting OperatorGroup failed") } + log.Infof("syncing operator group %v", op) err, targetedNamespaces := a.updateNamespaceList(op) log.Debugf("Got targetedNamespaces: '%v'", targetedNamespaces) diff --git a/test/e2e/operator_groups_e2e_test.go b/test/e2e/operator_groups_e2e_test.go index d9c135fa7a..d4846291a5 100644 --- a/test/e2e/operator_groups_e2e_test.go +++ b/test/e2e/operator_groups_e2e_test.go @@ -116,11 +116,6 @@ func TestOperatorGroup(t *testing.T) { Name: "e2e-operator-group", Namespace: testNamespace, }, - Spec: v1alpha2.OperatorGroupSpec{ - Selector: metav1.LabelSelector{ - MatchLabels: matchingLabel, - }, - }, } _, err = crc.OperatorsV1alpha2().OperatorGroups(testNamespace).Create(&operatorGroup) require.NoError(t, err) @@ -128,6 +123,16 @@ func TestOperatorGroup(t *testing.T) { Namespaces: []*corev1.Namespace{createdOtherNamespace}, } + // instead of setting the label selector initially, do it here to force immediate reconcile + fetchedOpGroup, err := crc.OperatorsV1alpha2().OperatorGroups(testNamespace).Get(operatorGroup.GetName(), metav1.GetOptions{}) + fetchedOpGroup.Spec = v1alpha2.OperatorGroupSpec{ + Selector: metav1.LabelSelector{ + MatchLabels: matchingLabel, + }, + } + _, err = crc.OperatorsV1alpha2().OperatorGroups(testNamespace).Update(fetchedOpGroup) + require.NoError(t, err) + log.Debug("Waiting on operator group to have correct status") err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { fetched, fetchErr := crc.OperatorsV1alpha2().OperatorGroups(testNamespace).Get(operatorGroup.Name, metav1.GetOptions{}) From 5b7d1407047d07823fdfc72d4069a7784ade1e92 Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Thu, 1 Nov 2018 11:49:29 -0400 Subject: [PATCH 28/33] fix(olm): operator group annotations and listers --- pkg/controller/operators/olm/operator.go | 30 +++---- pkg/controller/operators/olm/operator_test.go | 4 +- pkg/controller/operators/olm/operatorgroup.go | 86 ++++++------------- test/e2e/operator_groups_e2e_test.go | 5 +- 4 files changed, 39 insertions(+), 86 deletions(-) diff --git a/pkg/controller/operators/olm/operator.go b/pkg/controller/operators/olm/operator.go index 33b52e360b..1d474e55e1 100644 --- a/pkg/controller/operators/olm/operator.go +++ b/pkg/controller/operators/olm/operator.go @@ -27,9 +27,6 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/informers" - cappsv1 "k8s.io/client-go/listers/apps/v1" - cv1 "k8s.io/client-go/listers/core/v1" - crbacv1 "k8s.io/client-go/listers/rbac/v1" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" @@ -48,21 +45,15 @@ const ( type Operator struct { *queueinformer.Operator - csvQueue workqueue.RateLimitingInterface - client versioned.Interface - resolver install.StrategyResolverInterface - lister operatorlister.OperatorLister - roleLister crbacv1.RoleLister - roleBindingLister crbacv1.RoleBindingLister - clusterRoleLister crbacv1.ClusterRoleLister - clusterRoleBindingLister crbacv1.ClusterRoleBindingLister - csvLister map[string]csvlister.ClusterServiceVersionLister - operatorGroupLister map[string]operatorgrouplister.OperatorGroupLister - deploymentLister map[string]cappsv1.DeploymentLister - namespaceLister cv1.NamespaceLister - annotator *annotator.Annotator - recorder record.EventRecorder - cleanupFunc func() + csvQueue workqueue.RateLimitingInterface + client versioned.Interface + resolver install.StrategyResolverInterface + lister operatorlister.OperatorLister + csvLister map[string]csvlister.ClusterServiceVersionLister + operatorGroupLister map[string]operatorgrouplister.OperatorGroupLister + annotator *annotator.Annotator + recorder record.EventRecorder + cleanupFunc func() } func NewOperator(crClient versioned.Interface, opClient operatorclient.ClientInterface, resolver install.StrategyResolverInterface, wakeupInterval time.Duration, annotations map[string]string, namespaces []string) (*Operator, error) { @@ -210,13 +201,12 @@ func NewOperator(crClient versioned.Interface, opClient operatorclient.ClientInt // set up watch on deployments depInformers := []cache.SharedIndexInformer{} - op.deploymentLister = make(map[string]cappsv1.DeploymentLister, len(namespaces)) for _, namespace := range namespaces { log.Debugf("watching deployments in namespace %s", namespace) informerFactory := informers.NewSharedInformerFactoryWithOptions(opClient.KubernetesInterface(), wakeupInterval, informers.WithNamespace(namespace)) informer := informerFactory.Apps().V1().Deployments() depInformers = append(depInformers, informer.Informer()) - op.deploymentLister[namespace] = informer.Lister() + op.lister.AppsV1().RegisterDeploymentLister(namespace, informer.Lister()) } depQueue := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "csv-deployments") diff --git a/pkg/controller/operators/olm/operator_test.go b/pkg/controller/operators/olm/operator_test.go index 35543425f7..98b9713c41 100644 --- a/pkg/controller/operators/olm/operator_test.go +++ b/pkg/controller/operators/olm/operator_test.go @@ -1273,7 +1273,7 @@ func TestSyncOperatorGroups(t *testing.T) { }, LastUpdated: timeNow(), }, - expectedAnnotation: map[string]string{"olm.targetNamespaces": testNS}, + expectedAnnotation: map[string]string{"olm.targetNamespaces": testNS, "olm.operatorGroup": "operator-group-1", "olm.operatorNamespace": testNS}, }, } @@ -1298,7 +1298,7 @@ func TestSyncOperatorGroups(t *testing.T) { if tc.expectedAnnotation != nil { // assuming CSVs are in correct namespace for _, ns := range tc.namespaces { - deployments, err := op.deploymentLister[ns.Name].List(labels.Everything()) + deployments, err := op.lister.AppsV1().DeploymentLister().Deployments(ns.GetName()).List(labels.Everything()) if err != nil { t.Fatal(err) } diff --git a/pkg/controller/operators/olm/operatorgroup.go b/pkg/controller/operators/olm/operatorgroup.go index d3db4dae4d..e4176e5fd5 100644 --- a/pkg/controller/operators/olm/operatorgroup.go +++ b/pkg/controller/operators/olm/operatorgroup.go @@ -2,6 +2,7 @@ package olm import ( "fmt" + "reflect" "strings" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" @@ -42,7 +43,7 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error { } nsListJoined := strings.Join(nsList, ",") - if err := a.annotateDeployments(op.GetNamespace(), nsListJoined); err != nil { + if err := a.annotateDeployments(op, nsListJoined); err != nil { log.Errorf("annotateDeployments error: %v", err) return err } @@ -51,58 +52,19 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error { // annotate csvs csvsInNamespace := a.csvsInNamespace(op.Namespace) for _, csv := range csvsInNamespace { - a.addAnnotationsToCSV(csv, op, nsListJoined) - // TODO: generate a patch - if _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(csv.GetNamespace()).Update(csv); err != nil { - log.Errorf("Update for existing CSV failed: %v", err) - return err + origCSVannotations := csv.GetAnnotations() + a.addAnnotationsToObjectMeta(&csv.ObjectMeta, op, nsListJoined) + if reflect.DeepEqual(origCSVannotations, csv.GetAnnotations()) == false { + // CRDs don't support strategic merge patching, but in the future if they do this should be updated to patch + if _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(csv.GetNamespace()).Update(csv); err != nil { + log.Errorf("Update for existing CSV failed: %v", err) + } } if err := a.copyCsvToTargetNamespace(csv, op, targetedNamespaces); err != nil { return err } } - - // // create new CSV instead of DeepCopy as namespace and resource version (and status) will be different - // newCSV := v1alpha1.ClusterServiceVersion{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: csv.Name, - // }, - // Spec: *csv.Spec.DeepCopy(), - // Status: v1alpha1.ClusterServiceVersionStatus{ - // Message: "CSV copied to target namespace", - // Reason: v1alpha1.CSVReasonCopied, - // LastUpdateTime: timeNow(), - // }, - // } - // - // a.addAnnotationsToCSV(&newCSV, op, nsListJoined) - // - // - // for _, ns := range targetedNamespaces { - // newCSV.SetNamespace(ns.Name) - // if ns.Name != op.Namespace { - // log.Debugf("Copying CSV %v to namespace %v", csv.GetName(), ns.GetName()) - // _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.GetName()).Create(&newCSV) - // if k8serrors.IsAlreadyExists(err) { - // a.addAnnotationsToCSV(csv, op, nsListJoined) - // if _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.GetName()).Update(csv); err != nil { - // log.Errorf("Update CSV in target namespace failed: %v", err) - // return err - // } - // } else if err != nil { - // log.Errorf("Create for new CSV failed: %v", err) - // return err - // } - // } else { - // a.addAnnotationsToCSV(csv, op, nsListJoined) - // if _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.GetName()).Update(csv); err != nil { - // log.Errorf("Update for existing CSV failed: %v", err) - // return err - // } - // } - // } - //} log.Debug("CSV annotation completed") //TODO: ensure RBAC on operator serviceaccount @@ -142,11 +104,12 @@ func (a *Operator) copyCsvToTargetNamespace(csv *v1alpha1.ClusterServiceVersion, if err != nil { log.Errorf("Create failed, yet get failed: %v", err) } - // update the potentially different annotations - fetchedCSV.Annotations = csv.Annotations - if _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.Name).Update(fetchedCSV); err != nil { - log.Errorf("Update CSV in target namespace failed: %v", err) - return err + if reflect.DeepEqual(fetchedCSV.Annotations, csv.Annotations) == false { + // CRDs don't support strategic merge patching, but in the future if they do this should be updated to patch + if _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.Name).Update(fetchedCSV); err != nil { + log.Errorf("Update CSV in target namespace failed: %v", err) + return err + } } } else if err != nil { log.Errorf("Create for new CSV failed: %v", err) @@ -156,10 +119,10 @@ func (a *Operator) copyCsvToTargetNamespace(csv *v1alpha1.ClusterServiceVersion, return nil } -func (a *Operator) addAnnotationsToCSV(csv *v1alpha1.ClusterServiceVersion, op *v1alpha2.OperatorGroup, targetNamespaces string) { - metav1.SetMetaDataAnnotation(&csv.ObjectMeta, "olm.targetNamespaces", targetNamespaces) - metav1.SetMetaDataAnnotation(&csv.ObjectMeta, "olm.operatorNamespace", op.GetNamespace()) - metav1.SetMetaDataAnnotation(&csv.ObjectMeta, "olm.operatorGroup", op.GetName()) +func (a *Operator) addAnnotationsToObjectMeta(obj *metav1.ObjectMeta, op *v1alpha2.OperatorGroup, targetNamespaces string) { + metav1.SetMetaDataAnnotation(obj, "olm.targetNamespaces", targetNamespaces) + metav1.SetMetaDataAnnotation(obj, "olm.operatorNamespace", op.GetNamespace()) + metav1.SetMetaDataAnnotation(obj, "olm.operatorGroup", op.GetName()) } func namespacesChanged(clusterNamespaces []*corev1.Namespace, statusNamespaces []*corev1.Namespace) bool { @@ -232,10 +195,12 @@ func (a *Operator) ensureClusterRoles(op *v1alpha2.OperatorGroup) error { } clusterRole := &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("owned-crd-manager-%s", csv.GetName()), + }, Rules: managerPolicyRules, } ownerutil.AddNonBlockingOwner(clusterRole, csv) - clusterRole.SetGenerateName(fmt.Sprintf("owned-crd-manager-%s-", csv.Spec.DisplayName)) _, err := a.OpClient.KubernetesInterface().RbacV1().ClusterRoles().Create(clusterRole) if k8serrors.IsAlreadyExists(err) { if _, err = a.OpClient.UpdateClusterRole(clusterRole); err != nil { @@ -284,9 +249,9 @@ func (a *Operator) ensureClusterRoles(op *v1alpha2.OperatorGroup) error { return nil } -func (a *Operator) annotateDeployments(operatorNamespace string, targetNamespaceString string) error { +func (a *Operator) annotateDeployments(op *v1alpha2.OperatorGroup, targetNamespaceString string) error { // write above namespaces to watch in every deployment in operator namespace - deploymentList, err := a.deploymentLister[operatorNamespace].List(labels.Everything()) + deploymentList, err := a.lister.AppsV1().DeploymentLister().Deployments(op.GetNamespace()).List(labels.Everything()) if err != nil { log.Errorf("deployment list failed: %v\n", err) return err @@ -308,12 +273,11 @@ func (a *Operator) annotateDeployments(operatorNamespace string, targetNamespace } originalDeploy := deploy.DeepCopy() - metav1.SetMetaDataAnnotation(&deploy.Spec.Template.ObjectMeta, "olm.targetNamespaces", targetNamespaceString) + a.addAnnotationsToObjectMeta(&deploy.Spec.Template.ObjectMeta, op, targetNamespaceString) if _, _, err := a.OpClient.PatchDeployment(originalDeploy, deploy); err != nil { log.Errorf("patch deployment failed: %v\n", err) return err } - } return nil diff --git a/test/e2e/operator_groups_e2e_test.go b/test/e2e/operator_groups_e2e_test.go index d4846291a5..3ec2886421 100644 --- a/test/e2e/operator_groups_e2e_test.go +++ b/test/e2e/operator_groups_e2e_test.go @@ -182,7 +182,7 @@ func TestOperatorGroup(t *testing.T) { require.Equal(t, createdCSV.Name, fetchedCSV.Name) require.Equal(t, createdCSV.Spec, fetchedCSV.Spec) - log.Debug("Waiting on deployment to have correct annotation") + log.Debug("Waiting on deployment to have correct annotations") err = wait.Poll(pollInterval, pollDuration, func() (bool, error) { createdDeployment, err := c.GetDeployment(testNamespace, "operator-deployment") if err != nil { @@ -191,8 +191,7 @@ func TestOperatorGroup(t *testing.T) { } return false, err } - // TODO: verify operatorNamespace annotation, operatorGroup annotation - if createdDeployment.Spec.Template.Annotations["olm.targetNamespaces"] == otherNamespaceName { + if checkOperatorGroupAnnotations(&createdDeployment.Spec.Template, &operatorGroup, otherNamespaceName) == nil { return true, nil } return false, nil From ac50ddc8ad6963429103fb626351a7273615be7b Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Fri, 2 Nov 2018 16:06:09 -0400 Subject: [PATCH 29/33] fix(olm): do not check namespace if casting failed --- pkg/controller/operators/olm/operator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/controller/operators/olm/operator.go b/pkg/controller/operators/olm/operator.go index 1d474e55e1..61e820c162 100644 --- a/pkg/controller/operators/olm/operator.go +++ b/pkg/controller/operators/olm/operator.go @@ -751,11 +751,11 @@ func (a *Operator) crdOwnerConflicts(in *v1alpha1.ClusterServiceVersion, csvsInN // syncNamespace is the method that gets called when we see a namespace event in the cluster func (a *Operator) syncNamespace(obj interface{}) (syncError error) { namespace, ok := obj.(*corev1.Namespace) - namespaceName := namespace.GetName() if !ok { log.Debugf("wrong type: %#v", obj) return fmt.Errorf("casting Namespace failed") } + namespaceName := namespace.GetName() log.Infof("syncing Namespace: %s", namespaceName) if err := a.annotator.AnnotateNamespace(namespace); err != nil { From cd201c5c7225a54a5f4732428efb4d9a420bf5a8 Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Tue, 6 Nov 2018 15:48:41 -0500 Subject: [PATCH 30/33] fix(unit): create OperatorGroups with initial fakes Instead of creating operator groups in a "second phase", create the resources at the same time along with everything else. This removes a small amount of namespace specific code from NewFakeOperator and makes things more consistent. --- pkg/controller/operators/olm/operator_test.go | 164 +++++++++--------- 1 file changed, 79 insertions(+), 85 deletions(-) diff --git a/pkg/controller/operators/olm/operator_test.go b/pkg/controller/operators/olm/operator_test.go index 98b9713c41..0bc459a021 100644 --- a/pkg/controller/operators/olm/operator_test.go +++ b/pkg/controller/operators/olm/operator_test.go @@ -102,17 +102,11 @@ func apiResourcesForObjects(objs []runtime.Object) []*metav1.APIResourceList { } func NewFakeOperator(clientObjs []runtime.Object, k8sObjs []runtime.Object, extObjs []runtime.Object, regObjs []runtime.Object, resolver install.StrategyResolverInterface, namespaces []v1.Namespace) (*Operator, error) { - clientFake := fake.NewSimpleClientset(clientObjs...) k8sClientFake := k8sfake.NewSimpleClientset(k8sObjs...) k8sClientFake.Resources = apiResourcesForObjects(append(extObjs, regObjs...)) opClientFake := operatorclient.NewClient(k8sClientFake, apiextensionsfake.NewSimpleClientset(extObjs...), apiregistrationfake.NewSimpleClientset(regObjs...)) annotations := map[string]string{"test": "annotation"} - for _, ns := range namespaces { - _, err := opClientFake.KubernetesInterface().CoreV1().Namespaces().Create(&ns) - if err != nil { - return nil, err - } - } + clientFake := fake.NewSimpleClientset(clientObjs...) var nsList []string for ix := range namespaces { @@ -1101,35 +1095,37 @@ func TestSyncOperatorGroups(t *testing.T) { }) tests := []struct { - name string - expectedEqual bool - initialCsvs []runtime.Object - initialCrds []runtime.Object - initialObjs []runtime.Object + name string + expectedEqual bool + // first item in initialObjs must be an OperatorGroup + initialObjs []runtime.Object + initialCrds []runtime.Object + // first item in initialK8sObjs must be a namespace + initialK8sObjs []runtime.Object initialApis []runtime.Object - namespaces []v1.Namespace - inputGroup v1alpha2.OperatorGroup expectedStatus v1alpha2.OperatorGroupStatus expectedAnnotation map[string]string }{ { name: "operator group with no matching namespace, no CSVs", expectedEqual: true, - namespaces: []v1.Namespace{ - { + initialObjs: []runtime.Object{ + &v1alpha2.OperatorGroup{ ObjectMeta: metav1.ObjectMeta{ - Name: testNS, + Name: "operator-group-1", + Namespace: testNS, + }, + Spec: v1alpha2.OperatorGroupSpec{ + Selector: metav1.LabelSelector{ + MatchLabels: aLabel, + }, }, }, }, - inputGroup: v1alpha2.OperatorGroup{ - ObjectMeta: metav1.ObjectMeta{ - Name: "operator-group-1", - Namespace: testNS, - }, - Spec: v1alpha2.OperatorGroupSpec{ - Selector: metav1.LabelSelector{ - MatchLabels: aLabel, + initialK8sObjs: []runtime.Object{ + &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: testNS, }, }, }, @@ -1138,25 +1134,27 @@ func TestSyncOperatorGroups(t *testing.T) { { name: "operator group with matching namespace, no CSVs", expectedEqual: true, - namespaces: []v1.Namespace{ - { + initialObjs: []runtime.Object{ + &v1alpha2.OperatorGroup{ ObjectMeta: metav1.ObjectMeta{ - Name: testNS, - Labels: aLabel, - Annotations: map[string]string{"test": "annotation"}, + Name: "operator-group-1", + Namespace: testNS, + }, + Spec: v1alpha2.OperatorGroupSpec{ + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "matchLabel", + }, + }, }, }, }, - inputGroup: v1alpha2.OperatorGroup{ - ObjectMeta: metav1.ObjectMeta{ - Name: "operator-group-1", - Namespace: testNS, - }, - Spec: v1alpha2.OperatorGroupSpec{ - Selector: metav1.LabelSelector{ - MatchLabels: map[string]string{ - "app": "matchLabel", - }, + initialK8sObjs: []runtime.Object{ + &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: testNS, + Labels: aLabel, + Annotations: map[string]string{"test": "annotation"}, }, }, }, @@ -1176,16 +1174,19 @@ func TestSyncOperatorGroups(t *testing.T) { { name: "operator group with matching namespace, CSV present", expectedEqual: false, - namespaces: []v1.Namespace{ - { + initialObjs: []runtime.Object{ + &v1alpha2.OperatorGroup{ ObjectMeta: metav1.ObjectMeta{ - Name: testNS, - Labels: aLabel, - Annotations: map[string]string{"test": "annotation"}, + Name: "operator-group-1", + Namespace: testNS, + Labels: aLabel, + }, + Spec: v1alpha2.OperatorGroupSpec{ + Selector: metav1.LabelSelector{ + MatchLabels: aLabel, + }, }, }, - }, - initialCsvs: []runtime.Object{ csv("csv1", testNS, "", @@ -1195,20 +1196,15 @@ func TestSyncOperatorGroups(t *testing.T) { v1alpha1.CSVPhaseSucceeded, ), }, - initialObjs: []runtime.Object{ - deployment("csv1-dep1", testNS), - }, - inputGroup: v1alpha2.OperatorGroup{ - ObjectMeta: metav1.ObjectMeta{ - Name: "operator-group-1", - Namespace: testNS, - Labels: aLabel, - }, - Spec: v1alpha2.OperatorGroupSpec{ - Selector: metav1.LabelSelector{ - MatchLabels: aLabel, + initialK8sObjs: []runtime.Object{ + &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: testNS, + Labels: aLabel, + Annotations: map[string]string{"test": "annotation"}, }, }, + deployment("csv1-dep1", testNS), }, expectedStatus: v1alpha2.OperatorGroupStatus{ Namespaces: []*v1.Namespace{ @@ -1227,16 +1223,19 @@ func TestSyncOperatorGroups(t *testing.T) { { name: "operator group with matching namespace, CSV present", expectedEqual: true, - namespaces: []v1.Namespace{ - { + initialObjs: []runtime.Object{ + &v1alpha2.OperatorGroup{ ObjectMeta: metav1.ObjectMeta{ - Name: testNS, - Labels: aLabel, - Annotations: map[string]string{"test": "annotation"}, + Name: "operator-group-1", + Namespace: testNS, + Labels: aLabel, + }, + Spec: v1alpha2.OperatorGroupSpec{ + Selector: metav1.LabelSelector{ + MatchLabels: aLabel, + }, }, }, - }, - initialCsvs: []runtime.Object{ csv("csv1", testNS, "", @@ -1246,20 +1245,15 @@ func TestSyncOperatorGroups(t *testing.T) { v1alpha1.CSVPhaseSucceeded, ), }, - initialObjs: []runtime.Object{ - ownedDeployment, - }, - inputGroup: v1alpha2.OperatorGroup{ - ObjectMeta: metav1.ObjectMeta{ - Name: "operator-group-1", - Namespace: testNS, - Labels: aLabel, - }, - Spec: v1alpha2.OperatorGroupSpec{ - Selector: metav1.LabelSelector{ - MatchLabels: aLabel, + initialK8sObjs: []runtime.Object{ + &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: testNS, + Labels: aLabel, + Annotations: map[string]string{"test": "annotation"}, }, }, + ownedDeployment, }, expectedStatus: v1alpha2.OperatorGroupStatus{ Namespaces: []*v1.Namespace{ @@ -1279,7 +1273,8 @@ func TestSyncOperatorGroups(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - op, err := NewFakeOperator(tc.initialCsvs, tc.initialObjs, tc.initialCrds, tc.initialApis, &install.StrategyResolver{}, tc.namespaces) + namespaceList := []v1.Namespace{*tc.initialK8sObjs[0].(*v1.Namespace)} + op, err := NewFakeOperator(tc.initialObjs, tc.initialK8sObjs, tc.initialCrds, tc.initialApis, &install.StrategyResolver{}, namespaceList) require.NoError(t, err) stopCh := make(chan struct{}) @@ -1287,17 +1282,16 @@ func TestSyncOperatorGroups(t *testing.T) { ready, _ := op.Run(stopCh) <-ready - // Could not put this in initialObjs - got "no kind is registered for the type v1alpha2.OperatorGroup" - _, err = op.client.OperatorsV1alpha2().OperatorGroups(tc.inputGroup.Namespace).Create(&tc.inputGroup) - require.NoError(t, err) + operatorGroup, ok := tc.initialObjs[0].(*v1alpha2.OperatorGroup) + require.True(t, ok) - err = op.syncOperatorGroups(&tc.inputGroup) + err = op.syncOperatorGroups(operatorGroup) require.NoError(t, err) - assert.Equal(t, tc.expectedStatus, tc.inputGroup.Status) + assert.Equal(t, tc.expectedStatus, operatorGroup.Status) if tc.expectedAnnotation != nil { // assuming CSVs are in correct namespace - for _, ns := range tc.namespaces { + for _, ns := range namespaceList { deployments, err := op.lister.AppsV1().DeploymentLister().Deployments(ns.GetName()).List(labels.Everything()) if err != nil { t.Fatal(err) From 337b156e956c55405d468d19c0098c67eded6ef7 Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Tue, 6 Nov 2018 16:08:33 -0500 Subject: [PATCH 31/33] chore(olm): refactor updateNamespaceList return error as the last argument --- pkg/controller/operators/olm/operatorgroup.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/controller/operators/olm/operatorgroup.go b/pkg/controller/operators/olm/operatorgroup.go index e4176e5fd5..40dcf9729f 100644 --- a/pkg/controller/operators/olm/operatorgroup.go +++ b/pkg/controller/operators/olm/operatorgroup.go @@ -24,7 +24,7 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error { } log.Infof("syncing operator group %v", op) - err, targetedNamespaces := a.updateNamespaceList(op) + targetedNamespaces, err := a.updateNamespaceList(op) log.Debugf("Got targetedNamespaces: '%v'", targetedNamespaces) if err != nil { log.Errorf("updateNamespaceList error: %v", err) @@ -142,20 +142,20 @@ func namespacesChanged(clusterNamespaces []*corev1.Namespace, statusNamespaces [ return false } -func (a *Operator) updateNamespaceList(op *v1alpha2.OperatorGroup) (error, []*corev1.Namespace) { +func (a *Operator) updateNamespaceList(op *v1alpha2.OperatorGroup) ([]*corev1.Namespace, error) { selector, err := metav1.LabelSelectorAsSelector(&op.Spec.Selector) if err != nil { - return err, nil + return nil, err } namespaceList, err := a.lister.CoreV1().NamespaceLister().List(selector) if err != nil { - return err, nil + return nil, err } if !namespacesChanged(namespaceList, op.Status.Namespaces) { // status is current with correct namespaces, so no further updates required - return nil, namespaceList + return namespaceList, nil } log.Debugf("Namespace change detected, found: %v", namespaceList) op.Status.Namespaces = make([]*corev1.Namespace, len(namespaceList)) @@ -163,9 +163,9 @@ func (a *Operator) updateNamespaceList(op *v1alpha2.OperatorGroup) (error, []*co op.Status.LastUpdated = timeNow() _, err = a.client.OperatorsV1alpha2().OperatorGroups(op.Namespace).UpdateStatus(op) if err != nil { - return err, namespaceList + return namespaceList, err } - return nil, namespaceList + return namespaceList, nil } func (a *Operator) ensureClusterRoles(op *v1alpha2.OperatorGroup) error { From ab2704478427d09fd1ff9fedde5ceaa549ce7884 Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Tue, 6 Nov 2018 17:47:46 -0500 Subject: [PATCH 32/33] fix(olm): remove duplicated code This is already handled in a more clear way in removeDanglingChildCSVs. --- pkg/controller/operators/olm/operator.go | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/pkg/controller/operators/olm/operator.go b/pkg/controller/operators/olm/operator.go index 61e820c162..943431c8cc 100644 --- a/pkg/controller/operators/olm/operator.go +++ b/pkg/controller/operators/olm/operator.go @@ -438,25 +438,6 @@ func (a *Operator) syncClusterServiceVersion(obj interface{}) (syncError error) outCSV, syncError := a.transitionCSVState(*clusterServiceVersion) - opNamespace, ok := clusterServiceVersion.Annotations["olm.operatorNamespace"] - if ok { - // ensure "parent" CSV has not been deleted - csvs := a.csvsInNamespace(opNamespace) - csv, found := csvs[clusterServiceVersion.Name] - if !found { - targetNamespaces, ok := clusterServiceVersion.Annotations["olm.targetNamespaces"] - if !ok { - return fmt.Errorf("Did not find targetNamespaces annotation on %v", csv) - } - for _, ns := range strings.Split(targetNamespaces, ",") { - if err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns).Delete(csv.Name, &metav1.DeleteOptions{}); err != nil { - return err - } - } - return nil - } - } - // no changes in status, don't update if outCSV.Status.Phase == clusterServiceVersion.Status.Phase && outCSV.Status.Reason == clusterServiceVersion.Status.Reason && outCSV.Status.Message == clusterServiceVersion.Status.Message { return From b49c9bc45312ac190ff13bad283a1caa1667ac53 Mon Sep 17 00:00:00 2001 From: Jeff Peeler Date: Tue, 6 Nov 2018 17:51:26 -0500 Subject: [PATCH 33/33] fix(olm): make operator group copy CSVs more efficiently Essentially, optimize for the update case rather than create since creates only happen once. --- pkg/controller/operators/olm/operatorgroup.go | 56 ++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/pkg/controller/operators/olm/operatorgroup.go b/pkg/controller/operators/olm/operatorgroup.go index 40dcf9729f..a5d202bdee 100644 --- a/pkg/controller/operators/olm/operatorgroup.go +++ b/pkg/controller/operators/olm/operatorgroup.go @@ -76,19 +76,36 @@ func (a *Operator) copyCsvToTargetNamespace(csv *v1alpha1.ClusterServiceVersion, if ns.Name == operatorGroup.GetNamespace() { continue } - // create new CSV instead of DeepCopy as namespace and resource version (and status) will be different - newCSV := v1alpha1.ClusterServiceVersion{ - ObjectMeta: metav1.ObjectMeta{ - Name: csv.Name, - Annotations: csv.Annotations, - }, - Spec: *csv.Spec.DeepCopy(), - } - newCSV.SetNamespace(ns.Name) + fetchedCSV, err := a.csvLister[ns.GetName()].ClusterServiceVersions(ns.GetName()).Get(csv.GetName()) + if k8serrors.IsAlreadyExists(err) { + log.Debugf("Found existing CSV (%v), checking annotations", fetchedCSV.GetName()) + if reflect.DeepEqual(fetchedCSV.Annotations, csv.Annotations) == false { + fetchedCSV.Annotations = csv.Annotations + // CRDs don't support strategic merge patching, but in the future if they do this should be updated to patch + log.Debugf("Updating CSV %v in namespace %v", fetchedCSV.GetName(), ns.GetName()) + if _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.Name).Update(fetchedCSV); err != nil { + log.Errorf("Update CSV in target namespace failed: %v", err) + return err + } + } + continue + } else if k8serrors.IsNotFound(err) { + // create new CSV instead of DeepCopy as namespace and resource version (and status) will be different + newCSV := v1alpha1.ClusterServiceVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: csv.Name, + Annotations: csv.Annotations, + }, + Spec: *csv.Spec.DeepCopy(), + } + newCSV.SetNamespace(ns.Name) - log.Debugf("Copying/updating CSV %v to/in namespace %v", csv.GetName(), ns.Name) - createdCSV, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.Name).Create(&newCSV) - if err == nil { + log.Debugf("Copying CSV %v to namespace %v", csv.GetName(), ns.GetName()) + createdCSV, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.Name).Create(&newCSV) + if err != nil { + log.Errorf("Create for new CSV failed: %v", err) + return err + } createdCSV.Status = v1alpha1.ClusterServiceVersionStatus{ Message: "CSV copied to target namespace", Reason: v1alpha1.CSVReasonCopied, @@ -98,21 +115,8 @@ func (a *Operator) copyCsvToTargetNamespace(csv *v1alpha1.ClusterServiceVersion, log.Errorf("Status update for CSV failed: %v", err) return err } - } - if k8serrors.IsAlreadyExists(err) { - fetchedCSV, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.Name).Get(csv.GetName(), metav1.GetOptions{}) - if err != nil { - log.Errorf("Create failed, yet get failed: %v", err) - } - if reflect.DeepEqual(fetchedCSV.Annotations, csv.Annotations) == false { - // CRDs don't support strategic merge patching, but in the future if they do this should be updated to patch - if _, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(ns.Name).Update(fetchedCSV); err != nil { - log.Errorf("Update CSV in target namespace failed: %v", err) - return err - } - } } else if err != nil { - log.Errorf("Create for new CSV failed: %v", err) + log.Errorf("CSV fetch for %v failed: %v", csv.GetName(), err) return err } }