IAM Folder Reconciler - revert fetching latest folder info from the folder to improve perf (#111779)

This commit is contained in:
Mihai Turdean
2025-09-30 16:59:06 +00:00
committed by GitHub
parent 98ef07bcb0
commit 7be0c100b6
14 changed files with 100 additions and 447 deletions
+11
View File
@@ -3,6 +3,17 @@
# https://docs.tilt.dev/api.html#api.version_settings
version_settings(constraint='>=0.22.2')
custom_build(
'iam-folder-reconciler',
command='docker buildx build --tag $EXPECTED_REF -f ./apps/iam/local/Dockerfile .',
deps=[
'apps/iam',
'pkg',
],
disable_push=True,
dir='../..',
)
k8s_yaml([filename for filename in listdir('local/yamls') if filename.lower().endswith(('.yaml', '.yml'))])
# Port forward Grafana to localhost:3000
-16
View File
@@ -1,16 +0,0 @@
FROM golang:1.24-alpine AS builder
WORKDIR /build
COPY go.mod go.sum ./
COPY vendor* ./vendor
RUN test -f vendor/modules.txt || go mod download
COPY cmd cmd
COPY pkg pkg
RUN go build -o "target/operator" cmd/operator/*.go
FROM alpine AS runtime
COPY --from=builder /build/target/operator /usr/bin/operator
ENTRYPOINT ["/usr/bin/operator"]
-31
View File
@@ -1,31 +0,0 @@
package main
import (
"fmt"
"net/http"
utilnet "k8s.io/apimachinery/pkg/util/net"
"github.com/grafana/authlib/authn"
)
type authRoundTripper struct {
tokenExchangeClient *authn.TokenExchangeClient
transport http.RoundTripper
}
func (t *authRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
tokenResponse, err := t.tokenExchangeClient.Exchange(req.Context(), authn.TokenExchangeRequest{
Audiences: []string{"folder.grafana.app"},
Namespace: "*",
})
if err != nil {
return nil, fmt.Errorf("failed to exchange token: %w", err)
}
// clone the request as RTs are not expected to mutate the passed request
req = utilnet.CloneRequest(req)
req.Header.Set("X-Access-Token", "Bearer "+tokenResponse.Token)
return t.transport.RoundTrip(req)
}
-131
View File
@@ -1,131 +0,0 @@
package main
import (
"fmt"
"os"
"strconv"
"strings"
"github.com/grafana/grafana-app-sdk/plugin/kubeconfig"
"github.com/grafana/grafana-app-sdk/simple"
"github.com/grafana/grafana/pkg/services/authz"
)
const (
ConnTypeGRPC = "grpc"
ConnTypeHTTP = "http"
)
type Config struct {
OTelConfig simple.OpenTelemetryConfig
WebhookServer WebhookServerConfig
KubeConfig *kubeconfig.NamespacedConfig
ZanzanaClient authz.ZanzanaClientConfig
FolderReconciler FolderReconcilerConfig
}
type WebhookServerConfig struct {
Port int
TLSCertPath string
TLSKeyPath string
}
type FolderReconcilerConfig struct {
Namespace string
MaxConcurrentWorkers uint64
}
func LoadConfigFromEnv() (*Config, error) {
cfg := Config{}
cfg.OTelConfig.ServiceName = os.Getenv("OTEL_SERVICE_NAME")
switch strings.ToLower(os.Getenv("OTEL_CONN_TYPE")) {
case ConnTypeGRPC:
cfg.OTelConfig.ConnType = ConnTypeGRPC
case ConnTypeHTTP:
cfg.OTelConfig.ConnType = ConnTypeHTTP
case "":
// Default
cfg.OTelConfig.ConnType = ConnTypeHTTP
default:
return nil, fmt.Errorf("unknown OTEL_CONN_TYPE '%s'", os.Getenv("OTEL_CONN_TYPE"))
}
cfg.OTelConfig.Host = os.Getenv("OTEL_HOST")
portStr := os.Getenv("OTEL_PORT")
if portStr == "" {
if cfg.OTelConfig.ConnType == ConnTypeGRPC {
// Default OTel GRPC port
cfg.OTelConfig.Port = 4317
} else {
// Default OTel HTTP port
cfg.OTelConfig.Port = 4318
}
} else {
var err error
cfg.OTelConfig.Port, err = strconv.Atoi(portStr)
if err != nil {
return nil, fmt.Errorf("invalid OTEL_PORT '%s': %w", portStr, err)
}
}
whPortStr := os.Getenv("WEBHOOK_PORT")
if whPortStr == "" {
cfg.WebhookServer.Port = 8443
} else {
var err error
cfg.WebhookServer.Port, err = strconv.Atoi(whPortStr)
if err != nil {
return nil, fmt.Errorf("invalid WEBHOOK_PORT '%s': %w", whPortStr, err)
}
}
cfg.WebhookServer.TLSCertPath = os.Getenv("WEBHOOK_CERT_PATH")
cfg.WebhookServer.TLSKeyPath = os.Getenv("WEBHOOK_KEY_PATH")
// Load the kube config
kubeConfigFile := os.Getenv("KUBE_CONFIG_FILE")
if kubeConfigFile != "" {
kubeConfig, err := LoadKubeConfigFromFile(kubeConfigFile)
if err != nil {
return nil, fmt.Errorf("unable to load kubernetes configuration from file '%s': %w", kubeConfigFile, err)
}
cfg.KubeConfig = kubeConfig
} else if folderAppURL := os.Getenv("FOLDER_APP_URL"); folderAppURL != "" {
exchangeUrl := os.Getenv("TOKEN_EXCHANGE_URL")
authToken := os.Getenv("AUTH_TOKEN")
namespace := os.Getenv("FOLDER_APP_NAMESPACE")
if exchangeUrl == "" || authToken == "" {
return nil, fmt.Errorf("TOKEN_EXCHANGE_URL and AUTH_TOKEN must be set when FOLDER_APP_URL is set")
}
kubeConfig, err := LoadKubeConfigFromFolderAppURL(folderAppURL, exchangeUrl, authToken, namespace)
if err != nil {
return nil, fmt.Errorf("unable to load kubernetes configuration from folder app URL '%s': %w", folderAppURL, err)
}
cfg.KubeConfig = kubeConfig
} else {
kubeConfig, err := LoadInClusterConfig()
if err != nil {
return nil, fmt.Errorf("unable to load in-cluster kubernetes configuration: %w", err)
}
cfg.KubeConfig = kubeConfig
}
cfg.ZanzanaClient.URL = os.Getenv("ZANZANA_ADDR")
cfg.ZanzanaClient.Token = os.Getenv("ZANZANA_TOKEN")
cfg.ZanzanaClient.TokenExchangeURL = os.Getenv("TOKEN_EXCHANGE_URL")
cfg.ZanzanaClient.ServerCertFile = os.Getenv("ZANZANA_SERVER_CERT_FILE")
cfg.FolderReconciler.Namespace = os.Getenv("FOLDER_RECONCILER_NAMESPACE")
maxConcurrentWorkersStr := os.Getenv("FOLDER_RECONCILER_MAX_CONCURRENT_WORKERS")
if maxConcurrentWorkersStr == "" {
cfg.FolderReconciler.MaxConcurrentWorkers = 20
} else {
maxConcurrentWorkers, err := strconv.ParseUint(maxConcurrentWorkersStr, 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid FOLDER_RECONCILER_MAX_CONCURRENT_WORKERS '%s': %w", maxConcurrentWorkersStr, err)
}
cfg.FolderReconciler.MaxConcurrentWorkers = maxConcurrentWorkers
}
return &cfg, nil
}
-85
View File
@@ -1,85 +0,0 @@
package main
import (
"fmt"
"net/http"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/transport"
"github.com/grafana/authlib/authn"
"github.com/grafana/grafana-app-sdk/plugin/kubeconfig"
)
// LoadInClusterConfig loads a kubernetes in-cluster config.
// Since the in-cluster config doesn't have a namespace, it defaults to "default"
func LoadInClusterConfig() (*kubeconfig.NamespacedConfig, error) {
cfg, err := rest.InClusterConfig()
if err != nil {
return nil, err
}
cfg.APIPath = "/apis"
return &kubeconfig.NamespacedConfig{
RestConfig: *cfg,
Namespace: "default",
}, nil
}
// LoadKubeConfigFromEnv loads a NamespacedConfig from the value of an environment variable
func LoadKubeConfigFromFolderAppURL(folderAppURL, exchangeUrl, authToken, namespace string) (*kubeconfig.NamespacedConfig, error) {
tokenExchangeClient, err := authn.NewTokenExchangeClient(authn.TokenExchangeConfig{
TokenExchangeURL: exchangeUrl,
Token: authToken,
})
if err != nil {
return nil, fmt.Errorf("failed to create token exchange client: %w", err)
}
return &kubeconfig.NamespacedConfig{
RestConfig: rest.Config{
APIPath: "/apis",
Host: folderAppURL,
WrapTransport: transport.WrapperFunc(func(rt http.RoundTripper) http.RoundTripper {
return &authRoundTripper{
tokenExchangeClient: tokenExchangeClient,
transport: rt,
}
}),
TLSClientConfig: rest.TLSClientConfig{
Insecure: true,
},
},
Namespace: namespace,
}, nil
}
// LoadKubeConfigFromFile loads a NamespacedConfig from a file on-disk (such as a mounted secret)
func LoadKubeConfigFromFile(configPath string) (*kubeconfig.NamespacedConfig, error) {
// Load the kubeconfig file
config, err := clientcmd.LoadFromFile(configPath)
if err != nil {
return nil, fmt.Errorf("failed to load kubeconfig from %s: %w", configPath, err)
}
// Build the REST config from the kubeconfig
restConfig, err := clientcmd.NewDefaultClientConfig(*config, &clientcmd.ConfigOverrides{}).ClientConfig()
if err != nil {
return nil, fmt.Errorf("failed to create REST config: %w", err)
}
// Get the namespace from the current context, default to "default" if not set
namespace := "default"
if config.CurrentContext != "" {
if context, exists := config.Contexts[config.CurrentContext]; exists && context.Namespace != "" {
namespace = context.Namespace
}
}
restConfig.APIPath = "/apis"
return &kubeconfig.NamespacedConfig{
RestConfig: *restConfig,
Namespace: namespace,
}, nil
}
-87
View File
@@ -1,87 +0,0 @@
package main
import (
"context"
"log/slog"
"os"
"os/signal"
"github.com/grafana/grafana-app-sdk/k8s"
"github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana-app-sdk/operator"
"github.com/grafana/grafana-app-sdk/simple"
"github.com/grafana/grafana/apps/iam/pkg/app"
"github.com/prometheus/client_golang/prometheus"
)
func main() {
// Configure the default logger to use slog
logging.DefaultLogger = logging.NewSLogLogger(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
//Load the config from the environment
cfg, err := LoadConfigFromEnv()
if err != nil {
logging.DefaultLogger.With("error", err).Error("Unable to load config from environment")
panic(err)
}
// Set up tracing
if cfg.OTelConfig.Host != "" {
err = simple.SetTraceProvider(simple.OpenTelemetryConfig{
Host: cfg.OTelConfig.Host,
Port: cfg.OTelConfig.Port,
ConnType: cfg.OTelConfig.ConnType,
ServiceName: cfg.OTelConfig.ServiceName,
})
if err != nil {
logging.DefaultLogger.With("error", err).Error("Unable to set trace provider")
panic(err)
}
}
// Create the operator config and the runner
operatorConfig := operator.RunnerConfig{
KubeConfig: cfg.KubeConfig.RestConfig,
WebhookConfig: operator.RunnerWebhookConfig{
Port: cfg.WebhookServer.Port,
TLSConfig: k8s.TLSConfig{
CertPath: cfg.WebhookServer.TLSCertPath,
KeyPath: cfg.WebhookServer.TLSKeyPath,
},
},
MetricsConfig: operator.RunnerMetricsConfig{
Enabled: true,
},
}
runner, err := operator.NewRunner(operatorConfig)
if err != nil {
logging.DefaultLogger.With("error", err).Error("Unable to create operator runner")
panic(err)
}
// Context and cancel for the operator's Run method
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
defer cancel()
// Create app config from operator config
appCfg := app.AppConfig{
ZanzanaClientCfg: cfg.ZanzanaClient,
Namespace: cfg.FolderReconciler.Namespace,
InformerConfig: app.InformerConfig{
MaxConcurrentWorkers: cfg.FolderReconciler.MaxConcurrentWorkers,
},
MetricsRegisterer: prometheus.DefaultRegisterer,
}
// Run
logging.DefaultLogger.Info("Starting operator")
err = runner.Run(ctx, app.Provider(appCfg))
if err != nil {
logging.DefaultLogger.With("error", err).Error("Operator exited with error")
panic(err)
}
logging.DefaultLogger.Info("Normal operator exit")
}
+2 -3
View File
@@ -21,18 +21,15 @@ replace github.com/grafana/grafana/pkg/aggregator => ../../pkg/aggregator
replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20250911094103-5456b6e45604
require (
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c
github.com/grafana/grafana v6.1.6+incompatible
github.com/grafana/grafana-app-sdk v0.45.0
github.com/grafana/grafana-app-sdk/logging v0.45.0
github.com/grafana/grafana-app-sdk/plugin v0.45.0
github.com/grafana/grafana/apps/folder v0.0.0
github.com/grafana/grafana/pkg/apimachinery v0.0.0
github.com/prometheus/client_golang v1.23.2
go.opentelemetry.io/otel v1.38.0
go.opentelemetry.io/otel/trace v1.38.0
k8s.io/apimachinery v0.34.1
k8s.io/client-go v0.34.1
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b
)
@@ -205,6 +202,7 @@ require (
github.com/googleapis/gax-go/v2 v2.14.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/grafana/alerting v0.0.0-20250925200825-7a889aa4934d // indirect
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c // indirect
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781 // indirect
github.com/grafana/dataplane/sdata v0.0.9 // indirect
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 // indirect
@@ -439,6 +437,7 @@ require (
k8s.io/api v0.34.1 // indirect
k8s.io/apiextensions-apiserver v0.34.1 // indirect
k8s.io/apiserver v0.34.1 // indirect
k8s.io/client-go v0.34.1 // indirect
k8s.io/component-base v0.34.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kms v0.34.1 // indirect
-2
View File
@@ -737,8 +737,6 @@ github.com/grafana/grafana-app-sdk v0.45.0 h1:niFqYovxuw9vnUB9qoxEgmupqriG7Gns9Z
github.com/grafana/grafana-app-sdk v0.45.0/go.mod h1:1pYGEBrgG8i6pKmmsNXvtAr15jZ4iLtyHU4yj7T6XaI=
github.com/grafana/grafana-app-sdk/logging v0.45.0 h1:0SH6nYZpiLBZRwUq4J6+1vo8xuHKJjnO95/2pGOoA8w=
github.com/grafana/grafana-app-sdk/logging v0.45.0/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA=
github.com/grafana/grafana-app-sdk/plugin v0.45.0 h1:rK4FL5h7SqGBDeUdrUjHVGnwgN6w8deQAKx8gJ27Iew=
github.com/grafana/grafana-app-sdk/plugin v0.45.0/go.mod h1:fZ6lWVMWr0EpkmyocxZ7MTTc9x6b2jYM93+gjlMgVD4=
github.com/grafana/grafana-aws-sdk v1.2.0 h1:LLR4/g91WBuCRwm2cbWfCREq565+GxIFe08nqqIcIuw=
github.com/grafana/grafana-aws-sdk v1.2.0/go.mod h1:bBo7qOmM3f61vO+2JxTolNUph1l2TmtzmWcU9/Im+8A=
github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0 h1:0TYrkzAc3u0HX+9GK86cGrLTUAcmQfl3/LEB3tL+SOA=
+63
View File
@@ -0,0 +1,63 @@
# Build stage
FROM golang:1.24.6-alpine AS builder
# Set working directory
WORKDIR /app
# Install bash (required for update-workspace.sh) and build tools (required for CGO)
RUN apk add --no-cache bash build-base
# Copy source code
COPY ./apps ./apps
COPY ./pkg ./pkg
COPY ./conf ./conf
COPY ./go.mod ./go.mod
COPY ./go.sum ./go.sum
COPY ./go.work ./go.work
COPY ./go.work.sum ./go.work.sum
COPY ./build.go ./build.go
COPY ./package.json ./package.json
# Update workspace
COPY scripts/go-workspace/update-workspace.sh ./scripts/go-workspace/update-workspace.sh
RUN bash ./scripts/go-workspace/update-workspace.sh
# Build the application in dev mode to output binaries directly to ./bin/
RUN go run build.go -dev build-backend
# Final stage
FROM alpine:latest
# Install ca-certificates for HTTPS requests and wget for health checks
RUN apk --no-cache add ca-certificates tzdata wget
# Create non-root user
RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S appuser -G appgroup
# Set working directory
WORKDIR /usr/share/grafana
# Copy all built binaries and conf from builder stage
COPY --from=builder /app/bin/ ./bin/
COPY --from=builder /app/conf/ ./conf/
# Create necessary directories and add binaries to PATH
RUN mkdir -p /etc/grafana-config && \
chown -R appuser:appgroup /usr/share/grafana /etc/grafana-config
# Switch to non-root user
USER appuser
# Add binaries to PATH
ENV PATH="/usr/share/grafana/bin:${PATH}"
# Expose ports for metrics and profiling
EXPOSE 8080 6060
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/metrics || exit 1
# Run the application with the command from the YAML (can be overridden)
CMD ["grafana", "server", "target", "--config=/etc/grafana-config/operator.ini", "--homepath=/usr/share/grafana"]
+3 -2
View File
@@ -56,7 +56,8 @@ spec:
spec:
containers:
- command:
- grafana-server
- grafana
- server
- target
- --config=/etc/grafana-config/operator.ini
- --homepath=/usr/share/grafana
@@ -78,7 +79,7 @@ spec:
secretKeyRef:
name: iam-operator-secrets
key: grpc_auth_token
image: grafana/grafana-dev:12.3.0-17863745596
image: iam-folder-reconciler
imagePullPolicy: IfNotPresent
name: iam-folder-reconciler
volumeMounts:
-1
View File
@@ -79,7 +79,6 @@ func New(cfg app.Config) (app.App, error) {
folderReconciler, err := reconcilers.NewFolderReconciler(reconcilers.ReconcilerConfig{
ZanzanaCfg: appSpecificConfig.ZanzanaClientCfg,
KubeConfig: &cfg.KubeConfig,
Metrics: metrics,
})
if err != nil {
+21 -12
View File
@@ -8,19 +8,14 @@ import (
"github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana-app-sdk/operator"
foldersKind "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/services/authz"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
"k8s.io/client-go/rest"
)
// FolderStore interface for retrieving folder information
type FolderStore interface {
GetFolderParent(ctx context.Context, namespace, uid string) (string, error)
}
// PermissionStore interface for managing folder permissions
type PermissionStore interface {
GetFolderParents(ctx context.Context, namespace, folderUID string) ([]string, error)
@@ -31,13 +26,11 @@ type PermissionStore interface {
// ReconcilerConfig represents the app-specific configuration
type ReconcilerConfig struct {
ZanzanaCfg authz.ZanzanaClientConfig
KubeConfig *rest.Config
Metrics *ReconcilerMetrics
}
type FolderReconciler struct {
permissionStore PermissionStore
folderStore FolderStore
metrics *ReconcilerMetrics
}
@@ -50,12 +43,10 @@ func NewFolderReconciler(cfg ReconcilerConfig) (operator.Reconciler, error) {
}
// Create dependencies
folderStore := NewAPIFolderStore(cfg.KubeConfig)
permissionStore := NewZanzanaPermissionStore(zanzanaClient)
folderReconciler := &FolderReconciler{
permissionStore: permissionStore,
folderStore: folderStore,
metrics: cfg.Metrics,
}
@@ -137,11 +128,11 @@ func (r *FolderReconciler) handleUpdateFolder(ctx context.Context, folder *folde
folderUID := folder.Name
namespace := folder.Namespace
parentUID, err := r.folderStore.GetFolderParent(ctx, namespace, folderUID)
parentUID, err := getFolderParent(ctx, folder)
if err != nil {
logger.Error("Error getting folder parent", "error", err)
if r.metrics != nil {
r.metrics.RecordReconcileFailure(action, "folder_store")
r.metrics.RecordReconcileFailure(action, "failure_informer")
}
return operator.ReconcileResult{}, err
}
@@ -217,3 +208,21 @@ func validateFolder(folder *foldersKind.Folder) error {
}
return nil
}
func getFolderParent(ctx context.Context, folder *foldersKind.Folder) (string, error) {
tracer := otel.GetTracerProvider().Tracer("iam-folder-reconciler")
_, span := tracer.Start(ctx, "get-folder-parent",
trace.WithAttributes(
attribute.String("folder.uid", folder.Name),
),
)
defer span.End()
folderMeta, err := utils.MetaAccessor(folder)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, "failed to get folder meta accessor")
return "", err
}
return folderMeta.GetFolder(), nil
}
@@ -1,76 +0,0 @@
package reconcilers
import (
"context"
"fmt"
foldersKind "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
)
var _ FolderStore = (*APIFolderStore)(nil)
func NewAPIFolderStore(config *rest.Config) FolderStore {
return &APIFolderStore{config}
}
type APIFolderStore struct {
config *rest.Config
}
func (s *APIFolderStore) GetFolderParent(ctx context.Context, namespace, uid string) (string, error) {
tracer := otel.GetTracerProvider().Tracer("iam-folder-reconciler")
ctx, span := tracer.Start(ctx, "APIFolderStore.GetFolderParent",
trace.WithAttributes(
attribute.String("folder.uid", uid),
attribute.String("folder.namespace", namespace),
),
)
defer span.End()
client, err := s.client(namespace)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, "failed to create kubernetes client")
return "", fmt.Errorf("create resource client: %w", err)
}
// Get the folder by UID
unstructuredObj, err := client.Get(ctx, uid, metav1.GetOptions{})
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, "failed to get folder from kubernetes API")
return "", fmt.Errorf("get folder %s: %w", uid, err)
}
object, err := utils.MetaAccessor(unstructuredObj)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, "failed to get meta accessor from folder object")
return "", fmt.Errorf("get meta accessor: %w", err)
}
parentUID := object.GetFolder()
span.SetAttributes(attribute.String("folder.parent_uid", parentUID))
span.SetStatus(codes.Ok, "successfully retrieved folder parent")
span.AddEvent("folder.parent.retrieved", trace.WithAttributes(
attribute.String("parent.uid", parentUID),
))
return parentUID, nil
}
func (s *APIFolderStore) client(namespace string) (dynamic.ResourceInterface, error) {
client, err := dynamic.NewForConfig(s.config)
if err != nil {
return nil, err
}
return client.Resource(foldersKind.FolderResourceInfo.GroupVersionResource()).Namespace(namespace), nil
}
-1
View File
@@ -22,7 +22,6 @@ func NewReconcilerMetrics(registerer prometheus.Registerer, namespace string) *R
"success_no_changes_needed",
"failure_informer",
"failure_permission_store",
"failure_folder_store",
"failure_unknown",
},
}