Skip to content

Commit

Permalink
fix: post-parse comma-separated flag '--populated-labels'
Browse files Browse the repository at this point in the history
  • Loading branch information
achetronic committed Feb 16, 2024
1 parent 4e17a4b commit fc33dd2
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 77 deletions.
21 changes: 11 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,24 @@ that are not exposed natively
In our platform team, we needed to build some useful dashboards to show our developers the status of the pipelines
per project to make them compete for the first position on the reliability podium.

There are some metrics that are not exposed to achieve that goal, so we decided to do this little tool
There are some metrics that are not exposed, which are necessary to achieve our goal,
so we decided to create this small tool.

## Flags

Every configuration parameter can be defined by flags that can be passed to the CLI.
They are described in the following table:

| Name | Description | Default Example | |
|:---------------------|:----------------------------------------------------|:---------------:|------------------------------------------------------------|
| `--log-level` | Define the verbosity of the logs | `info` | `--log-level info` |
| `--disable-trace` | Disable traces from logs | `false` | `--disable-trace true` |
| `--kubeconfig` | Path to kubeconfig | `-` | `--kubeconfig="~/.kube/config"` |
| `--metrics-port` | Port where metrics web-server will run | `2112` | `--metrics-port 9090` |
| `--metrics-host` | Host where metrics web-server will run | `0.0.0.0` | `--metrics-host 10.10.10.1` |
| `--populated-labels` | Comma-separated list of labels populated on metrics | `-` | `--populated-labels "apiVersion,pipelineName,projectName"` |
| Name | Description | Default Example | |
|:---------------------|:------------------------------------------------------------------------|:---------------:|------------------------------------------------------------|
| `--log-level` | Define the verbosity of the logs | `info` | `--log-level info` |
| `--disable-trace` | Disable traces from logs | `false` | `--disable-trace true` |
| `--kubeconfig` | Path to kubeconfig | `-` | `--kubeconfig="~/.kube/config"` |
| `--metrics-port` | Port where metrics web-server will run | `2112` | `--metrics-port 9090` |
| `--metrics-host` | Host where metrics web-server will run | `0.0.0.0` | `--metrics-host 10.10.10.1` |
| `--populated-labels` | (Repeatable or comma-separated list) Object labels populated on metrics | `-` | `--populated-labels "apiVersion,pipelineName,projectName"` |

> For Prometheus SDK it is mandatory to register the metrics before using them.
> For Prometheus SDK, it is mandatory to register the metrics before using them.
> Due to this, if you use `--populated-labels` flag and the label is not present in some PipelineRun or TaskRun
> the label will be populated with `#` as value
Expand Down
31 changes: 8 additions & 23 deletions internal/cmd/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,7 @@ func NewCommand() *cobra.Command {
// It's declared here for documentation purposes only
cmd.Flags().String("kubeconfig", "~/.kube/config", "Path to the kubeconfig file")

cmd.Flags().StringSlice("populated-labels", []string{}, "Comma-separated list of labels populated on metrics")

//cmd.Flags().Bool("watch-all-namespaces", false, "Enable watching resources on all namespaces")
//cmd.Flags().String("watch-namespace", "default", "Namespace to watch")

// Conditions
//cmd.MarkFlagsOneRequired("watch-all-namespaces", "watch-namespace")
//cmd.MarkFlagsMutuallyExclusive("watch-all-namespaces", "watch-namespace")
cmd.Flags().StringSlice("populated-labels", []string{}, "(Repeatable or comma-separated list) Object labels populated on metrics")

return cmd
}
Expand Down Expand Up @@ -97,23 +90,15 @@ func RunCommand(cmd *cobra.Command, args []string) {
log.Fatalf(PopulatedLabelsFlagErrorMessage, err)
}

