Skip to content
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
44 changes: 35 additions & 9 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ const (

// SourceTypeFile is the type for registry data stored in local files
SourceTypeFile = "file"

// SourceTypeManaged is the type for registries directly managed via API
// Managed registries do not sync from external sources
SourceTypeManaged = "managed"
)

// StorageType is an enum of supported storage backends for registry data.
Expand Down Expand Up @@ -103,14 +107,17 @@ type RegistryConfig struct {
Format string `yaml:"format"`

// Type-specific configurations (only one should be set)
Git *GitConfig `yaml:"git,omitempty"`
API *APIConfig `yaml:"api,omitempty"`
File *FileConfig `yaml:"file,omitempty"`
Git *GitConfig `yaml:"git,omitempty"`
API *APIConfig `yaml:"api,omitempty"`
File *FileConfig `yaml:"file,omitempty"`
Managed *ManagedConfig `yaml:"managed,omitempty"`

// Per-registry sync policy
// Note: Not applicable for managed registries (will be ignored if Managed is set)
SyncPolicy *SyncPolicyConfig `yaml:"syncPolicy,omitempty"`

// Per-registry filtering rules
// Note: Not applicable for managed registries (will be ignored if Managed is set)
Filter *FilterConfig `yaml:"filter,omitempty"`
}

Expand Down Expand Up @@ -150,6 +157,13 @@ type FileConfig struct {
Path string `yaml:"path"`
}

// ManagedConfig defines configuration for managed registries
// Managed registries are directly manipulated via API and do not sync from external sources
// Note: Initially empty, may be used as a placeholder for future configuration options
type ManagedConfig struct {
// Future fields can be added here as needed
}

