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 }