Skip to content

Commit 023aa5c

Browse files
authored
feat: Add manifest update Command for Configuration Management (#308)
* feat: nrdot-builder cli tool * feat: Add support for JSON output flag
1 parent a2fa5ed commit 023aa5c

File tree

15 files changed

+1181
-21
lines changed

15 files changed

+1181
-21
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2025 New Relic Corporation. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package cmd
4+
5+
import (
6+
"fmt"
7+
"newrelic-collector-builder/cmd/manifest"
8+
9+
"github.com/spf13/cobra"
10+
)
11+
12+
var manifestConfigPath string
13+
14+
// manifestCmd represents the manifest command
15+
var manifestCmd = &cobra.Command{
16+
Use: "manifest",
17+
Short: "Manage the OCB manifest file",
18+
Long: `
19+
The manifest command allows you to manage the OCB manifest file.`,
20+
Run: func(cmd *cobra.Command, args []string) {
21+
fmt.Println("manifest called")
22+
},
23+
}
24+
25+
func init() {
26+
rootCmd.AddCommand(manifestCmd)
27+
// Register the update subcommand
28+
manifestCmd.AddCommand(manifest.UpdateCmd)
29+
30+
// Define a persistent flag for `manifestCmd`
31+
manifestCmd.PersistentFlags().StringVarP(
32+
&manifestConfigPath,
33+
"config",
34+
"c",
35+
"",
36+
"Path to the manifest configuration file",
37+
)
38+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# This is an invalid config file for testing purposes. It should not be used in production.
2+
#
3+
4+
dist:
5+
module: github.com/newrelic/nrdot-collector-releases/nrdot-collector-host
6+
name: nrdot-collector-host
7+
description: NRDOT Collector Host
8+
version: 1.1.0
9+
output_path: ./_build
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
dist:
2+
module: go.opentelemetry.io/collector/cmd/otelcorecol
3+
name: otelcorecol
4+
description: Local OpenTelemetry Collector binary, testing only.
5+
version: 0.125.0-dev
6+
7+
receivers:
8+
- gomod: go.opentelemetry.io/collector/receiver/nopreceiver v0.125.0
9+
- gomod: go.opentelemetry.io/collector/receiver/otlpreceiver v0.125.0
10+
exporters:
11+
- gomod: go.opentelemetry.io/collector/exporter/debugexporter v0.125.0
12+
- gomod: go.opentelemetry.io/collector/exporter/nopexporter v0.125.0
13+
- gomod: go.opentelemetry.io/collector/exporter/otlpexporter v0.125.0
14+
- gomod: go.opentelemetry.io/collector/exporter/otlphttpexporter v0.125.0
15+
extensions:
16+
- gomod: go.opentelemetry.io/collector/extension/memorylimiterextension v0.125.0
17+
- gomod: go.opentelemetry.io/collector/extension/zpagesextension v0.125.0
18+
processors:
19+
- gomod: go.opentelemetry.io/collector/processor/batchprocessor v0.125.0
20+
- gomod: go.opentelemetry.io/collector/processor/memorylimiterprocessor v0.125.0
21+
connectors:
22+
- gomod: go.opentelemetry.io/collector/connector/forwardconnector v0.125.0
23+
24+
providers:
25+
- gomod: go.opentelemetry.io/collector/confmap/provider/envprovider v1.31.0
26+
- gomod: go.opentelemetry.io/collector/confmap/provider/fileprovider v1.31.0
27+
- gomod: go.opentelemetry.io/collector/confmap/provider/httpprovider v1.31.0
28+
- gomod: go.opentelemetry.io/collector/confmap/provider/httpsprovider v1.31.0
29+
- gomod: go.opentelemetry.io/collector/confmap/provider/yamlprovider v1.31.0
30+
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// Copyright 2025 New Relic Corporation. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package manifest
4+
5+
import (
6+
"encoding/json"
7+
"fmt"
8+
"newrelic-collector-builder/internal/manifest"
9+
"path/filepath"
10+
11+
"github.com/knadh/koanf/parsers/yaml"
12+
"github.com/knadh/koanf/v2"
13+
"golang.org/x/mod/semver"
14+
15+
"github.com/knadh/koanf/providers/file"
16+
"github.com/spf13/cobra"
17+
"go.uber.org/zap"
18+
)
19+
20+
// UpdateCmd represents the `manifest update` subcommand
21+
var UpdateCmd = &cobra.Command{
22+
Use: "update",
23+
Short: "Update the manifest file",
24+
Long: "Update the manifest file to ensure otel components are up to date.",
25+
26+
RunE: func(cmd *cobra.Command, args []string) error {
27+
configPath, _ := cmd.Flags().GetString("config")
28+
29+
jsonOutput, _ := cmd.Root().PersistentFlags().GetBool("json")
30+
verbose, _ := cmd.Root().PersistentFlags().GetBool("verbose")
31+
32+
matches, _ := filepath.Glob(configPath)
33+
34+
if len(matches) == 0 {
35+
fmt.Println("No files matched the pattern.")
36+
return nil
37+
}
38+
39+
var otelColVersion string
40+
41+
for _, match := range matches {
42+
cfg, _, err := initConfig(match, verbose)
43+
44+
if err != nil {
45+
return err
46+
}
47+
48+
if err = cfg.Validate(); err != nil {
49+
return fmt.Errorf("invalid configuration: %w", err)
50+
}
51+
52+
if err = cfg.SetGoPath(); err != nil {
53+
return fmt.Errorf("go not found: %w", err)
54+
}
55+
56+
if err = cfg.SetOtelColVersion(); err != nil {
57+
return fmt.Errorf("go not found: %w", err)
58+
}
59+
60+
if err = cfg.ParseModules(); err != nil {
61+
return fmt.Errorf("invalid module configuration: %w", err)
62+
}
63+
64+
updatedCfg, err := manifest.UpdateConfigModules(cfg)
65+
if err != nil {
66+
return fmt.Errorf("failed to update configuration: %w", err)
67+
}
68+
69+
if err = manifest.WriteConfigFile(updatedCfg); err != nil {
70+
return fmt.Errorf("failed to write configuration file: %w", err)
71+
}
72+
73+
if otelColVersion == "" || semver.Compare(otelColVersion, cfg.OtelColVersion) > 0 {
74+
otelColVersion = cfg.OtelColVersion
75+
}
76+
}
77+
78+
if jsonOutput {
79+
// print JSON output of all otel versions
80+
output := struct {
81+
OtelColVersion string `json:"otelColVersion"`
82+
}{
83+
OtelColVersion: otelColVersion,
84+
}
85+
b, err := json.Marshal(output)
86+
if err != nil {
87+
return fmt.Errorf("failed to marshal JSON output: %w", err)
88+
}
89+
fmt.Println(string(b))
90+
}
91+
92+
return nil
93+
94+
},
95+
}
96+
97+
func initConfig(cfgFile string, verbose bool) (*manifest.Config, *koanf.Koanf, error) {
98+
var err error
99+
log, err := zap.NewDevelopment()
100+
if err != nil {
101+
return nil, nil, fmt.Errorf("failed to create logger: %w", err)
102+
}
103+
104+
cfg := &manifest.Config{
105+
Logger: log,
106+
Verbose: verbose,
107+
}
108+
109+
if cfg.Verbose {
110+
cfg.Logger.Info("Using config file", zap.String("path", cfgFile))
111+
}
112+
// load the config file
113+
provider := file.Provider(cfgFile)
114+
115+
k := koanf.New(".")
116+
117+
if err = k.Load(provider, yaml.Parser()); err != nil {
118+
return nil, nil, fmt.Errorf("failed to load configuration file: %w", err)
119+
}
120+
121+
if err = k.UnmarshalWithConf("", cfg, koanf.UnmarshalConf{Tag: "mapstructure"}); err != nil {
122+
return nil, nil, fmt.Errorf("failed to unmarshal configuration: %w", err)
123+
}
124+
125+
cfg.Path = cfgFile
126+
cfg.Dir = filepath.Dir(cfgFile)
127+
128+
return cfg, k, nil
129+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright 2025 New Relic Corporation. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package manifest
4+
5+
import (
6+
"errors"
7+
"os"
8+
"testing"
9+
10+
"github.com/spf13/cobra"
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/mock"
13+
)
14+
15+
type MockConfig struct {
16+
mock.Mock
17+
}
18+
19+
func (m *MockConfig) Validate() error {
20+
args := m.Called()
21+
return args.Error(0)
22+
}
23+
24+
func (m *MockConfig) SetGoPath() error {
25+
args := m.Called()
26+
return args.Error(0)
27+
}
28+
29+
func (m *MockConfig) ParseModules() error {
30+
args := m.Called()
31+
return args.Error(0)
32+
}
33+
34+
func TestUpdateCmd_RunE(t *testing.T) {
35+
36+
// Load the test-config.yaml file
37+
testConfigPath := "testdata/test-config.yaml"
38+
yamlData, err := os.ReadFile(testConfigPath)
39+
assert.NoError(t, err)
40+
41+
// Create a temporary file to simulate writing to a file
42+
tempFile, err := os.CreateTemp("", "test-config-*.yaml")
43+
assert.NoError(t, err)
44+
defer os.Remove(tempFile.Name()) // Clean up the file after the test
45+
46+
// Write the loaded YAML data to the temporary file
47+
_, err = tempFile.Write(yamlData)
48+
assert.NoError(t, err)
49+
tempFile.Close() // Close the file to ensure the changes are flushed
50+
51+
cmd := &cobra.Command{}
52+
cmd.Flags().String("config", tempFile.Name(), "")
53+
54+
mockConfig := new(MockConfig)
55+
mockConfig.On("Validate").Return(nil)
56+
mockConfig.On("SetGoPath").Return(nil)
57+
mockConfig.On("ParseModules").Return(nil)
58+
59+
err := UpdateCmd.RunE(cmd, []string{})
60+
assert.NoError(t, err)
61+
62+
mockConfig.AssertExpectations(t)
63+
}
64+
65+
func TestUpdateCmd_RunE_InvalidConfig(t *testing.T) {
66+
67+
cmd := &cobra.Command{}
68+
cmd.Flags().String("config", "test-config.yaml", "")
69+
70+
mockConfig := new(MockConfig)
71+
mockConfig.On("Validate").Return(errors.New("invalid configuration"))
72+
73+
err := UpdateCmd.RunE(cmd, []string{})
74+
assert.Error(t, err)
75+
assert.Contains(t, err.Error(), "invalid configuration")
76+
77+
mockConfig.AssertExpectations(t)
78+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright 2025 New Relic Corporation. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package cmd
4+
5+
import (
6+
"os"
7+
8+
"github.com/spf13/cobra"
9+
)
10+
11+
var jsonOutput bool
12+
var verbose bool
13+
14+
// rootCmd represents the base command when called without any subcommands
15+
var rootCmd = &cobra.Command{
16+
Use: "nrdot-collector-builder",
17+
Short: "NRDOT client for building the OpenTelemetry Collector",
18+
Long: `
19+
A CLI tool for building the OpenTelemetry Collector with NRDOT extensions.
20+
This tool allows you to create a custom OpenTelemetry Collector binary with NRDOT extensions and configurations.
21+
It simplifies the process of building and deploying the collector with NRDOT-specific features.
22+
`,
23+
}
24+
25+
func init() {
26+
rootCmd.PersistentFlags().BoolVar(&jsonOutput, "json", false, "Output results in JSON format")
27+
rootCmd.PersistentFlags().BoolVar(&verbose, "verbose", false, "Verbose output")
28+
}
29+
30+
// Execute adds all child commands to the root command and sets flags appropriately.
31+
// This is called by main.main(). It only needs to happen once to the rootCmd.
32+
func Execute() {
33+
34+
err := rootCmd.Execute()
35+
if err != nil {
36+
os.Exit(1)
37+
}
38+
39+
}

cmd/nrdot-collector-builder/go.mod

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
module newrelic-collector-builder
2+
3+
go 1.24.1
4+
5+
require (
6+
github.com/knadh/koanf/parsers/yaml v1.0.0
7+
github.com/stretchr/testify v1.10.0
8+
)
9+
10+
require (
11+
github.com/davecgh/go-spew v1.1.1 // indirect
12+
github.com/kr/pretty v0.3.1 // indirect
13+
github.com/pmezard/go-difflib v1.0.0 // indirect
14+
github.com/rogpeppe/go-internal v1.10.0 // indirect
15+
github.com/stretchr/objx v0.5.2 // indirect
16+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
17+
)
18+
19+
require (
20+
github.com/fsnotify/fsnotify v1.9.0 // indirect
21+
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
22+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
23+
github.com/knadh/koanf/maps v0.1.2 // indirect
24+
github.com/knadh/koanf/providers/file v1.2.0
25+
github.com/knadh/koanf/v2 v2.2.0
26+
github.com/mitchellh/copystructure v1.2.0 // indirect
27+
github.com/mitchellh/reflectwalk v1.0.2 // indirect
28+
github.com/spf13/cobra v1.9.1
29+
github.com/spf13/pflag v1.0.6 // indirect
30+
go.uber.org/multierr v1.11.0
31+
go.uber.org/zap v1.27.0
32+
golang.org/x/mod v0.24.0
33+
golang.org/x/sys v0.32.0 // indirect
34+
gopkg.in/yaml.v3 v3.0.1
35+
)

0 commit comments

Comments
 (0)