// SyncPolicyConfig defines synchronization settings
type SyncPolicyConfig struct {
Interval string `yaml:"interval"`
Expand Down Expand Up @@ -520,13 +534,19 @@ func (c *Config) validate() error {
func (*Config) validateRegistryConfig(reg *RegistryConfig, index int) error {
prefix := fmt.Sprintf("registry[%d] (%s)", index, reg.Name)

// Validate sync policy
if err := validateSyncPolicy(reg.SyncPolicy, prefix); err != nil {
// Validate exactly one source type is configured
if err := validateSourceTypeCount(reg, prefix); err != nil {
return err
}

// Validate exactly one source type is configured
if err := validateSourceTypeCount(reg, prefix); err != nil {
// Managed registries don't require sync policy or filter
// If syncPolicy or filter are set for managed registries, they will be silently ignored
if reg.Managed != nil {
return nil
}

// Non-managed registries require sync policy
if err := validateSyncPolicy(reg.SyncPolicy, prefix); err != nil {
return err
}

Expand Down Expand Up @@ -560,12 +580,15 @@ func validateSourceTypeCount(reg *RegistryConfig, prefix string) error {
if reg.File != nil {
configCount++
}
if reg.Managed != nil {
configCount++
}

if configCount == 0 {
return fmt.Errorf("%s: one of git, api, or file configuration must be specified", prefix)
return fmt.Errorf("%s: one of git, api, file, or managed configuration must be specified", prefix)
}
if configCount > 1 {
return fmt.Errorf("%s: only one of git, api, or file configuration may be specified", prefix)
return fmt.Errorf("%s: only one of git, api, file, or managed configuration may be specified", prefix)
}

return nil
Expand Down Expand Up @@ -692,5 +715,8 @@ func (r *RegistryConfig) GetType() string {
if r.File != nil {
return SourceTypeFile
}
if r.Managed != nil {
return SourceTypeManaged
}
return ""
}
202 changes: 200 additions & 2 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ func TestConfigValidate(t *testing.T) {
},
},
wantErr: true,
errMsg: "one of git, api, or file configuration must be specified",
errMsg: "one of git, api, file, or managed configuration must be specified",
},
{
name: "missing_file_path",
Expand Down Expand Up @@ -446,7 +446,141 @@ func TestConfigValidate(t *testing.T) {
},
},
wantErr: true,
errMsg: "only one of git, api, or file configuration may be specified",
errMsg: "only one of git, api, file, or managed configuration may be specified",
},
{
name: "valid_managed_registry_no_sync_policy",
config: &Config{
Registries: []RegistryConfig{
{
Name: "managed-registry",
Managed: &ManagedConfig{},
// No SyncPolicy required for managed registries
},
},
},
wantErr: false,
},
{
name: "valid_managed_registry_with_ignored_sync_policy",
config: &Config{
Registries: []RegistryConfig{
{
Name: "managed-registry",
Managed: &ManagedConfig{},
// SyncPolicy is silently ignored for managed registries
SyncPolicy: &SyncPolicyConfig{
Interval: "30m",
},
},
},
},
wantErr: false,
},
{
name: "valid_managed_registry_with_ignored_filter",
config: &Config{
Registries: []RegistryConfig{
{
Name: "managed-registry",
Managed: &ManagedConfig{},
// Filter is silently ignored for managed registries
Filter: &FilterConfig{
Tags: &TagFilterConfig{
Include: []string{"test"},
},
},
},
},
},
wantErr: false,
},
{
name: "managed_and_file_source_specified",
config: &Config{
Registries: []RegistryConfig{
{
Name: "multi-source",
Managed: &ManagedConfig{},
File: &FileConfig{
Path: "/tmp/registry.json",
},
SyncPolicy: &SyncPolicyConfig{
Interval: "30m",
},
},
},
},
wantErr: true,
errMsg: "only one of git, api, file, or managed configuration may be specified",
},
{
name: "managed_and_git_source_specified",
config: &Config{
Registries: []RegistryConfig{
{
Name: "multi-source",
Managed: &ManagedConfig{},
Git: &GitConfig{
Repository: "https://github.com/example/repo.git",
},
SyncPolicy: &SyncPolicyConfig{
Interval: "30m",
},
},
},
},
wantErr: true,
errMsg: "only one of git, api, file, or managed configuration may be specified",
},
{
name: "managed_and_api_source_specified",
config: &Config{
Registries: []RegistryConfig{
{
Name: "multi-source",
Managed: &ManagedConfig{},
API: &APIConfig{
Endpoint: "http://example.com",
},
SyncPolicy: &SyncPolicyConfig{
Interval: "30m",
},
},
},
},
wantErr: true,
errMsg: "only one of git, api, file, or managed configuration may be specified",
},
{
name: "mixed_managed_and_synced_registries",
config: &Config{
Registries: []RegistryConfig{
{
Name: "file-registry",
File: &FileConfig{
Path: "/data/registry.json",
},
SyncPolicy: &SyncPolicyConfig{
Interval: "30m",
},
},
{
Name: "managed-registry",
Managed: &ManagedConfig{},
},
{
Name: "git-registry",
Git: &GitConfig{
Repository: "https://github.com/example/repo.git",
},
SyncPolicy: &SyncPolicyConfig{
Interval: "1h",
},
},
},
},
wantErr: false,
},
{
name: "valid_config_with_file_storage",
Expand Down Expand Up @@ -1460,3 +1594,67 @@ func TestGetClientSecret(t *testing.T) {
assert.Contains(t, err.Error(), "failed to read client secret")
})
}

func TestRegistryConfig_GetType(t *testing.T) {
t.Parallel()

tests := []struct {
name string
registryConf *RegistryConfig
expectedType string
}{
{
name: "git type",
registryConf: &RegistryConfig{
Name: "git-registry",
Git: &GitConfig{
Repository: "https://github.com/example/repo.git",
},
},
expectedType: SourceTypeGit,
},
{
name: "api type",
registryConf: &RegistryConfig{
Name: "api-registry",
API: &APIConfig{
Endpoint: "https://api.example.com",
},
},
expectedType: SourceTypeAPI,
},
{
name: "file type",
registryConf: &RegistryConfig{
Name: "file-registry",
File: &FileConfig{
Path: "/data/registry.json",
},
},
expectedType: SourceTypeFile,
},
{
name: "managed type",
registryConf: &RegistryConfig{
Name: "managed-registry",
Managed: &ManagedConfig{},
},
expectedType: SourceTypeManaged,
},
{
name: "no type configured returns empty string",
registryConf: &RegistryConfig{
Name: "invalid-registry",
},
expectedType: "",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
result := tt.registryConf.GetType()
assert.Equal(t, tt.expectedType, result)
})
}
}
Loading
Loading