github.com/jmrodri/operator-sdk@v0.5.0/pkg/metrics/metrics.go (about)

     1  // Copyright 2018 The Operator-SDK Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package metrics
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  
    21  	"github.com/operator-framework/operator-sdk/pkg/k8sutil"
    22  
    23  	v1 "k8s.io/api/core/v1"
    24  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/types"
    27  	"k8s.io/apimachinery/pkg/util/intstr"
    28  	"k8s.io/client-go/rest"
    29  	crclient "sigs.k8s.io/controller-runtime/pkg/client"
    30  	logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
    31  )
    32  
    33  var log = logf.Log.WithName("metrics")
    34  
    35  // PrometheusPortName defines the port name used in kubernetes deployment and service resources
    36  const PrometheusPortName = "metrics"
    37  
    38  // ExposeMetricsPort creates a Kubernetes Service to expose the passed metrics port.
    39  func ExposeMetricsPort(ctx context.Context, port int32) (*v1.Service, error) {
    40  	// We do not need to check the validity of the port, as controller-runtime
    41  	// would error out and we would never get to this stage.
    42  	s, err := initOperatorService(port, PrometheusPortName)
    43  	if err != nil {
    44  		if err == k8sutil.ErrNoNamespace {
    45  			log.Info("Skipping metrics Service creation; not running in a cluster.")
    46  			return nil, nil
    47  		}
    48  		return nil, fmt.Errorf("failed to initialize service object for metrics: %v", err)
    49  	}
    50  	service, err := createService(ctx, s)
    51  	if err != nil {
    52  		return nil, fmt.Errorf("failed to create or get service for metrics: %v", err)
    53  	}
    54  
    55  	return service, nil
    56  }
    57  
    58  func createService(ctx context.Context, s *v1.Service) (*v1.Service, error) {
    59  	config, err := rest.InClusterConfig()
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  
    64  	client, err := crclient.New(config, crclient.Options{})
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  
    69  	if err := client.Create(ctx, s); err != nil {
    70  		if !apierrors.IsAlreadyExists(err) {
    71  			return nil, err
    72  		}
    73  		// Get existing Service and return it
    74  		existingService := &v1.Service{}
    75  		err := client.Get(ctx, types.NamespacedName{
    76  			Name:      s.Name,
    77  			Namespace: s.Namespace,
    78  		}, existingService)
    79  		if err != nil {
    80  			return nil, err
    81  		}
    82  		log.Info("Metrics Service object already exists", "name", existingService.Name)
    83  		return existingService, nil
    84  	}
    85  
    86  	log.Info("Metrics Service object created", "name", s.Name)
    87  	return s, nil
    88  }
    89  
    90  // initOperatorService returns the static service which exposes specifed port.
    91  func initOperatorService(port int32, portName string) (*v1.Service, error) {
    92  	operatorName, err := k8sutil.GetOperatorName()
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  	namespace, err := k8sutil.GetOperatorNamespace()
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  	service := &v1.Service{
   101  		ObjectMeta: metav1.ObjectMeta{
   102  			Name:      operatorName,
   103  			Namespace: namespace,
   104  			Labels:    map[string]string{"name": operatorName},
   105  		},
   106  		TypeMeta: metav1.TypeMeta{
   107  			Kind:       "Service",
   108  			APIVersion: "v1",
   109  		},
   110  		Spec: v1.ServiceSpec{
   111  			Ports: []v1.ServicePort{
   112  				{
   113  					Port:     port,
   114  					Protocol: v1.ProtocolTCP,
   115  					TargetPort: intstr.IntOrString{
   116  						Type:   intstr.Int,
   117  						IntVal: port,
   118  					},
   119  					Name: portName,
   120  				},
   121  			},
   122  			Selector: map[string]string{"name": operatorName},
   123  		},
   124  	}
   125  	return service, nil
   126  }