Skip to content

enable default collector #1044

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

Merged
merged 5 commits into from
Apr 22, 2025
Merged
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
64 changes: 64 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import (
"slices"
"strconv"
"strings"
"time"

"github.com/nginx/agent/v3/internal/datasource/file"

uuidLibrary "github.com/nginx/agent/v3/pkg/id"
selfsignedcerts "github.com/nginx/agent/v3/pkg/tls"
Expand Down Expand Up @@ -115,6 +118,8 @@ func ResolveConfig() (*Config, error) {
Labels: resolveLabels(),
}

checkCollectorConfiguration(collector, config)

slog.Debug("Agent config", "config", config)
slog.Info("Enabled features", "features", config.Features)
slog.Info("Excluded files from being watched for file changes", "exclude_files",
Expand All @@ -123,6 +128,60 @@ func ResolveConfig() (*Config, error) {
return config, nil
}

func checkCollectorConfiguration(collector *Collector, config *Config) {
if isOTelExporterConfigured(collector) && config.IsGrpcClientConfigured() && config.IsAuthConfigured() &&
config.IsTLSConfigured() {
slog.Info("No collector configuration found in NGINX Agent config, command server configuration found." +
"Using default collector configuration")
defaultCollector(collector, config)
}
}

func defaultCollector(collector *Collector, config *Config) {
token := config.Command.Auth.Token
if config.Command.Auth.TokenPath != "" {
slog.Debug("Reading token from file", "path", config.Command.Auth.TokenPath)
pathToken, err := file.ReadFromFile(config.Command.Auth.TokenPath)
if err != nil {
slog.Error("Error adding token to default collector, "+
"default collector configuration not started", "error", err)

return
}
token = pathToken
}

collector.Receivers.HostMetrics = &HostMetrics{
Scrapers: &HostMetricsScrapers{
CPU: &CPUScraper{},
Disk: &DiskScraper{},
Filesystem: &FilesystemScraper{},
Memory: &MemoryScraper{},
Network: nil,
},
CollectionInterval: 1 * time.Minute,
InitialDelay: 1 * time.Second,
}

collector.Exporters.OtlpExporters = append(collector.Exporters.OtlpExporters, OtlpExporter{
Server: config.Command.Server,
TLS: config.Command.TLS,
Compression: "",
Authenticator: "headers_setter",
})

header := []Header{
{
Action: "insert",
Key: "authorization",
Value: token,
},
}
collector.Extensions.HeadersSetter = &HeadersSetter{
Headers: header,
}
}

func setVersion(version, commit string) {
RootCommand.Version = version + "-" + commit
viperInstance.SetDefault(VersionKey, version)
Expand Down Expand Up @@ -911,3 +970,8 @@ func resolveMapStructure(key string, object any) error {

return nil
}

func isOTelExporterConfigured(collector *Collector) bool {
return len(collector.Exporters.OtlpExporters) == 0 && collector.Exporters.PrometheusExporter == nil &&
collector.Exporters.Debug == nil
}
17 changes: 17 additions & 0 deletions internal/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,23 @@ func (c *Config) IsDirectoryAllowed(directory string) bool {
return isAllowedDir(directory, c.AllowedDirectories)
}

func (c *Config) IsGrpcClientConfigured() bool {
return c.Command != nil &&
c.Command.Server != nil &&
c.Command.Server.Host != "" &&
c.Command.Server.Port != 0 &&
c.Command.Server.Type == Grpc
}

func (c *Config) IsAuthConfigured() bool {
return c.Command.Auth != nil &&
(c.Command.Auth.Token != "" || c.Command.Auth.TokenPath != "")
}

func (c *Config) IsTLSConfigured() bool {
return c.Command.TLS != nil
}

func (c *Config) IsFeatureEnabled(feature string) bool {
for _, enabledFeature := range c.Features {
if enabledFeature == feature {
Expand Down
37 changes: 37 additions & 0 deletions internal/datasource/file/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// 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 file

import (
"bytes"
"errors"
"fmt"
"os"
)

// ReadFromFile reads the contents from a file, trims the white space, trims newlines
// then returns the contents as a string
func ReadFromFile(path string) (string, error) {
if path == "" {
return "", errors.New("failed to read file since file path is empty")
}

var content string
contentBytes, err := os.ReadFile(path)
if err != nil {
return "", fmt.Errorf("unable to read from file: %w", err)
}

contentBytes = bytes.TrimSpace(contentBytes)
contentBytes = bytes.TrimRight(contentBytes, "\n")
content = string(contentBytes)

if content == "" {
return "", errors.New("failed to read from file, file is empty")
}

return content, nil
}
64 changes: 64 additions & 0 deletions internal/datasource/file/file_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// 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 file

import (
"os"
"testing"

"github.com/nginx/agent/v3/test/helpers"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func Test_RetrieveTokenFromFile(t *testing.T) {
dir := t.TempDir()
tokenFile := helpers.CreateFileWithErrorCheck(t, dir, "test-tkn")
defer helpers.RemoveFileWithErrorCheck(t, tokenFile.Name())
tests := []struct {
name string
path string
expected string
expectedErrMsg string
createToken bool
}{
{
name: "Test 1: File exists",
createToken: true,
path: tokenFile.Name(),
expected: "test-tkn",
expectedErrMsg: "",
},
{
name: "Test 2: File does not exist",
createToken: false,
path: "test-tkn",
expected: "",
expectedErrMsg: "unable to read from file: open test-tkn: no such file or directory",
},
{
name: "Test 3: Empty path",
createToken: false,
path: "",
expected: "",
expectedErrMsg: "failed to read file since file path is empty",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.createToken {
writeErr := os.WriteFile(tokenFile.Name(), []byte(" test-tkn\n"), 0o600)
require.NoError(t, writeErr)
}

token, err := ReadFromFile(tt.path)
if err != nil {
assert.Equal(t, tt.expectedErrMsg, err.Error())
}
assert.Equal(t, tt.expected, token)
})
}
}
30 changes: 4 additions & 26 deletions internal/grpc/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@
package grpc

import (
"bytes"
"context"
"crypto/tls"
"errors"
"fmt"
"log/slog"
"net"
"os"
"strings"
"sync"

"github.com/nginx/agent/v3/internal/datasource/file"

"github.com/cenkalti/backoff/v4"
grpcRetry "github.com/grpc-ecosystem/go-grpc-middleware/retry"

Expand Down Expand Up @@ -243,7 +243,8 @@ func addPerRPCCredentials(agentConfig *config.Config, resourceID string, opts []
token := agentConfig.Command.Auth.Token

if agentConfig.Command.Auth.TokenPath != "" {
tk, err := retrieveTokenFromFile(agentConfig.Command.Auth.TokenPath)
slog.Debug("Reading token from file", "path", agentConfig.Command.Auth.TokenPath)
tk, err := file.ReadFromFile(agentConfig.Command.Auth.TokenPath)
if err == nil {
token = tk
} else {
Expand All @@ -263,29 +264,6 @@ func addPerRPCCredentials(agentConfig *config.Config, resourceID string, opts []
return opts
}

func retrieveTokenFromFile(path string) (string, error) {
if path == "" {
return "", errors.New("token file path is empty")
}

slog.Debug("Reading token from file", "path", path)
var keyVal string
keyBytes, err := os.ReadFile(path)
if err != nil {
return "", fmt.Errorf("unable to read token from file: %w", err)
}

keyBytes = bytes.TrimSpace(keyBytes)
keyBytes = bytes.TrimRight(keyBytes, "\n")
keyVal = string(keyBytes)

if keyVal == "" {
return "", errors.New("failed to load token, token file is empty")
}

return keyVal, nil
}

// Have to create our own UnaryClientInterceptor function since protovalidate only provides a UnaryServerInterceptor
// https://pkg.go.dev/github.com/grpc-ecosystem/go-grpc-middleware/v2@v2.1.0/interceptors/protovalidate
func ProtoValidatorUnaryClientInterceptor() (grpc.UnaryClientInterceptor, error) {
Expand Down
61 changes: 0 additions & 61 deletions internal/grpc/grpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ package grpc
import (
"context"
"fmt"
"os"
"testing"

"google.golang.org/grpc/credentials"
Expand Down Expand Up @@ -356,66 +355,6 @@ func Test_ValidateGrpcError(t *testing.T) {
assert.IsType(t, &backoff.PermanentError{}, result)
}

// nolint:revive,gocognit
func Test_retrieveTokenFromFile(t *testing.T) {
tests := []struct {
name string
path string
want string
wantErrMsg string
createToken bool
}{
{
name: "Test 1: File exists",
createToken: true,
path: "test-tkn",
want: "test-tkn",
wantErrMsg: "",
},
{
name: "Test 2: File does not exist",
createToken: false,
path: "test-tkn",
want: "",
wantErrMsg: "unable to read token from file: open test-tkn: no such file or directory",
},
{
name: "Test 3: Empty path",
createToken: false,
path: "",
want: "",
wantErrMsg: "token file path is empty",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
defer func() {
if tt.createToken {
err := os.Remove(tt.path)
if err != nil {
t.Log(err)
}
}
}()

if tt.createToken {
err := os.WriteFile(tt.path, []byte(tt.path), 0o600)
if err != nil {
t.Fatal(err)
}
}

got, err := retrieveTokenFromFile(tt.path)
if err != nil {
if err.Error() != tt.wantErrMsg {
t.Errorf("retrieveTokenFromFile() error = %v, wantErr %v", err, tt.wantErrMsg)
}
}
assert.Equalf(t, tt.want, got, "retrieveTokenFromFile(%v)", tt.path)
})
}
}

func Test_getTransportCredentials(t *testing.T) {
tests := []struct {
want credentials.TransportCredentials
Expand Down
10 changes: 1 addition & 9 deletions internal/plugin/plugin_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func addResourcePlugin(plugins []bus.Plugin, agentConfig *config.Config) []bus.P
}

func addCommandAndFilePlugins(ctx context.Context, plugins []bus.Plugin, agentConfig *config.Config) []bus.Plugin {
if isGrpcClientConfigured(agentConfig) {
if agentConfig.IsGrpcClientConfigured() {
grpcConnection, err := grpc.NewGrpcConnection(ctx, agentConfig)
if err != nil {
slog.WarnContext(ctx, "Failed to create gRPC connection", "error", err)
Expand Down Expand Up @@ -79,11 +79,3 @@ func addWatcherPlugin(plugins []bus.Plugin, agentConfig *config.Config) []bus.Pl

return plugins
}

func isGrpcClientConfigured(agentConfig *config.Config) bool {
return agentConfig.Command != nil &&
agentConfig.Command.Server != nil &&
agentConfig.Command.Server.Host != "" &&
agentConfig.Command.Server.Port != 0 &&
agentConfig.Command.Server.Type == config.Grpc
}
Loading