1
0
mirror of https://github.com/coreos/prometheus-operator.git synced 2026-02-05 15:46:31 +01:00
Files
prometheus-operator/test/framework/framework.go
Simon Pasquier f61936b22d test: add test for #8312
Signed-off-by: Simon Pasquier <spasquie@redhat.com>
2026-02-04 10:17:05 +01:00

877 lines
31 KiB
Go

// Copyright 2016 The prometheus-operator 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.
package framework
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"slices"
"strings"
"testing"
"time"
"github.com/blang/semver/v4"
"github.com/cespare/xxhash/v2"
"github.com/gogo/protobuf/proto"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apiclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
certutil "k8s.io/client-go/util/cert"
"k8s.io/utils/ptr"
"github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring"
monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
monitoringv1alpha1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1alpha1"
v1monitoringclient "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned/typed/monitoring/v1"
v1alpha1monitoringclient "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned/typed/monitoring/v1alpha1"
v1beta1monitoringclient "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned/typed/monitoring/v1beta1"
"github.com/prometheus-operator/prometheus-operator/pkg/operator"
)
const (
prometheusOperatorServiceDeploymentName = "prometheus-operator"
prometheusOperatorCertsSecretName = "prometheus-operator-certs"
admissionWebhookServiceName = "prometheus-operator-admission-webhook"
standaloneAdmissionHookSecretName = "admission-webhook-certs"
operatorTLSDir = "/etc/tls/private"
)
type Framework struct {
KubeClient kubernetes.Interface
MonClientV1 v1monitoringclient.MonitoringV1Interface
MonClientV1alpha1 v1alpha1monitoringclient.MonitoringV1alpha1Interface
MonClientV1beta1 v1beta1monitoringclient.MonitoringV1beta1Interface
APIServerClient apiclient.Interface
HTTPClient *http.Client
MasterHost string
DefaultTimeout time.Duration
RestConfig *rest.Config
operatorVersion semver.Version
opImage string
exampleDir string
resourcesDir string
}
// New setups a test framework and returns it.
func New(kubeconfig, opImage, exampleDir, resourcesDir string, operatorVersion semver.Version) (*Framework, error) {
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
return nil, fmt.Errorf("build config from flags failed: %w", err)
}
cli, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("creating new kube-client failed: %w", err)
}
apiCli, err := apiclient.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("creating new kube-client failed: %w", err)
}
httpc := cli.CoreV1().RESTClient().(*rest.RESTClient).Client
mClientV1, err := v1monitoringclient.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("creating v1 monitoring client failed: %w", err)
}
mClientv1alpha1, err := v1alpha1monitoringclient.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("creating v1alpha1 monitoring client failed: %w", err)
}
mClientv1beta1, err := v1beta1monitoringclient.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("creating v1beta1 monitoring client failed: %w", err)
}
nodes, err := cli.CoreV1().Nodes().List(context.Background(), metav1.ListOptions{})
if err != nil {
return nil, fmt.Errorf("failed to list nodes: %w", err)
}
if len(nodes.Items) < 1 {
return nil, errors.New("no nodes returned")
}
f := &Framework{
RestConfig: config,
MasterHost: config.Host,
KubeClient: cli,
MonClientV1: mClientV1,
MonClientV1alpha1: mClientv1alpha1,
MonClientV1beta1: mClientv1beta1,
APIServerClient: apiCli,
HTTPClient: httpc,
DefaultTimeout: time.Minute,
operatorVersion: operatorVersion,
opImage: opImage,
exampleDir: exampleDir,
resourcesDir: resourcesDir,
}
return f, nil
}
func (f *Framework) MakeEchoService(name, group string, serviceType v1.ServiceType) *v1.Service {
service := &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("echo-%s", name),
Labels: map[string]string{
"group": group,
},
},
Spec: v1.ServiceSpec{
Type: serviceType,
Ports: []v1.ServicePort{
{
Name: "web",
Port: 9090,
TargetPort: intstr.FromString("web"),
},
},
Selector: map[string]string{
"echo": name,
},
},
}
return service
}
func (f *Framework) MakeEchoDeployment(group string) *appsv1.Deployment {
return &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "echoserver",
},
Spec: appsv1.DeploymentSpec{
Replicas: proto.Int32(1),
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"echo": group,
},
},
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"echo": group,
},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "echoserver",
Image: "k8s.gcr.io/echoserver:1.10",
Ports: []v1.ContainerPort{
{
Name: "web",
ContainerPort: 8443,
},
},
},
},
},
},
},
}
}
type PrometheusOperatorOpts struct {
Namespace string
AllowedNamespaces []string
DeniedNamespaces []string
PrometheusNamespaces []string
AlertmanagerNamespaces []string
EnableAdmissionWebhook bool
ClusterRoleBindings bool
EnableScrapeConfigs bool
AdditionalArgs []string
EnabledFeatureGates []operator.FeatureGateName
}
func (f *Framework) CreateOrUpdatePrometheusOperator(
ctx context.Context,
namespace string,
namespaceAllowlist,
namespaceDenylist,
prometheusInstanceNamespaces,
alertmanagerInstanceNamespaces []string,
createResourceAdmissionHooks,
createClusterRoleBindings,
createScrapeConfigCrd bool,
enabledFeatureGates ...operator.FeatureGateName,
) ([]FinalizerFn, error) {
return f.CreateOrUpdatePrometheusOperatorWithOpts(
ctx,
PrometheusOperatorOpts{
Namespace: namespace,
AllowedNamespaces: namespaceAllowlist,
DeniedNamespaces: namespaceDenylist,
PrometheusNamespaces: prometheusInstanceNamespaces,
AlertmanagerNamespaces: alertmanagerInstanceNamespaces,
EnableAdmissionWebhook: createResourceAdmissionHooks,
ClusterRoleBindings: createClusterRoleBindings,
EnableScrapeConfigs: createScrapeConfigCrd,
EnabledFeatureGates: enabledFeatureGates,
},
)
}
// CreateOrUpdatePrometheusOperatorWithOpts creates or updates a Prometheus
// Operator Kubernetes Deployment inside the specified namespace using the
// specified operator image. Semver is used to control the installation for
// different versions of Prometheus Operator. In addition one can specify the
// namespaces to watch, which defaults to all namespaces. It returns a slice
// of functions to tear down the deployment.
func (f *Framework) CreateOrUpdatePrometheusOperatorWithOpts(
ctx context.Context,
opts PrometheusOperatorOpts,
) ([]FinalizerFn, error) {
var finalizers []FinalizerFn
_, err := f.createOrUpdateServiceAccount(
ctx,
opts.Namespace,
fmt.Sprintf("%s/rbac/prometheus-operator/prometheus-operator-service-account.yaml", f.exampleDir),
)
if err != nil {
return nil, fmt.Errorf("failed to create or update prometheus operator service account: %w", err)
}
clusterRole, err := clusterRoleFromYaml(opts.Namespace, f.exampleDir+"/rbac/prometheus-operator/prometheus-operator-cluster-role.yaml")
if err != nil {
return nil, fmt.Errorf("failed to load prometheus-operator cluster role: %w", err)
}
// Use a unique cluster role name to avoid parallel tests doing concurrent
// updates to the same resource.
xxh := xxhash.New()
if _, err := xxh.Write([]byte(opts.Namespace)); err != nil {
return nil, fmt.Errorf("failed to write hash: %w", err)
}
clusterRole.Name = fmt.Sprintf("%s-%x", clusterRole.Name, xxh.Sum64())
clusterRole.Rules = append(clusterRole.Rules, CRDCreateRule, CRDMonitoringRule)
if slices.Contains(opts.EnabledFeatureGates, operator.PrometheusAgentDaemonSetFeature) {
daemonsetRule := rbacv1.PolicyRule{
APIGroups: []string{"apps"},
Resources: []string{"daemonsets"},
Verbs: []string{"*"},
}
clusterRole.Rules = append(clusterRole.Rules, daemonsetRule)
}
clusterRole, err = f.CreateOrUpdateClusterRole(ctx, clusterRole)
if err != nil {
return nil, fmt.Errorf("failed to create/update prometheus cluster role: %w", err)
}
finalizers = append(finalizers, func() error {
return f.DeleteClusterRole(ctx, clusterRole.Name)
})
if opts.ClusterRoleBindings {
// Grant permissions on all namespaces.
fn, err := f.createOrUpdateClusterRoleBinding(ctx, opts.Namespace, clusterRole, f.exampleDir+"/rbac/prometheus-operator/prometheus-operator-cluster-role-binding.yaml")
if err != nil {
return nil, fmt.Errorf("failed to create or update prometheus cluster role binding: %w", err)
}
finalizers = append(finalizers, fn)
} else {
// Grant permissions on specific namespaces.
var namespaces []string
namespaces = append(namespaces, opts.AllowedNamespaces...)
namespaces = append(namespaces, opts.PrometheusNamespaces...)
namespaces = append(namespaces, opts.AlertmanagerNamespaces...)
for _, n := range namespaces {
if _, err := f.createOrUpdateRoleBindingForSubjectNamespace(ctx, n, opts.Namespace, clusterRole, fmt.Sprintf("%s/prometheus-operator-role-binding.yaml", f.resourcesDir)); err != nil {
return nil, fmt.Errorf("failed to create or update prometheus operator role binding: %w", err)
}
}
}
err = f.CreateOrUpdateCRDAndWaitUntilReady(ctx, monitoringv1.AlertmanagerName, func(opts metav1.ListOptions) (runtime.Object, error) {
return f.MonClientV1.Alertmanagers(v1.NamespaceAll).List(ctx, opts)
})
if err != nil {
return nil, fmt.Errorf("initialize Alertmanager CRD: %w", err)
}
err = f.CreateOrUpdateCRDAndWaitUntilReady(ctx, monitoringv1.PodMonitorName, func(opts metav1.ListOptions) (runtime.Object, error) {
return f.MonClientV1.PodMonitors(v1.NamespaceAll).List(ctx, opts)
})
if err != nil {
return nil, fmt.Errorf("initialize PodMonitor CRD: %w", err)
}
err = f.CreateOrUpdateCRDAndWaitUntilReady(ctx, monitoringv1.ProbeName, func(opts metav1.ListOptions) (object runtime.Object, err error) {
return f.MonClientV1.Probes(v1.NamespaceAll).List(ctx, opts)
})
if err != nil {
return nil, fmt.Errorf("initialize Probe CRD: %w", err)
}
err = f.CreateOrUpdateCRDAndWaitUntilReady(ctx, monitoringv1.PrometheusName, func(opts metav1.ListOptions) (runtime.Object, error) {
return f.MonClientV1.Prometheuses(v1.NamespaceAll).List(ctx, opts)
})
if err != nil {
return nil, fmt.Errorf("initialize Prometheus CRD: %w", err)
}
err = f.CreateOrUpdateCRDAndWaitUntilReady(ctx, monitoringv1.PrometheusRuleName, func(opts metav1.ListOptions) (runtime.Object, error) {
return f.MonClientV1.PrometheusRules(v1.NamespaceAll).List(ctx, opts)
})
if err != nil {
return nil, fmt.Errorf("initialize PrometheusRule CRD: %w", err)
}
err = f.CreateOrUpdateCRDAndWaitUntilReady(ctx, monitoringv1.ServiceMonitorName, func(opts metav1.ListOptions) (runtime.Object, error) {
return f.MonClientV1.ServiceMonitors(v1.NamespaceAll).List(ctx, opts)
})
if err != nil {
return nil, fmt.Errorf("initialize ServiceMonitor CRD: %w", err)
}
err = f.CreateOrUpdateCRDAndWaitUntilReady(ctx, monitoringv1.ThanosRulerName, func(opts metav1.ListOptions) (runtime.Object, error) {
return f.MonClientV1.ThanosRulers(v1.NamespaceAll).List(ctx, opts)
})
if err != nil {
return nil, fmt.Errorf("initialize ThanosRuler CRD: %w", err)
}
err = f.CreateOrUpdateCRDAndWaitUntilReady(ctx, monitoringv1alpha1.AlertmanagerConfigName, func(opts metav1.ListOptions) (runtime.Object, error) {
return f.MonClientV1alpha1.AlertmanagerConfigs(v1.NamespaceAll).List(ctx, opts)
})
if err != nil {
return nil, fmt.Errorf("initialize AlertmanagerConfig v1alpha1 CRD: %w", err)
}
err = WaitForCRDReady(func(opts metav1.ListOptions) (runtime.Object, error) {
return f.MonClientV1beta1.AlertmanagerConfigs(v1.NamespaceAll).List(ctx, opts)
})
if err != nil {
return nil, fmt.Errorf("wait for AlertmanagerConfig v1beta1 CRD: %w", err)
}
err = f.CreateOrUpdateCRDAndWaitUntilReady(ctx, monitoringv1alpha1.PrometheusAgentName, func(opts metav1.ListOptions) (runtime.Object, error) {
return f.MonClientV1alpha1.PrometheusAgents(v1.NamespaceAll).List(ctx, opts)
})
if err != nil {
return nil, fmt.Errorf("initialize PrometheusAgent v1alpha1 CRD: %w", err)
}
if opts.EnableScrapeConfigs {
err = f.CreateOrUpdateCRDAndWaitUntilReady(ctx, monitoringv1alpha1.ScrapeConfigName, func(opts metav1.ListOptions) (runtime.Object, error) {
return f.MonClientV1alpha1.ScrapeConfigs(v1.NamespaceAll).List(ctx, opts)
})
if err != nil {
return nil, fmt.Errorf("initialize ScrapeConfig v1alpha1 CRD: %w", err)
}
}
certBytes, keyBytes, err := certutil.GenerateSelfSignedCertKey(fmt.Sprintf("%s.%s.svc", prometheusOperatorServiceDeploymentName, opts.Namespace), nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to generate certificate and key: %w", err)
}
if err := f.CreateOrUpdateSecretWithCert(ctx, certBytes, keyBytes, opts.Namespace, prometheusOperatorCertsSecretName); err != nil {
return nil, fmt.Errorf("failed to create or update prometheus-operator TLS secret: %w", err)
}
deploy, err := MakeDeployment(f.exampleDir + "/rbac/prometheus-operator/prometheus-operator-deployment.yaml")
if err != nil {
return nil, err
}
// Make sure only that only one instance of the Prometheus operator is running during update.
deploy.Spec.Strategy.Type = appsv1.RecreateDeploymentStrategyType
deploy.Spec.Template.Spec.Containers[0].Args = append(deploy.Spec.Template.Spec.Containers[0].Args, "--log-level=debug")
var featureGates string
if len(opts.EnabledFeatureGates) > 0 {
featureGates = "-feature-gates="
}
for _, fGate := range opts.EnabledFeatureGates {
featureGates += fmt.Sprintf("%s=true,", fGate)
}
if featureGates != "" {
// Remove the trailing comma
deploy.Spec.Template.Spec.Containers[0].Args = append(deploy.Spec.Template.Spec.Containers[0].Args, featureGates[:len(featureGates)-1])
}
var webhookServerImage string
if f.opImage != "" {
// Override operator image used, if specified when running tests.
deploy.Spec.Template.Spec.Containers[0].Image = f.opImage
repoAndTag := strings.Split(f.opImage, ":")
if len(repoAndTag) != 2 {
return nil, fmt.Errorf(
"expected operator image '%v' split by colon to result in two substrings but got '%v'",
f.opImage,
repoAndTag,
)
}
// Override Prometheus config reloader image
for i, arg := range deploy.Spec.Template.Spec.Containers[0].Args {
if strings.Contains(arg, "--prometheus-config-reloader=") {
deploy.Spec.Template.Spec.Containers[0].Args[i] = "--prometheus-config-reloader=" +
"quay.io/prometheus-operator/prometheus-config-reloader:" +
repoAndTag[1]
}
}
webhookServerImage = "quay.io/prometheus-operator/admission-webhook:" + repoAndTag[1]
}
deploy.Name = prometheusOperatorServiceDeploymentName
for _, ns := range opts.AllowedNamespaces {
deploy.Spec.Template.Spec.Containers[0].Args = append(
deploy.Spec.Template.Spec.Containers[0].Args,
fmt.Sprintf("--namespaces=%v", ns),
)
}
for _, ns := range opts.DeniedNamespaces {
deploy.Spec.Template.Spec.Containers[0].Args = append(
deploy.Spec.Template.Spec.Containers[0].Args,
fmt.Sprintf("--deny-namespaces=%v", ns),
)
}
for _, ns := range opts.PrometheusNamespaces {
deploy.Spec.Template.Spec.Containers[0].Args = append(
deploy.Spec.Template.Spec.Containers[0].Args,
fmt.Sprintf("--prometheus-instance-namespaces=%v", ns),
)
}
for _, ns := range opts.AlertmanagerNamespaces {
deploy.Spec.Template.Spec.Containers[0].Args = append(
deploy.Spec.Template.Spec.Containers[0].Args,
fmt.Sprintf("--alertmanager-instance-namespaces=%v", ns),
)
}
// Load the certificate and key from the created secret into the operator
deploy.Spec.Template.Spec.Volumes = append(deploy.Spec.Template.Spec.Volumes,
v1.Volume{
Name: "cert",
VolumeSource: v1.VolumeSource{Secret: &v1.SecretVolumeSource{SecretName: prometheusOperatorCertsSecretName}}})
deploy.Spec.Template.Spec.Containers[0].VolumeMounts = append(deploy.Spec.Template.Spec.Containers[0].VolumeMounts,
v1.VolumeMount{Name: "cert", MountPath: operatorTLSDir, ReadOnly: true})
// The addition of rule admission webhooks requires TLS, so enable it and
// switch to a more common https port
if opts.EnableAdmissionWebhook {
deploy.Spec.Template.Spec.Containers[0].Args = append(
deploy.Spec.Template.Spec.Containers[0].Args,
"--web.enable-tls=true",
fmt.Sprintf("--web.listen-address=%v", ":8443"),
)
}
deploy.Spec.Template.Spec.Containers[0].Args = append(
deploy.Spec.Template.Spec.Containers[0].Args,
opts.AdditionalArgs...,
)
err = f.CreateOrUpdateDeploymentAndWaitUntilReady(ctx, opts.Namespace, deploy)
if err != nil {
return nil, err
}
service, err := MakeService(fmt.Sprintf("%s/rbac/prometheus-operator/prometheus-operator-service.yaml", f.exampleDir))
if err != nil {
return finalizers, fmt.Errorf("cannot parse service file: %w", err)
}
service.Namespace = opts.Namespace
service.Spec.ClusterIP = ""
service.Spec.Ports = []v1.ServicePort{{Name: "https", Port: 443, TargetPort: intstr.FromInt(8443)}}
if _, err := f.CreateOrUpdateServiceAndWaitUntilReady(ctx, opts.Namespace, service); err != nil {
return finalizers, fmt.Errorf("failed to create or update prometheus operator service: %w", err)
}
if opts.EnableAdmissionWebhook {
webhookService, b, err := f.CreateOrUpdateAdmissionWebhookServer(ctx, opts.Namespace, webhookServerImage)
if err != nil {
return nil, fmt.Errorf("failed to create webhook server: %w", err)
}
finalizer, err := f.createOrUpdateMutatingHook(ctx, b, opts.Namespace, fmt.Sprintf("%s/prometheus-operator-mutatingwebhook.yaml", f.resourcesDir))
if err != nil {
return nil, fmt.Errorf("failed to create or update mutating webhook for PrometheusRule objects: %w", err)
}
finalizers = append(finalizers, finalizer)
finalizer, err = f.createOrUpdateValidatingHook(ctx, b, opts.Namespace, fmt.Sprintf("%s/prometheus-operator-validatingwebhook.yaml", f.resourcesDir))
if err != nil {
return nil, fmt.Errorf("failed to create or update validating webhook for PrometheusRule objects: %w", err)
}
finalizers = append(finalizers, finalizer)
finalizer, err = f.createOrUpdateValidatingHook(ctx, b, opts.Namespace, fmt.Sprintf("%s/alertmanager-config-validating-webhook.yaml", f.resourcesDir))
if err != nil {
return nil, fmt.Errorf("failed to create or update validating webhook for AlertManagerConfig objects: %w", err)
}
finalizers = append(finalizers, finalizer)
finalizer, err = f.configureAlertmanagerConfigConversion(ctx, webhookService, b)
if err != nil {
return nil, fmt.Errorf("failed to configure conversion webhook for AlertManagerConfig objects: %w", err)
}
finalizers = append(finalizers, finalizer)
}
return finalizers, nil
}
// DeletePrometheusOperatorClusterResource delete Prometheus Operator cluster wide resources.
func (f *Framework) DeletePrometheusOperatorClusterResource(ctx context.Context) error {
group := monitoring.GroupName
alertmanagerCRD, err := f.MakeCRD(fmt.Sprintf("%s/prometheus-operator-crd/%s_%s.yaml", f.exampleDir, group, monitoringv1.AlertmanagerName))
if err != nil {
return fmt.Errorf("failed to make alertmanager CRD: %w", err)
}
err = f.DeleteCRD(ctx, fmt.Sprintf("%s.%s", alertmanagerCRD.Name, group))
if err != nil && !apierrors.IsNotFound(err) {
return fmt.Errorf("failed to delete alertmanager CRD: %w", err)
}
podMonitorCRD, err := f.MakeCRD(fmt.Sprintf("%s/prometheus-operator-crd/%s_%s.yaml", f.exampleDir, group, monitoringv1.PodMonitorName))
if err != nil {
return fmt.Errorf("failed to make podMonitor CRD: %w", err)
}
err = f.DeleteCRD(ctx, fmt.Sprintf("%s.%s", podMonitorCRD.Name, group))
if err != nil && !apierrors.IsNotFound(err) {
return fmt.Errorf("failed to delete podMonitor CRD: %w", err)
}
probeCRD, err := f.MakeCRD(fmt.Sprintf("%s/prometheus-operator-crd/%s_%s.yaml", f.exampleDir, group, monitoringv1.ProbeName))
if err != nil {
return fmt.Errorf("failed to make probe CRD: %w", err)
}
err = f.DeleteCRD(ctx, fmt.Sprintf("%s.%s", probeCRD.Name, group))
if err != nil && !apierrors.IsNotFound(err) {
return fmt.Errorf("failed to delete probe CRD: %w", err)
}
prometheusCRD, err := f.MakeCRD(fmt.Sprintf("%s/prometheus-operator-crd/%s_%s.yaml", f.exampleDir, group, monitoringv1.PrometheusName))
if err != nil {
return fmt.Errorf("failed to make prometheus CRD: %w", err)
}
err = f.DeleteCRD(ctx, fmt.Sprintf("%s.%s", prometheusCRD.Name, group))
if err != nil && !apierrors.IsNotFound(err) {
return fmt.Errorf("failed to delete prometheus CRD: %w", err)
}
prometheusRuleCRD, err := f.MakeCRD(fmt.Sprintf("%s/prometheus-operator-crd/%s_%s.yaml", f.exampleDir, group, monitoringv1.PrometheusRuleName))
if err != nil {
return fmt.Errorf("failed to make prometheusRule CRD: %w", err)
}
err = f.DeleteCRD(ctx, fmt.Sprintf("%s.%s", prometheusRuleCRD.Name, group))
if err != nil && !apierrors.IsNotFound(err) {
return fmt.Errorf("failed to delete prometheusRule CRD: %w", err)
}
serviceMonitorCRD, err := f.MakeCRD(fmt.Sprintf("%s/prometheus-operator-crd/%s_%s.yaml", f.exampleDir, group, monitoringv1.ServiceMonitorName))
if err != nil {
return fmt.Errorf("failed to make serviceMonitor CRD: %w", err)
}
err = f.DeleteCRD(ctx, fmt.Sprintf("%s.%s", serviceMonitorCRD.Name, group))
if err != nil && !apierrors.IsNotFound(err) {
return fmt.Errorf("failed to delete serviceMonitor CRD: %w", err)
}
thanosRulerCRD, err := f.MakeCRD(fmt.Sprintf("%s/prometheus-operator-crd/%s_%s.yaml", f.exampleDir, group, monitoringv1.ThanosRulerName))
if err != nil {
return fmt.Errorf("failed to make thanosRuler CRD: %w", err)
}
err = f.DeleteCRD(ctx, fmt.Sprintf("%s.%s", thanosRulerCRD.Name, group))
if err != nil && !apierrors.IsNotFound(err) {
return fmt.Errorf("failed to delete thanosRuler CRD: %w", err)
}
alertmanagerConfigCRD, err := f.MakeCRD(fmt.Sprintf("%s/prometheus-operator-crd/%s_%s.yaml", f.exampleDir, group, monitoringv1alpha1.AlertmanagerConfigName))
if err != nil {
return fmt.Errorf("failed to make alertmanagerConfig CRD: %w", err)
}
err = f.DeleteCRD(ctx, fmt.Sprintf("%s.%s", alertmanagerConfigCRD.Name, group))
if err != nil && !apierrors.IsNotFound(err) {
return fmt.Errorf("failed to delete alertmanagerConfig CRD: %w", err)
}
operatorMutatingHook, err := parseMutatingHookYaml(fmt.Sprintf("%s/prometheus-operator-mutatingwebhook.yaml", f.resourcesDir))
if err != nil {
return fmt.Errorf("failed to parse operator mutatingwebhook: %w", err)
}
err = f.deleteMutatingWebhook(ctx, operatorMutatingHook.Name)
if err != nil && !apierrors.IsNotFound(err) {
return fmt.Errorf("failed to delete operator mutatingwebhook: %w", err)
}
operatorValidatingHook, err := parseValidatingHookYaml(fmt.Sprintf("%s/prometheus-operator-validatingwebhook.yaml", f.resourcesDir))
if err != nil {
return fmt.Errorf("failed to parse operator validatingwebhook: %w", err)
}
err = f.deleteValidatingWebhook(ctx, operatorValidatingHook.Name)
if err != nil && !apierrors.IsNotFound(err) {
return fmt.Errorf("failed to delete operator mutatingwebhook: %w", err)
}
AlertmanagerConfigValidatingHook, err := parseValidatingHookYaml(fmt.Sprintf("%s/alertmanager-config-validating-webhook.yaml", f.resourcesDir))
if err != nil {
return fmt.Errorf("failed to parse alertmanager config mutatingwebhook: %w", err)
}
err = f.deleteValidatingWebhook(ctx, AlertmanagerConfigValidatingHook.Name)
if err != nil && !apierrors.IsNotFound(err) {
return fmt.Errorf("failed to delete alertmanager config mutatingwebhook: %w", err)
}
return nil
}
func (f *Framework) SetupPrometheusRBAC(ctx context.Context, t *testing.T, testCtx *TestCtx, ns string) {
t.Helper()
clusterRole, err := clusterRoleFromYaml(ns, f.exampleDir+"/rbac/prometheus/prometheus-cluster-role.yaml")
if err != nil {
t.Fatalf("failed to load prometheus cluster role: %v", err)
}
cr, err := f.CreateOrUpdateClusterRole(ctx, clusterRole)
if err != nil {
t.Fatalf("failed to create or update prometheus cluster role: %v", err)
}
finalizerFn, err := f.createOrUpdateServiceAccount(ctx, ns, f.exampleDir+"/rbac/prometheus/prometheus-service-account.yaml")
if err != nil {
t.Fatalf("failed to create or update prometheus service account: %v", err)
}
testCtx.AddFinalizerFn(finalizerFn)
finalizerFn, err = f.createOrUpdateRoleBinding(ctx, ns, cr, f.resourcesDir+"/prometheus-role-binding.yml")
if err != nil {
t.Fatalf("failed to create prometheus role binding: %v", err)
}
testCtx.AddFinalizerFn(finalizerFn)
}
func (f *Framework) SetupPrometheusRBACGlobal(ctx context.Context, t *testing.T, testCtx *TestCtx, ns string) {
t.Helper()
clusterRole, err := clusterRoleFromYaml(ns, f.exampleDir+"/rbac/prometheus/prometheus-cluster-role.yaml")
if err != nil {
t.Fatalf("failed to load prometheus cluster role: %v", err)
}
if _, err := f.CreateOrUpdateClusterRole(ctx, clusterRole); err != nil && !apierrors.IsAlreadyExists(err) {
t.Fatalf("failed to create or update prometheus cluster role: %v", err)
}
finalizerFn, err := f.createOrUpdateServiceAccount(ctx, ns, f.exampleDir+"/rbac/prometheus/prometheus-service-account.yaml")
if err != nil {
t.Fatalf("failed to create or update prometheus service account: %v", err)
}
testCtx.AddFinalizerFn(finalizerFn)
finalizerFn, err = f.createOrUpdateClusterRoleBinding(ctx, ns, clusterRole, f.exampleDir+"/rbac/prometheus/prometheus-cluster-role-binding.yaml")
if err != nil {
t.Fatalf("failed to create or update prometheus cluster role binding: %v", err)
}
testCtx.AddFinalizerFn(finalizerFn)
}
func (f *Framework) configureAlertmanagerConfigConversion(ctx context.Context, svc *v1.Service, cert []byte) (FinalizerFn, error) {
patch, err := f.MakeCRD(fmt.Sprintf("%s/alertmanager-crd-conversion/patch.json", f.exampleDir))
if err != nil {
return nil, err
}
crd, err := f.GetCRD(ctx, patch.Name)
if err != nil {
return nil, err
}
originalBytes, err := json.Marshal(crd)
if err != nil {
return nil, err
}
patch.Spec.Conversion.Webhook.ClientConfig.Service.Name = svc.Name
patch.Spec.Conversion.Webhook.ClientConfig.Service.Namespace = svc.Namespace
patch.Spec.Conversion.Webhook.ClientConfig.Service.Port = &svc.Spec.Ports[0].Port
patch.Spec.Conversion.Webhook.ClientConfig.CABundle = cert
crd.Spec.Conversion = patch.Spec.Conversion
patchBytes, err := json.Marshal(crd)
if err != nil {
return nil, err
}
jsonResult, err := strategicpatch.StrategicMergePatch(originalBytes, patchBytes, apiextensionsv1.CustomResourceDefinition{})
if err != nil {
return nil, fmt.Errorf("failed to generate merge patch: %w", err)
}
crd, err = f.APIServerClient.ApiextensionsV1().CustomResourceDefinitions().Patch(
ctx,
crd.Name,
types.StrategicMergePatchType,
jsonResult,
metav1.PatchOptions{},
)
if err != nil {
return nil, fmt.Errorf("failed to patch CustomResourceDefinition object: %w", err)
}
if crd.Spec.Conversion.Strategy != apiextensionsv1.WebhookConverter {
return nil, fmt.Errorf("expected conversion strategy to be %s, got %s", apiextensionsv1.WebhookConverter, crd.Spec.Conversion.Strategy)
}
finalizerFn := func() error {
crd, err := f.GetCRD(ctx, patch.Name)
if err != nil {
return err
}
crd.Spec.Conversion = nil
_, err = f.APIServerClient.ApiextensionsV1().CustomResourceDefinitions().Update(ctx, crd, metav1.UpdateOptions{})
if err != nil {
return fmt.Errorf("unable to reset conversion configuration of AlertmanagerConfig CRD: %w", err)
}
return err
}
return finalizerFn, nil
}
// CreateOrUpdateAdmissionWebhookServer deploys an HTTPS server which acts as a
// validating and mutating webhook server for PrometheusRule and
// AlertManagerConfig. It is also able to convert AlertmanagerConfig objects
// from v1alpha1 to v1beta1.
// Returns the service and the certificate authority which can be used to trust the TLS certificate of the server.
func (f *Framework) CreateOrUpdateAdmissionWebhookServer(
ctx context.Context,
namespace string,
image string,
) (*v1.Service, []byte, error) {
certBytes, keyBytes, err := certutil.GenerateSelfSignedCertKey(
fmt.Sprintf("%s.%s.svc", admissionWebhookServiceName, namespace),
nil,
nil,
)
if err != nil {
return nil, nil, fmt.Errorf("failed to generate certificate and key: %w", err)
}
if err := f.CreateOrUpdateSecretWithCert(ctx, certBytes, keyBytes, namespace, standaloneAdmissionHookSecretName); err != nil {
return nil, nil, fmt.Errorf("failed to create or update admission webhook secret: %w", err)
}
deploy, err := MakeDeployment(fmt.Sprintf("%s/admission-webhook/deployment.yaml", f.exampleDir))
if err != nil {
return nil, nil, err
}
// Adjust replica count in case of single-node clusters because the
// deployment manifest has anti-affinity rules.
nodes, err := f.Nodes(ctx)
if err != nil {
return nil, nil, err
}
if len(nodes) == 1 {
deploy.Spec.Replicas = ptr.To(int32(1))
deploy.Spec.Template.Spec.Affinity = nil
deploy.Spec.Strategy = appsv1.DeploymentStrategy{}
}
deploy.Spec.Template.Spec.Containers[0].Args = append(deploy.Spec.Template.Spec.Containers[0].Args, "--log-level=debug")
if image != "" {
// Override operator image used, if specified when running tests.
deploy.Spec.Template.Spec.Containers[0].Image = image
repoAndTag := strings.Split(image, ":")
if len(repoAndTag) != 2 {
return nil, nil, fmt.Errorf(
"expected image '%v' split by colon to result in two substrings but got '%v'",
image,
repoAndTag,
)
}
}
_, err = f.createOrUpdateServiceAccount(ctx, namespace, fmt.Sprintf("%s/admission-webhook/service-account.yaml", f.exampleDir))
if err != nil {
return nil, nil, err
}
err = f.CreateOrUpdateDeploymentAndWaitUntilReady(ctx, namespace, deploy)
if err != nil {
return nil, nil, err
}
service, err := MakeService(fmt.Sprintf("%s/admission-webhook/service.yaml", f.exampleDir))
if err != nil {
return nil, nil, fmt.Errorf("cannot parse service file: %w", err)
}
service.Namespace = namespace
if _, err := f.CreateOrUpdateServiceAndWaitUntilReady(ctx, namespace, service); err != nil {
return nil, nil, fmt.Errorf("failed to create or update admission webhook server service: %w", err)
}
return service, certBytes, nil
}
func removeLabelsPatch(labels ...string) ([]byte, error) {
type patch struct {
Op string `json:"op"`
Path string `json:"path"`
}
var patches []patch
encoder := strings.NewReplacer("/", "~1", "~", "~0")
for _, label := range labels {
patches = append(patches, patch{Op: "remove", Path: "/metadata/labels/" + encoder.Replace(label)})
}
return json.Marshal(patches)
}