Skip to content
Draft
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
39 changes: 39 additions & 0 deletions internal/collectors/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package collectors

import "github.com/SUSE/connect-ng/pkg/profiles"

// defaultCollectorStates defines the default behavior per collector.
// true = opt-out (enabled by default), false = opt-in (disabled by default)
var defaultCollectorStates = map[string]bool{
"pci_devices": true,
"kernel_modules": true,
// Add other collectors here to define their default behavior
}

// DefaultCollectorState safely returns the default state for a given collector.
func DefaultCollectorState(collectorName string) bool {
if state, ok := defaultCollectorStates[collectorName]; ok {
return state
}
return false // Fallback if not found in defaults map
}

// CollectorOptions implements profiles.CollectorOptions interface
// This struct holds the actual collector configuration data
type CollectorOptions struct {
collectors map[string]profiles.CollectorConfig
}

func NewCollectorOptions(collectors map[string]profiles.CollectorConfig) *CollectorOptions {
return &CollectorOptions{
collectors: collectors,
}
}

func (c *CollectorOptions) IsCollectorEnabled(collectorName string) bool {
if config, ok := c.collectors[collectorName]; ok {
return config.Enabled
}

return DefaultCollectorState(collectorName)
}
17 changes: 14 additions & 3 deletions internal/connect/collectors.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,20 @@ func FetchSystemInformation(arch string) (collectors.Result, error) {

// Fetch system profile information
func FetchSystemProfiles(arch string, updateCache bool) (collectors.Result, error) {
var usedCollectors = []collectors.Collector{
collectors.PCI{UpdateDataIDs: updateCache},
collectors.LSMOD{UpdateDataIDs: updateCache},
// Get global collector configuration
collectorOpts := GetCollectorConfig()

collectorsMap := map[string]collectors.Collector{
"pci_devices": collectors.PCI{UpdateDataIDs: updateCache},
"kernel_modules": collectors.LSMOD{UpdateDataIDs: updateCache},
}

var usedCollectors = []collectors.Collector{}

for collectorName, collector := range collectorsMap {
if collectorOpts.IsCollectorEnabled(collectorName) {
usedCollectors = append(usedCollectors, collector)
}
}

var err error
Expand Down
100 changes: 99 additions & 1 deletion internal/connect/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ import (
"strconv"
"strings"

"github.com/SUSE/connect-ng/internal/collectors"
"github.com/SUSE/connect-ng/internal/util"
"github.com/SUSE/connect-ng/pkg/profiles"
"github.com/SUSE/connect-ng/pkg/registration"
"gopkg.in/yaml.v3"
)

const (
Expand Down Expand Up @@ -108,8 +111,29 @@ func ReadFromConfiguration(path string) (*Options, error) {
util.Debug.Printf("Reading configuration from: %s\n", path)
if f, err := os.Open(path); err == nil {
defer f.Close()
return parseFromConfiguration(f, cfg)

content, err := io.ReadAll(f)
if err != nil {
return nil, err
}

cfg.Path = path
cfg, err = parseFromConfiguration(bytes.NewReader(content), cfg)
if err != nil {
return nil, err
}

// Parse collectors section with YAML
err = parseAndSetCollectorConfig(content)
if err != nil {
return nil, err
}

return cfg, nil
}

// Empty config files, use defaults
SetCollectorConfig(collectors.NewCollectorOptions(map[string]profiles.CollectorConfig{}))
return cfg, nil
}

Expand All @@ -118,8 +142,27 @@ func ReadFromConfiguration(path string) (*Options, error) {
// otherwise it will return an empty configuration and an error object.
func parseFromConfiguration(r io.Reader, cfg *Options) (*Options, error) {
scanner := bufio.NewScanner(r)
inCollectorsSection := false

for scanner.Scan() {
line := scanner.Text()

// Skip collectors section entirely (will be parsed separately)
if strings.TrimSpace(line) == "collectors:" {
inCollectorsSection = true
continue
}

// Skip lines inside collectors section
if inCollectorsSection {
// Check if still collectors section
if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") {
continue
}
// Non-indented line, section ended
inCollectorsSection = false
}

parts := strings.SplitN(line, ":", 2)
if len(parts) < 2 {
continue
Expand Down Expand Up @@ -166,6 +209,44 @@ func parseFromConfiguration(r io.Reader, cfg *Options) (*Options, error) {
return cfg, nil
}

// parseAndSetCollectorConfig parses only the collectors section
func parseAndSetCollectorConfig(content []byte) error {
var collectorsConfig struct {
Collectors map[string]struct {
// We use the *bool to distinguish between explicitly set false values
// and nil values (non existing in the config file)
Enabled *bool `yaml:"enabled"`
} `yaml:"collectors"`
}

err := yaml.Unmarshal(content, &collectorsConfig)
if err != nil {
return fmt.Errorf("error unmarshling collectors section, %s", err)
}

if collectorsConfig.Collectors == nil {
SetCollectorConfig(collectors.NewCollectorOptions(map[string]profiles.CollectorConfig{}))
return nil
}

collectorsMap := make(map[string]profiles.CollectorConfig)
for name, cfg := range collectorsConfig.Collectors {
var enabled bool
if cfg.Enabled != nil {
enabled = *cfg.Enabled
} else {
enabled = collectors.DefaultCollectorState(name)
}
collectorsMap[name] = profiles.CollectorConfig{
Enabled: enabled,
}
}

collectorOptions := collectors.NewCollectorOptions(collectorsMap)
SetCollectorConfig(collectorOptions)
return nil
}

// Change the base url to be used when talking to the server to the one being
// provided.
func (opts *Options) ChangeBaseURL(baseUrl string) {
Expand Down Expand Up @@ -216,3 +297,20 @@ func (opts *Options) IsScc() bool {
}
return false
}

// Package-level collector configuration
var collectorOptions profiles.CollectorOptions = profiles.NoCollectorOptions{}

// SetCollectorConfig sets the global collector configuration
func SetCollectorConfig(opts profiles.CollectorOptions) {
if opts == nil {
collectorOptions = profiles.NoCollectorOptions{}
return
}
collectorOptions = opts
}

// GetCollectorConfig returns the current global collector configuration
func GetCollectorConfig() profiles.CollectorOptions {
return collectorOptions
}
98 changes: 98 additions & 0 deletions internal/connect/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"reflect"
"strings"
"testing"

"github.com/SUSE/connect-ng/internal/collectors"
)

var cfg1 = `---
Expand All @@ -27,6 +29,31 @@ badkey: badval

`

var cfg3 = `---
url: https://scc.suse.com
insecure: false

collectors:
pci_devices:
enabled: false
kernel_modules:
enabled: true
`

var cfg4 = `---
collectors:
pci_devices:
kernel_modules:
enabled: false
unknown_collector:
`

var collectorsList = []string{
"pci_devices",
"kernel_modules",
"unknown_collector",
}

func TestParseConfig(t *testing.T) {
r := strings.NewReader(cfg1)

Expand Down Expand Up @@ -63,6 +90,77 @@ func TestParseConfig2(t *testing.T) {
}
}

func TestParseConfigWithCollectors(t *testing.T) {
content := []byte(cfg3)

// Parse standard config
opts := DefaultOptions()
r := strings.NewReader(cfg3)
parseFromConfiguration(r, opts)

// Verify standard config parsed correctly
if opts.BaseURL != "https://scc.suse.com" {
t.Errorf("Unexpected BaseURL '%v'; expecting 'https://scc.suse.com'", opts.BaseURL)
}
if opts.Insecure != false {
t.Errorf("Unexpected Insecure '%v'; expecting false", opts.Insecure)
}

// Parse collectors config
parseAndSetCollectorConfig(content)
collectorOpts := GetCollectorConfig()

// Verify collectors config
expectedStates := map[string]bool{
"pci_devices": false,
"kernel_modules": true,
"unknown_collector": false,
}

for _, collector := range collectorsList {
if collectorOpts.IsCollectorEnabled(collector) != expectedStates[collector] {
t.Errorf("collector %s enabled state should be %v", collector, expectedStates[collector])
}
}
}

func TestParseConfigWithoutCollectors(t *testing.T) {
content := []byte(cfg1)

// Parse collectors config (no collectors section)
parseAndSetCollectorConfig(content)
collectorOpts := GetCollectorConfig()

// Collectors should use their default state
for _, collector := range collectorsList {
expectedState := collectors.DefaultCollectorState(collector)
if collectorOpts.IsCollectorEnabled(collector) != expectedState {
t.Errorf("%s enabled state should be %v by default", collector, expectedState)
}
}
}

func TestParseConfigWithMissingEnabled(t *testing.T) {
content := []byte(cfg4)

// Parse collectors config
parseAndSetCollectorConfig(content)
collectorOpts := GetCollectorConfig()

// Verify collectors fallback mapping correctly
expectedStates := map[string]bool{
"pci_devices": true, // Defaults to true
"kernel_modules": false, // Explicitly set
"unknown_collector": false, // Defaults to false
}

for _, collector := range collectorsList {
if collectorOpts.IsCollectorEnabled(collector) != expectedStates[collector] {
t.Errorf("collector %s enabled state should be %v", collector, expectedStates[collector])
}
}
}

func TestSaveLoad(t *testing.T) {
path := filepath.Join(t.TempDir(), "SUSEConnect.test")
c1 := DefaultOptions()
Expand Down
23 changes: 23 additions & 0 deletions pkg/profiles/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package profiles

// CollectorConfig holds configs for a single collector
type CollectorConfig struct {
Enabled bool
}

// CollectorOptions defines configuration for all data collectors
// This interface allows internal implementations to provide collector
// configuration without exposing internal types
type CollectorOptions interface {
// IsCollectorEnabled checks if a collector is enabled
IsCollectorEnabled(collectorName string) bool
}

// NoCollectorOptions is a default implementation that enables all collectors
// Useful for testing and when collector options are not available
type NoCollectorOptions struct{}

func (NoCollectorOptions) IsCollectorEnabled(collectorName string) bool {
// Collectors are disabled by default
return false
}
Loading