osbuild2: add support for org.osbuild.cloud-init stage

Add support for the `org.osbuild.cloud-init` osbuild stage [1],
which allows one to configure cloud-init by creating
configuration files under `/etc/cloud/cloud.cfg.d/`.

Add unit test cases for the newly added stage.

[1] https://github.com/osbuild/osbuild/pull/689

Signed-off-by: Tomas Hozza <thozza@redhat.com>
This commit is contained in:
Tomas Hozza 2021-06-30 16:38:26 +02:00 committed by Ondřej Budai
parent 9957f378ca
commit 20e1cfeba4
4 changed files with 166 additions and 0 deletions

View file

@ -0,0 +1,73 @@
package osbuild2
import (
"encoding/json"
"fmt"
)
type CloudInitStageOptions struct {
ConfigFiles map[string]CloudInitConfigFile `json:"configuration_files,omitempty"`
}
func (CloudInitStageOptions) isStageOptions() {}
func NewCloudInitStage(options *CloudInitStageOptions) *Stage {
return &Stage{
Type: "org.osbuild.cloud-init",
Options: options,
}
}
// Represents a cloud-init configuration file
type CloudInitConfigFile struct {
SystemInfo *CloudInitConfigSystemInfo `json:"system_info,omitempty"`
}
// Unexported struct for use in CloudInitConfigFile's MarshalJSON() to prevent recursion
type cloudInitConfigFile struct {
SystemInfo *CloudInitConfigSystemInfo `json:"system_info,omitempty"`
}
func (c CloudInitConfigFile) MarshalJSON() ([]byte, error) {
if c.SystemInfo == nil {
return nil, fmt.Errorf("at least one cloud-init configuration option must be specified")
}
configFile := cloudInitConfigFile(c)
return json.Marshal(configFile)
}
// Represents the 'system_info' configuration section
type CloudInitConfigSystemInfo struct {
DefaultUser *CloudInitConfigDefaultUser `json:"default_user,omitempty"`
}
// Unexported struct for use in CloudInitConfigSystemInfo's MarshalJSON() to prevent recursion
type cloudInitConfigSystemInfo struct {
DefaultUser *CloudInitConfigDefaultUser `json:"default_user,omitempty"`
}
func (si CloudInitConfigSystemInfo) MarshalJSON() ([]byte, error) {
if si.DefaultUser == nil {
return nil, fmt.Errorf("at least one configuration option must be specified for 'system_info' section")
}
systemInfo := cloudInitConfigSystemInfo(si)
return json.Marshal(systemInfo)
}
// Configuration of the 'default' user created by cloud-init.
type CloudInitConfigDefaultUser struct {
Name string `json:"name,omitempty"`
}
// Unexported struct for use in CloudInitConfigDefaultUser's MarshalJSON() to prevent recursion
type cloudInitConfigDefaultUser struct {
Name string `json:"name,omitempty"`
}
func (du CloudInitConfigDefaultUser) MarshalJSON() ([]byte, error) {
if du.Name == "" {
return nil, fmt.Errorf("at least one configuration option must be specified for 'default_user' section")
}
defaultUser := cloudInitConfigDefaultUser(du)
return json.Marshal(defaultUser)
}

View file

@ -0,0 +1,61 @@
package osbuild2
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewCloudInitStage(t *testing.T) {
expectedStage := &Stage{
Type: "org.osbuild.cloud-init",
Options: &CloudInitStageOptions{},
}
actualStage := NewCloudInitStage(&CloudInitStageOptions{})
assert.Equal(t, expectedStage, actualStage)
}
func TestCloudInitStage_MarshalJSON_Invalid(t *testing.T) {
tests := []struct {
name string
options CloudInitStageOptions
}{
{
name: "no-config-file-section",
options: CloudInitStageOptions{
ConfigFiles: map[string]CloudInitConfigFile{
"00-default_user.cfg": {},
},
},
},
{
name: "no-system-info-section-option",
options: CloudInitStageOptions{
ConfigFiles: map[string]CloudInitConfigFile{
"00-default_user.cfg": {
SystemInfo: &CloudInitConfigSystemInfo{},
},
},
},
},
{
name: "no-default-user-section-option",
options: CloudInitStageOptions{
ConfigFiles: map[string]CloudInitConfigFile{
"00-default_user.cfg": {
SystemInfo: &CloudInitConfigSystemInfo{
DefaultUser: &CloudInitConfigDefaultUser{},
},
},
},
},
},
}
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)
})
}
}

View file

@ -85,6 +85,8 @@ func (stage *Stage) UnmarshalJSON(data []byte) error {
options = new(GroupsStageOptions)
case "org.osbuild.timezone":
options = new(TimezoneStageOptions)
case "org.osbuild.cloud-init":
options = new(CloudInitStageOptions)
case "org.osbuild.chrony":
options = new(ChronyStageOptions)
case "org.osbuild.dracut":

View file

@ -52,6 +52,36 @@ func TestStage_UnmarshalJSON(t *testing.T) {
},
wantErr: true,
},
{
name: "cloud-init",
fields: fields{
Type: "org.osbuild.cloud-init",
Options: &CloudInitStageOptions{},
},
args: args{
data: []byte(`{"type":"org.osbuild.cloud-init","options":{}}`),
},
},
{
name: "cloud-init-data",
fields: fields{
Type: "org.osbuild.cloud-init",
Options: &CloudInitStageOptions{
ConfigFiles: map[string]CloudInitConfigFile{
"00-default_user.cfg": {
SystemInfo: &CloudInitConfigSystemInfo{
DefaultUser: &CloudInitConfigDefaultUser{
Name: "ec2-user",
},
},
},
},
},
},
args: args{
data: []byte(`{"type":"org.osbuild.cloud-init","options":{"configuration_files":{"00-default_user.cfg":{"system_info":{"default_user":{"name":"ec2-user"}}}}}}`),
},
},
{
name: "chrony",
fields: fields{