Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add labels to Agent Configuration #945

Open
wants to merge 3 commits into
base: v3
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions api/grpc/mpi/v1/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) F5, Inc.
//
// This source code is licensed under the Apache License, Version 2.0 license found in the
// LICENSE file in the root directory of this source tree.

package v1

import (
"google.golang.org/protobuf/types/known/structpb"
)

// ConvertToStructs converts a map[string]any into a slice of *structpb.Struct.
// Each key-value pair in the input map is converted into a *structpb.Struct,
// where the key is used as the field name, and the value is added to the Struct.
//
// Parameters:
// - input: A map[string]any containing key-value pairs to be converted.
//
// Returns:
// - []*structpb.Struct: A slice of *structpb.Struct, where each map entry is converted into a struct.
// - error: An error if any value in the input map cannot be converted into a *structpb.Struct.
//
// Example:
//
// input := map[string]any{
// "key1": "value1",
// "key2": 123,
// "key3": true,
// }
// structs, err := ConvertToStructs(input)
// // structs will contain a slice of *structpb.Struct
// // err will be nil if all conversions succeed.
func ConvertToStructs(input map[string]any) ([]*structpb.Struct, error) {
structs := []*structpb.Struct{}
for key, value := range input {
// Convert each value in the map to *structpb.Struct
structValue, err := structpb.NewStruct(map[string]any{
key: value,
})
if err != nil {
return structs, err
}
structs = append(structs, structValue)
}

return structs, nil
}
72 changes: 72 additions & 0 deletions api/grpc/mpi/v1/helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (c) F5, Inc.
//
// This source code is licensed under the Apache License, Version 2.0 license found in the
// LICENSE file in the root directory of this source tree.

package v1

import (
"testing"

"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/structpb"
)

func TestConvertToStructs(t *testing.T) {
tests := []struct {
name string
input map[string]any
expected []*structpb.Struct
wantErr bool
}{
{
name: "Test 1: Valid input with simple key-value pairs",
input: map[string]any{
"key1": "value1",
"key2": 123,
"key3": true,
},
expected: []*structpb.Struct{
{
Fields: map[string]*structpb.Value{
"key1": structpb.NewStringValue("value1"),
},
},
{
Fields: map[string]*structpb.Value{
"key2": structpb.NewNumberValue(123),
},
},
{
Fields: map[string]*structpb.Value{
"key3": structpb.NewBoolValue(true),
},
},
},
wantErr: false,
},
{
name: "Test 2: Empty input map",
input: make(map[string]any),
expected: []*structpb.Struct{},
wantErr: false,
},
{
name: "Test 3: Invalid input type",
input: map[string]any{
"key1": func() {}, // Unsupported type
},
expected: []*structpb.Struct{},
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ConvertToStructs(tt.input)

assert.Equal(t, tt.expected, got)
assert.Equal(t, tt.wantErr, err != nil)
})
}
}
77 changes: 77 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ package config

import (
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
"os"
"path/filepath"
"slices"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -104,6 +106,7 @@ func ResolveConfig() (*Config, error) {
Common: resolveCommon(),
Watchers: resolveWatchers(),
Features: viperInstance.GetStringSlice(FeaturesKey),
Labels: resolveLabels(),
}

slog.Debug("Agent config", "config", config)
Expand Down Expand Up @@ -202,6 +205,7 @@ func registerFlags() {
"A comma-separated list of features enabled for the agent.",
)

registerCommonFlags(fs)
registerCommandFlags(fs)
registerCollectorFlags(fs)

Expand All @@ -218,6 +222,14 @@ func registerFlags() {
})
}

func registerCommonFlags(fs *flag.FlagSet) {
fs.StringToString(
LabelsRootKey,
DefaultLabels(),
"A list of labels associated with these instances",
)
}

func registerCommandFlags(fs *flag.FlagSet) {
fs.String(
CommandServerHostKey,
Expand Down Expand Up @@ -406,6 +418,71 @@ func resolveLog() *Log {
}
}

func resolveLabels() map[string]interface{} {
input := viperInstance.GetStringMapString(LabelsRootKey)
result := make(map[string]interface{})

for key, value := range input {
trimmedValue := strings.TrimSpace(value)

switch {
case trimmedValue == "" || trimmedValue == "nil": // Handle empty values as nil
result[key] = nil

case parseInt(trimmedValue) != nil: // Integer
result[key] = parseInt(trimmedValue)

case parseFloat(trimmedValue) != nil: // Float
result[key] = parseFloat(trimmedValue)

case parseBool(trimmedValue) != nil: // Boolean
result[key] = parseBool(trimmedValue)

case parseJSON(trimmedValue) != nil: // JSON object/array
result[key] = parseJSON(trimmedValue)

default: // String
result[key] = trimmedValue
}
}

return result
}

// Parsing helper functions return the parsed value or nil if parsing fails
func parseInt(value string) interface{} {
if intValue, err := strconv.Atoi(value); err == nil {
return intValue
}

return nil
}

func parseFloat(value string) interface{} {
if floatValue, err := strconv.ParseFloat(value, 64); err == nil {
return floatValue
}

return nil
}

func parseBool(value string) interface{} {
if boolValue, err := strconv.ParseBool(value); err == nil {
return boolValue
}

return nil
}

func parseJSON(value string) interface{} {
var jsonValue interface{}
if err := json.Unmarshal([]byte(value), &jsonValue); err == nil {
return jsonValue
}

return nil
}

func resolveDataPlaneConfig() *DataPlaneConfig {
return &DataPlaneConfig{
Nginx: &NginxDataPlaneConfig{
Expand Down
Loading