//watchAllNamespacesFlag, err := cmd.Flags().GetBool("watch-all-namespaces")
//if err != nil {
// log.Fatalf(WatchAllNamespacesFlagErrorMessage, err)
//}
//
//watchNamespaceFlag, err := cmd.Flags().GetString("watch-namespace")
//if err != nil {
// log.Fatalf(WatchNamespaceFlagErrorMessage, err)
//}
// Handle a potentially confusing situation:
// Cobra flags' library does not properly parse
// comma-separated lists depending on the environment
// the CLI is running (i.e. Kubernetes),
populatedLabelsFlag = globals.SplitCommaSeparatedValues(populatedLabelsFlag)

// Store populated labels in context to use them later
globals.ExecContext.Context = context.WithValue(globals.ExecContext.Context,
"flag-populated-labels", populatedLabelsFlag)
//globals.ExecContext.Context = context.WithValue(globals.ExecContext.Context,
// "flag-watch-all-namespaces", watchAllNamespacesFlag)
//globals.ExecContext.Context = context.WithValue(globals.ExecContext.Context,
// "flag-watch-namespace", watchNamespaceFlag)

// Register metrics into Prometheus Registry
metrics.RegisterMetrics(populatedLabelsFlag)
Expand All @@ -124,7 +109,7 @@ func RunCommand(cmd *cobra.Command, args []string) {
// Process PipelineRun resources in the background
// TODO: Errors for watcher must be shown inside the watcher as this is a goroutine
go func() {
err := kubernetes.WatchPipelineRuns(globals.ExecContext.Context, client)
err := kubernetes.WatchPipelineRuns(&globals.ExecContext.Context, client)
if err != nil {

}
Expand All @@ -133,7 +118,7 @@ func RunCommand(cmd *cobra.Command, args []string) {
// Process TaskRun resources in the background
// TODO: Errors for watcher must be shown inside the watcher as this is a goroutine
go func() {
err := kubernetes.WatchTaskRuns(globals.ExecContext.Context, client)
err := kubernetes.WatchTaskRuns(&globals.ExecContext.Context, client)
if err != nil {

}
Expand Down
24 changes: 24 additions & 0 deletions internal/globals/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package globals

import "strings"

// CopyMap return a map that is a real copy of the original
// Ref: https://go.dev/blog/maps
func CopyMap(src map[string]interface{}) map[string]interface{} {
m := make(map[string]interface{}, len(src))
for k, v := range src {
m[k] = v
}
return m
}

// SplitCommaSeparatedValues get a list of strings and return a new list
// where each element containing commas is divided in separated elements
func SplitCommaSeparatedValues(input []string) []string {
var result []string
for _, item := range input {
parts := strings.Split(item, ",")
result = append(result, parts...)
}
return result
}
45 changes: 12 additions & 33 deletions internal/kubernetes/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package kubernetes

import (
"context"
"fmt"
"github.com/prometheus/client_golang/prometheus"
"go.uber.org/zap"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -44,15 +43,15 @@ func NewClient() (client *dynamic.DynamicClient, err error) {

// GetNamespaces get a list of all namespaces existing in the cluster
// TODO: Evaluate if this method is needed
func GetNamespaces(ctx context.Context, client *dynamic.DynamicClient) (namespaces *unstructured.UnstructuredList, err error) {
func GetNamespaces(ctx *context.Context, client *dynamic.DynamicClient) (namespaces *unstructured.UnstructuredList, err error) {

resourceId := schema.GroupVersionResource{
Group: "",
Version: "v1",
Resource: "namespaces",
}

namespaceList, err := client.Resource(resourceId).List(ctx, metav1.ListOptions{})
namespaceList, err := client.Resource(resourceId).List(*ctx, metav1.ListOptions{})

if err != nil {
return namespaces, err
Expand All @@ -61,27 +60,8 @@ func GetNamespaces(ctx context.Context, client *dynamic.DynamicClient) (namespac
return namespaceList, err
}

// GetAllPipelineRuns
// TODO: Evaluate if this method is needed
func GetAllPipelineRuns(ctx context.Context, client *dynamic.DynamicClient) (resources []unstructured.Unstructured, err error) {

globals.ExecContext.Logger.Info("pepe")

resourceId := schema.GroupVersionResource{
Group: "tekton.dev",
Version: "v1",
Resource: "pipelineruns",
}

list, err := client.Resource(resourceId).Namespace("freeclip").List(ctx, metav1.ListOptions{})
_ = list
//log.Print(list.Items[0])

return resources, nil
}

// WatchPipelineRuns TODO
func WatchPipelineRuns(ctx context.Context, client *dynamic.DynamicClient) (err error) {
func WatchPipelineRuns(ctx *context.Context, client *dynamic.DynamicClient) (err error) {

populatedLabels := map[string]string{}
calculatedLabels := map[string]string{}
Expand All @@ -94,7 +74,7 @@ func WatchPipelineRuns(ctx context.Context, client *dynamic.DynamicClient) (err
}

// TODO
pipelineRunWatcher, err := client.Resource(resourceId).Watch(ctx, metav1.ListOptions{})
pipelineRunWatcher, err := client.Resource(resourceId).Watch(*ctx, metav1.ListOptions{})
if err != nil {
return err
}
Expand Down Expand Up @@ -177,7 +157,7 @@ func WatchPipelineRuns(ctx context.Context, client *dynamic.DynamicClient) (err
}

// WatchTaskRuns TODO
func WatchTaskRuns(ctx context.Context, client *dynamic.DynamicClient) (err error) {
func WatchTaskRuns(ctx *context.Context, client *dynamic.DynamicClient) (err error) {

populatedLabels := map[string]string{}
calculatedLabels := map[string]string{}
Expand All @@ -190,7 +170,7 @@ func WatchTaskRuns(ctx context.Context, client *dynamic.DynamicClient) (err erro
}

// TODO: Delete the namespace once the controller is fully working
taskRunWatcher, err := client.Resource(resourceId).Namespace("freeclip").Watch(ctx, metav1.ListOptions{})
taskRunWatcher, err := client.Resource(resourceId).Namespace("freeclip").Watch(*ctx, metav1.ListOptions{})
if err != nil {
return err
}
Expand Down Expand Up @@ -278,13 +258,12 @@ func GetObjectLabels(obj *runtime.Object) (labelsMap map[string]string, err erro

labelsMap = make(map[string]string)

// Iterar sobre el mapa original y hacer el "casting" de los valores
// Iterate over the original map and cast its values
for key, value := range objectLabels {
strValue, ok := value.(string)
if !ok {
// Manejo del error si el valor no es un string
fmt.Printf("El valor para '%s' no es un string y no puede ser convertido.\n", key)
continue // Opcionalmente, puedes decidir cómo manejar este caso.
globals.ExecContext.Logger.Infof("Value of label '%s' is not a string. Ignoring it", key)
continue
}
labelsMap[key] = strValue
}
Expand All @@ -294,7 +273,7 @@ func GetObjectLabels(obj *runtime.Object) (labelsMap map[string]string, err erro

// GetObjectPopulatedLabels return only user's desired labels from an object of type runtime.Object
// Desired labels are defined by flag "--populated-labels"
func GetObjectPopulatedLabels(ctx context.Context, object *runtime.Object) (labelsMap map[string]string, err error) {
func GetObjectPopulatedLabels(ctx *context.Context, object *runtime.Object) (labelsMap map[string]string, err error) {

// Read labels from event's resource
objectLabels, err := GetObjectLabels(object)
Expand All @@ -307,9 +286,9 @@ func GetObjectPopulatedLabels(ctx context.Context, object *runtime.Object) (labe
return labelsMap, nil
}

// TODO
// Recover flag 'populated-labels' from context
populatedLabels := map[string]string{}
populatedLabelsFlag := ctx.Value("flag-populated-labels").([]string)
populatedLabelsFlag := (*ctx).Value("flag-populated-labels").([]string)

//
parsedLabelsMap, _ := metrics.GetProcessedLabels(populatedLabelsFlag) // TODO: Handle error
Expand Down
11 changes: 0 additions & 11 deletions internal/kubernetes/utils.go

This file was deleted.

0 comments on commit fc33dd2

Please sign in to comment.