diff --git a/internal/osbuild2/dnf_automatic_config_stage.go b/internal/osbuild2/dnf_automatic_config_stage.go new file mode 100644 index 000000000..54eed7cbb --- /dev/null +++ b/internal/osbuild2/dnf_automatic_config_stage.go @@ -0,0 +1,70 @@ +package osbuild2 + +import "fmt" + +type DNFAutomaticUpgradeTypeValue string + +// Valid values of the 'upgrade_type' option +const ( + DNFAutomaticUpgradeTypeDefault DNFAutomaticUpgradeTypeValue = "default" + DNFAutomaticUpgradeTypeSecurity DNFAutomaticUpgradeTypeValue = "security" +) + +// DNFAutomaticConfigCommands represents the 'commands' configuration section. +type DNFAutomaticConfigCommands struct { + // Whether packages comprising the available updates should be installed + ApplyUpdates *bool `json:"apply_updates,omitempty"` + // What kind of upgrades to look at + UpgradeType DNFAutomaticUpgradeTypeValue `json:"upgrade_type,omitempty"` +} + +// DNFAutomaticConfig represents DNF Automatic configuration. +type DNFAutomaticConfig struct { + Commands *DNFAutomaticConfigCommands `json:"commands,omitempty"` +} + +type DNFAutomaticConfigStageOptions struct { + Config *DNFAutomaticConfig `json:"config,omitempty"` +} + +func (DNFAutomaticConfigStageOptions) isStageOptions() {} + +// NewDNFAutomaticConfigStageOptions creates a new DNFAutomaticConfig Stage options object. +func NewDNFAutomaticConfigStageOptions(config *DNFAutomaticConfig) *DNFAutomaticConfigStageOptions { + return &DNFAutomaticConfigStageOptions{ + Config: config, + } +} + +func (o DNFAutomaticConfigStageOptions) validate() error { + if o.Config != nil && o.Config.Commands != nil { + allowedUpgradeTypeValues := []DNFAutomaticUpgradeTypeValue{ + DNFAutomaticUpgradeTypeDefault, + DNFAutomaticUpgradeTypeSecurity, + "", // default empty value when the option is not set + } + valid := false + for _, value := range allowedUpgradeTypeValues { + if o.Config.Commands.UpgradeType == value { + valid = true + break + } + } + if !valid { + return fmt.Errorf("'upgrade_type' option does not allow %q as a value", o.Config.Commands.UpgradeType) + } + } + + return nil +} + +func NewDNFAutomaticConfigStage(options *DNFAutomaticConfigStageOptions) *Stage { + if err := options.validate(); err != nil { + panic(err) + } + + return &Stage{ + Type: "org.osbuild.dnf-automatic.config", + Options: options, + } +} diff --git a/internal/osbuild2/dnf_automatic_config_stage_test.go b/internal/osbuild2/dnf_automatic_config_stage_test.go new file mode 100644 index 000000000..af2eb5588 --- /dev/null +++ b/internal/osbuild2/dnf_automatic_config_stage_test.go @@ -0,0 +1,79 @@ +package osbuild2 + +import ( + "testing" + + "github.com/osbuild/osbuild-composer/internal/common" + "github.com/stretchr/testify/assert" +) + +func TestNewDNFAutomaticConfigStage(t *testing.T) { + stageOptions := NewDNFAutomaticConfigStageOptions(&DNFAutomaticConfig{}) + expectedStage := &Stage{ + Type: "org.osbuild.dnf-automatic.config", + Options: stageOptions, + } + actualStage := NewDNFAutomaticConfigStage(stageOptions) + assert.Equal(t, expectedStage, actualStage) +} + +func TestDNFAutomaticConfigStageOptionsValidate(t *testing.T) { + tests := []struct { + name string + options DNFAutomaticConfigStageOptions + err bool + }{ + { + name: "empty-options", + options: DNFAutomaticConfigStageOptions{}, + err: false, + }, + { + name: "invalid-upgrade_type", + options: DNFAutomaticConfigStageOptions{ + Config: &DNFAutomaticConfig{ + Commands: &DNFAutomaticConfigCommands{ + ApplyUpdates: common.BoolToPtr(true), + UpgradeType: "invalid", + }, + }, + }, + err: true, + }, + { + name: "valid-data-1", + options: DNFAutomaticConfigStageOptions{ + Config: &DNFAutomaticConfig{ + Commands: &DNFAutomaticConfigCommands{ + ApplyUpdates: common.BoolToPtr(true), + UpgradeType: DNFAutomaticUpgradeTypeDefault, + }, + }, + }, + err: false, + }, + { + name: "valid-data-2", + options: DNFAutomaticConfigStageOptions{ + Config: &DNFAutomaticConfig{ + Commands: &DNFAutomaticConfigCommands{ + ApplyUpdates: common.BoolToPtr(false), + UpgradeType: DNFAutomaticUpgradeTypeSecurity, + }, + }, + }, + err: false, + }, + } + for idx, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.err { + assert.Errorf(t, tt.options.validate(), "%q didn't return an error [idx: %d]", tt.name, idx) + assert.Panics(t, func() { NewDNFAutomaticConfigStage(&tt.options) }) + } else { + assert.NoErrorf(t, tt.options.validate(), "%q returned an error [idx: %d]", tt.name, idx) + assert.NotPanics(t, func() { NewDNFAutomaticConfigStage(&tt.options) }) + } + }) + } +} diff --git a/internal/osbuild2/stage.go b/internal/osbuild2/stage.go index 30d2e45cd..3cc2f0083 100644 --- a/internal/osbuild2/stage.go +++ b/internal/osbuild2/stage.go @@ -71,6 +71,8 @@ func (stage *Stage) UnmarshalJSON(data []byte) error { options = new(ChronyStageOptions) case "org.osbuild.dnf.config": options = new(DNFConfigStageOptions) + case "org.osbuild.dnf-automatic.config": + options = new(DNFAutomaticConfigStageOptions) case "org.osbuild.dracut": options = new(DracutStageOptions) case "org.osbuild.dracut.conf": diff --git a/internal/osbuild2/stage_test.go b/internal/osbuild2/stage_test.go index be0f46b71..52a40c4ec 100644 --- a/internal/osbuild2/stage_test.go +++ b/internal/osbuild2/stage_test.go @@ -146,6 +146,16 @@ func TestStage_UnmarshalJSON(t *testing.T) { data: []byte(`{"type":"org.osbuild.dnf.config","options":{}}`), }, }, + { + name: "dnf-automatic-config", + fields: fields{ + Type: "org.osbuild.dnf-automatic.config", + Options: &DNFAutomaticConfigStageOptions{}, + }, + args: args{ + data: []byte(`{"type":"org.osbuild.dnf-automatic.config","options":{}}`), + }, + }, { name: "dracut", fields: fields{