diff --git a/cmd/manager/main.go b/cmd/manager/main.go index a7b32e577..205461259 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -30,10 +30,8 @@ import ( apiextensionsv1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" k8slabels "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/selection" - "k8s.io/apimachinery/pkg/types" corev1client "k8s.io/client-go/kubernetes/typed/core/v1" _ "k8s.io/client-go/plugin/pkg/client/auth" - "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" crcache "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/client" @@ -162,41 +160,21 @@ func main() { os.Exit(1) } - installNamespaceMapper := helmclient.ObjectToStringMapper(func(obj client.Object) (string, error) { - ext := obj.(*ocv1alpha1.ClusterExtension) - return ext.Spec.InstallNamespace, nil - }) coreClient, err := corev1client.NewForConfig(mgr.GetConfig()) if err != nil { setupLog.Error(err, "unable to create core client") os.Exit(1) } tokenGetter := authentication.NewTokenGetter(coreClient, authentication.WithExpirationDuration(1*time.Hour)) - - restConfigMapper := func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) { - cExt, ok := o.(*ocv1alpha1.ClusterExtension) - if !ok { - return c, nil - } - namespacedName := types.NamespacedName{ - Name: cExt.Spec.ServiceAccount.Name, - Namespace: cExt.Spec.InstallNamespace, - } - tempConfig := rest.AnonymousClientConfig(c) - tempConfig.WrapTransport = func(rt http.RoundTripper) http.RoundTripper { - return &authentication.TokenInjectingRoundTripper{ - Tripper: rt, - TokenGetter: tokenGetter, - Key: namespacedName, - } - } - return tempConfig, nil - } + clientRestConfigMapper := action.ServiceAccountRestConfigMapper(tokenGetter) cfgGetter, err := helmclient.NewActionConfigGetter(mgr.GetConfig(), mgr.GetRESTMapper(), - helmclient.StorageNamespaceMapper(installNamespaceMapper), - helmclient.ClientNamespaceMapper(installNamespaceMapper), - helmclient.RestConfigMapper(restConfigMapper), + helmclient.StorageDriverMapper(action.ChunkedStorageDriverMapper(coreClient, mgr.GetAPIReader(), systemNamespace)), + helmclient.ClientNamespaceMapper(func(obj client.Object) (string, error) { + ext := obj.(*ocv1alpha1.ClusterExtension) + return ext.Spec.InstallNamespace, nil + }), + helmclient.ClientRestConfigMapper(clientRestConfigMapper), ) if err != nil { setupLog.Error(err, "unable to config for creating helm client") @@ -283,7 +261,7 @@ func main() { Applier: applier, InstalledBundleGetter: &controllers.DefaultInstalledBundleGetter{ActionClientGetter: acg}, Finalizers: clusterExtensionFinalizers, - Watcher: contentmanager.New(restConfigMapper, mgr.GetConfig(), mgr.GetRESTMapper()), + Watcher: contentmanager.New(clientRestConfigMapper, mgr.GetConfig(), mgr.GetRESTMapper()), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "ClusterExtension") os.Exit(1) diff --git a/config/base/rbac/role.yaml b/config/base/rbac/role.yaml index 0130a1662..5b45bd02c 100644 --- a/config/base/rbac/role.yaml +++ b/config/base/rbac/role.yaml @@ -24,18 +24,6 @@ rules: verbs: - list - watch -- apiGroups: - - "" - resources: - - secrets - verbs: - - create - - delete - - get - - list - - patch - - update - - watch - apiGroups: - "" resources: @@ -65,3 +53,23 @@ rules: verbs: - patch - update +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: manager-role + namespace: system +rules: +- apiGroups: + - "" + resources: + - secrets + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch diff --git a/config/base/rbac/role_binding.yaml b/config/base/rbac/role_binding.yaml index 2070ede44..fa331e3d4 100644 --- a/config/base/rbac/role_binding.yaml +++ b/config/base/rbac/role_binding.yaml @@ -10,3 +10,17 @@ subjects: - kind: ServiceAccount name: controller-manager namespace: system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: manager-rolebinding + namespace: system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: manager-role +subjects: + - kind: ServiceAccount + name: controller-manager + namespace: system diff --git a/config/samples/olm_v1alpha1_clusterextension.yaml b/config/samples/olm_v1alpha1_clusterextension.yaml index b66c75ac0..5631163ca 100644 --- a/config/samples/olm_v1alpha1_clusterextension.yaml +++ b/config/samples/olm_v1alpha1_clusterextension.yaml @@ -1,10 +1,104 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: argocd +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: argocd-installer + namespace: argocd +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: argocd-installer-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: argocd-installer-clusterrole +subjects: +- kind: ServiceAccount + name: argocd-installer + namespace: argocd +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: argocd-installer-clusterrole +rules: +- apiGroups: ["*"] + resources: ["*"] + verbs: ["*"] +- apiGroups: [apiextensions.k8s.io] + resources: [customresourcedefinitions] + verbs: [get, list, watch, create, update, patch, delete] + resourceNames: + - appprojects.argoproj.io + - argocds.argoproj.io + - applications.argoproj.io + - argocdexports.argoproj.io + - applicationsets.argoproj.io +- apiGroups: [rbac.authorization.k8s.io] + resources: [clusterroles] + verbs: [get, list, watch, create, update, patch, delete] + resourceNames: + - argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx + - argocd-operator-metrics-reader + - argocd-operator.v0-22gmilmgp91wu25is5i2ec598hni8owq3l71bbkl7iz3 +- apiGroups: [rbac.authorization.k8s.io] + resources: [clusterrolebindings] + verbs: [get, list, watch, create, update, patch, delete] + resourceNames: + - argocd-operator.v0-1dhiybrldl1gyksid1dk2dqjsc72psdybc7iyvse5gpx + - argocd-operator.v0-22gmilmgp91wu25is5i2ec598hni8owq3l71bbkl7iz3 +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: argocd-installer-role + namespace: argocd +rules: +- apiGroups: [""] + resources: [serviceaccounts] + verbs: [get, list, watch, create, update, patch, delete] + resourceNames: [argocd-operator-controller-manager] +- apiGroups: [""] + resources: [configmaps] + verbs: [get, list, watch, create, update, patch, delete] + resourceNames: [argocd-operator-manager-config] +- apiGroups: [""] + resources: [services] + verbs: [get, list, watch, create, update, patch, delete] + resourceNames: [argocd-operator-controller-manager-metrics-service] +- apiGroups: [apps] + resources: [deployments] + verbs: [get, list, watch, create, update, patch, delete] + resourceNames: [argocd-operator-controller-manager] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: argocd-installer-binding + namespace: argocd +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: argocd-installer-role +subjects: +- kind: ServiceAccount + name: argocd-installer + namespace: argocd +--- apiVersion: olm.operatorframework.io/v1alpha1 kind: ClusterExtension metadata: - name: clusterextension-sample + name: argocd spec: - installNamespace: default + installNamespace: argocd packageName: argocd-operator version: 0.6.0 serviceAccount: - name: default + name: argocd-installer +--- diff --git a/go.mod b/go.mod index 4ad97adf8..a904952f4 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/onsi/gomega v1.34.1 github.com/operator-framework/api v0.26.0 github.com/operator-framework/catalogd v0.20.0 - github.com/operator-framework/helm-operator-plugins v0.3.1 + github.com/operator-framework/helm-operator-plugins v0.4.0 github.com/operator-framework/operator-registry v1.45.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.9.0 @@ -100,9 +100,9 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dimchansky/utfbom v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/cli v27.0.3+incompatible // indirect + github.com/docker/cli v27.1.1+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect - github.com/docker/docker v26.1.4+incompatible // indirect + github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/docker-credential-helpers v0.8.1 // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect diff --git a/go.sum b/go.sum index 2622d45e9..d3ed3a025 100644 --- a/go.sum +++ b/go.sum @@ -208,12 +208,12 @@ github.com/distribution/distribution/v3 v3.0.0-alpha.1 h1:jn7I1gvjOvmLztH1+1cLiU github.com/distribution/distribution/v3 v3.0.0-alpha.1/go.mod h1:LCp4JZp1ZalYg0W/TN05jarCQu+h4w7xc7ZfQF4Y/cY= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v27.0.3+incompatible h1:usGs0/BoBW8MWxGeEtqPMkzOY56jZ6kYlSN5BLDioCQ= -github.com/docker/cli v27.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE= +github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v26.1.4+incompatible h1:vuTpXDuoga+Z38m1OZHzl7NKisKWaWlhjQk7IDPSLsU= -github.com/docker/docker v26.1.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/docker-credential-helpers v0.8.1 h1:j/eKUktUltBtMzKqmfLB0PAgqYyMHOp5vfsD1807oKo= github.com/docker/docker-credential-helpers v0.8.1/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= @@ -590,8 +590,8 @@ github.com/operator-framework/api v0.26.0 h1:YVntU2NkVl5zSLLwK5kFcH6P3oSvN9QDgTs github.com/operator-framework/api v0.26.0/go.mod h1:3IxOwzVUeGxYlzfwKCcfCyS+q3EEhWA/4kv7UehbeyM= github.com/operator-framework/catalogd v0.20.0 h1:m5ugxf9fjEUaNHy81lSu6jFzTEt0XpEo44+T7g9On+U= github.com/operator-framework/catalogd v0.20.0/go.mod h1:F4KehkAI/bpDI4IVXNxQ7dlWtVBYvc2qkxSa7mIFGRk= -github.com/operator-framework/helm-operator-plugins v0.3.1 h1:D8hF0ufF+2ZtuttYUu4yBlsmHvic/zENm6n9h83ITI0= -github.com/operator-framework/helm-operator-plugins v0.3.1/go.mod h1:5Kx1PyLnRVPyQmLq+frv+HJgSZzXG+W6LavSCxzm8sI= +github.com/operator-framework/helm-operator-plugins v0.4.0 h1:JuN4u7+8XbyK7nnwj5A4eQwpnA9q3DmlZGGFJr7nBDk= +github.com/operator-framework/helm-operator-plugins v0.4.0/go.mod h1:v+6lqkG1vNg64y4WwEpnLn+b/Sr0gLfrWPIjsHci+E8= github.com/operator-framework/operator-lib v0.14.0 h1:er+BgZymZD1im2wytLJiPLZpGALAX6N0gXaHx3PKbO4= github.com/operator-framework/operator-lib v0.14.0/go.mod h1:wUu4Xb9xzXnIpglvaZ3yucTMSlqGXHIoUEH9+5gWiu0= github.com/operator-framework/operator-registry v1.45.0 h1:9c5NshWjPncdZtWEY0cfMnAjx3pShVnjw5UmZXp/xNE= diff --git a/internal/action/restconfig.go b/internal/action/restconfig.go new file mode 100644 index 000000000..a93432fa0 --- /dev/null +++ b/internal/action/restconfig.go @@ -0,0 +1,32 @@ +package action + +import ( + "context" + "net/http" + + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + + ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1" + "github.com/operator-framework/operator-controller/internal/authentication" +) + +func ServiceAccountRestConfigMapper(tokenGetter *authentication.TokenGetter) func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) { + return func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) { + cExt := o.(*ocv1alpha1.ClusterExtension) + saKey := types.NamespacedName{ + Name: cExt.Spec.ServiceAccount.Name, + Namespace: cExt.Spec.InstallNamespace, + } + saConfig := rest.AnonymousClientConfig(c) + saConfig.Wrap(func(rt http.RoundTripper) http.RoundTripper { + return &authentication.TokenInjectingRoundTripper{ + Tripper: rt, + TokenGetter: tokenGetter, + Key: saKey, + } + }) + return saConfig, nil + } +} diff --git a/internal/action/storagedriver.go b/internal/action/storagedriver.go new file mode 100644 index 000000000..db8c02ddb --- /dev/null +++ b/internal/action/storagedriver.go @@ -0,0 +1,110 @@ +package action + +import ( + "context" + "fmt" + + "helm.sh/helm/v3/pkg/storage/driver" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + k8slabels "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/watch" + clientcorev1 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + logf "sigs.k8s.io/controller-runtime/pkg/log" + + helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client" + "github.com/operator-framework/helm-operator-plugins/pkg/storage" +) + +func ChunkedStorageDriverMapper(secretsGetter clientcorev1.SecretsGetter, reader client.Reader, namespace string) helmclient.ObjectToStorageDriverMapper { + secretsClient := newSecretsDelegatingClient(secretsGetter, reader, namespace) + return func(ctx context.Context, object client.Object, config *rest.Config) (driver.Driver, error) { + log := logf.FromContext(ctx).V(2) + ownerRefs := []metav1.OwnerReference{*metav1.NewControllerRef(object, object.GetObjectKind().GroupVersionKind())} + ownerRefSecretClient := helmclient.NewOwnerRefSecretClient(secretsClient, ownerRefs, func(secret *corev1.Secret) bool { + return secret.Type == storage.SecretTypeChunkedIndex + }) + return storage.NewChunkedSecrets(ownerRefSecretClient, "operator-controller", storage.ChunkedSecretsConfig{ + ChunkSize: 1024 * 1024, + MaxReadChunks: 10, + MaxWriteChunks: 10, + Log: func(format string, args ...interface{}) { log.Info(fmt.Sprintf(format, args...)) }, + }), nil + } +} + +var _ clientcorev1.SecretInterface = &secretsDelegatingClient{} + +type secretsDelegatingClient struct { + clientcorev1.SecretInterface + reader client.Reader + namespace string +} + +func newSecretsDelegatingClient(secretsGetter clientcorev1.SecretsGetter, reader client.Reader, namespace string) clientcorev1.SecretInterface { + return &secretsDelegatingClient{ + SecretInterface: secretsGetter.Secrets(namespace), + namespace: namespace, + reader: reader, + } +} + +func (s secretsDelegatingClient) Get(ctx context.Context, name string, opts metav1.GetOptions) (*corev1.Secret, error) { + var secret corev1.Secret + if err := s.reader.Get(ctx, client.ObjectKey{Namespace: s.namespace, Name: name}, &secret, &client.GetOptions{Raw: &opts}); err != nil { + return nil, err + } + return &secret, nil +} + +func (s secretsDelegatingClient) List(ctx context.Context, opts metav1.ListOptions) (*corev1.SecretList, error) { + listOpts, err := metaOptionsToClientOptions(s.namespace, opts) + if err != nil { + return nil, err + } + + var secrets corev1.SecretList + if err := s.reader.List(ctx, &secrets, listOpts); err != nil { + return nil, err + } + return &secrets, nil +} + +func (s secretsDelegatingClient) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { + panic("intentionally not implemented: watch is not intended to be called") +} + +func metaOptionsToClientOptions(namespace string, opts metav1.ListOptions) (*client.ListOptions, error) { + clientListOptions := &client.ListOptions{ + Namespace: namespace, + Limit: opts.Limit, + Continue: opts.Continue, + } + + if opts.LabelSelector != "" { + labelSelector, err := k8slabels.Parse(opts.LabelSelector) + if err != nil { + return nil, err + } + clientListOptions.LabelSelector = labelSelector + } + + if opts.FieldSelector != "" { + fieldSelector, err := fields.ParseSelector(opts.FieldSelector) + if err != nil { + return nil, err + } + clientListOptions.FieldSelector = fieldSelector + } + + opts.LabelSelector = "" + opts.FieldSelector = "" + opts.Limit = 0 + opts.Continue = "" + clientListOptions.Raw = &opts + + return clientListOptions, nil +} diff --git a/internal/controllers/clusterextension_controller.go b/internal/controllers/clusterextension_controller.go index 16142ffdf..d84033253 100644 --- a/internal/controllers/clusterextension_controller.go +++ b/internal/controllers/clusterextension_controller.go @@ -81,7 +81,7 @@ type InstalledBundleGetter interface { //+kubebuilder:rbac:groups=olm.operatorframework.io,resources=clusterextensions,verbs=get;list;watch;update;patch //+kubebuilder:rbac:groups=olm.operatorframework.io,resources=clusterextensions/status,verbs=update;patch //+kubebuilder:rbac:groups=olm.operatorframework.io,resources=clusterextensions/finalizers,verbs=update -//+kubebuilder:rbac:groups=core,resources=secrets,verbs=create;update;patch;delete;get;list;watch +//+kubebuilder:rbac:namespace=system,groups=core,resources=secrets,verbs=create;update;patch;delete;deletecollection;get;list;watch //+kubebuilder:rbac:groups=core,resources=serviceaccounts/token,verbs=create //+kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,verbs=get @@ -196,6 +196,7 @@ func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alp return ctrl.Result{}, nil } + l.V(1).Info("getting installed bundle") installedBundle, err := r.InstalledBundleGetter.GetInstalledBundle(ctx, ext) if err != nil { ext.Status.InstalledBundle = nil