blueprint: move pipeline generation into its own package
Introduce the `distro` package, which contains an interface for OS implementations. Its main purpose is to convert a blueprint to a distro-specific pipeline. Also introduce the `distro/fedora30` package. It is the first implementation of the distro interface. Most of its code has been copied with minimal modifications from the blueprint package. The `blueprint` package is now back to serving a single purpose: representing a weldr blueprint. It does not depend on the `pipeline` package anymore. Change osbuild-composer and osbuild-pipeline to use the new API, hard-coding "fedora-30". This looks a bit weird now, but is the same behavior as before. All test cases now also take an "distro" key in the "compose" object.
This commit is contained in:
parent
3ae39e54c5
commit
b33ed9e5d2
28 changed files with 661 additions and 541 deletions
|
|
@ -7,6 +7,9 @@ import (
|
|||
"os"
|
||||
|
||||
"github.com/osbuild/osbuild-composer/internal/blueprint"
|
||||
|
||||
"github.com/osbuild/osbuild-composer/internal/distro"
|
||||
_ "github.com/osbuild/osbuild-composer/internal/distro/fedora30"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
|
@ -27,7 +30,9 @@ func main() {
|
|||
panic("Colud not parse blueprint")
|
||||
}
|
||||
}
|
||||
pipeline, err := blueprint.ToPipeline(format)
|
||||
|
||||
f30 := distro.New("fedora-30")
|
||||
pipeline, err := f30.Pipeline(blueprint, format)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,6 @@
|
|||
// Package blueprint contains primitives for representing weldr blueprints and
|
||||
// translating them to OSBuild pipelines
|
||||
// Package blueprint contains primitives for representing weldr blueprints
|
||||
package blueprint
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/osbuild/osbuild-composer/internal/pipeline"
|
||||
)
|
||||
|
||||
// An InvalidOutputFormatError is returned when a requested output format is
|
||||
// not supported. The requested format is included as the error message.
|
||||
type InvalidOutputFormatError struct {
|
||||
|
|
@ -40,45 +33,7 @@ type Group struct {
|
|||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type output interface {
|
||||
translate(b *Blueprint) (*pipeline.Pipeline, error)
|
||||
getName() string
|
||||
getMime() string
|
||||
}
|
||||
|
||||
var outputs = map[string]output{
|
||||
"ami": &amiOutput{},
|
||||
"ext4-filesystem": &ext4Output{},
|
||||
"live-iso": &liveIsoOutput{},
|
||||
"partitioned-disk": &diskOutput{},
|
||||
"qcow2": &qcow2Output{},
|
||||
"openstack": &openstackOutput{},
|
||||
"tar": &tarOutput{},
|
||||
"vhd": &vhdOutput{},
|
||||
"vmdk": &vmdkOutput{},
|
||||
}
|
||||
|
||||
// ListOutputFormats returns a sorted list of the supported output formats
|
||||
func ListOutputFormats() []string {
|
||||
formats := make([]string, 0, len(outputs))
|
||||
for name := range outputs {
|
||||
formats = append(formats, name)
|
||||
}
|
||||
sort.Strings(formats)
|
||||
|
||||
return formats
|
||||
}
|
||||
|
||||
// ToPipeline converts the blueprint to a pipeline for a given output format.
|
||||
func (b *Blueprint) ToPipeline(outputFormat string) (*pipeline.Pipeline, error) {
|
||||
if output, exists := outputs[outputFormat]; exists {
|
||||
return output.translate(b)
|
||||
}
|
||||
|
||||
return nil, &InvalidOutputFormatError{outputFormat}
|
||||
}
|
||||
|
||||
func (b *Blueprint) getKernelCustomization() *KernelCustomization {
|
||||
func (b *Blueprint) GetKernelCustomization() *KernelCustomization {
|
||||
if b.Customizations == nil {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -86,21 +41,11 @@ func (b *Blueprint) getKernelCustomization() *KernelCustomization {
|
|||
return b.Customizations.Kernel
|
||||
}
|
||||
|
||||
// FilenameFromType gets the canonical filename and MIME type for a given
|
||||
// output format
|
||||
func FilenameFromType(outputFormat string) (string, string, error) {
|
||||
if output, exists := outputs[outputFormat]; exists {
|
||||
return output.getName(), output.getMime(), nil
|
||||
}
|
||||
|
||||
return "", "", &InvalidOutputFormatError{outputFormat}
|
||||
}
|
||||
|
||||
func (p Package) ToNameVersion() string {
|
||||
// Omit version to prevent all packages with prefix of name to be installed
|
||||
if p.Version == "*" {
|
||||
return p.Name
|
||||
}
|
||||
// Omit version to prevent all packages with prefix of name to be installed
|
||||
if p.Version == "*" {
|
||||
return p.Name
|
||||
}
|
||||
|
||||
return p.Name + "-" + p.Version
|
||||
return p.Name + "-" + p.Version
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,9 @@
|
|||
package blueprint_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/osbuild/osbuild-composer/internal/blueprint"
|
||||
"github.com/osbuild/osbuild-composer/internal/pipeline"
|
||||
)
|
||||
|
||||
func TestInvalidOutputFormatError_Error(t *testing.T) {
|
||||
|
|
@ -29,167 +25,3 @@ func TestInvalidOutputFormatError_Error(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListOutputFormats(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "basic",
|
||||
want: []string{
|
||||
"ami",
|
||||
"ext4-filesystem",
|
||||
"live-iso",
|
||||
"openstack",
|
||||
"partitioned-disk",
|
||||
"qcow2",
|
||||
"tar",
|
||||
"vhd",
|
||||
"vmdk",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := blueprint.ListOutputFormats(); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("ListOutputFormats() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlueprint_ToPipeline(t *testing.T) {
|
||||
pipelinePath := "../../test/cases/"
|
||||
fileInfos, err := ioutil.ReadDir(pipelinePath)
|
||||
if err != nil {
|
||||
t.Errorf("Could not read pipelines directory '%s': %v", pipelinePath, err)
|
||||
}
|
||||
for _, fileInfo := range fileInfos {
|
||||
type compose struct {
|
||||
OutputFormat string `json:"output-format"`
|
||||
Blueprint *blueprint.Blueprint `json:"blueprint"`
|
||||
}
|
||||
var tt struct {
|
||||
Compose *compose `json:"compose"`
|
||||
Pipeline *pipeline.Pipeline `json:"pipeline,omitempty"`
|
||||
}
|
||||
file, err := ioutil.ReadFile(pipelinePath + fileInfo.Name())
|
||||
if err != nil {
|
||||
t.Errorf("Colud not read test-case '%s': %v", fileInfo.Name(), err)
|
||||
}
|
||||
err = json.Unmarshal([]byte(file), &tt)
|
||||
if err != nil {
|
||||
t.Errorf("Colud not parse test-case '%s': %v", fileInfo.Name(), err)
|
||||
}
|
||||
if tt.Compose == nil || tt.Compose.Blueprint == nil {
|
||||
t.Logf("Skipping '%s'.", fileInfo.Name())
|
||||
continue
|
||||
}
|
||||
t.Run(tt.Compose.OutputFormat, func(t *testing.T) {
|
||||
got, err := tt.Compose.Blueprint.ToPipeline(tt.Compose.OutputFormat)
|
||||
if (err != nil) != (tt.Pipeline == nil) {
|
||||
t.Errorf("Blueprint.ToPipeline() error = %v", err)
|
||||
return
|
||||
}
|
||||
if tt.Pipeline != nil {
|
||||
if !reflect.DeepEqual(got, tt.Pipeline) {
|
||||
// Without this the "difference" is just a list of pointers.
|
||||
gotJson, _ := json.Marshal(got)
|
||||
fileJson, _ := json.Marshal(tt.Pipeline)
|
||||
t.Errorf("Blueprint.ToPipeline() =\n%v,\nwant =\n%v", string(gotJson), string(fileJson))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilenameFromType(t *testing.T) {
|
||||
type args struct {
|
||||
outputFormat string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
want1 string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "ami",
|
||||
args: args{"ami"},
|
||||
want: "image.ami",
|
||||
want1: "application/x-qemu-disk",
|
||||
},
|
||||
{
|
||||
name: "ext4",
|
||||
args: args{"ext4-filesystem"},
|
||||
want: "filesystem.img",
|
||||
want1: "application/octet-stream",
|
||||
},
|
||||
{
|
||||
name: "live-iso",
|
||||
args: args{"live-iso"},
|
||||
want: "image.iso",
|
||||
want1: "application/x-iso9660-image",
|
||||
},
|
||||
{
|
||||
name: "openstack",
|
||||
args: args{"openstack"},
|
||||
want: "image.qcow2",
|
||||
want1: "application/x-qemu-disk",
|
||||
},
|
||||
{
|
||||
name: "partitioned-disk",
|
||||
args: args{"partitioned-disk"},
|
||||
want: "disk.img",
|
||||
want1: "application/octet-stream",
|
||||
},
|
||||
{
|
||||
name: "qcow2",
|
||||
args: args{"qcow2"},
|
||||
want: "image.qcow2",
|
||||
want1: "application/x-qemu-disk",
|
||||
},
|
||||
{
|
||||
name: "tar",
|
||||
args: args{"tar"},
|
||||
want: "root.tar.xz",
|
||||
want1: "application/x-tar",
|
||||
},
|
||||
{
|
||||
name: "vhd",
|
||||
args: args{"vhd"},
|
||||
want: "image.vhd",
|
||||
want1: "application/x-vhd",
|
||||
},
|
||||
{
|
||||
name: "vmdk",
|
||||
args: args{"vmdk"},
|
||||
want: "disk.vmdk",
|
||||
want1: "application/x-vmdk",
|
||||
},
|
||||
{
|
||||
name: "invalid-output-type",
|
||||
args: args{"foobar"},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, got1, err := blueprint.FilenameFromType(tt.args.outputFormat)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("FilenameFromType() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !tt.wantErr {
|
||||
if got != tt.want {
|
||||
t.Errorf("FilenameFromType() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
if got1 != tt.want1 {
|
||||
t.Errorf("FilenameFromType() got1 = %v, want %v", got1, tt.want1)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,5 @@
|
|||
package blueprint
|
||||
|
||||
import (
|
||||
"github.com/osbuild/osbuild-composer/internal/crypt"
|
||||
"github.com/osbuild/osbuild-composer/internal/pipeline"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Customizations struct {
|
||||
Hostname *string `json:"hostname,omitempty"`
|
||||
Kernel *KernelCustomization `json:"kernel,omitempty"`
|
||||
|
|
@ -72,260 +64,9 @@ type ServicesCustomization struct {
|
|||
}
|
||||
|
||||
type CustomizationError struct {
|
||||
message string
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *CustomizationError) Error() string {
|
||||
return e.message
|
||||
}
|
||||
|
||||
func (c *Customizations) customizeAll(p *pipeline.Pipeline) error {
|
||||
c.customizeHostname(p)
|
||||
c.customizeGroup(p)
|
||||
if err := c.customizeUserAndSSHKey(p); err != nil {
|
||||
return err
|
||||
}
|
||||
c.customizeTimezone(p)
|
||||
c.customizeNTPServers(p)
|
||||
c.customizeLanguages(p)
|
||||
c.customizeKeyboard(p)
|
||||
c.customizeFirewall(p)
|
||||
c.customizeServices(p)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Customizations) customizeHostname(p *pipeline.Pipeline) {
|
||||
if c.Hostname == nil {
|
||||
return
|
||||
}
|
||||
|
||||
p.AddStage(
|
||||
pipeline.NewHostnameStage(
|
||||
&pipeline.HostnameStageOptions{Hostname: *c.Hostname},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *Customizations) customizeGroup(p *pipeline.Pipeline) {
|
||||
if len(c.Group) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
groups := make(map[string]pipeline.GroupsStageOptionsGroup)
|
||||
for _, group := range c.Group {
|
||||
if userCustomizationsContainUsername(c.User, group.Name) {
|
||||
log.Println("group with name ", group.Name, " was not created, because user with same name was defined!")
|
||||
continue
|
||||
}
|
||||
|
||||
groupData := pipeline.GroupsStageOptionsGroup{}
|
||||
if group.GID != nil {
|
||||
gid := strconv.Itoa(*group.GID)
|
||||
groupData.GID = &gid
|
||||
}
|
||||
|
||||
groups[group.Name] = groupData
|
||||
}
|
||||
|
||||
p.AddStage(
|
||||
pipeline.NewGroupsStage(
|
||||
&pipeline.GroupsStageOptions{Groups: groups},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *Customizations) assertAllUsersExistForSSHCustomizations() error {
|
||||
for _, sshkey := range c.SSHKey {
|
||||
userFound := false
|
||||
for _, user := range c.User {
|
||||
if user.Name == sshkey.User {
|
||||
userFound = true
|
||||
}
|
||||
}
|
||||
|
||||
if !userFound {
|
||||
return &CustomizationError{"Cannot set SSH key for non-existing user " + sshkey.User}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Customizations) customizeUserAndSSHKey(p *pipeline.Pipeline) error {
|
||||
if len(c.User) == 0 {
|
||||
if len(c.SSHKey) > 0 {
|
||||
return &CustomizationError{"SSH key customization defined but no user customizations are defined"}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// return error if ssh key customization without user defined in user customization if found
|
||||
if e := c.assertAllUsersExistForSSHCustomizations(); e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
users := make(map[string]pipeline.UsersStageOptionsUser)
|
||||
for _, user := range c.User {
|
||||
|
||||
if user.Password != nil && !crypt.PasswordIsCrypted(*user.Password) {
|
||||
cryptedPassword, err := crypt.CryptSHA512(*user.Password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user.Password = &cryptedPassword
|
||||
}
|
||||
|
||||
userData := pipeline.UsersStageOptionsUser{
|
||||
Groups: user.Groups,
|
||||
Description: user.Description,
|
||||
Home: user.Home,
|
||||
Shell: user.Shell,
|
||||
Password: user.Password,
|
||||
Key: user.Key,
|
||||
}
|
||||
|
||||
if user.UID != nil {
|
||||
uid := strconv.Itoa(*user.UID)
|
||||
userData.UID = &uid
|
||||
}
|
||||
|
||||
if user.GID != nil {
|
||||
gid := strconv.Itoa(*user.GID)
|
||||
userData.GID = &gid
|
||||
}
|
||||
|
||||
// process sshkey customizations
|
||||
if additionalKeys := findKeysForUser(c.SSHKey, user.Name); len(additionalKeys) > 0 {
|
||||
joinedKeys := strings.Join(additionalKeys, "\n")
|
||||
|
||||
if userData.Key != nil {
|
||||
*userData.Key += "\n" + joinedKeys
|
||||
} else {
|
||||
userData.Key = &joinedKeys
|
||||
}
|
||||
}
|
||||
|
||||
users[user.Name] = userData
|
||||
}
|
||||
|
||||
p.AddStage(
|
||||
pipeline.NewUsersStage(
|
||||
&pipeline.UsersStageOptions{Users: users},
|
||||
),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Customizations) customizeTimezone(p *pipeline.Pipeline) {
|
||||
if c.Timezone == nil || c.Timezone.Timezone == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: lorax (anaconda) automatically installs chrony if timeservers are defined
|
||||
// except for the case when chrony is explicitly removed from installed packages (using -chrony)
|
||||
// this is currently not supported, no checks whether chrony is installed are not performed
|
||||
|
||||
p.AddStage(
|
||||
pipeline.NewTimezoneStage(&pipeline.TimezoneStageOptions{
|
||||
Zone: *c.Timezone.Timezone,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *Customizations) customizeNTPServers(p *pipeline.Pipeline) {
|
||||
if c.Timezone == nil || len(c.Timezone.NTPServers) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
p.AddStage(
|
||||
pipeline.NewChronyStage(&pipeline.ChronyStageOptions{
|
||||
Timeservers: c.Timezone.NTPServers,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *Customizations) customizeLanguages(p *pipeline.Pipeline) {
|
||||
if c.Locale == nil || len(c.Locale.Languages) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: you can specify more languages in customization
|
||||
// The first one is the primary one, we can set in the locale stage, this should currently work
|
||||
// Also, ALL the listed languages are installed using langpack-* packages
|
||||
// This is currently not implemented!
|
||||
// See anaconda src: pyanaconda/payload/dnfpayload.py:772
|
||||
|
||||
p.AddStage(
|
||||
pipeline.NewLocaleStage(&pipeline.LocaleStageOptions{
|
||||
Language: c.Locale.Languages[0],
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *Customizations) customizeKeyboard(p *pipeline.Pipeline) {
|
||||
if c.Locale == nil || c.Locale.Keyboard == nil {
|
||||
return
|
||||
}
|
||||
|
||||
p.AddStage(
|
||||
pipeline.NewKeymapStage(&pipeline.KeymapStageOptions{
|
||||
Keymap: *c.Locale.Keyboard,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *Customizations) customizeFirewall(p *pipeline.Pipeline) {
|
||||
if c.Firewall == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var enabledServices, disabledServices []string
|
||||
|
||||
if c.Firewall.Services != nil {
|
||||
enabledServices = c.Firewall.Services.Enabled
|
||||
disabledServices = c.Firewall.Services.Disabled
|
||||
}
|
||||
|
||||
p.AddStage(
|
||||
pipeline.NewFirewallStage(&pipeline.FirewallStageOptions{
|
||||
Ports: c.Firewall.Ports,
|
||||
EnabledServices: enabledServices,
|
||||
DisabledServices: disabledServices,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *Customizations) customizeServices(p *pipeline.Pipeline) {
|
||||
if c.Services == nil {
|
||||
return
|
||||
}
|
||||
|
||||
p.AddStage(
|
||||
pipeline.NewSystemdStage(&pipeline.SystemdStageOptions{
|
||||
EnabledServices: c.Services.Enabled,
|
||||
DisabledServices: c.Services.Disabled,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func findKeysForUser(sshKeyCustomizations []SSHKeyCustomization, user string) (keys []string) {
|
||||
for _, sshKey := range sshKeyCustomizations {
|
||||
if sshKey.User == user {
|
||||
keys = append(keys, sshKey.Key)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func userCustomizationsContainUsername(userCustomizations []UserCustomization, name string) bool {
|
||||
for _, usr := range userCustomizations {
|
||||
if usr.Name == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return e.Message
|
||||
}
|
||||
|
|
|
|||
48
internal/distro/distro.go
Normal file
48
internal/distro/distro.go
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package distro
|
||||
|
||||
import (
|
||||
"github.com/osbuild/osbuild-composer/internal/blueprint"
|
||||
"github.com/osbuild/osbuild-composer/internal/pipeline"
|
||||
)
|
||||
|
||||
type Distro interface {
|
||||
// Returns a sorted list of the output formats this distro supports.
|
||||
ListOutputFormats() []string
|
||||
|
||||
// Returns the canonical filename and MIME type for a given output
|
||||
// format
|
||||
FilenameFromType(outputFormat string) (string, string, error)
|
||||
|
||||
// Returns an osbuild pipeline that generates an image in the given
|
||||
// output format with all packages and customizations specified in the
|
||||
// given blueprint. `outputFormat` must be one returned by
|
||||
// ListOutputFormats().
|
||||
Pipeline(b *blueprint.Blueprint, outputFormat string) (*pipeline.Pipeline, error)
|
||||
}
|
||||
|
||||
// An InvalidOutputFormatError is returned when a requested output format is
|
||||
// not supported. The requested format is included as the error message.
|
||||
type InvalidOutputFormatError struct {
|
||||
Format string
|
||||
}
|
||||
|
||||
func (e *InvalidOutputFormatError) Error() string {
|
||||
return e.Format
|
||||
}
|
||||
|
||||
var registered = map[string]Distro{}
|
||||
|
||||
func New(name string) Distro {
|
||||
distro, ok := registered[name]
|
||||
if !ok {
|
||||
panic("unknown distro: " + name)
|
||||
}
|
||||
return distro
|
||||
}
|
||||
|
||||
func Register(name string, distro Distro) {
|
||||
if _, exists := registered[name]; exists {
|
||||
panic("a distro with this name already exists: " + name)
|
||||
}
|
||||
registered[name] = distro
|
||||
}
|
||||
66
internal/distro/distro_test.go
Normal file
66
internal/distro/distro_test.go
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
package distro_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/osbuild/osbuild-composer/internal/blueprint"
|
||||
"github.com/osbuild/osbuild-composer/internal/distro"
|
||||
"github.com/osbuild/osbuild-composer/internal/pipeline"
|
||||
|
||||
_ "github.com/osbuild/osbuild-composer/internal/distro/fedora30"
|
||||
)
|
||||
|
||||
func TestDistro_Pipeline(t *testing.T) {
|
||||
pipelinePath := "../../test/cases/"
|
||||
fileInfos, err := ioutil.ReadDir(pipelinePath)
|
||||
if err != nil {
|
||||
t.Errorf("Could not read pipelines directory '%s': %v", pipelinePath, err)
|
||||
}
|
||||
for _, fileInfo := range fileInfos {
|
||||
type compose struct {
|
||||
Distro string `json:"distro"`
|
||||
OutputFormat string `json:"output-format"`
|
||||
Blueprint *blueprint.Blueprint `json:"blueprint"`
|
||||
}
|
||||
var tt struct {
|
||||
Compose *compose `json:"compose"`
|
||||
Pipeline *pipeline.Pipeline `json:"pipeline,omitempty"`
|
||||
}
|
||||
file, err := ioutil.ReadFile(pipelinePath + fileInfo.Name())
|
||||
if err != nil {
|
||||
t.Errorf("Colud not read test-case '%s': %v", fileInfo.Name(), err)
|
||||
}
|
||||
err = json.Unmarshal([]byte(file), &tt)
|
||||
if err != nil {
|
||||
t.Errorf("Colud not parse test-case '%s': %v", fileInfo.Name(), err)
|
||||
}
|
||||
if tt.Compose == nil || tt.Compose.Blueprint == nil {
|
||||
t.Logf("Skipping '%s'.", fileInfo.Name())
|
||||
continue
|
||||
}
|
||||
t.Run(tt.Compose.OutputFormat, func(t *testing.T) {
|
||||
d := distro.New(tt.Compose.Distro)
|
||||
if d == nil {
|
||||
t.Errorf("unknown distro: %v", tt.Compose.Distro)
|
||||
return
|
||||
}
|
||||
got, err := d.Pipeline(tt.Compose.Blueprint, tt.Compose.OutputFormat)
|
||||
if (err != nil) != (tt.Pipeline == nil) {
|
||||
t.Errorf("distro.Pipeline() error = %v", err)
|
||||
return
|
||||
}
|
||||
if tt.Pipeline != nil {
|
||||
if !reflect.DeepEqual(got, tt.Pipeline) {
|
||||
// Without this the "difference" is just a list of pointers.
|
||||
gotJson, _ := json.Marshal(got)
|
||||
fileJson, _ := json.Marshal(tt.Pipeline)
|
||||
t.Errorf("d.Pipeline() =\n%v,\nwant =\n%v", string(gotJson), string(fileJson))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
package blueprint
|
||||
package fedora30
|
||||
|
||||
import "github.com/osbuild/osbuild-composer/internal/pipeline"
|
||||
import (
|
||||
"github.com/osbuild/osbuild-composer/internal/blueprint"
|
||||
"github.com/osbuild/osbuild-composer/internal/pipeline"
|
||||
)
|
||||
|
||||
type amiOutput struct{}
|
||||
|
||||
func (t *amiOutput) translate(b *Blueprint) (*pipeline.Pipeline, error) {
|
||||
func (t *amiOutput) translate(b *blueprint.Blueprint) (*pipeline.Pipeline, error) {
|
||||
packages := [...]string{
|
||||
"@Core",
|
||||
"chrony",
|
||||
|
|
@ -30,7 +33,7 @@ func (t *amiOutput) translate(b *Blueprint) (*pipeline.Pipeline, error) {
|
|||
addF30QemuAssembler(p, "qcow2", t.getName())
|
||||
|
||||
if b.Customizations != nil {
|
||||
err := b.Customizations.customizeAll(p)
|
||||
err := customizeAll(p, b.Customizations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
261
internal/distro/fedora30/customizations.go
Normal file
261
internal/distro/fedora30/customizations.go
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
package fedora30
|
||||
|
||||
import (
|
||||
"github.com/osbuild/osbuild-composer/internal/blueprint"
|
||||
"github.com/osbuild/osbuild-composer/internal/crypt"
|
||||
"github.com/osbuild/osbuild-composer/internal/pipeline"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func customizeAll(p *pipeline.Pipeline, c *blueprint.Customizations) error {
|
||||
customizeHostname(p, c)
|
||||
customizeGroup(p, c)
|
||||
if err := customizeUserAndSSHKey(p, c); err != nil {
|
||||
return err
|
||||
}
|
||||
customizeTimezone(p, c)
|
||||
customizeNTPServers(p, c)
|
||||
customizeLanguages(p, c)
|
||||
customizeKeyboard(p, c)
|
||||
customizeFirewall(p, c)
|
||||
customizeServices(p, c)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func customizeHostname(p *pipeline.Pipeline, c *blueprint.Customizations) {
|
||||
if c.Hostname == nil {
|
||||
return
|
||||
}
|
||||
|
||||
p.AddStage(
|
||||
pipeline.NewHostnameStage(
|
||||
&pipeline.HostnameStageOptions{Hostname: *c.Hostname},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func customizeGroup(p *pipeline.Pipeline, c *blueprint.Customizations) {
|
||||
if len(c.Group) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
groups := make(map[string]pipeline.GroupsStageOptionsGroup)
|
||||
for _, group := range c.Group {
|
||||
if userCustomizationsContainUsername(c.User, group.Name) {
|
||||
log.Println("group with name ", group.Name, " was not created, because user with same name was defined!")
|
||||
continue
|
||||
}
|
||||
|
||||
groupData := pipeline.GroupsStageOptionsGroup{}
|
||||
if group.GID != nil {
|
||||
gid := strconv.Itoa(*group.GID)
|
||||
groupData.GID = &gid
|
||||
}
|
||||
|
||||
groups[group.Name] = groupData
|
||||
}
|
||||
|
||||
p.AddStage(
|
||||
pipeline.NewGroupsStage(
|
||||
&pipeline.GroupsStageOptions{Groups: groups},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func assertAllUsersExistForSSHCustomizations(c *blueprint.Customizations) error {
|
||||
for _, sshkey := range c.SSHKey {
|
||||
userFound := false
|
||||
for _, user := range c.User {
|
||||
if user.Name == sshkey.User {
|
||||
userFound = true
|
||||
}
|
||||
}
|
||||
|
||||
if !userFound {
|
||||
return &blueprint.CustomizationError{"Cannot set SSH key for non-existing user " + sshkey.User}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func customizeUserAndSSHKey(p *pipeline.Pipeline, c *blueprint.Customizations) error {
|
||||
if len(c.User) == 0 {
|
||||
if len(c.SSHKey) > 0 {
|
||||
return &blueprint.CustomizationError{"SSH key customization defined but no user customizations are defined"}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// return error if ssh key customization without user defined in user customization if found
|
||||
if e := assertAllUsersExistForSSHCustomizations(c); e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
users := make(map[string]pipeline.UsersStageOptionsUser)
|
||||
for _, user := range c.User {
|
||||
|
||||
if user.Password != nil && !crypt.PasswordIsCrypted(*user.Password) {
|
||||
cryptedPassword, err := crypt.CryptSHA512(*user.Password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user.Password = &cryptedPassword
|
||||
}
|
||||
|
||||
userData := pipeline.UsersStageOptionsUser{
|
||||
Groups: user.Groups,
|
||||
Description: user.Description,
|
||||
Home: user.Home,
|
||||
Shell: user.Shell,
|
||||
Password: user.Password,
|
||||
Key: user.Key,
|
||||
}
|
||||
|
||||
if user.UID != nil {
|
||||
uid := strconv.Itoa(*user.UID)
|
||||
userData.UID = &uid
|
||||
}
|
||||
|
||||
if user.GID != nil {
|
||||
gid := strconv.Itoa(*user.GID)
|
||||
userData.GID = &gid
|
||||
}
|
||||
|
||||
// process sshkey customizations
|
||||
if additionalKeys := findKeysForUser(c.SSHKey, user.Name); len(additionalKeys) > 0 {
|
||||
joinedKeys := strings.Join(additionalKeys, "\n")
|
||||
|
||||
if userData.Key != nil {
|
||||
*userData.Key += "\n" + joinedKeys
|
||||
} else {
|
||||
userData.Key = &joinedKeys
|
||||
}
|
||||
}
|
||||
|
||||
users[user.Name] = userData
|
||||
}
|
||||
|
||||
p.AddStage(
|
||||
pipeline.NewUsersStage(
|
||||
&pipeline.UsersStageOptions{Users: users},
|
||||
),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func customizeTimezone(p *pipeline.Pipeline, c *blueprint.Customizations) {
|
||||
if c.Timezone == nil || c.Timezone.Timezone == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: lorax (anaconda) automatically installs chrony if timeservers are defined
|
||||
// except for the case when chrony is explicitly removed from installed packages (using -chrony)
|
||||
// this is currently not supported, no checks whether chrony is installed are not performed
|
||||
|
||||
p.AddStage(
|
||||
pipeline.NewTimezoneStage(&pipeline.TimezoneStageOptions{
|
||||
Zone: *c.Timezone.Timezone,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func customizeNTPServers(p *pipeline.Pipeline, c *blueprint.Customizations) {
|
||||
if c.Timezone == nil || len(c.Timezone.NTPServers) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
p.AddStage(
|
||||
pipeline.NewChronyStage(&pipeline.ChronyStageOptions{
|
||||
Timeservers: c.Timezone.NTPServers,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func customizeLanguages(p *pipeline.Pipeline, c *blueprint.Customizations) {
|
||||
if c.Locale == nil || len(c.Locale.Languages) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: you can specify more languages in customization
|
||||
// The first one is the primary one, we can set in the locale stage, this should currently work
|
||||
// Also, ALL the listed languages are installed using langpack-* packages
|
||||
// This is currently not implemented!
|
||||
// See anaconda src: pyanaconda/payload/dnfpayload.py:772
|
||||
|
||||
p.AddStage(
|
||||
pipeline.NewLocaleStage(&pipeline.LocaleStageOptions{
|
||||
Language: c.Locale.Languages[0],
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func customizeKeyboard(p *pipeline.Pipeline, c *blueprint.Customizations) {
|
||||
if c.Locale == nil || c.Locale.Keyboard == nil {
|
||||
return
|
||||
}
|
||||
|
||||
p.AddStage(
|
||||
pipeline.NewKeymapStage(&pipeline.KeymapStageOptions{
|
||||
Keymap: *c.Locale.Keyboard,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func customizeFirewall(p *pipeline.Pipeline, c *blueprint.Customizations) {
|
||||
if c.Firewall == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var enabledServices, disabledServices []string
|
||||
|
||||
if c.Firewall.Services != nil {
|
||||
enabledServices = c.Firewall.Services.Enabled
|
||||
disabledServices = c.Firewall.Services.Disabled
|
||||
}
|
||||
|
||||
p.AddStage(
|
||||
pipeline.NewFirewallStage(&pipeline.FirewallStageOptions{
|
||||
Ports: c.Firewall.Ports,
|
||||
EnabledServices: enabledServices,
|
||||
DisabledServices: disabledServices,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func customizeServices(p *pipeline.Pipeline, c *blueprint.Customizations) {
|
||||
if c.Services == nil {
|
||||
return
|
||||
}
|
||||
|
||||
p.AddStage(
|
||||
pipeline.NewSystemdStage(&pipeline.SystemdStageOptions{
|
||||
EnabledServices: c.Services.Enabled,
|
||||
DisabledServices: c.Services.Disabled,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func findKeysForUser(sshKeyCustomizations []blueprint.SSHKeyCustomization, user string) (keys []string) {
|
||||
for _, sshKey := range sshKeyCustomizations {
|
||||
if sshKey.User == user {
|
||||
keys = append(keys, sshKey.Key)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func userCustomizationsContainUsername(userCustomizations []blueprint.UserCustomization, name string) bool {
|
||||
for _, usr := range userCustomizations {
|
||||
if usr.Name == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
package blueprint
|
||||
package fedora30
|
||||
|
||||
import "github.com/osbuild/osbuild-composer/internal/pipeline"
|
||||
import (
|
||||
"github.com/osbuild/osbuild-composer/internal/blueprint"
|
||||
"github.com/osbuild/osbuild-composer/internal/pipeline"
|
||||
)
|
||||
|
||||
type diskOutput struct{}
|
||||
|
||||
func (t *diskOutput) translate(b *Blueprint) (*pipeline.Pipeline, error) {
|
||||
func (t *diskOutput) translate(b *blueprint.Blueprint) (*pipeline.Pipeline, error) {
|
||||
packages := [...]string{
|
||||
"@core",
|
||||
"chrony",
|
||||
|
|
@ -20,13 +23,13 @@ func (t *diskOutput) translate(b *Blueprint) (*pipeline.Pipeline, error) {
|
|||
p := getCustomF30PackageSet(packages[:], excludedPackages[:], b)
|
||||
addF30LocaleStage(p)
|
||||
addF30FSTabStage(p)
|
||||
addF30GRUB2Stage(p, b.getKernelCustomization())
|
||||
addF30GRUB2Stage(p, b.GetKernelCustomization())
|
||||
addF30FixBlsStage(p)
|
||||
addF30SELinuxStage(p)
|
||||
addF30QemuAssembler(p, "raw", t.getName())
|
||||
|
||||
if b.Customizations != nil {
|
||||
err := b.Customizations.customizeAll(p)
|
||||
err := customizeAll(p, b.Customizations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
package blueprint
|
||||
package fedora30
|
||||
|
||||
import "github.com/osbuild/osbuild-composer/internal/pipeline"
|
||||
import (
|
||||
"github.com/osbuild/osbuild-composer/internal/blueprint"
|
||||
"github.com/osbuild/osbuild-composer/internal/pipeline"
|
||||
)
|
||||
|
||||
type ext4Output struct{}
|
||||
|
||||
func (t *ext4Output) translate(b *Blueprint) (*pipeline.Pipeline, error) {
|
||||
func (t *ext4Output) translate(b *blueprint.Blueprint) (*pipeline.Pipeline, error) {
|
||||
packages := [...]string{
|
||||
"policycoreutils",
|
||||
"selinux-policy-targeted",
|
||||
|
|
@ -18,13 +21,13 @@ func (t *ext4Output) translate(b *Blueprint) (*pipeline.Pipeline, error) {
|
|||
}
|
||||
p := getCustomF30PackageSet(packages[:], excludedPackages[:], b)
|
||||
addF30LocaleStage(p)
|
||||
addF30GRUB2Stage(p, b.getKernelCustomization())
|
||||
addF30GRUB2Stage(p, b.GetKernelCustomization())
|
||||
addF30FixBlsStage(p)
|
||||
addF30SELinuxStage(p)
|
||||
addF30RawFSAssembler(p, t.getName())
|
||||
|
||||
if b.Customizations != nil {
|
||||
err := b.Customizations.customizeAll(p)
|
||||
err := customizeAll(p, b.Customizations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package blueprint
|
||||
package fedora30
|
||||
|
||||
import (
|
||||
"github.com/osbuild/osbuild-composer/internal/blueprint"
|
||||
"github.com/osbuild/osbuild-composer/internal/pipeline"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
|
@ -89,7 +90,7 @@ func getF30Pipeline() *pipeline.Pipeline {
|
|||
return p
|
||||
}
|
||||
|
||||
func getCustomF30PackageSet(packages []string, excludedPackages []string, blueprint *Blueprint) *pipeline.Pipeline {
|
||||
func getCustomF30PackageSet(packages []string, excludedPackages []string, blueprint *blueprint.Blueprint) *pipeline.Pipeline {
|
||||
p := &pipeline.Pipeline{
|
||||
BuildPipeline: getF30BuildPipeline(),
|
||||
}
|
||||
|
|
@ -122,7 +123,7 @@ func getCustomF30PackageSet(packages []string, excludedPackages []string, bluepr
|
|||
return p
|
||||
}
|
||||
|
||||
func addF30GRUB2Stage(p *pipeline.Pipeline, kernelCustomization *KernelCustomization) {
|
||||
func addF30GRUB2Stage(p *pipeline.Pipeline, kernelCustomization *blueprint.KernelCustomization) {
|
||||
id, err := uuid.Parse("76a22bf4-f153-4541-b6c7-0332c0dfaeac")
|
||||
if err != nil {
|
||||
panic("invalid UUID")
|
||||
|
|
@ -1,17 +1,20 @@
|
|||
package blueprint
|
||||
package fedora30
|
||||
|
||||
import "github.com/osbuild/osbuild-composer/internal/pipeline"
|
||||
import (
|
||||
"github.com/osbuild/osbuild-composer/internal/blueprint"
|
||||
"github.com/osbuild/osbuild-composer/internal/pipeline"
|
||||
)
|
||||
|
||||
type liveIsoOutput struct{}
|
||||
|
||||
func (t *liveIsoOutput) translate(b *Blueprint) (*pipeline.Pipeline, error) {
|
||||
func (t *liveIsoOutput) translate(b *blueprint.Blueprint) (*pipeline.Pipeline, error) {
|
||||
// TODO!
|
||||
p := getF30Pipeline()
|
||||
addF30SELinuxStage(p)
|
||||
addF30QemuAssembler(p, "raw", t.getName())
|
||||
|
||||
if b.Customizations != nil {
|
||||
err := b.Customizations.customizeAll(p)
|
||||
err := customizeAll(p, b.Customizations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
package blueprint
|
||||
package fedora30
|
||||
|
||||
import "github.com/osbuild/osbuild-composer/internal/pipeline"
|
||||
import (
|
||||
"github.com/osbuild/osbuild-composer/internal/blueprint"
|
||||
"github.com/osbuild/osbuild-composer/internal/pipeline"
|
||||
)
|
||||
|
||||
type openstackOutput struct{}
|
||||
|
||||
func (t *openstackOutput) translate(b *Blueprint) (*pipeline.Pipeline, error) {
|
||||
func (t *openstackOutput) translate(b *blueprint.Blueprint) (*pipeline.Pipeline, error) {
|
||||
packages := [...]string{
|
||||
"@Core",
|
||||
"chrony",
|
||||
|
|
@ -24,13 +27,13 @@ func (t *openstackOutput) translate(b *Blueprint) (*pipeline.Pipeline, error) {
|
|||
p := getCustomF30PackageSet(packages[:], excludedPackages[:], b)
|
||||
addF30LocaleStage(p)
|
||||
addF30FSTabStage(p)
|
||||
addF30GRUB2Stage(p, b.getKernelCustomization())
|
||||
addF30GRUB2Stage(p, b.GetKernelCustomization())
|
||||
addF30FixBlsStage(p)
|
||||
addF30SELinuxStage(p)
|
||||
addF30QemuAssembler(p, "qcow2", t.getName())
|
||||
|
||||
if b.Customizations != nil {
|
||||
err := b.Customizations.customizeAll(p)
|
||||
err := customizeAll(p, b.Customizations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
59
internal/distro/fedora30/os.go
Normal file
59
internal/distro/fedora30/os.go
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
package fedora30
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/osbuild/osbuild-composer/internal/blueprint"
|
||||
"github.com/osbuild/osbuild-composer/internal/distro"
|
||||
"github.com/osbuild/osbuild-composer/internal/pipeline"
|
||||
)
|
||||
|
||||
type Fedora30 struct {
|
||||
outputs map[string]output
|
||||
}
|
||||
|
||||
type output interface {
|
||||
translate(b *blueprint.Blueprint) (*pipeline.Pipeline, error)
|
||||
getName() string
|
||||
getMime() string
|
||||
}
|
||||
|
||||
func init() {
|
||||
distro.Register("fedora-30", &Fedora30{
|
||||
outputs: map[string]output {
|
||||
"ami": &amiOutput{},
|
||||
"ext4-filesystem": &ext4Output{},
|
||||
"live-iso": &liveIsoOutput{},
|
||||
"partitioned-disk": &diskOutput{},
|
||||
"qcow2": &qcow2Output{},
|
||||
"openstack": &openstackOutput{},
|
||||
"tar": &tarOutput{},
|
||||
"vhd": &vhdOutput{},
|
||||
"vmdk": &vmdkOutput{},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ListOutputFormats returns a sorted list of the supported output formats
|
||||
func (f *Fedora30) ListOutputFormats() []string {
|
||||
formats := make([]string, 0, len(f.outputs))
|
||||
for name := range f.outputs {
|
||||
formats = append(formats, name)
|
||||
}
|
||||
sort.Strings(formats)
|
||||
return formats
|
||||
}
|
||||
|
||||
func (f *Fedora30) FilenameFromType(outputFormat string) (string, string, error) {
|
||||
if output, exists := f.outputs[outputFormat]; exists {
|
||||
return output.getName(), output.getMime(), nil
|
||||
}
|
||||
return "", "", &distro.InvalidOutputFormatError{outputFormat}
|
||||
}
|
||||
|
||||
func (f *Fedora30) Pipeline(b *blueprint.Blueprint, outputFormat string) (*pipeline.Pipeline, error) {
|
||||
if output, exists := f.outputs[outputFormat]; exists {
|
||||
return output.translate(b)
|
||||
}
|
||||
return nil, &distro.InvalidOutputFormatError{outputFormat}
|
||||
}
|
||||
119
internal/distro/fedora30/os_test.go
Normal file
119
internal/distro/fedora30/os_test.go
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
package fedora30_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/osbuild/osbuild-composer/internal/distro"
|
||||
_ "github.com/osbuild/osbuild-composer/internal/distro/fedora30"
|
||||
)
|
||||
|
||||
func TestListOutputFormats(t *testing.T) {
|
||||
want := []string{
|
||||
"ami",
|
||||
"ext4-filesystem",
|
||||
"live-iso",
|
||||
"openstack",
|
||||
"partitioned-disk",
|
||||
"qcow2",
|
||||
"tar",
|
||||
"vhd",
|
||||
"vmdk",
|
||||
}
|
||||
|
||||
f30 := distro.New("fedora-30")
|
||||
if got := f30.ListOutputFormats(); !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("ListOutputFormats() = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilenameFromType(t *testing.T) {
|
||||
type args struct {
|
||||
outputFormat string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
want1 string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "ami",
|
||||
args: args{"ami"},
|
||||
want: "image.ami",
|
||||
want1: "application/x-qemu-disk",
|
||||
},
|
||||
{
|
||||
name: "ext4",
|
||||
args: args{"ext4-filesystem"},
|
||||
want: "filesystem.img",
|
||||
want1: "application/octet-stream",
|
||||
},
|
||||
{
|
||||
name: "live-iso",
|
||||
args: args{"live-iso"},
|
||||
want: "image.iso",
|
||||
want1: "application/x-iso9660-image",
|
||||
},
|
||||
{
|
||||
name: "openstack",
|
||||
args: args{"openstack"},
|
||||
want: "image.qcow2",
|
||||
want1: "application/x-qemu-disk",
|
||||
},
|
||||
{
|
||||
name: "partitioned-disk",
|
||||
args: args{"partitioned-disk"},
|
||||
want: "disk.img",
|
||||
want1: "application/octet-stream",
|
||||
},
|
||||
{
|
||||
name: "qcow2",
|
||||
args: args{"qcow2"},
|
||||
want: "image.qcow2",
|
||||
want1: "application/x-qemu-disk",
|
||||
},
|
||||
{
|
||||
name: "tar",
|
||||
args: args{"tar"},
|
||||
want: "root.tar.xz",
|
||||
want1: "application/x-tar",
|
||||
},
|
||||
{
|
||||
name: "vhd",
|
||||
args: args{"vhd"},
|
||||
want: "image.vhd",
|
||||
want1: "application/x-vhd",
|
||||
},
|
||||
{
|
||||
name: "vmdk",
|
||||
args: args{"vmdk"},
|
||||
want: "disk.vmdk",
|
||||
want1: "application/x-vmdk",
|
||||
},
|
||||
{
|
||||
name: "invalid-output-type",
|
||||
args: args{"foobar"},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
f30 := distro.New("fedora-30")
|
||||
got, got1, err := f30.FilenameFromType(tt.args.outputFormat)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("FilenameFromType() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !tt.wantErr {
|
||||
if got != tt.want {
|
||||
t.Errorf("FilenameFromType() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
if got1 != tt.want1 {
|
||||
t.Errorf("FilenameFromType() got1 = %v, want %v", got1, tt.want1)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
package blueprint
|
||||
package fedora30
|
||||
|
||||
import "github.com/osbuild/osbuild-composer/internal/pipeline"
|
||||
import (
|
||||
"github.com/osbuild/osbuild-composer/internal/blueprint"
|
||||
"github.com/osbuild/osbuild-composer/internal/pipeline"
|
||||
)
|
||||
|
||||
type qcow2Output struct{}
|
||||
|
||||
func (t *qcow2Output) translate(b *Blueprint) (*pipeline.Pipeline, error) {
|
||||
func (t *qcow2Output) translate(b *blueprint.Blueprint) (*pipeline.Pipeline, error) {
|
||||
packages := [...]string{
|
||||
"kernel-core",
|
||||
"@Fedora Cloud Server",
|
||||
|
|
@ -25,13 +28,13 @@ func (t *qcow2Output) translate(b *Blueprint) (*pipeline.Pipeline, error) {
|
|||
p := getCustomF30PackageSet(packages[:], excludedPackages[:], b)
|
||||
addF30LocaleStage(p)
|
||||
addF30FSTabStage(p)
|
||||
addF30GRUB2Stage(p, b.getKernelCustomization())
|
||||
addF30GRUB2Stage(p, b.GetKernelCustomization())
|
||||
addF30FixBlsStage(p)
|
||||
addF30SELinuxStage(p)
|
||||
addF30QemuAssembler(p, "qcow2", t.getName())
|
||||
|
||||
if b.Customizations != nil {
|
||||
err := b.Customizations.customizeAll(p)
|
||||
err := customizeAll(p, b.Customizations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
package blueprint
|
||||
package fedora30
|
||||
|
||||
import "github.com/osbuild/osbuild-composer/internal/pipeline"
|
||||
import (
|
||||
"github.com/osbuild/osbuild-composer/internal/blueprint"
|
||||
"github.com/osbuild/osbuild-composer/internal/pipeline"
|
||||
)
|
||||
|
||||
type tarOutput struct{}
|
||||
|
||||
func (t *tarOutput) translate(b *Blueprint) (*pipeline.Pipeline, error) {
|
||||
func (t *tarOutput) translate(b *blueprint.Blueprint) (*pipeline.Pipeline, error) {
|
||||
packages := [...]string{
|
||||
"policycoreutils",
|
||||
"selinux-policy-targeted",
|
||||
|
|
@ -18,13 +21,13 @@ func (t *tarOutput) translate(b *Blueprint) (*pipeline.Pipeline, error) {
|
|||
}
|
||||
p := getCustomF30PackageSet(packages[:], excludedPackages[:], b)
|
||||
addF30LocaleStage(p)
|
||||
addF30GRUB2Stage(p, b.getKernelCustomization())
|
||||
addF30GRUB2Stage(p, b.GetKernelCustomization())
|
||||
addF30FixBlsStage(p)
|
||||
addF30SELinuxStage(p)
|
||||
addF30TarAssembler(p, t.getName(), "xz")
|
||||
|
||||
if b.Customizations != nil {
|
||||
err := b.Customizations.customizeAll(p)
|
||||
err := customizeAll(p, b.Customizations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
package blueprint
|
||||
package fedora30
|
||||
|
||||
import "github.com/osbuild/osbuild-composer/internal/pipeline"
|
||||
import (
|
||||
"github.com/osbuild/osbuild-composer/internal/blueprint"
|
||||
"github.com/osbuild/osbuild-composer/internal/pipeline"
|
||||
)
|
||||
|
||||
type vhdOutput struct{}
|
||||
|
||||
func (t *vhdOutput) translate(b *Blueprint) (*pipeline.Pipeline, error) {
|
||||
func (t *vhdOutput) translate(b *blueprint.Blueprint) (*pipeline.Pipeline, error) {
|
||||
packages := [...]string{
|
||||
"@Core",
|
||||
"chrony",
|
||||
|
|
@ -23,13 +26,13 @@ func (t *vhdOutput) translate(b *Blueprint) (*pipeline.Pipeline, error) {
|
|||
p := getCustomF30PackageSet(packages[:], excludedPackages[:], b)
|
||||
addF30LocaleStage(p)
|
||||
addF30FSTabStage(p)
|
||||
addF30GRUB2Stage(p, b.getKernelCustomization())
|
||||
addF30GRUB2Stage(p, b.GetKernelCustomization())
|
||||
addF30FixBlsStage(p)
|
||||
addF30SELinuxStage(p)
|
||||
addF30QemuAssembler(p, "qcow2", t.getName())
|
||||
|
||||
if b.Customizations != nil {
|
||||
err := b.Customizations.customizeAll(p)
|
||||
err := customizeAll(p, b.Customizations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
package blueprint
|
||||
package fedora30
|
||||
|
||||
import "github.com/osbuild/osbuild-composer/internal/pipeline"
|
||||
import (
|
||||
"github.com/osbuild/osbuild-composer/internal/blueprint"
|
||||
"github.com/osbuild/osbuild-composer/internal/pipeline"
|
||||
)
|
||||
|
||||
type vmdkOutput struct{}
|
||||
|
||||
func (t *vmdkOutput) translate(b *Blueprint) (*pipeline.Pipeline, error) {
|
||||
func (t *vmdkOutput) translate(b *blueprint.Blueprint) (*pipeline.Pipeline, error) {
|
||||
packages := [...]string{
|
||||
"@core",
|
||||
"chrony",
|
||||
|
|
@ -21,13 +24,13 @@ func (t *vmdkOutput) translate(b *Blueprint) (*pipeline.Pipeline, error) {
|
|||
p := getCustomF30PackageSet(packages[:], excludedPackages[:], b)
|
||||
addF30LocaleStage(p)
|
||||
addF30FSTabStage(p)
|
||||
addF30GRUB2Stage(p, b.getKernelCustomization())
|
||||
addF30GRUB2Stage(p, b.GetKernelCustomization())
|
||||
addF30FixBlsStage(p)
|
||||
addF30SELinuxStage(p)
|
||||
addF30QemuAssembler(p, "vmdk", t.getName())
|
||||
|
||||
if b.Customizations != nil {
|
||||
err := b.Customizations.customizeAll(p)
|
||||
err := customizeAll(p, b.Customizations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -16,6 +16,9 @@ import (
|
|||
"github.com/osbuild/osbuild-composer/internal/pipeline"
|
||||
"github.com/osbuild/osbuild-composer/internal/target"
|
||||
|
||||
"github.com/osbuild/osbuild-composer/internal/distro"
|
||||
_ "github.com/osbuild/osbuild-composer/internal/distro/fedora30"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
|
|
@ -393,7 +396,8 @@ func (s *Store) PushCompose(composeID uuid.UUID, bp *blueprint.Blueprint, compos
|
|||
targets := []*target.Target{
|
||||
target.NewLocalTarget(target.NewLocalTargetOptions("/var/lib/osbuild-composer/outputs/" + composeID.String())),
|
||||
}
|
||||
pipeline, err := bp.ToPipeline(composeType)
|
||||
f30 := distro.New("fedora-30")
|
||||
pipeline, err := f30.Pipeline(bp, composeType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -471,7 +475,8 @@ func (s *Store) GetImage(composeID uuid.UUID) (*Image, error) {
|
|||
if compose.QueueStatus != "FINISHED" {
|
||||
return nil, &InvalidRequestError{"compose was not finished"}
|
||||
}
|
||||
name, mime, err := blueprint.FilenameFromType(compose.OutputType)
|
||||
f30 := distro.New("fedora-30")
|
||||
name, mime, err := f30.FilenameFromType(compose.OutputType)
|
||||
if err != nil {
|
||||
panic("invalid output type")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ import (
|
|||
"github.com/osbuild/osbuild-composer/internal/blueprint"
|
||||
"github.com/osbuild/osbuild-composer/internal/rpmmd"
|
||||
"github.com/osbuild/osbuild-composer/internal/store"
|
||||
|
||||
"github.com/osbuild/osbuild-composer/internal/distro"
|
||||
_ "github.com/osbuild/osbuild-composer/internal/distro/fedora30"
|
||||
)
|
||||
|
||||
type API struct {
|
||||
|
|
@ -847,7 +850,8 @@ func (api *API) composeTypesHandler(writer http.ResponseWriter, request *http.Re
|
|||
Types []composeType `json:"types"`
|
||||
}
|
||||
|
||||
for _, format := range blueprint.ListOutputFormats() {
|
||||
f30 := distro.New("fedora-30")
|
||||
for _, format := range f30.ListOutputFormats() {
|
||||
reply.Types = append(reply.Types, composeType{format, true})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"compose": {
|
||||
"distro": "fedora-30",
|
||||
"filename": "image.ami",
|
||||
"output-format": "ami",
|
||||
"blueprint": {}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"compose": {
|
||||
"distro": "fedora-30",
|
||||
"output-format": "partitioned-disk",
|
||||
"filename": "disk.img",
|
||||
"blueprint": {}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"compose": {
|
||||
"distro": "fedora-30",
|
||||
"output-format": "ext4-filesystem",
|
||||
"filename": "filesystem.img",
|
||||
"blueprint": {}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"compose": {
|
||||
"distro": "fedora-30",
|
||||
"output-format": "openstack",
|
||||
"filename": "image.qcow2",
|
||||
"blueprint": {}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"compose": {
|
||||
"distro": "fedora-30",
|
||||
"output-format": "qcow2",
|
||||
"filename": "image.qcow2",
|
||||
"blueprint": {}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"compose": {
|
||||
"distro": "fedora-30",
|
||||
"output-format": "vhd",
|
||||
"filename": "image.vhd",
|
||||
"blueprint": {}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"compose": {
|
||||
"distro": "fedora-30",
|
||||
"output-format": "vmdk",
|
||||
"filename": "disk.vmdk",
|
||||
"blueprint": {}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue