Repository: osbuild/osbuild-composer
Pull request #2006 (2021-11-12 14:23:39)

Multiple new stages
Add support for new stages available in osbuild 41.

Replaces: #1991
Replaces: #1986
Replaces: #1984


This pull request includes:

- [ ] adequate testing for the new functionality or fixed issue
- [ ] adequate documentation informing people about the change such as
  - [ ] submit a PR for the [guides](https://github.com/osbuild/guides) repository if this PR changed any behavior described there: https://www.osbuild.org/guides/

<!--
Thanks for proposing a change to osbuild-composer!

Please don't remove the above check list. These are things that each pull
request must have before it is merged. It helps maintainers to not forget
anything.

If the reason for ticking any of the boxes is ambiguous, please add a short
note explaining why.

In addition, if this pull request fixes a downstream issue, please refer to
test/README.md and add these additional items:

- [ ] 1st commit of any `rhbz#` related PR contains bug reproducer; CI reports FAIL or
- [ ] PR contains automated tests for new functionality and
- [ ] QE has approved reproducer/new tests and
- [ ] Subsequent commits provide bug fixes without modifying the reproducer; CI reports PASS and
- [ ] QE approves this PR; RHBZ status is set to `MODIFIED + Verified=Tested`
-->



Patch:
diff --git a/internal/osbuild2/authconfig_stage.go b/internal/osbuild2/authconfig_stage.go
new file mode 100644
index 000000000..0be838610
--- /dev/null
+++ b/internal/osbuild2/authconfig_stage.go
@@ -0,0 +1,13 @@
+package osbuild2
+
+type AuthconfigStageOptions struct {
+}
+
+func (AuthconfigStageOptions) isStageOptions() {}
+
+func NewAuthconfigStage(options *AuthconfigStageOptions) *Stage {
+	return &Stage{
+		Type:    "org.osbuild.authconfig",
+		Options: options,
+	}
+}
diff --git a/internal/osbuild2/pwquality_conf_stage.go b/internal/osbuild2/pwquality_conf_stage.go
new file mode 100644
index 000000000..0d00f0edd
--- /dev/null
+++ b/internal/osbuild2/pwquality_conf_stage.go
@@ -0,0 +1,23 @@
+package osbuild2
+
+type PwqualityConfConfig struct {
+	Minlen   *int `json:"minlen,omitempty"`
+	Dcredit  *int `json:"dcredit,omitempty"`
+	Ucredit  *int `json:"ucredit,omitempty"`
+	Lcredit  *int `json:"lcredit,omitempty"`
+	Ocredit  *int `json:"ocredit,omitempty"`
+	Minclass *int `json:"minclass,omitempty"`
+}
+
+type PwqualityConfStageOptions struct {
+	Config PwqualityConfConfig `json:"config"`
+}
+
+func (PwqualityConfStageOptions) isStageOptions() {}
+
+func NewPwqualityConfStage(options *PwqualityConfStageOptions) *Stage {
+	return &Stage{
+		Type:    "org.osbuild.pwquality.conf",
+		Options: options,
+	}
+}
diff --git a/internal/osbuild2/stage.go b/internal/osbuild2/stage.go
index 6e6fc4bc0..a5fbd6c30 100644
--- a/internal/osbuild2/stage.go
+++ b/internal/osbuild2/stage.go
@@ -148,6 +148,12 @@ func (stage *Stage) UnmarshalJSON(data []byte) error {
 		inputs = new(FilesInputs)
 	case "org.osbuild.sshd.config":
 		options = new(SshdConfigStageOptions)
+	case "org.osbuild.authconfig":
+		options = new(AuthconfigStageOptions)
+	case "org.osbuild.pwquality.conf":
+		options = new(PwqualityConfStageOptions)
+	case "org.osbuild.yum.config":
+		options = new(YumConfigStageOptions)
 	default:
 		return fmt.Errorf("unexpected stage type: %s", rawStage.Type)
 	}
diff --git a/internal/osbuild2/yum_config_stage.go b/internal/osbuild2/yum_config_stage.go
new file mode 100644
index 000000000..89d6f2cc8
--- /dev/null
+++ b/internal/osbuild2/yum_config_stage.go
@@ -0,0 +1,57 @@
+package osbuild2
+
+import (
+	"fmt"
+)
+
+type YumConfigConfig struct {
+	HttpCaching *string `json:"http_caching,omitempty"`
+}
+
+type YumConfigPlugins struct {
+	Langpacks *YumConfigPluginsLangpacks `json:"langpacks,omitempty"`
+}
+
+type YumConfigPluginsLangpacks struct {
+	Locales []string `json:"locales"`
+}
+
+type YumConfigStageOptions struct {
+	Config  *YumConfigConfig  `json:"config,omitempty"`
+	Plugins *YumConfigPlugins `json:"plugins,omitempty"`
+}
+
+func (YumConfigStageOptions) isStageOptions() {}
+
+func (o YumConfigStageOptions) validate() error {
+	// Allow values from the osbuild schema
+	if o.Config != nil && o.Config.HttpCaching != nil {
+		valid := false
+		allowed_http_caching_values := []string{"all", "packages", "lazy:packages", "none"}
+		for _, v := range allowed_http_caching_values {
+			if v == *o.Config.HttpCaching {
+				valid = true
+			}
+		}
+		if !valid {
+			return fmt.Errorf("yum config parameter http_caching does not allow %s as a value", *o.Config.HttpCaching)
+		}
+	}
+
+	if o.Plugins != nil && o.Plugins.Langpacks != nil && len(o.Plugins.Langpacks.Locales) < 1 {
+		return fmt.Errorf("locales must contain at least one element")
+	}
+
+	return nil
+}
+
+func NewYumConfigStage(options *YumConfigStageOptions) *Stage {
+	if err := options.validate(); err != nil {
+		panic(err)
+	}
+
+	return &Stage{
+		Type:    "org.osbuild.yum.config",
+		Options: options,
+	}
+}


Test patch:
diff --git a/internal/osbuild2/authconfig_stage_test.go b/internal/osbuild2/authconfig_stage_test.go
new file mode 100644
index 000000000..c069e545f
--- /dev/null
+++ b/internal/osbuild2/authconfig_stage_test.go
@@ -0,0 +1,16 @@
+package osbuild2
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestNewAuthconfigStage(t *testing.T) {
+	expectedStage := &Stage{
+		Type:    "org.osbuild.authconfig",
+		Options: &AuthconfigStageOptions{},
+	}
+	actualStage := NewAuthconfigStage(&AuthconfigStageOptions{})
+	assert.Equal(t, expectedStage, actualStage)
+}
diff --git a/internal/osbuild2/pwquality_conf_stage_test.go b/internal/osbuild2/pwquality_conf_stage_test.go
new file mode 100644
index 000000000..25d3eccce
--- /dev/null
+++ b/internal/osbuild2/pwquality_conf_stage_test.go
@@ -0,0 +1,54 @@
+package osbuild2
+
+import (
+	"encoding/json"
+	"reflect"
+	"testing"
+
+	"github.com/osbuild/osbuild-composer/internal/common"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestNewPwqualityConfStage(t *testing.T) {
+	expectedStage := &Stage{
+		Type:    "org.osbuild.pwquality.conf",
+		Options: &PwqualityConfStageOptions{},
+	}
+	actualStage := NewPwqualityConfStage(&PwqualityConfStageOptions{})
+	assert.Equal(t, expectedStage, actualStage)
+}
+
+func TestJsonPwqualityConfStage(t *testing.T) {
+	// First test that the JSON can be parsed into the expected structure.
+	expectedOptions := PwqualityConfStageOptions{
+		Config: PwqualityConfConfig{
+			Minlen:   common.IntToPtr(9),
+			Minclass: common.IntToPtr(0),
+			Dcredit:  common.IntToPtr(1),
+		},
+	}
+	inputString := `{
+		"config": {
+		  "minlen": 9,
+		  "minclass": 0,
+		  "dcredit": 1
+		}
+	  }`
+	var inputOptions PwqualityConfStageOptions
+	err := json.Unmarshal([]byte(inputString), &inputOptions)
+	assert.NoError(t, err, "failed to parse JSON yum config")
+	assert.True(t, reflect.DeepEqual(expectedOptions, inputOptions))
+
+	// Second try the other way around with stress on missing values
+	// for those parameters that the user didn't specify.
+	inputOptions = PwqualityConfStageOptions{
+		Config: PwqualityConfConfig{
+			Minlen:   common.IntToPtr(9),
+			Minclass: common.IntToPtr(0),
+		},
+	}
+	expectedString := `{"config":{"minlen":9,"minclass":0}}`
+	inputBytes, err := json.Marshal(inputOptions)
+	assert.NoError(t, err, "failed to marshal sshd config into JSON")
+	assert.Equal(t, expectedString, string(inputBytes))
+}
diff --git a/internal/osbuild2/stage_test.go b/internal/osbuild2/stage_test.go
index bd318921c..763a70f00 100644
--- a/internal/osbuild2/stage_test.go
+++ b/internal/osbuild2/stage_test.go
@@ -647,6 +647,36 @@ func TestStage_UnmarshalJSON(t *testing.T) {
 				data: []byte(`{"type":"org.osbuild.sshd.config","options":{"config":{}}}`),
 			},
 		},
+		{
+			name: "authconfig",
+			fields: fields{
+				Type:    "org.osbuild.authconfig",
+				Options: &AuthconfigStageOptions{},
+			},
+			args: args{
+				data: []byte(`{"type":"org.osbuild.authconfig","options":{}}`),
+			},
+		},
+		{
+			name: "pwquality.conf",
+			fields: fields{
+				Type:    "org.osbuild.pwquality.conf",
+				Options: &PwqualityConfStageOptions{},
+			},
+			args: args{
+				data: []byte(`{"type":"org.osbuild.pwquality.conf","options":{"config":{}}}`),
+			},
+		},
+		{
+			name: "yum.config",
+			fields: fields{
+				Type:    "org.osbuild.yum.config",
+				Options: &YumConfigStageOptions{},
+			},
+			args: args{
+				data: []byte(`{"type":"org.osbuild.yum.config","options":{}}`),
+			},
+		},
 	}
 	for idx, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
