From fa02c74a673d9a76e4360b6c329c1eb088375c14 Mon Sep 17 00:00:00 2001 From: Tamal Saha Date: Mon, 11 Nov 2024 18:53:53 -0800 Subject: [PATCH] Add client org reconciler Signed-off-by: Tamal Saha --- Dockerfile.dbg | 14 +- Makefile | 2 +- go.mod | 4 +- go.sum | 8 +- pkg/apiserver/apiserver.go | 12 + pkg/controllers/clientorg/clientorg.go | 23 + .../namespace/namespace_controller.go | 476 ++++++++++++++++++ .../prometheus/prometheus_controller.go | 26 +- pkg/registry/ui/dashboardgroup/storage.go | 68 ++- .../kmodules.xyz/client-go/api/v1/cluster.go | 24 + .../client-go/api/v1/zz_generated.deepcopy.go | 48 ++ .../client-go/client/delegated.go | 60 ++- .../monitoring-agent-api/api/v1/appbinding.go | 10 +- .../api/v1/openapi_generated.go | 12 + vendor/modules.txt | 4 +- 15 files changed, 743 insertions(+), 48 deletions(-) create mode 100644 pkg/controllers/clientorg/clientorg.go create mode 100644 pkg/controllers/namespace/namespace_controller.go diff --git a/Dockerfile.dbg b/Dockerfile.dbg index 52f0c942e..f003a2349 100644 --- a/Dockerfile.dbg +++ b/Dockerfile.dbg @@ -12,10 +12,22 @@ # See the License for the specific language governing permissions and # limitations under the License. +FROM ghcr.io/appscode/dlv:1.23 + FROM {ARG_FROM} LABEL org.opencontainers.image.source https://github.com/open-viz/grafana-tools +RUN set -x \ + && apt-get update \ + && apt-get upgrade -y \ + && apt-get install -y --no-install-recommends ca-certificates \ + && rm -rf /var/lib/apt/lists/* /usr/share/doc /usr/share/man /tmp/* \ + && echo 'Etc/UTC' > /etc/timezone + ADD bin/{ARG_OS}_{ARG_ARCH}/{ARG_BIN} /{ARG_BIN} +COPY --from=0 /usr/local/bin/dlv /bin/dlv + +EXPOSE 40000 -ENTRYPOINT ["/{ARG_BIN}"] +ENTRYPOINT ["/bin/dlv", "--listen=:40000", "--headless=true", "--api-version=2", "exec", "/{ARG_BIN}", "--"] diff --git a/Makefile b/Makefile index 84a5b1514..9b6a68641 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,7 @@ endif SRC_PKGS := cmd pkg # directories which hold app source excluding tests (not vendored) SRC_DIRS := $(SRC_PKGS) test hack/gendocs # directories which hold app source (not vendored) -DOCKER_PLATFORMS := linux/amd64 linux/arm linux/arm64 +DOCKER_PLATFORMS := linux/amd64 linux/arm64 BIN_PLATFORMS := $(DOCKER_PLATFORMS) # Used internally. Users should pass GOOS and/or GOARCH. diff --git a/go.mod b/go.mod index c93f61625..d6b527e6c 100644 --- a/go.mod +++ b/go.mod @@ -34,9 +34,9 @@ require ( k8s.io/klog/v2 v2.130.1 k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 kmodules.xyz/authorizer v0.29.1 - kmodules.xyz/client-go v0.30.32 + kmodules.xyz/client-go v0.30.38 kmodules.xyz/custom-resources v0.30.0 - kmodules.xyz/monitoring-agent-api v0.30.2 + kmodules.xyz/monitoring-agent-api v0.30.4 moul.io/http2curl/v2 v2.3.1-0.20221024080105-10c404f653f7 sigs.k8s.io/controller-runtime v0.18.4 x-helm.dev/apimachinery v0.0.16 diff --git a/go.sum b/go.sum index 59dbc7669..3748659cc 100644 --- a/go.sum +++ b/go.sum @@ -774,14 +774,14 @@ kmodules.xyz/apiversion v0.2.0 h1:vAQYqZFm4xu4pbB1cAdHbFEPES6EQkcR4wc06xdTOWk= kmodules.xyz/apiversion v0.2.0/go.mod h1:oPX8g8LvlPdPX3Yc5YvCzJHQnw3YF/X4/jdW0b1am80= kmodules.xyz/authorizer v0.29.1 h1:uByGGoryKbZcfiEAhjcK/Y345I9mygNQP7DVpkMbNQQ= kmodules.xyz/authorizer v0.29.1/go.mod h1:kZRhclL8twzyt2bQuJQJbpYww2sc+qFr8I5PPoq/sWY= -kmodules.xyz/client-go v0.30.32 h1:y1qb4IJwYdkROLcc7e0UcJSDj8D2YeLsawAWHnCF+JU= -kmodules.xyz/client-go v0.30.32/go.mod h1:CAu+JlA8RVGtj6LQHu0Q1w2mnFUajuti49c7T1AvGdM= +kmodules.xyz/client-go v0.30.38 h1:kAQ3FdgX2HbkmfFGEoeKz7fmJYWo1Ndgdum50aaHyI0= +kmodules.xyz/client-go v0.30.38/go.mod h1:CAu+JlA8RVGtj6LQHu0Q1w2mnFUajuti49c7T1AvGdM= kmodules.xyz/crd-schema-fuzz v0.29.1 h1:zJTlWYOrT5dsVVHW8HGcnR/vaWfxQfNh11QwTtkYpcs= kmodules.xyz/crd-schema-fuzz v0.29.1/go.mod h1:n708z9YQqLMP2KNLQVgBcRJw1QpSWLvpNCEi+KJDOYE= kmodules.xyz/custom-resources v0.30.0 h1:vR3CbseHMLwR4GvtcJJuRuwIV8voKqFqNii27rMcm1o= kmodules.xyz/custom-resources v0.30.0/go.mod h1:ZsTuI2mLG2s3byre7bHmpxJ9w0HDqAkRTL1+izGFI24= -kmodules.xyz/monitoring-agent-api v0.30.2 h1:sAgz5P5EXZqhlj1NzJ+QltAgeIx5bGSMj+aYy2EiKaw= -kmodules.xyz/monitoring-agent-api v0.30.2/go.mod h1:BoZFPDDRB7J39CcUsSDlzgW8PQCwik4ILPleyUob+Mg= +kmodules.xyz/monitoring-agent-api v0.30.4 h1:6CTKxYJKpWDsDYb0WRBHGFoW3xQof05d+W8CC34BZMc= +kmodules.xyz/monitoring-agent-api v0.30.4/go.mod h1:ZuTQ5uGi6H80QLsOTuuC7m58dfXDGUv0YB+s059gnr4= moul.io/http2curl/v2 v2.3.1-0.20221024080105-10c404f653f7 h1:NykkTlRB+X40z86cLHdEmuoTxhNKhQebLT379b1EumA= moul.io/http2curl/v2 v2.3.1-0.20221024080105-10c404f653f7/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index 63bf47b00..242b64f01 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -27,6 +27,7 @@ import ( "go.openviz.dev/apimachinery/apis/ui" uiinstall "go.openviz.dev/apimachinery/apis/ui/install" uiapi "go.openviz.dev/apimachinery/apis/ui/v1alpha1" + namespacecontroller "go.openviz.dev/grafana-tools/pkg/controllers/namespace" promtehsucontroller "go.openviz.dev/grafana-tools/pkg/controllers/prometheus" "go.openviz.dev/grafana-tools/pkg/controllers/ranchertoken" servicemonitorcontroller "go.openviz.dev/grafana-tools/pkg/controllers/servicemonitor" @@ -202,6 +203,17 @@ func (c completedConfig) New(ctx context.Context) (*UIServer, error) { Group: monitoring.GroupName, Kind: monitoringv1.PrometheusesKind, }, func(ctx context.Context, mgr ctrl.Manager) { + if err = namespacecontroller.NewReconciler( + mgr.GetClient(), + bc, + cid, + c.ExtraConfig.HubUID, + d, + ).SetupWithManager(mgr); err != nil { + klog.Error(err, "unable to create controller", "controller", "ClientOrg") + os.Exit(1) + } + if err = promtehsucontroller.NewReconciler( mgr.GetClient(), bc, diff --git a/pkg/controllers/clientorg/clientorg.go b/pkg/controllers/clientorg/clientorg.go new file mode 100644 index 000000000..2945b8e94 --- /dev/null +++ b/pkg/controllers/clientorg/clientorg.go @@ -0,0 +1,23 @@ +/* +Copyright AppsCode Inc. and Contributors + +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. +*/ + +package clientorg + +import "fmt" + +func MonitoringNamespace(clientNS string) string { + return fmt.Sprintf("%s-monitoring", clientNS) +} diff --git a/pkg/controllers/namespace/namespace_controller.go b/pkg/controllers/namespace/namespace_controller.go new file mode 100644 index 000000000..fbd447119 --- /dev/null +++ b/pkg/controllers/namespace/namespace_controller.go @@ -0,0 +1,476 @@ +/* +Copyright AppsCode Inc. and Contributors + +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. +*/ + +package namespace + +import ( + "context" + "fmt" + + "go.openviz.dev/apimachinery/apis/openviz/v1alpha1" + openvizapi "go.openviz.dev/apimachinery/apis/openviz/v1alpha1" + "go.openviz.dev/grafana-tools/pkg/controllers/clientorg" + "go.openviz.dev/grafana-tools/pkg/controllers/prometheus" + "go.openviz.dev/grafana-tools/pkg/detector" + + monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" + core "k8s.io/api/core/v1" + rbac "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/json" + "k8s.io/klog/v2" + "k8s.io/utils/ptr" + kutil "kmodules.xyz/client-go" + kmapi "kmodules.xyz/client-go/api/v1" + cu "kmodules.xyz/client-go/client" + clustermeta "kmodules.xyz/client-go/cluster" + core_util "kmodules.xyz/client-go/core/v1" + "kmodules.xyz/client-go/meta" + appcatalog "kmodules.xyz/custom-resources/apis/appcatalog/v1alpha1" + mona "kmodules.xyz/monitoring-agent-api/api/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +// ClientOrgReconciler reconciles a GatewayConfig object +type ClientOrgReconciler struct { + kc client.Client + scheme *runtime.Scheme + bc *prometheus.Client + clusterUID string + hubUID string + d detector.Detector +} + +func NewReconciler(kc client.Client, bc *prometheus.Client, clusterUID, hubUID string, d detector.Detector) *ClientOrgReconciler { + return &ClientOrgReconciler{ + kc: kc, + scheme: kc.Scheme(), + bc: bc, + clusterUID: clusterUID, + hubUID: hubUID, + d: d, + } +} + +const ( + srcRefKey = "meta.appcode.com/source" + srcHashKey = "meta.appcode.com/hash" + + crClientOrgMonitoring = "appscode:client-org:monitoring" +) + +func (r *ClientOrgReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := log.FromContext(ctx) + + var ns core.Namespace + if err := r.kc.Get(ctx, req.NamespacedName, &ns); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + if ns.Labels[kmapi.ClientOrgKey] == "" { + return ctrl.Result{}, nil + } + clientOrgId := ns.Annotations[kmapi.AceOrgIDKey] + if clientOrgId == "" { + return ctrl.Result{}, nil + } + + if ready, err := r.d.Ready(); err != nil || !ready { + return ctrl.Result{}, err + } + if r.d.RancherManaged() && r.d.Federated() { + return ctrl.Result{}, fmt.Errorf("client organization mode is not supported when federated prometheus is used in a Rancher managed cluster") + } + + if ns.DeletionTimestamp != nil { + if r.bc != nil { + err := r.bc.Unregister(mona.PrometheusContext{ + ClusterUID: r.clusterUID, + ProjectId: "", + Default: false, + IssueToken: true, + ClientOrgID: clientOrgId, + }) + if err != nil { + return ctrl.Result{}, err + } + } + + vt, err := cu.CreateOrPatch(context.TODO(), r.kc, &ns, func(in client.Object, createOp bool) client.Object { + obj := in.(*core.Namespace) + obj.ObjectMeta = core_util.RemoveFinalizer(obj.ObjectMeta, mona.PrometheusKey) + + return obj + }) + if err != nil { + return ctrl.Result{}, err + } + klog.Infof("%s Namespace %s to remove finalizer %s", vt, ns.Name, mona.PrometheusKey) + return ctrl.Result{}, nil + } + + vt, err := cu.CreateOrPatch(context.TODO(), r.kc, &ns, func(in client.Object, createOp bool) client.Object { + obj := in.(*core.Namespace) + obj.ObjectMeta = core_util.AddFinalizer(obj.ObjectMeta, mona.PrometheusKey) + + return obj + }) + if err != nil { + return ctrl.Result{}, err + } + klog.Infof("%s Namespace %s to add finalizer %s", vt, ns.Name, mona.PrometheusKey) + + // create {client}-monitoring namespace + monNamespace := core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: clientorg.MonitoringNamespace(ns.Name), + }, + } + if err := r.kc.Get(ctx, client.ObjectKeyFromObject(&monNamespace), &monNamespace); apierrors.IsNotFound(err) { + if err := r.kc.Create(ctx, &monNamespace); err != nil { + return ctrl.Result{}, err + } + } else if err != nil { + return ctrl.Result{}, err + } + + // create client-org monitoring permission to generate grafana links + rb := rbac.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: crClientOrgMonitoring, + Namespace: monNamespace.Name, + }, + } + rbvt, err := cu.CreateOrPatch(context.TODO(), r.kc, &rb, func(in client.Object, createOp bool) client.Object { + obj := in.(*rbac.RoleBinding) + + obj.RoleRef = rbac.RoleRef{ + APIGroup: rbac.GroupName, + Kind: "ClusterRole", + Name: crClientOrgMonitoring, + } + + obj.Subjects = []rbac.Subject{ + { + APIGroup: rbac.GroupName, + Kind: "Group", + Name: fmt.Sprintf("ace.org.%s", clientOrgId), + }, + } + + return obj + }) + if err != nil { + return ctrl.Result{}, err + } + klog.Infof("%s role binding %s/%s", rbvt, rb.Namespace, rb.Name) + + // confirm trickter sa registered + var saList core.ServiceAccountList + if err := r.kc.List(ctx, &saList); err != nil { + return ctrl.Result{}, err + } + var saKey types.NamespacedName + for _, sa := range saList.Items { + if sa.Name != prometheus.ServiceAccountTrickster { + continue + } + if sa.Annotations[prometheus.RegisteredKey] != "" { + } + + saKey = types.NamespacedName{ + Namespace: sa.Namespace, + Name: sa.Name, + } + break + } + if saKey.Namespace == "" { + return ctrl.Result{}, fmt.Errorf("service account %s is not registered", prometheus.ServiceAccountTrickster) + } + + var promList monitoringv1.PrometheusList + if err := r.kc.List(ctx, &promList, client.InNamespace(saKey.Namespace)); err != nil { + return ctrl.Result{}, err + } else if len(promList.Items) == 0 { + return ctrl.Result{}, fmt.Errorf("prometheus not found in namespace %s", saKey.Namespace) + } else if len(promList.Items) > 1 { + return ctrl.Result{}, fmt.Errorf("more than one prometheus found in namespace %s", saKey.Namespace) + } + // prom := promList.Items[0] + + var promService core.Service + err = r.kc.Get(context.TODO(), client.ObjectKeyFromObject(promList.Items[0]), &promService) + if err != nil { + return ctrl.Result{}, err + } + + var saToken string + var caCrt string + s, err := cu.GetServiceAccountTokenSecret(r.kc, saKey) + if err != nil { + return ctrl.Result{}, err + } + saToken = string(s.Data["token"]) + caCrt = string(s.Data["ca.crt"]) + + var pcfg mona.PrometheusConfig + pcfg.Service = mona.ServiceSpec{ + Scheme: "http", + Name: promService.Name, + Namespace: promService.Namespace, + Port: "", + Path: "", + Query: "", + } + for _, p := range promService.Spec.Ports { + if p.Name == prometheus.PortPrometheus { + pcfg.Service.Port = fmt.Sprintf("%d", p.Port) + } + } + // pcfg.URL = fmt.Sprintf("%s/api/v1/namespaces/%s/services/%s:%s:%s/proxy/", r.cfg.Host, pcfg.Service.Namespace, pcfg.Service.Scheme, pcfg.Service.Name, pcfg.Service.Port) + + // remove basic auth and client cert auth + //if rancherToken != nil { + // pcfg.BearerToken = rancherToken.Token + //} else { + pcfg.BearerToken = saToken + // } + pcfg.BasicAuth = mona.BasicAuth{} + pcfg.TLS.Cert = "" + pcfg.TLS.Key = "" + pcfg.TLS.Ca = caCrt + + cm, err := clustermeta.ClusterMetadata(r.kc) + if err != nil { + return ctrl.Result{}, err + } + state := cm.State() + + applyMarkers := func(in client.Object, createOp bool) client.Object { + obj := in.(*core.Namespace) + if obj.Annotations == nil { + obj.Annotations = map[string]string{} + } + obj.Annotations[prometheus.RegisteredKey] = state + return obj + } + + if r.bc != nil && + monNamespace.Annotations[prometheus.RegisteredKey] != state { + resp, err := r.bc.Register(mona.PrometheusContext{ + HubUID: r.hubUID, + ClusterUID: r.clusterUID, + ProjectId: "", + Default: false, + IssueToken: true, + ClientOrgID: clientOrgId, + }, pcfg) + if err != nil { + return ctrl.Result{}, err + } + + err = r.CreateGrafanaAppBinding(monNamespace.Name, resp) + if err != nil { + return ctrl.Result{}, err + } + + rbvt, err := cu.CreateOrPatch(context.TODO(), r.kc, &monNamespace, applyMarkers) + if err != nil { + return ctrl.Result{}, err + } + klog.Infof("%s namespace %s with %s annotation", rbvt, monNamespace.Name, prometheus.RegisteredKey) + } + + var dashboardList v1alpha1.GrafanaDashboardList + if err := r.kc.List(ctx, &dashboardList); err != nil { + return ctrl.Result{}, err + } + for _, dashboard := range dashboardList.Items { + if _, found := dashboard.Annotations[srcRefKey]; found { + continue + } + copiedDashboard := dashboard.DeepCopy() + copiedDashboard.Namespace = monNamespace.Name + + opresult, err := cu.CreateOrPatch(ctx, r.kc, copiedDashboard, func(obj client.Object, createOp bool) client.Object { + if createOp { + copiedDashboard.ResourceVersion = "" + } + + if copiedDashboard.Annotations == nil { + copiedDashboard.Annotations = map[string]string{} + } + copiedDashboard.Annotations[srcRefKey] = client.ObjectKeyFromObject(&dashboard).String() + copiedDashboard.Annotations[srcHashKey] = meta.ObjectHash(&dashboard) + + // use client Grafana appbinding + copiedDashboard.Spec.GrafanaRef = &kmapi.ObjectReference{ + Namespace: monNamespace.Name, + Name: "grafana", + } + + return copiedDashboard + }) + if err != nil { + return ctrl.Result{}, err + } + if opresult != kutil.VerbUnchanged { + log.Info(fmt.Sprintf("%s GrafanaDashboard %s/%s", opresult, copiedDashboard.Namespace, copiedDashboard.Name)) + } + } + + return ctrl.Result{}, nil +} + +func (r *ClientOrgReconciler) CreateGrafanaAppBinding(monNamespace string, resp *prometheus.GrafanaDatasourceResponse) error { + ab := appcatalog.AppBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "grafana", + Namespace: monNamespace, + }, + } + + abvt, err := cu.CreateOrPatch(context.TODO(), r.kc, &ab, func(in client.Object, createOp bool) client.Object { + obj := in.(*appcatalog.AppBinding) + + //ref := metav1.NewControllerRef(prom, schema.GroupVersionKind{ + // Group: monitoring.GroupName, + // Version: monitoringv1.Version, + // Kind: "Prometheus", + //}) + //obj.OwnerReferences = []metav1.OwnerReference{*ref} + // + //if obj.Annotations == nil { + // obj.Annotations = make(map[string]string) + //} + //obj.Annotations["monitoring.appscode.com/is-default-grafana"] = "true" + + obj.Spec.Type = "Grafana" + obj.Spec.AppRef = nil + obj.Spec.ClientConfig = appcatalog.ClientConfig{ + URL: ptr.To(resp.Grafana.URL), + //Service: &appcatalog.ServiceReference{ + // Scheme: "http", + // Namespace: svc.Namespace, + // Name: svc.Name, + // Port: 0, + // Path: "", + // Query: "", + //}, + //InsecureSkipTLSVerify: false, + //CABundle: nil, + //ServerName: "", + } + obj.Spec.Secret = &core.LocalObjectReference{ + Name: ab.Name + "-auth", + } + + // TODO: handle TLS config returned in resp + if caCert := r.bc.CACert(); len(caCert) > 0 { + obj.Spec.ClientConfig.CABundle = caCert + } + + params := openvizapi.GrafanaConfiguration{ + TypeMeta: metav1.TypeMeta{ + Kind: "GrafanaConfiguration", + APIVersion: openvizapi.SchemeGroupVersion.String(), + }, + Datasource: resp.Datasource, + FolderID: resp.FolderID, + } + paramBytes, err := json.Marshal(params) + if err != nil { + panic(err) + } + obj.Spec.Parameters = &runtime.RawExtension{ + Raw: paramBytes, + } + + return obj + }) + if err == nil { + klog.Infof("%s AppBinding %s/%s", abvt, ab.Namespace, ab.Name) + + authSecret := core.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: ab.Name + "-auth", + Namespace: monNamespace, + }, + } + + svt, e2 := cu.CreateOrPatch(context.TODO(), r.kc, &authSecret, func(in client.Object, createOp bool) client.Object { + obj := in.(*core.Secret) + + ref := metav1.NewControllerRef(&ab, schema.GroupVersionKind{ + Group: appcatalog.SchemeGroupVersion.Group, + Version: appcatalog.SchemeGroupVersion.Version, + Kind: "AppBinding", + }) + obj.OwnerReferences = []metav1.OwnerReference{*ref} + + obj.StringData = map[string]string{ + "token": resp.Grafana.BearerToken, + } + + return obj + }) + if e2 == nil { + klog.Infof("%s Grafana auth secret %s/%s", svt, authSecret.Namespace, authSecret.Name) + } + } + + return err +} + +// SetupWithManager sets up the controller with the Manager. +func (r *ClientOrgReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&core.Namespace{}, builder.WithPredicates(predicate.NewPredicateFuncs(func(obj client.Object) bool { + return obj.GetLabels()[kmapi.ClientOrgKey] == "true" + }))). + Watches(&core.ServiceAccount{}, handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, _ client.Object) []reconcile.Request { + var list core.NamespaceList + if err := r.kc.List(ctx, &list, client.MatchingLabels{ + kmapi.ClientOrgKey: "true", + }); err != nil { + return nil + } + reqs := make([]reconcile.Request, 0, len(list.Items)) + for _, ns := range list.Items { + reqs = append(reqs, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: ns.Name, + }, + }) + } + return reqs + }), builder.WithPredicates(predicate.NewPredicateFuncs(func(obj client.Object) bool { + return obj.GetName() == prometheus.ServiceAccountTrickster && + obj.GetLabels()[prometheus.RegisteredKey] != "" + }))). + Named("namespace"). + Complete(r) +} diff --git a/pkg/controllers/prometheus/prometheus_controller.go b/pkg/controllers/prometheus/prometheus_controller.go index afbf70196..083125edd 100644 --- a/pkg/controllers/prometheus/prometheus_controller.go +++ b/pkg/controllers/prometheus/prometheus_controller.go @@ -54,11 +54,11 @@ import ( ) const ( - portPrometheus = "http-web" - saTrickster = "trickster" - crTrickster = "appscode:trickster:proxy" + PortPrometheus = "http-web" + ServiceAccountTrickster = "trickster" + crTrickster = "appscode:trickster:proxy" - registeredKey = mona.GroupName + "/registered" + RegisteredKey = mona.GroupName + "/registered" tokenIDKey = mona.GroupName + "/token-id" presetsMonitoring = "monitoring-presets" appBindingPrometheus = "default-prometheus" @@ -227,7 +227,7 @@ func (r *PrometheusReconciler) SetupClusterForPrometheus(ctx context.Context, pr } else { sa := core.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ - Name: saTrickster, + Name: ServiceAccountTrickster, Namespace: key.Namespace, }, } @@ -310,7 +310,7 @@ func (r *PrometheusReconciler) SetupClusterForPrometheus(ctx context.Context, pr obj.Subjects = []rbac.Subject{ { Kind: "ServiceAccount", - Name: saTrickster, + Name: ServiceAccountTrickster, Namespace: key.Namespace, }, } @@ -338,7 +338,7 @@ func (r *PrometheusReconciler) SetupClusterForPrometheus(ctx context.Context, pr Query: "", } for _, p := range svc.Spec.Ports { - if p.Name == portPrometheus { + if p.Name == PortPrometheus { pcfg.Service.Port = fmt.Sprintf("%d", p.Port) } } @@ -360,7 +360,7 @@ func (r *PrometheusReconciler) SetupClusterForPrometheus(ctx context.Context, pr if obj.Annotations == nil { obj.Annotations = map[string]string{} } - obj.Annotations[registeredKey] = state + obj.Annotations[RegisteredKey] = state if rancherToken != nil { obj.Annotations[tokenIDKey] = rancherToken.TokenID } else { @@ -370,16 +370,16 @@ func (r *PrometheusReconciler) SetupClusterForPrometheus(ctx context.Context, pr } // fix legacy deployments - if rb.Annotations[registeredKey] == "true" { + if rb.Annotations[RegisteredKey] == "true" { rbvt, err = cu.CreateOrPatch(context.TODO(), r.kc, &rb, applyMarkers) if err != nil { return err } - klog.Infof("%s rolebinding %s/%s with %s annotation", rbvt, rb.Namespace, rb.Name, registeredKey) + klog.Infof("%s rolebinding %s/%s with %s annotation", rbvt, rb.Namespace, rb.Name, RegisteredKey) return nil } else if r.bc != nil && - (rb.Annotations[registeredKey] != state || + (rb.Annotations[RegisteredKey] != state || (rancherToken != nil && rb.Annotations[tokenIDKey] != rancherToken.TokenID)) { var projectId string if !isDefault { @@ -414,7 +414,7 @@ func (r *PrometheusReconciler) SetupClusterForPrometheus(ctx context.Context, pr if err != nil { return err } - klog.Infof("%s rolebinding %s/%s with %s annotation", rbvt, rb.Namespace, rb.Name, registeredKey) + klog.Infof("%s rolebinding %s/%s with %s annotation", rbvt, rb.Namespace, rb.Name, RegisteredKey) } return nil @@ -634,7 +634,7 @@ func (r *PrometheusReconciler) CreatePrometheusAppBinding(prom *monitoringv1.Pro // ServerName: "", } for _, p := range svc.Spec.Ports { - if p.Name == portPrometheus { + if p.Name == PortPrometheus { obj.Spec.ClientConfig.Service.Port = p.Port } } diff --git a/pkg/registry/ui/dashboardgroup/storage.go b/pkg/registry/ui/dashboardgroup/storage.go index f316c0ef5..07a2cda6a 100644 --- a/pkg/registry/ui/dashboardgroup/storage.go +++ b/pkg/registry/ui/dashboardgroup/storage.go @@ -26,15 +26,18 @@ import ( openvizapi "go.openviz.dev/apimachinery/apis/openviz/v1alpha1" uiapi "go.openviz.dev/apimachinery/apis/ui/v1alpha1" + "go.openviz.dev/grafana-tools/pkg/controllers/clientorg" "go.openviz.dev/grafana-tools/pkg/detector" "github.com/grafana-tools/sdk" "github.com/pkg/errors" + core "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/json" + "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authorization/authorizer" apirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" @@ -94,6 +97,10 @@ func (r *Storage) Create(ctx context.Context, obj runtime.Object, _ rest.Validat return nil, apierrors.NewBadRequest("missing apirequest") } + if ready, err := r.d.Ready(); !ready { + return nil, apierrors.NewInternalError(err) + } + ds, err := r.datasource(in) if err != nil { return nil, err @@ -182,6 +189,11 @@ func (r *Storage) getDashboardLink( timeRange *uiapi.TimeRange, embed bool, ) (*uiapi.DashboardResponse, error) { + user, ok := apirequest.UserFrom(ctx) + if !ok { + return nil, apierrors.NewBadRequest("missing user info") + } + var d openvizapi.GrafanaDashboard if req.ObjectReference != nil { ns := req.Namespace @@ -194,11 +206,23 @@ func (r *Storage) getDashboardLink( } } else if req.Title != "" { var dashboardList openvizapi.GrafanaDashboardList - // any namespace, using default grafana and with the given title - if err := r.kc.List(ctx, &dashboardList, client.MatchingFields{ - mona.DefaultGrafanaKey: "true", - openvizapi.GrafanaDashboardTitleKey: req.Title, - }); err != nil { + dsNamespace, useClientDashboard, err := useClientOrgDashboard(ctx, r.kc, user) + if err != nil { + return nil, err + } + if useClientDashboard { + // in {client}-monitoring namespace + err = r.kc.List(ctx, &dashboardList, client.InNamespace(dsNamespace), client.MatchingFields{ + openvizapi.GrafanaDashboardTitleKey: req.Title, + }) + } else { + // any namespace, using default grafana and with the given title + err = r.kc.List(ctx, &dashboardList, client.MatchingFields{ + mona.DefaultGrafanaKey: "true", + openvizapi.GrafanaDashboardTitleKey: req.Title, + }) + } + if err != nil { return nil, err } if len(dashboardList.Items) == 0 { @@ -213,11 +237,6 @@ func (r *Storage) getDashboardLink( d = dashboardList.Items[0] } - user, ok := apirequest.UserFrom(ctx) - if !ok { - return nil, apierrors.NewBadRequest("missing user info") - } - { attrs := authorizer.AttributesRecord{ User: user, @@ -232,7 +251,7 @@ func (r *Storage) getDashboardLink( return nil, apierrors.NewInternalError(err) } if decision != authorizer.DecisionAllow { - return nil, apierrors.NewForbidden(r.gr, d.Name, errors.New(why)) + return nil, apierrors.NewForbidden(r.gr, d.Namespace+"/"+d.Name, errors.New(why)) } } if d.Status.Dashboard == nil { @@ -258,7 +277,10 @@ func (r *Storage) getDashboardLink( return nil, apierrors.NewInternalError(err) } if decision != authorizer.DecisionAllow { - return nil, apierrors.NewForbidden(r.gr, g.Name, errors.New(why)) + return nil, apierrors.NewForbidden(schema.GroupResource{ + Group: appcatalog.GroupName, + Resource: appcatalogapi.ResourceApps, + }, g.Namespace+"/"+g.Name, errors.New(why)) } } @@ -336,6 +358,28 @@ func (r *Storage) getDashboardLink( return resp, nil } +func useClientOrgDashboard(ctx context.Context, kc client.Client, user user.Info) (string, bool, error) { + orgId, found := user.GetExtra()[kmapi.AceOrgIDKey] + if !found || len(orgId) == 0 || len(orgId) > 1 { + return "", false, nil + } + + var nsList core.NamespaceList + err := kc.List(ctx, &nsList, client.MatchingLabels{ + kmapi.ClientOrgKey: "true", + }) + if err != nil { + return "", false, err + } + + for _, ns := range nsList.Items { + if ns.Annotations[kmapi.AceOrgIDKey] == orgId[0] { + return clientorg.MonitoringNamespace(ns.Name), true, nil + } + } + return "", false, nil +} + func toEmbeddedPanel(p *sdk.Panel, grafanaHost string, d openvizapi.GrafanaDashboard, refreshInterval string, timeRange *uiapi.TimeRange, req *uiapi.DashboardRequest, panelMap map[string]int) (*uiapi.PanelLinkResponse, error) { includePanel := func(title string) bool { if len(panelMap) == 0 { diff --git a/vendor/kmodules.xyz/client-go/api/v1/cluster.go b/vendor/kmodules.xyz/client-go/api/v1/cluster.go index ca9579cff..b40cd8d1e 100644 --- a/vendor/kmodules.xyz/client-go/api/v1/cluster.go +++ b/vendor/kmodules.xyz/client-go/api/v1/cluster.go @@ -35,12 +35,21 @@ const ( HostingProviderGeneric HostingProvider = "Generic" HostingProviderGKE HostingProvider = "GKE" HostingProviderLinode HostingProvider = "Linode" + HostingProviderAkamai HostingProvider = "Akamai" HostingProviderPacket HostingProvider = "Packet" HostingProviderRancher HostingProvider = "Rancher" HostingProviderScaleway HostingProvider = "Scaleway" HostingProviderVultr HostingProvider = "Vultr" ) +func (h HostingProvider) ConvertToPreferredProvider() HostingProvider { + switch h { + case HostingProviderLinode: + return HostingProviderAkamai + } + return h +} + const ( AceInfoConfigMapName = "ace-info" @@ -48,8 +57,13 @@ const ( ClusterDisplayNameKey string = "cluster.appscode.com/display-name" ClusterProviderNameKey string = "cluster.appscode.com/provider" + AceOrgIDKey string = "ace.appscode.com/org-id" ClientOrgKey string = "ace.appscode.com/client-org" ClientKeyPrefix string = "client.ace.appscode.com/" + + ClusterClaimKeyID string = "id.k8s.io" + ClusterClaimKeyInfo string = "cluster.ace.info" + ClusterClaimKeyFeatures string = "features.ace.info" ) type ClusterMetadata struct { @@ -186,3 +200,13 @@ const ( CAPIProviderCAPZ CAPIProvider = "capz" CAPIProviderCAPH CAPIProvider = "caph" ) + +type ClusterClaimInfo struct { + ClusterMetadata ClusterInfo `json:"clusterMetadata"` +} + +type ClusterClaimFeatures struct { + EnabledFeatures []string `json:"enabledFeatures,omitempty"` + ExternallyManagedFeatures []string `json:"externallyManagedFeatures,omitempty"` + DisabledFeatures []string `json:"disabledFeatures,omitempty"` +} diff --git a/vendor/kmodules.xyz/client-go/api/v1/zz_generated.deepcopy.go b/vendor/kmodules.xyz/client-go/api/v1/zz_generated.deepcopy.go index f44550fbd..dcf3b711c 100644 --- a/vendor/kmodules.xyz/client-go/api/v1/zz_generated.deepcopy.go +++ b/vendor/kmodules.xyz/client-go/api/v1/zz_generated.deepcopy.go @@ -119,6 +119,54 @@ func (in *CertificateSpec) DeepCopy() *CertificateSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterClaimFeatures) DeepCopyInto(out *ClusterClaimFeatures) { + *out = *in + if in.EnabledFeatures != nil { + in, out := &in.EnabledFeatures, &out.EnabledFeatures + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ExternallyManagedFeatures != nil { + in, out := &in.ExternallyManagedFeatures, &out.ExternallyManagedFeatures + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.DisabledFeatures != nil { + in, out := &in.DisabledFeatures, &out.DisabledFeatures + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterClaimFeatures. +func (in *ClusterClaimFeatures) DeepCopy() *ClusterClaimFeatures { + if in == nil { + return nil + } + out := new(ClusterClaimFeatures) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterClaimInfo) DeepCopyInto(out *ClusterClaimInfo) { + *out = *in + in.ClusterMetadata.DeepCopyInto(&out.ClusterMetadata) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterClaimInfo. +func (in *ClusterClaimInfo) DeepCopy() *ClusterClaimInfo { + if in == nil { + return nil + } + out := new(ClusterClaimInfo) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterInfo) DeepCopyInto(out *ClusterInfo) { *out = *in diff --git a/vendor/kmodules.xyz/client-go/client/delegated.go b/vendor/kmodules.xyz/client-go/client/delegated.go index fd36791a6..6a4c4eca7 100644 --- a/vendor/kmodules.xyz/client-go/client/delegated.go +++ b/vendor/kmodules.xyz/client-go/client/delegated.go @@ -18,6 +18,7 @@ package client import ( "context" + "net/http" "strings" apiutil2 "kmodules.xyz/client-go/client/apiutil" @@ -26,7 +27,9 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/authentication/user" restclient "k8s.io/client-go/rest" + "k8s.io/client-go/transport" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/apiutil" ) @@ -36,6 +39,9 @@ import ( // NewDelegatingClientInput encapsulates the input parameters to create a new delegating client. type NewDelegatingClientInput struct { + config *restclient.Config + options client.Options + CacheReader client.Reader Client client.Client UncachedObjects []client.Object @@ -58,9 +64,11 @@ func NewDelegatingClient(in NewDelegatingClientInput) (client.Client, error) { uncachedGVKs[gvk] = struct{}{} } - return &delegatingClient{ - scheme: in.Client.Scheme(), - mapper: in.Client.RESTMapper(), + return &DelegatingClient{ + config: in.config, + options: in.options, + scheme: in.Client.Scheme(), + mapper: in.Client.RESTMapper(), Reader: &delegatingReader{ CacheReader: in.CacheReader, ClientReader: in.Client, @@ -75,7 +83,7 @@ func NewDelegatingClient(in NewDelegatingClientInput) (client.Client, error) { }, nil } -type delegatingClient struct { +type DelegatingClient struct { client.Reader client.Writer client.StatusClient @@ -83,25 +91,57 @@ type delegatingClient struct { scheme *runtime.Scheme mapper meta.RESTMapper + + config *restclient.Config + options client.Options +} + +func (d *DelegatingClient) RestConfig() *restclient.Config { + return d.config +} + +func (d *DelegatingClient) Impersonate(u user.Info) (*restclient.Config, client.Client, error) { + config := restclient.CopyConfig(d.config) + config.Impersonate = restclient.ImpersonationConfig{ + UserName: u.GetName(), + UID: u.GetUID(), + Groups: u.GetGroups(), + Extra: u.GetExtra(), + } + + // share the transport between all clients + optionsShallowCopy := d.options + if d.options.HTTPClient != nil { + optionsShallowCopy.HTTPClient = &http.Client{ + Transport: transport.NewImpersonatingRoundTripper(transport.ImpersonationConfig{ + UserName: u.GetName(), + UID: u.GetUID(), + Groups: u.GetGroups(), + Extra: u.GetExtra(), + }, d.options.HTTPClient.Transport), + } + } + cc, err := NewClient(config, optionsShallowCopy) + return config, cc, err } // GroupVersionKindFor returns the GroupVersionKind for the given object. -func (d *delegatingClient) GroupVersionKindFor(obj runtime.Object) (schema.GroupVersionKind, error) { +func (d *DelegatingClient) GroupVersionKindFor(obj runtime.Object) (schema.GroupVersionKind, error) { return apiutil.GVKForObject(obj, d.scheme) } // IsObjectNamespaced returns true if the GroupVersionKind of the object is namespaced. -func (d *delegatingClient) IsObjectNamespaced(obj runtime.Object) (bool, error) { +func (d *DelegatingClient) IsObjectNamespaced(obj runtime.Object) (bool, error) { return apiutil.IsObjectNamespaced(obj, d.scheme, d.mapper) } // Scheme returns the scheme this client is using. -func (d *delegatingClient) Scheme() *runtime.Scheme { +func (d *DelegatingClient) Scheme() *runtime.Scheme { return d.scheme } // RESTMapper returns the rest mapper this client is using. -func (d *delegatingClient) RESTMapper() meta.RESTMapper { +func (d *DelegatingClient) RESTMapper() meta.RESTMapper { return d.mapper } @@ -167,7 +207,7 @@ func (d *delegatingReader) List(ctx context.Context, list client.ObjectList, opt return d.CacheReader.List(ctx, list, opts...) } -func (d *delegatingClient) SubResource(subResource string) client.SubResourceClient { +func (d *DelegatingClient) SubResource(subResource string) client.SubResourceClient { return d.SubResourceClientConstructor.SubResource(subResource) } @@ -181,6 +221,8 @@ func NewClient(config *restclient.Config, options client.Options) (client.Client return nil, err } co := NewDelegatingClientInput{ + config: config, + options: options, Client: c, Cachable: cachable, } diff --git a/vendor/kmodules.xyz/monitoring-agent-api/api/v1/appbinding.go b/vendor/kmodules.xyz/monitoring-agent-api/api/v1/appbinding.go index ea27ea10d..32796ef59 100644 --- a/vendor/kmodules.xyz/monitoring-agent-api/api/v1/appbinding.go +++ b/vendor/kmodules.xyz/monitoring-agent-api/api/v1/appbinding.go @@ -61,10 +61,12 @@ type TLSConfig struct { } type PrometheusContext struct { - HubUID string `json:"hubUID,omitempty"` - ClusterUID string `json:"clusterUID"` - ProjectId string `json:"projectId,omitempty"` - Default bool `json:"default"` + HubUID string `json:"hubUID,omitempty"` + ClusterUID string `json:"clusterUID"` + ProjectId string `json:"projectId,omitempty"` + Default bool `json:"default"` + IssueToken bool `json:"issueToken,omitempty"` + ClientOrgID string `json:"clientOrgID,omitempty"` } type GrafanaContext struct { diff --git a/vendor/kmodules.xyz/monitoring-agent-api/api/v1/openapi_generated.go b/vendor/kmodules.xyz/monitoring-agent-api/api/v1/openapi_generated.go index a98bb2c93..e25773429 100644 --- a/vendor/kmodules.xyz/monitoring-agent-api/api/v1/openapi_generated.go +++ b/vendor/kmodules.xyz/monitoring-agent-api/api/v1/openapi_generated.go @@ -389,6 +389,18 @@ func schema_kmodulesxyz_monitoring_agent_api_api_v1_PrometheusContext(ref common Format: "", }, }, + "issueToken": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + Format: "", + }, + }, + "clientOrgID": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, }, Required: []string{"clusterUID", "default"}, }, diff --git a/vendor/modules.txt b/vendor/modules.txt index df6f2c05a..4a1ba0124 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1414,7 +1414,7 @@ kmodules.xyz/authorizer/apiserver kmodules.xyz/authorizer/rbac kmodules.xyz/authorizer/rbac/helpers kmodules.xyz/authorizer/rbac/validation -# kmodules.xyz/client-go v0.30.32 +# kmodules.xyz/client-go v0.30.38 ## explicit; go 1.22.0 kmodules.xyz/client-go kmodules.xyz/client-go/api/v1 @@ -1435,7 +1435,7 @@ kmodules.xyz/client-go/tools/clientcmd kmodules.xyz/custom-resources/apis/appcatalog kmodules.xyz/custom-resources/apis/appcatalog/v1alpha1 kmodules.xyz/custom-resources/crds -# kmodules.xyz/monitoring-agent-api v0.30.2 +# kmodules.xyz/monitoring-agent-api v0.30.4 ## explicit; go 1.22.0 kmodules.xyz/monitoring-agent-api/api/v1 # moul.io/http2curl/v2 v2.3.1-0.20221024080105-10c404f653f7