From 864fe0df49f7dea765cc8529cc3b01652b8d1be5 Mon Sep 17 00:00:00 2001 From: danish9039 Date: Sun, 5 Jan 2025 03:16:41 +0530 Subject: [PATCH 1/7] add feature command Signed-off-by: danish9039 --- otelcol/command.go | 19 +++++++++++++++ service/display_feature.go | 44 ++++++++++++++++++++++++++++++++++ service/internal/graph/host.go | 4 ++-- 3 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 service/display_feature.go diff --git a/otelcol/command.go b/otelcol/command.go index 6efd16df562..6ef0809a15c 100644 --- a/otelcol/command.go +++ b/otelcol/command.go @@ -4,12 +4,16 @@ package otelcol // import "go.opentelemetry.io/collector/otelcol" import ( + // Standard library "errors" "flag" + // Third party "github.com/spf13/cobra" + // Project internal "go.opentelemetry.io/collector/featuregate" + "go.opentelemetry.io/collector/service" ) // NewCommand constructs a new cobra.Command using the given CollectorSettings. @@ -36,6 +40,7 @@ func NewCommand(set CollectorSettings) *cobra.Command { return col.Run(cmd.Context()) }, } + rootCmd.AddCommand(newFeaturesCommand()) rootCmd.AddCommand(newComponentsCommand(set)) rootCmd.AddCommand(newValidateSubCommand(set, flagSet)) rootCmd.Flags().AddGoFlagSet(flagSet) @@ -63,3 +68,17 @@ func updateSettingsUsingFlags(set *CollectorSettings, flags *flag.FlagSet) error } return nil } + +func newFeaturesCommand() *cobra.Command { + return &cobra.Command{ + Use: "features [feature-id]", + Short: "Display feature gates information", + Long: "Display information about available feature gates and their status", + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) > 0 { + return service.DisplayFeature(args[0]) + } + return service.DisplayFeatures() + }, + } +} diff --git a/service/display_feature.go b/service/display_feature.go new file mode 100644 index 00000000000..efb7def7f6b --- /dev/null +++ b/service/display_feature.go @@ -0,0 +1,44 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package service // import "go.opentelemetry.io/collector/service" + +import ( + "fmt" + "os" + "text/tabwriter" + + "go.opentelemetry.io/collector/service/internal/graph" +) + +func DisplayFeatures() error { + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + fmt.Fprintf(w, "ID\tEnabled\tStage\tDescription\n") + data := graph.GetFeaturesTableData() + for _, row := range data.Rows { + fmt.Fprintf(w, "%s\t%v\t%s\t%s\n", + row.ID, + row.Enabled, + row.Stage, + row.Description) + } + return w.Flush() +} + +func DisplayFeature(id string) error { + data := graph.GetFeaturesTableData() + for _, row := range data.Rows { + if row.ID == id { + fmt.Printf("Feature: %s\n", row.ID) + fmt.Printf("Enabled: %v\n", row.Enabled) + fmt.Printf("Stage: %s\n", row.Stage) + fmt.Printf("Description: %s\n", row.Description) + fmt.Printf("From Version: %s\n", row.FromVersion) + if row.ToVersion != "" { + fmt.Printf("To Version: %s\n", row.ToVersion) + } + return nil + } + } + return fmt.Errorf("feature %q not found", id) +} diff --git a/service/internal/graph/host.go b/service/internal/graph/host.go index 3aaef5e055f..4d72a8b5bb5 100644 --- a/service/internal/graph/host.go +++ b/service/internal/graph/host.go @@ -138,11 +138,11 @@ func (host *Host) zPagesRequest(w http.ResponseWriter, _ *http.Request) { func handleFeaturezRequest(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") zpages.WriteHTMLPageHeader(w, zpages.HeaderData{Title: "Feature Gates"}) - zpages.WriteHTMLFeaturesTable(w, getFeaturesTableData()) + zpages.WriteHTMLFeaturesTable(w, GetFeaturesTableData()) zpages.WriteHTMLPageFooter(w) } -func getFeaturesTableData() zpages.FeatureGateTableData { +func GetFeaturesTableData() zpages.FeatureGateTableData { data := zpages.FeatureGateTableData{} featuregate.GlobalRegistry().VisitAll(func(gate *featuregate.Gate) { data.Rows = append(data.Rows, zpages.FeatureGateTableRowData{ From dde94a2dd51af5de513b71b202a7c09168456eed Mon Sep 17 00:00:00 2001 From: danish9039 Date: Sun, 5 Jan 2025 03:30:55 +0530 Subject: [PATCH 2/7] .. Signed-off-by: danish9039 --- otelcol/command.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/otelcol/command.go b/otelcol/command.go index 6ef0809a15c..4558fef42e8 100644 --- a/otelcol/command.go +++ b/otelcol/command.go @@ -4,14 +4,11 @@ package otelcol // import "go.opentelemetry.io/collector/otelcol" import ( - // Standard library "errors" "flag" - // Third party "github.com/spf13/cobra" - // Project internal "go.opentelemetry.io/collector/featuregate" "go.opentelemetry.io/collector/service" ) From 3afa8e3e60259e571fee6a700c17a3e5bf1c8629 Mon Sep 17 00:00:00 2001 From: danish9039 Date: Tue, 7 Jan 2025 01:34:46 +0530 Subject: [PATCH 3/7] fix Signed-off-by: danish9039 --- otelcol/command.go | 56 ++++++++++++++++++++++++++-------- service/display_feature.go | 44 -------------------------- service/internal/graph/host.go | 4 +-- 3 files changed, 45 insertions(+), 59 deletions(-) delete mode 100644 service/display_feature.go diff --git a/otelcol/command.go b/otelcol/command.go index 4558fef42e8..c22f2ffbe77 100644 --- a/otelcol/command.go +++ b/otelcol/command.go @@ -6,11 +6,13 @@ package otelcol // import "go.opentelemetry.io/collector/otelcol" import ( "errors" "flag" - + "fmt" + "text/tabwriter" + "os" "github.com/spf13/cobra" "go.opentelemetry.io/collector/featuregate" - "go.opentelemetry.io/collector/service" + //"go.opentelemetry.io/collector/service" ) // NewCommand constructs a new cobra.Command using the given CollectorSettings. @@ -67,15 +69,43 @@ func updateSettingsUsingFlags(set *CollectorSettings, flags *flag.FlagSet) error } func newFeaturesCommand() *cobra.Command { - return &cobra.Command{ - Use: "features [feature-id]", - Short: "Display feature gates information", - Long: "Display information about available feature gates and their status", - RunE: func(cmd *cobra.Command, args []string) error { - if len(args) > 0 { - return service.DisplayFeature(args[0]) - } - return service.DisplayFeatures() - }, - } + return &cobra.Command{ + Use: "features [feature-id]", + Short: "Display feature gates information", + Long: "Display information about available feature gates and their status", + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) > 0 { + found := false + featuregate.GlobalRegistry().VisitAll(func(g *featuregate.Gate) { + if g.ID() == args[0] { + found = true + fmt.Printf("Feature: %s\n", g.ID()) + fmt.Printf("Enabled: %v\n", g.IsEnabled()) + fmt.Printf("Stage: %s\n", g.Stage()) + fmt.Printf("Description: %s\n", g.Description()) + fmt.Printf("From Version: %s\n", g.FromVersion()) + if g.ToVersion() != "" { + fmt.Printf("To Version: %s\n", g.ToVersion()) + } + } + }) + if !found { + return fmt.Errorf("feature %q not found", args[0]) + } + return nil + } + + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + fmt.Fprintf(w, "ID\tEnabled\tStage\tDescription\n") + featuregate.GlobalRegistry().VisitAll(func(g *featuregate.Gate) { + fmt.Fprintf(w, "%s\t%v\t%s\t%s\n", + g.ID(), + g.IsEnabled(), + g.Stage(), + g.Description()) + }) + return w.Flush() + }, + } } + diff --git a/service/display_feature.go b/service/display_feature.go deleted file mode 100644 index efb7def7f6b..00000000000 --- a/service/display_feature.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package service // import "go.opentelemetry.io/collector/service" - -import ( - "fmt" - "os" - "text/tabwriter" - - "go.opentelemetry.io/collector/service/internal/graph" -) - -func DisplayFeatures() error { - w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) - fmt.Fprintf(w, "ID\tEnabled\tStage\tDescription\n") - data := graph.GetFeaturesTableData() - for _, row := range data.Rows { - fmt.Fprintf(w, "%s\t%v\t%s\t%s\n", - row.ID, - row.Enabled, - row.Stage, - row.Description) - } - return w.Flush() -} - -func DisplayFeature(id string) error { - data := graph.GetFeaturesTableData() - for _, row := range data.Rows { - if row.ID == id { - fmt.Printf("Feature: %s\n", row.ID) - fmt.Printf("Enabled: %v\n", row.Enabled) - fmt.Printf("Stage: %s\n", row.Stage) - fmt.Printf("Description: %s\n", row.Description) - fmt.Printf("From Version: %s\n", row.FromVersion) - if row.ToVersion != "" { - fmt.Printf("To Version: %s\n", row.ToVersion) - } - return nil - } - } - return fmt.Errorf("feature %q not found", id) -} diff --git a/service/internal/graph/host.go b/service/internal/graph/host.go index 4d72a8b5bb5..3aaef5e055f 100644 --- a/service/internal/graph/host.go +++ b/service/internal/graph/host.go @@ -138,11 +138,11 @@ func (host *Host) zPagesRequest(w http.ResponseWriter, _ *http.Request) { func handleFeaturezRequest(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") zpages.WriteHTMLPageHeader(w, zpages.HeaderData{Title: "Feature Gates"}) - zpages.WriteHTMLFeaturesTable(w, GetFeaturesTableData()) + zpages.WriteHTMLFeaturesTable(w, getFeaturesTableData()) zpages.WriteHTMLPageFooter(w) } -func GetFeaturesTableData() zpages.FeatureGateTableData { +func getFeaturesTableData() zpages.FeatureGateTableData { data := zpages.FeatureGateTableData{} featuregate.GlobalRegistry().VisitAll(func(gate *featuregate.Gate) { data.Rows = append(data.Rows, zpages.FeatureGateTableRowData{ From 52c1c61ad1682aa47956828d0221fd5f32cad1ad Mon Sep 17 00:00:00 2001 From: danish9039 Date: Tue, 7 Jan 2025 02:35:13 +0530 Subject: [PATCH 4/7] .. Signed-off-by: danish9039 --- otelcol/command.go | 79 +++++++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 40 deletions(-) diff --git a/otelcol/command.go b/otelcol/command.go index c22f2ffbe77..632603106f5 100644 --- a/otelcol/command.go +++ b/otelcol/command.go @@ -7,12 +7,12 @@ import ( "errors" "flag" "fmt" + "os" "text/tabwriter" - "os" + "github.com/spf13/cobra" "go.opentelemetry.io/collector/featuregate" - //"go.opentelemetry.io/collector/service" ) // NewCommand constructs a new cobra.Command using the given CollectorSettings. @@ -69,43 +69,42 @@ func updateSettingsUsingFlags(set *CollectorSettings, flags *flag.FlagSet) error } func newFeaturesCommand() *cobra.Command { - return &cobra.Command{ - Use: "features [feature-id]", - Short: "Display feature gates information", - Long: "Display information about available feature gates and their status", - RunE: func(cmd *cobra.Command, args []string) error { - if len(args) > 0 { - found := false - featuregate.GlobalRegistry().VisitAll(func(g *featuregate.Gate) { - if g.ID() == args[0] { - found = true - fmt.Printf("Feature: %s\n", g.ID()) - fmt.Printf("Enabled: %v\n", g.IsEnabled()) - fmt.Printf("Stage: %s\n", g.Stage()) - fmt.Printf("Description: %s\n", g.Description()) - fmt.Printf("From Version: %s\n", g.FromVersion()) - if g.ToVersion() != "" { - fmt.Printf("To Version: %s\n", g.ToVersion()) - } - } - }) - if !found { - return fmt.Errorf("feature %q not found", args[0]) - } - return nil - } + return &cobra.Command{ + Use: "features [feature-id]", + Short: "Display feature gates information", + Long: "Display information about available feature gates and their status", + RunE: func(_ *cobra.Command, args []string) error { + if len(args) > 0 { + found := false + featuregate.GlobalRegistry().VisitAll(func(g *featuregate.Gate) { + if g.ID() == args[0] { + found = true + fmt.Printf("Feature: %s\n", g.ID()) + fmt.Printf("Enabled: %v\n", g.IsEnabled()) + fmt.Printf("Stage: %s\n", g.Stage()) + fmt.Printf("Description: %s\n", g.Description()) + fmt.Printf("From Version: %s\n", g.FromVersion()) + if g.ToVersion() != "" { + fmt.Printf("To Version: %s\n", g.ToVersion()) + } + } + }) + if !found { + return fmt.Errorf("feature %q not found", args[0]) + } + return nil + } - w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) - fmt.Fprintf(w, "ID\tEnabled\tStage\tDescription\n") - featuregate.GlobalRegistry().VisitAll(func(g *featuregate.Gate) { - fmt.Fprintf(w, "%s\t%v\t%s\t%s\n", - g.ID(), - g.IsEnabled(), - g.Stage(), - g.Description()) - }) - return w.Flush() - }, - } + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + fmt.Fprintf(w, "ID\tEnabled\tStage\tDescription\n") + featuregate.GlobalRegistry().VisitAll(func(g *featuregate.Gate) { + fmt.Fprintf(w, "%s\t%v\t%s\t%s\n", + g.ID(), + g.IsEnabled(), + g.Stage(), + g.Description()) + }) + return w.Flush() + }, + } } - From 017a7074ac44932e4d1ab9aedb4d05fd0d2a93b3 Mon Sep 17 00:00:00 2001 From: danish9039 Date: Thu, 9 Jan 2025 15:41:45 +0530 Subject: [PATCH 5/7] add test for newfeaturecommand Signed-off-by: danish9039 --- otelcol/command_test.go | 58 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/otelcol/command_test.go b/otelcol/command_test.go index 1a5680fe4ab..fa839826cb1 100644 --- a/otelcol/command_test.go +++ b/otelcol/command_test.go @@ -5,6 +5,8 @@ package otelcol import ( "context" + "io" + "os" "path/filepath" "testing" @@ -159,3 +161,59 @@ func Test_UseUnifiedEnvVarExpansionRules(t *testing.T) { }) } } + +func TestNewFeaturesCommand(t *testing.T) { + t.Run("list all features", func(t *testing.T) { + cmd := newFeaturesCommand() + require.NotNil(t, cmd) + + // Capture stdout + oldStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + err := cmd.RunE(cmd, []string{}) + require.NoError(t, err) + + w.Close() + out, _ := io.ReadAll(r) + os.Stdout = oldStdout + + output := string(out) + assert.Contains(t, output, "ID") + assert.Contains(t, output, "Enabled") + assert.Contains(t, output, "Stage") + assert.Contains(t, output, "Description") + }) + t.Run("specific feature details", func(t *testing.T) { + cmd := newFeaturesCommand() + + // Register a test feature gate in the global registry + featuregate.GlobalRegistry().MustRegister("test.feature", featuregate.StageBeta, + featuregate.WithRegisterDescription("Test feature description")) + + // Capture stdout + oldStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + err := cmd.RunE(cmd, []string{"test.feature"}) + require.NoError(t, err) + + w.Close() + out, _ := io.ReadAll(r) + os.Stdout = oldStdout + + output := string(out) + assert.Contains(t, output, "Feature: test.feature") + assert.Contains(t, output, "Description: Test feature description") + assert.Contains(t, output, "Stage: Beta") + }) + + t.Run("non-existent feature", func(t *testing.T) { + cmd := newFeaturesCommand() + err := cmd.RunE(cmd, []string{"non.existent.feature"}) + require.Error(t, err) + assert.Contains(t, err.Error(), "feature \"non.existent.feature\" not found") + }) +} From da098dc34923543e51fb46cf3afe7a90fe33fb29 Mon Sep 17 00:00:00 2001 From: danish9039 Date: Fri, 10 Jan 2025 00:41:33 +0530 Subject: [PATCH 6/7] fix Signed-off-by: danish9039 --- .chloggen/feature-command.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .chloggen/feature-command.yaml diff --git a/.chloggen/feature-command.yaml b/.chloggen/feature-command.yaml new file mode 100644 index 00000000000..5f7aab71ab4 --- /dev/null +++ b/.chloggen/feature-command.yaml @@ -0,0 +1,13 @@ +change_type: enhancement + +component: otelcol + +note: "Add feature command to display information about available features" + +issues: [11998] + +subtext: | + The feature command allows users to view detailed information about feature gates + including their status, stage, and description. + +change_logs: [user] From a0571a7591eb364f024a0ec06964309a9f30ea54 Mon Sep 17 00:00:00 2001 From: danish9039 Date: Sat, 18 Jan 2025 18:00:35 +0530 Subject: [PATCH 7/7] change to featuregate command Signed-off-by: danish9039 --- .chloggen/feature-command.yaml | 13 ------------- .chloggen/featuregate-command.yaml | 13 +++++++++++++ otelcol/command.go | 6 +++--- otelcol/command_test.go | 14 +++++++------- 4 files changed, 23 insertions(+), 23 deletions(-) delete mode 100644 .chloggen/feature-command.yaml create mode 100644 .chloggen/featuregate-command.yaml diff --git a/.chloggen/feature-command.yaml b/.chloggen/feature-command.yaml deleted file mode 100644 index 5f7aab71ab4..00000000000 --- a/.chloggen/feature-command.yaml +++ /dev/null @@ -1,13 +0,0 @@ -change_type: enhancement - -component: otelcol - -note: "Add feature command to display information about available features" - -issues: [11998] - -subtext: | - The feature command allows users to view detailed information about feature gates - including their status, stage, and description. - -change_logs: [user] diff --git a/.chloggen/featuregate-command.yaml b/.chloggen/featuregate-command.yaml new file mode 100644 index 00000000000..c3b6dcc1bb4 --- /dev/null +++ b/.chloggen/featuregate-command.yaml @@ -0,0 +1,13 @@ +change_type: enhancement + +component: otelcol + +note: "Add featuregate command to display information about available features" + +issues: [11998] + +subtext: | + The featuregate command allows users to view detailed information about feature gates + including their status, stage, and description. + +change_logs: [user] diff --git a/otelcol/command.go b/otelcol/command.go index 632603106f5..a0764494e91 100644 --- a/otelcol/command.go +++ b/otelcol/command.go @@ -39,7 +39,7 @@ func NewCommand(set CollectorSettings) *cobra.Command { return col.Run(cmd.Context()) }, } - rootCmd.AddCommand(newFeaturesCommand()) + rootCmd.AddCommand(newFeatureGateCommand()) rootCmd.AddCommand(newComponentsCommand(set)) rootCmd.AddCommand(newValidateSubCommand(set, flagSet)) rootCmd.Flags().AddGoFlagSet(flagSet) @@ -68,9 +68,9 @@ func updateSettingsUsingFlags(set *CollectorSettings, flags *flag.FlagSet) error return nil } -func newFeaturesCommand() *cobra.Command { +func newFeatureGateCommand() *cobra.Command { return &cobra.Command{ - Use: "features [feature-id]", + Use: "featuregate [feature-id]", Short: "Display feature gates information", Long: "Display information about available feature gates and their status", RunE: func(_ *cobra.Command, args []string) error { diff --git a/otelcol/command_test.go b/otelcol/command_test.go index fa839826cb1..56445eea4f3 100644 --- a/otelcol/command_test.go +++ b/otelcol/command_test.go @@ -162,9 +162,9 @@ func Test_UseUnifiedEnvVarExpansionRules(t *testing.T) { } } -func TestNewFeaturesCommand(t *testing.T) { - t.Run("list all features", func(t *testing.T) { - cmd := newFeaturesCommand() +func TestNewFeatureGateCommand(t *testing.T) { + t.Run("list all featuregates", func(t *testing.T) { + cmd := newFeatureGateCommand() require.NotNil(t, cmd) // Capture stdout @@ -185,8 +185,8 @@ func TestNewFeaturesCommand(t *testing.T) { assert.Contains(t, output, "Stage") assert.Contains(t, output, "Description") }) - t.Run("specific feature details", func(t *testing.T) { - cmd := newFeaturesCommand() + t.Run("specific featuregate details", func(t *testing.T) { + cmd := newFeatureGateCommand() // Register a test feature gate in the global registry featuregate.GlobalRegistry().MustRegister("test.feature", featuregate.StageBeta, @@ -210,8 +210,8 @@ func TestNewFeaturesCommand(t *testing.T) { assert.Contains(t, output, "Stage: Beta") }) - t.Run("non-existent feature", func(t *testing.T) { - cmd := newFeaturesCommand() + t.Run("non-existent featuregate", func(t *testing.T) { + cmd := newFeatureGateCommand() err := cmd.RunE(cmd, []string{"non.existent.feature"}) require.Error(t, err) assert.Contains(t, err.Error(), "feature \"non.existent.feature\" not found")