diff --git a/internal/osbuild2/yum_config_stage_test.go b/internal/osbuild2/yum_config_stage_test.go
new file mode 100644
index 000000000..918e74255
--- /dev/null
+++ b/internal/osbuild2/yum_config_stage_test.go
@@ -0,0 +1,112 @@
+package osbuild2
+
+import (
+	"encoding/json"
+	"reflect"
+	"testing"
+
+	"github.com/osbuild/osbuild-composer/internal/common"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestNewYumConfigStage(t *testing.T) {
+	expectedStage := &Stage{
+		Type:    "org.osbuild.yum.config",
+		Options: &YumConfigStageOptions{},
+	}
+	actualStage := NewYumConfigStage(&YumConfigStageOptions{})
+	assert.Equal(t, expectedStage, actualStage)
+}
+
+func TestJsonYumConfigStage(t *testing.T) {
+	expectedOptions := YumConfigStageOptions{
+		Config: &YumConfigConfig{
+			HttpCaching: common.StringToPtr("packages"),
+		},
+		Plugins: &YumConfigPlugins{
+			&YumConfigPluginsLangpacks{
+				Locales: []string{"en_US.UTF-8"},
+			},
+		},
+	}
+	inputString := `{"config": {
+		"http_caching": "packages"
+	  },
+	  "plugins": {
+		"langpacks": {
+		  "locales": [
+			"en_US.UTF-8"
+		  ]
+		}
+	  }}`
+	var inputOptions YumConfigStageOptions
+	err := json.Unmarshal([]byte(inputString), &inputOptions)
+	assert.NoError(t, err, "failed to parse JSON yum config")
+	assert.True(t, reflect.DeepEqual(expectedOptions, inputOptions))
+
+	inputOptions = YumConfigStageOptions{
+		Config: &YumConfigConfig{
+			HttpCaching: common.StringToPtr("packages"),
+		},
+	}
+	expectedString := `{"config":{"http_caching":"packages"}}`
+	inputBytes, err := json.Marshal(inputOptions)
+	assert.NoError(t, err, "failed to marshal YUM config into JSON")
+	assert.Equal(t, expectedString, string(inputBytes))
+}
+
+func TestYumConfigValidate(t *testing.T) {
+	tests := []struct {
+		options YumConfigStageOptions
+		valid   bool
+	}{
+		{
+			YumConfigStageOptions{},
+			true,
+		},
+		{
+			YumConfigStageOptions{
+				Plugins: &YumConfigPlugins{
+					Langpacks: &YumConfigPluginsLangpacks{
+						Locales: []string{},
+					},
+				},
+			},
+			false,
+		},
+		{
+			YumConfigStageOptions{
+				Plugins: &YumConfigPlugins{
+					Langpacks: &YumConfigPluginsLangpacks{
+						Locales: []string{"en_US.UTF-8"},
+					},
+				},
+			},
+			true,
+		},
+		{
+			YumConfigStageOptions{
+				Config: &YumConfigConfig{
+					HttpCaching: common.StringToPtr(""),
+				},
+			},
+			false,
+		},
+		{
+			YumConfigStageOptions{
+				Config: &YumConfigConfig{
+					HttpCaching: common.StringToPtr("all"),
+				},
+			},
+			true,
+		},
+	}
+	for _, test := range tests {
+		if test.valid {
+			require.NotPanics(t, func() { NewYumConfigStage(&test.options) })
+		} else {
+			require.Panics(t, func() { NewYumConfigStage(&test.options) })
+		}
+	}
+}