diff --git a/pkg/controller/operators/catalog/operator.go b/pkg/controller/operators/catalog/operator.go index 3309778508..faccef798f 100644 --- a/pkg/controller/operators/catalog/operator.go +++ b/pkg/controller/operators/catalog/operator.go @@ -61,6 +61,7 @@ const ( secretKind = "Secret" clusterRoleKind = "ClusterRole" clusterRoleBindingKind = "ClusterRoleBinding" + configMapKind = "ConfigMap" serviceAccountKind = "ServiceAccount" serviceKind = "Service" roleKind = "Role" @@ -1731,6 +1732,28 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error { plan.Status.Plan[i].Status = status + case configMapKind: + var cfg corev1.ConfigMap + err := json.Unmarshal([]byte(step.Resource.Manifest), &cfg) + if err != nil { + return errorwrap.Wrapf(err, "error parsing step manifest: %s", step.Resource.Name) + } + + // Update UIDs on all CSV OwnerReferences + updated, err := o.getUpdatedOwnerReferences(cfg.OwnerReferences, plan.Namespace) + if err != nil { + return errorwrap.Wrapf(err, "error generating ownerrefs for configmap: %s", cfg.GetName()) + } + cfg.SetOwnerReferences(updated) + cfg.SetNamespace(namespace) + + status, err := ensurer.EnsureConfigMap(plan.Namespace, &cfg) + if err != nil { + return err + } + + plan.Status.Plan[i].Status = status + default: if !isSupported(step.Resource.Kind) { // Not a supported resource diff --git a/pkg/controller/operators/catalog/operator_test.go b/pkg/controller/operators/catalog/operator_test.go index 4c9f221497..2a3598333b 100644 --- a/pkg/controller/operators/catalog/operator_test.go +++ b/pkg/controller/operators/catalog/operator_test.go @@ -413,6 +413,27 @@ func TestExecutePlan(t *testing.T) { want: []runtime.Object{serviceAccount("sa", namespace, "", objectReference("init secret"))}, err: nil, }, + { + testName: "CreateConfigMap", + in: withSteps(installPlan("p", namespace, v1alpha1.InstallPlanPhaseInstalling, "csv"), + []*v1alpha1.Step{ + { + Resource: v1alpha1.StepResource{ + CatalogSource: "catalog", + CatalogSourceNamespace: namespace, + Group: "", + Version: "v1", + Kind: "ConfigMap", + Name: "cfg", + Manifest: toManifest(t, configmap("cfg", namespace)), + }, + Status: v1alpha1.StepStatusUnknown, + }, + }, + ), + want: []runtime.Object{configmap("cfg", namespace)}, + err: nil, + }, { testName: "UpdateServiceAccountWithSameFields", in: withSteps(installPlan("p", namespace, v1alpha1.InstallPlanPhaseInstalling, "csv"), @@ -557,6 +578,8 @@ func TestExecutePlan(t *testing.T) { fetched, err = op.opClient.GetSecret(namespace, o.GetName()) case *corev1.Service: fetched, err = op.opClient.GetService(namespace, o.GetName()) + case *corev1.ConfigMap: + fetched, err = op.opClient.GetConfigMap(namespace, o.GetName()) case *v1beta1.CustomResourceDefinition: fetched, err = op.opClient.ApiextensionsV1beta1Interface().ApiextensionsV1beta1().CustomResourceDefinitions().Get(o.GetName(), getOpts) case *v1alpha1.ClusterServiceVersion: @@ -1322,6 +1345,13 @@ func serviceAccount(name, namespace, generateName string, secretRef *corev1.Obje } } +func configmap(name, namespace string) *corev1.ConfigMap { + return &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{Kind: configMapKind}, + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, + } +} + func objectReference(name string) *corev1.ObjectReference { if name == "" { return &corev1.ObjectReference{} diff --git a/pkg/controller/operators/catalog/step_ensurer.go b/pkg/controller/operators/catalog/step_ensurer.go index 1add2723d6..0602fd6f57 100644 --- a/pkg/controller/operators/catalog/step_ensurer.go +++ b/pkg/controller/operators/catalog/step_ensurer.go @@ -300,3 +300,26 @@ func (o *StepEnsurer) EnsureUnstructuredObject(client dynamic.ResourceInterface, status = v1alpha1.StepStatusPresent return } + +// EnsureConfigMap writes the specified ConfigMap object to the cluster. +func (o *StepEnsurer) EnsureConfigMap(namespace string, configmap *corev1.ConfigMap) (status v1alpha1.StepStatus, err error) { + _, createErr := o.kubeClient.KubernetesInterface().CoreV1().ConfigMaps(namespace).Create(configmap) + if createErr == nil { + status = v1alpha1.StepStatusCreated + return + } + + if !k8serrors.IsAlreadyExists(createErr) { + err = errorwrap.Wrapf(createErr, "error updating configmap: %s", configmap.GetName()) + return + } + + configmap.SetNamespace(namespace) + if _, updateErr := o.kubeClient.UpdateConfigMap(configmap); updateErr != nil { + err = errorwrap.Wrapf(updateErr, "error updating configmap: %s", configmap.GetName()) + return + } + + status = v1alpha1.StepStatusPresent + return +} diff --git a/pkg/lib/operatorclient/client.go b/pkg/lib/operatorclient/client.go index e6cc56c51a..afdc2d7977 100644 --- a/pkg/lib/operatorclient/client.go +++ b/pkg/lib/operatorclient/client.go @@ -31,6 +31,7 @@ type ClientInterface interface { ClusterRoleBindingClient ClusterRoleClient DeploymentClient + ConfigMapClient } // CustomResourceClient contains methods for the Custom Resource. @@ -127,6 +128,14 @@ type DeploymentClient interface { ListDeploymentsWithLabels(namespace string, labels labels.Set) (*appsv1.DeploymentList, error) } +// ConfigMapClient contains methods for the ConfigMap resource +type ConfigMapClient interface { + CreateConfigMap(*v1.ConfigMap) (*v1.ConfigMap, error) + GetConfigMap(namespace, name string) (*v1.ConfigMap, error) + UpdateConfigMap(modified *v1.ConfigMap) (*v1.ConfigMap, error) + DeleteConfigMap(namespace, name string, options *metav1.DeleteOptions) error +} + // Interface assertion. var _ ClientInterface = &Client{} diff --git a/pkg/lib/operatorclient/configmap.go b/pkg/lib/operatorclient/configmap.go new file mode 100644 index 0000000000..923e4e7376 --- /dev/null +++ b/pkg/lib/operatorclient/configmap.go @@ -0,0 +1,39 @@ +package operatorclient + +import ( + "fmt" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/klog" +) + +// CreateConfigMap creates the ConfigMap. +func (c *Client) CreateConfigMap(ig *corev1.ConfigMap) (*corev1.ConfigMap, error) { + return c.CoreV1().ConfigMaps(ig.GetNamespace()).Create(ig) +} + +// GetConfigMap returns the existing ConfigMap. +func (c *Client) GetConfigMap(namespace, name string) (*corev1.ConfigMap, error) { + return c.CoreV1().ConfigMaps(namespace).Get(name, metav1.GetOptions{}) +} + +// DeleteConfigMap deletes the ConfigMap. +func (c *Client) DeleteConfigMap(namespace, name string, options *metav1.DeleteOptions) error { + return c.CoreV1().ConfigMaps(namespace).Delete(name, options) +} + +// UpdateConfigMap will update the given ConfigMap resource. +func (c *Client) UpdateConfigMap(configmap *corev1.ConfigMap) (*corev1.ConfigMap, error) { + klog.V(4).Infof("[UPDATE ConfigMap]: %s", configmap.GetName()) + oldSa, err := c.GetConfigMap(configmap.GetNamespace(), configmap.GetName()) + if err != nil { + return nil, err + } + patchBytes, err := createPatch(oldSa, configmap) + if err != nil { + return nil, fmt.Errorf("error creating patch for ConfigMap: %v", err) + } + return c.CoreV1().ConfigMaps(configmap.GetNamespace()).Patch(configmap.GetName(), types.StrategicMergePatchType, patchBytes) +} diff --git a/pkg/lib/operatorclient/operatorclientmocks/mock_client.go b/pkg/lib/operatorclient/operatorclientmocks/mock_client.go index 9144643a86..a1109fa3d2 100644 --- a/pkg/lib/operatorclient/operatorclientmocks/mock_client.go +++ b/pkg/lib/operatorclient/operatorclientmocks/mock_client.go @@ -886,6 +886,65 @@ func (mr *MockClientInterfaceMockRecorder) ListDeploymentsWithLabels(namespace, return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListDeploymentsWithLabels", reflect.TypeOf((*MockClientInterface)(nil).ListDeploymentsWithLabels), namespace, labels) } +// CreateConfigMap mocks base method +func (m *MockClientInterface) CreateConfigMap(arg0 *v10.ConfigMap) (*v10.ConfigMap, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateConfigMap", arg0) + ret0, _ := ret[0].(*v10.ConfigMap) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateConfigMap indicates an expected call of CreateConfigMap +func (mr *MockClientInterfaceMockRecorder) CreateConfigMap(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateConfigMap", reflect.TypeOf((*MockClientInterface)(nil).CreateConfigMap), arg0) +} + +// GetConfigMap mocks base method +func (m *MockClientInterface) GetConfigMap(namespace, name string) (*v10.ConfigMap, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetConfigMap", namespace, name) + ret0, _ := ret[0].(*v10.ConfigMap) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetConfigMap indicates an expected call of GetConfigMap +func (mr *MockClientInterfaceMockRecorder) GetConfigMap(namespace, name interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConfigMap", reflect.TypeOf((*MockClientInterface)(nil).GetConfigMap), namespace, name) +} + +// UpdateConfigMap mocks base method +func (m *MockClientInterface) UpdateConfigMap(modified *v10.ConfigMap) (*v10.ConfigMap, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateConfigMap", modified) + ret0, _ := ret[0].(*v10.ConfigMap) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateConfigMap indicates an expected call of UpdateConfigMap +func (mr *MockClientInterfaceMockRecorder) UpdateConfigMap(modified interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateConfigMap", reflect.TypeOf((*MockClientInterface)(nil).UpdateConfigMap), modified) +} + +// DeleteConfigMap mocks base method +func (m *MockClientInterface) DeleteConfigMap(namespace, name string, options *v12.DeleteOptions) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteConfigMap", namespace, name, options) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteConfigMap indicates an expected call of DeleteConfigMap +func (mr *MockClientInterfaceMockRecorder) DeleteConfigMap(namespace, name, options interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteConfigMap", reflect.TypeOf((*MockClientInterface)(nil).DeleteConfigMap), namespace, name, options) +} + // MockCustomResourceClient is a mock of CustomResourceClient interface type MockCustomResourceClient struct { ctrl *gomock.Controller @@ -1916,3 +1975,85 @@ func (mr *MockDeploymentClientMockRecorder) ListDeploymentsWithLabels(namespace, mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListDeploymentsWithLabels", reflect.TypeOf((*MockDeploymentClient)(nil).ListDeploymentsWithLabels), namespace, labels) } + +// MockConfigMapClient is a mock of ConfigMapClient interface +type MockConfigMapClient struct { + ctrl *gomock.Controller + recorder *MockConfigMapClientMockRecorder +} + +// MockConfigMapClientMockRecorder is the mock recorder for MockConfigMapClient +type MockConfigMapClientMockRecorder struct { + mock *MockConfigMapClient +} + +// NewMockConfigMapClient creates a new mock instance +func NewMockConfigMapClient(ctrl *gomock.Controller) *MockConfigMapClient { + mock := &MockConfigMapClient{ctrl: ctrl} + mock.recorder = &MockConfigMapClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockConfigMapClient) EXPECT() *MockConfigMapClientMockRecorder { + return m.recorder +} + +// CreateConfigMap mocks base method +func (m *MockConfigMapClient) CreateConfigMap(arg0 *v10.ConfigMap) (*v10.ConfigMap, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateConfigMap", arg0) + ret0, _ := ret[0].(*v10.ConfigMap) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateConfigMap indicates an expected call of CreateConfigMap +func (mr *MockConfigMapClientMockRecorder) CreateConfigMap(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateConfigMap", reflect.TypeOf((*MockConfigMapClient)(nil).CreateConfigMap), arg0) +} + +// GetConfigMap mocks base method +func (m *MockConfigMapClient) GetConfigMap(namespace, name string) (*v10.ConfigMap, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetConfigMap", namespace, name) + ret0, _ := ret[0].(*v10.ConfigMap) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetConfigMap indicates an expected call of GetConfigMap +func (mr *MockConfigMapClientMockRecorder) GetConfigMap(namespace, name interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConfigMap", reflect.TypeOf((*MockConfigMapClient)(nil).GetConfigMap), namespace, name) +} + +// UpdateConfigMap mocks base method +func (m *MockConfigMapClient) UpdateConfigMap(modified *v10.ConfigMap) (*v10.ConfigMap, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateConfigMap", modified) + ret0, _ := ret[0].(*v10.ConfigMap) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateConfigMap indicates an expected call of UpdateConfigMap +func (mr *MockConfigMapClientMockRecorder) UpdateConfigMap(modified interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateConfigMap", reflect.TypeOf((*MockConfigMapClient)(nil).UpdateConfigMap), modified) +} + +// DeleteConfigMap mocks base method +func (m *MockConfigMapClient) DeleteConfigMap(namespace, name string, options *v12.DeleteOptions) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteConfigMap", namespace, name, options) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteConfigMap indicates an expected call of DeleteConfigMap +func (mr *MockConfigMapClientMockRecorder) DeleteConfigMap(namespace, name, options interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteConfigMap", reflect.TypeOf((*MockConfigMapClient)(nil).DeleteConfigMap), namespace, name, options) +}