osbuild2: add support for org.osbuild.sysctld stage

Add support for a new osbuild stage `org.osbuild.sysctld`, for
creating tmpfiles.d configuration files.

Add unit tests for the new stage.

Related to https://github.com/osbuild/osbuild/pull/804.

Signed-off-by: Tomas Hozza <thozza@redhat.com>
This commit is contained in:
Tomas Hozza 2021-09-10 17:56:23 +02:00 committed by Ondřej Budai
parent a5097b2141
commit d9610b97fc
4 changed files with 159 additions and 0 deletions

View file

@ -91,6 +91,8 @@ func (stage *Stage) UnmarshalJSON(data []byte) error {
options = new(ScriptStageOptions)
case "org.osbuild.sysconfig":
options = new(SysconfigStageOptions)
case "org.osbuild.sysctld":
options = new(SysctldStageOptions)
case "org.osbuild.tmpfilesd":
options = new(TmpfilesdStageOptions)
case "org.osbuild.kernel-cmdline":

View file

@ -499,6 +499,27 @@ func TestStage_UnmarshalJSON(t *testing.T) {
data: []byte(`{"type":"org.osbuild.sysconfig","options":{"kernel":{"update_default":true,"default_kernel":"kernel"},"network":{"networking":true,"no_zero_conf":true},"network-scripts":{"ifcfg":{"eth0":{"bootproto":"dhcp","device":"eth0","ipv6init":false,"onboot":true,"peerdns":true,"type":"Ethernet","userctl":true},"eth1":{"bootproto":"dhcp","device":"eth1","ipv6init":true,"onboot":true,"peerdns":true,"type":"Ethernet","userctl":false}}}}}`),
},
},
{
name: "sysctld",
fields: fields{
Type: "org.osbuild.sysctld",
Options: &SysctldStageOptions{
Filename: "example.conf",
Config: []SysctldConfigLine{
{
Key: "net.ipv4.conf.*.rp_filter",
Value: "2",
},
{
Key: "-net.ipv4.conf.all.rp_filter",
},
},
},
},
args: args{
data: []byte(`{"type":"org.osbuild.sysctld","options":{"filename":"example.conf","config":[{"key":"net.ipv4.conf.*.rp_filter","value":"2"},{"key":"-net.ipv4.conf.all.rp_filter"}]}}`),
},
},
{
name: "systemd",
fields: fields{

View file

@ -0,0 +1,66 @@
package osbuild2
import (
"encoding/json"
"fmt"
"strings"
)
// SysctldStageOptions represents a single sysctl.d configuration file.
type SysctldStageOptions struct {
// Filename of the configuration file to be created. Must end with '.conf'.
Filename string `json:"filename"`
// List of configuration directives. The list must contain at least one item.
Config []SysctldConfigLine `json:"config"`
}
func (SysctldStageOptions) isStageOptions() {}
// NewSysctldStageOptions creates a new PamLimitsConf Stage options object.
func NewSysctldStageOptions(filename string, config []SysctldConfigLine) *SysctldStageOptions {
return &SysctldStageOptions{
Filename: filename,
Config: config,
}
}
// Unexported alias for use in SysctldStageOptions's MarshalJSON() to prevent recursion
type sysctldStageOptions SysctldStageOptions
func (o SysctldStageOptions) MarshalJSON() ([]byte, error) {
if len(o.Config) == 0 {
return nil, fmt.Errorf("the 'Config' list must contain at least one item")
}
options := sysctldStageOptions(o)
return json.Marshal(options)
}
// NewSysctldStage creates a new Sysctld Stage object.
func NewSysctldStage(options *SysctldStageOptions) *Stage {
return &Stage{
Type: "org.osbuild.sysctld",
Options: options,
}
}
// SysctldConfigLine represents a single line in a sysctl.d configuration.
type SysctldConfigLine struct {
// Kernel parameter name.
// If the string starts with "-" and the Value is not set,
// then the key is excluded from being set by a matching glob.
Key string `json:"key"`
// Kernel parameter value.
// Must be set, unless the Key value starts with "-".
Value string `json:"value,omitempty"`
}
// Unexported alias for use in SysctldConfigLine's MarshalJSON() to prevent recursion.
type sysctldConfigLine SysctldConfigLine
func (l SysctldConfigLine) MarshalJSON() ([]byte, error) {
if l.Value == "" && !strings.HasPrefix(l.Key, "-") {
return nil, fmt.Errorf("only Keys starting with '-' can have an empty Value")
}
line := sysctldConfigLine(l)
return json.Marshal(line)
}

View file

@ -0,0 +1,70 @@
package osbuild2
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewSysctldStageOptions(t *testing.T) {
filename := "example.conf"
config := []SysctldConfigLine{{
Key: "net.ipv4.conf.default.rp_filter",
Value: "2",
}}
expectedOptions := &SysctldStageOptions{
Filename: filename,
Config: config,
}
actualOptions := NewSysctldStageOptions(filename, config)
assert.Equal(t, expectedOptions, actualOptions)
}
func TestNewSysctldStage(t *testing.T) {
expectedStage := &Stage{
Type: "org.osbuild.sysctld",
Options: &SysctldStageOptions{},
}
actualStage := NewSysctldStage(&SysctldStageOptions{})
assert.Equal(t, expectedStage, actualStage)
}
func TestSysctldStageOptions_MarshalJSON_Invalid(t *testing.T) {
tests := []struct {
name string
options SysctldStageOptions
}{
{
name: "empty-options",
options: SysctldStageOptions{},
},
}
for idx, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotBytes, err := json.Marshal(tt.options)
assert.NotNilf(t, err, "json.Marshal() didn't return an error, but: %s [idx: %d]", string(gotBytes), idx)
})
}
}
func TestSysctldConfigLine_MarshalJSON_Invalid(t *testing.T) {
tests := []struct {
name string
options SysctldConfigLine
}{
{
name: "no-value-without-prefix",
options: SysctldConfigLine{
Key: "key-without-prefix",
},
},
}
for idx, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotBytes, err := json.Marshal(tt.options)
assert.NotNilf(t, err, "json.Marshal() didn't return an error, but: %s [idx: %d]", string(gotBytes), idx)
})
}
}