debian-forge-composer/internal/cloudapi/v2/compose.go
Gianluca Zuccarelli 2da3a73308 cloudapi: json tailoring options
Add support for json tailoring files in the cloudapi. Expand the tests
to check that the options returned are okay and that tailoring options
and json tailoring options can't be provided at the same time.
2024-08-07 12:06:28 +02:00

1130 lines
34 KiB
Go

package v2
// ComposeRequest methods to make it easier to use and test
import (
"crypto/rand"
"fmt"
"math"
"math/big"
"reflect"
"github.com/osbuild/images/pkg/disk"
"github.com/osbuild/images/pkg/distrofactory"
"github.com/osbuild/images/pkg/reporegistry"
"github.com/osbuild/images/pkg/rhsm/facts"
"github.com/osbuild/images/pkg/subscription"
"github.com/osbuild/osbuild-composer/internal/blueprint"
"github.com/osbuild/osbuild-composer/internal/target"
)
// Return the string representation of the partitioning mode
// default to auto-lvm (should never happen)
func (bcpm BlueprintCustomizationsPartitioningMode) String() string {
switch bcpm {
case BlueprintCustomizationsPartitioningModeAutoLvm:
return "auto-lvm"
case BlueprintCustomizationsPartitioningModeLvm:
return "lvm"
case BlueprintCustomizationsPartitioningModeRaw:
return "raw"
default:
return "auto-lvm"
}
}
// GetCustomizationsFromBlueprintRequest populates a blueprint customization struct
// with the data from the blueprint section of a ComposeRequest, which is similar but
// slightly different from the Cloudapi's Customizations section
// This starts with a new empty blueprint.Customization object
// If there are no customizations, it returns nil
func (request *ComposeRequest) GetCustomizationsFromBlueprintRequest() (*blueprint.Customizations, error) {
if request.Blueprint.Customizations == nil {
return nil, nil
}
c := &blueprint.Customizations{}
rbpc := request.Blueprint.Customizations
if rbpc.Hostname != nil {
c.Hostname = rbpc.Hostname
}
if rbpc.Kernel != nil {
kernel := &blueprint.KernelCustomization{}
if rbpc.Kernel.Name != nil {
kernel.Name = *rbpc.Kernel.Name
}
if rbpc.Kernel.Append != nil {
kernel.Append = *rbpc.Kernel.Append
}
c.Kernel = kernel
}
if rbpc.Sshkey != nil {
keys := []blueprint.SSHKeyCustomization{}
for _, key := range *rbpc.Sshkey {
keys = append(keys, blueprint.SSHKeyCustomization{
User: key.User,
Key: key.Key,
})
}
c.SSHKey = keys
}
if rbpc.User != nil {
var userCustomizations []blueprint.UserCustomization
for _, user := range *rbpc.User {
uc := blueprint.UserCustomization{
Name: user.Name,
Description: user.Description,
Password: user.Password,
Key: user.Key,
Home: user.Home,
Shell: user.Shell,
UID: user.Uid,
GID: user.Gid,
}
if user.Groups != nil {
uc.Groups = append(uc.Groups, *user.Groups...)
}
userCustomizations = append(userCustomizations, uc)
}
c.User = userCustomizations
}
if rbpc.Group != nil {
var groupCustomizations []blueprint.GroupCustomization
for _, group := range *rbpc.Group {
gc := blueprint.GroupCustomization{
Name: group.Name,
GID: group.Gid,
}
groupCustomizations = append(groupCustomizations, gc)
}
c.Group = groupCustomizations
}
if rbpc.Timezone != nil {
tz := &blueprint.TimezoneCustomization{
Timezone: rbpc.Timezone.Timezone,
}
if rbpc.Timezone.Ntpservers != nil {
tz.NTPServers = append(tz.NTPServers, *rbpc.Timezone.Ntpservers...)
}
c.Timezone = tz
}
if rbpc.Locale != nil {
locale := &blueprint.LocaleCustomization{
Keyboard: rbpc.Locale.Keyboard,
}
if rbpc.Locale.Languages != nil {
locale.Languages = append(locale.Languages, *rbpc.Locale.Languages...)
}
c.Locale = locale
}
if rbpc.Firewall != nil {
firewall := &blueprint.FirewallCustomization{}
if rbpc.Firewall.Ports != nil {
firewall.Ports = append(firewall.Ports, *rbpc.Firewall.Ports...)
}
if rbpc.Firewall.Services != nil {
enabled := []string{}
if rbpc.Firewall.Services.Enabled != nil {
enabled = append(enabled, *rbpc.Firewall.Services.Enabled...)
}
disabled := []string{}
if rbpc.Firewall.Services.Disabled != nil {
disabled = append(disabled, *rbpc.Firewall.Services.Disabled...)
}
firewall.Services = &blueprint.FirewallServicesCustomization{
Enabled: enabled,
Disabled: disabled,
}
}
if rbpc.Firewall.Zones != nil {
var zones []blueprint.FirewallZoneCustomization
for _, zone := range *rbpc.Firewall.Zones {
zc := blueprint.FirewallZoneCustomization{}
if zone.Name != nil {
zc.Name = zone.Name
}
if zone.Sources != nil {
zc.Sources = append(zc.Sources, *zone.Sources...)
}
zones = append(zones, zc)
}
firewall.Zones = zones
}
c.Firewall = firewall
}
if rbpc.Services != nil {
servicesCustomization := &blueprint.ServicesCustomization{}
if rbpc.Services.Enabled != nil {
servicesCustomization.Enabled = make([]string, len(*rbpc.Services.Enabled))
copy(servicesCustomization.Enabled, *rbpc.Services.Enabled)
}
if rbpc.Services.Disabled != nil {
servicesCustomization.Disabled = make([]string, len(*rbpc.Services.Disabled))
copy(servicesCustomization.Disabled, *rbpc.Services.Disabled)
}
if rbpc.Services.Masked != nil {
servicesCustomization.Masked = make([]string, len(*rbpc.Services.Masked))
copy(servicesCustomization.Masked, *rbpc.Services.Masked)
}
c.Services = servicesCustomization
}
if rbpc.Filesystem != nil {
var fsCustomizations []blueprint.FilesystemCustomization
for _, f := range *rbpc.Filesystem {
fsCustomizations = append(fsCustomizations,
blueprint.FilesystemCustomization{
Mountpoint: f.Mountpoint,
MinSize: f.Minsize,
},
)
}
c.Filesystem = fsCustomizations
}
if rbpc.InstallationDevice != nil {
c.InstallationDevice = *rbpc.InstallationDevice
}
if rbpc.PartitioningMode != nil {
c.PartitioningMode = string(*rbpc.PartitioningMode)
}
if rbpc.Fdo != nil {
fdo := &blueprint.FDOCustomization{}
if rbpc.Fdo.DiunPubKeyHash != nil {
fdo.DiunPubKeyHash = *rbpc.Fdo.DiunPubKeyHash
}
if rbpc.Fdo.DiunPubKeyInsecure != nil {
fdo.DiunPubKeyInsecure = *rbpc.Fdo.DiunPubKeyInsecure
}
if rbpc.Fdo.DiunPubKeyRootCerts != nil {
fdo.DiunPubKeyRootCerts = *rbpc.Fdo.DiunPubKeyRootCerts
}
if rbpc.Fdo.DiMfgStringTypeMacIface != nil {
fdo.DiMfgStringTypeMacIface = *rbpc.Fdo.DiMfgStringTypeMacIface
}
if rbpc.Fdo.ManufacturingServerUrl != nil {
fdo.ManufacturingServerURL = *rbpc.Fdo.ManufacturingServerUrl
}
c.FDO = fdo
}
if rbpc.Openscap != nil {
oscap := &blueprint.OpenSCAPCustomization{
ProfileID: rbpc.Openscap.ProfileId,
}
if rbpc.Openscap.Datastream != nil {
oscap.DataStream = *rbpc.Openscap.Datastream
}
if rbpc.Openscap.Tailoring != nil && rbpc.Openscap.JsonTailoring != nil {
return nil, fmt.Errorf("OpenSCAP customization error: choose one option between OpenSCAP tailoring and OpenSCAP json tailoring")
}
if tailoring := rbpc.Openscap.Tailoring; tailoring != nil {
tc := blueprint.OpenSCAPTailoringCustomizations{}
if tailoring.Selected != nil && len(*tailoring.Selected) > 0 {
tc.Selected = append(tc.Selected, *tailoring.Selected...)
}
if tailoring.Unselected != nil && len(*tailoring.Unselected) > 0 {
tc.Unselected = append(tc.Unselected, *tailoring.Unselected...)
}
oscap.Tailoring = &tc
}
if jsonTailoring := rbpc.Openscap.JsonTailoring; jsonTailoring != nil {
oscap.JSONTailoring = &blueprint.OpenSCAPJSONTailoringCustomizations{
ProfileID: jsonTailoring.ProfileId,
Filepath: jsonTailoring.Filepath,
}
}
c.OpenSCAP = oscap
}
if rbpc.Ignition != nil {
ignition := &blueprint.IgnitionCustomization{}
if rbpc.Ignition.Embedded != nil {
ignition.Embedded = &blueprint.EmbeddedIgnitionCustomization{
Config: rbpc.Ignition.Embedded.Config,
}
}
if rbpc.Ignition.Firstboot != nil {
ignition.FirstBoot = &blueprint.FirstBootIgnitionCustomization{
ProvisioningURL: rbpc.Ignition.Firstboot.Url,
}
}
c.Ignition = ignition
}
if rbpc.Directories != nil {
var dirCustomizations []blueprint.DirectoryCustomization
for _, d := range *rbpc.Directories {
dirCustomization := blueprint.DirectoryCustomization{
Path: d.Path,
}
if d.Mode != nil {
dirCustomization.Mode = *d.Mode
}
if d.User != nil {
dirCustomization.User = *d.User
if uid, ok := dirCustomization.User.(float64); ok {
// check if uid can be converted to int64
if uid != float64(int64(uid)) {
return nil, fmt.Errorf("invalid user %f: must be an integer", uid)
}
dirCustomization.User = int64(uid)
}
}
if d.Group != nil {
dirCustomization.Group = *d.Group
if gid, ok := dirCustomization.Group.(float64); ok {
// check if gid can be converted to int64
if gid != float64(int64(gid)) {
return nil, fmt.Errorf("invalid group %f: must be an integer", gid)
}
dirCustomization.Group = int64(gid)
}
}
if d.EnsureParents != nil {
dirCustomization.EnsureParents = *d.EnsureParents
}
dirCustomizations = append(dirCustomizations, dirCustomization)
}
// Validate the directory customizations, because the Cloud API does not use the custom unmarshaller
_, err := blueprint.DirectoryCustomizationsToFsNodeDirectories(dirCustomizations)
if err != nil {
return nil, HTTPErrorWithInternal(ErrorInvalidCustomization, err)
}
c.Directories = dirCustomizations
}
if rbpc.Files != nil {
var fileCustomizations []blueprint.FileCustomization
for _, f := range *rbpc.Files {
fileCustomization := blueprint.FileCustomization{
Path: f.Path,
}
if f.Data != nil {
fileCustomization.Data = *f.Data
}
if f.Mode != nil {
fileCustomization.Mode = *f.Mode
}
if f.User != nil {
fileCustomization.User = *f.User
if uid, ok := fileCustomization.User.(float64); ok {
// check if uid can be converted to int64
if uid != float64(int64(uid)) {
return nil, fmt.Errorf("invalid user %f: must be an integer", uid)
}
fileCustomization.User = int64(uid)
}
}
if f.Group != nil {
fileCustomization.Group = *f.Group
if gid, ok := fileCustomization.Group.(float64); ok {
// check if gid can be converted to int64
if gid != float64(int64(gid)) {
return nil, fmt.Errorf("invalid group %f: must be an integer", gid)
}
fileCustomization.Group = int64(gid)
}
}
fileCustomizations = append(fileCustomizations, fileCustomization)
}
// Validate the file customizations, because the Cloud API does not use the custom unmarshaller
_, err := blueprint.FileCustomizationsToFsNodeFiles(fileCustomizations)
if err != nil {
return nil, HTTPErrorWithInternal(ErrorInvalidCustomization, err)
}
c.Files = fileCustomizations
}
if rbpc.Repositories != nil {
repoCustomizations := []blueprint.RepositoryCustomization{}
for _, repo := range *rbpc.Repositories {
repoCustomization := blueprint.RepositoryCustomization{
Id: repo.Id,
}
if repo.Name != nil {
repoCustomization.Name = *repo.Name
}
if repo.Filename != nil {
repoCustomization.Filename = *repo.Filename
}
if repo.Baseurls != nil && len(*repo.Baseurls) > 0 {
repoCustomization.BaseURLs = append(repoCustomization.BaseURLs, *repo.Baseurls...)
}
if repo.Gpgkeys != nil && len(*repo.Gpgkeys) > 0 {
repoCustomization.GPGKeys = append(repoCustomization.GPGKeys, *repo.Gpgkeys...)
}
if repo.Gpgcheck != nil {
repoCustomization.GPGCheck = repo.Gpgcheck
}
if repo.RepoGpgcheck != nil {
repoCustomization.RepoGPGCheck = repo.RepoGpgcheck
}
if repo.Enabled != nil {
repoCustomization.Enabled = repo.Enabled
}
if repo.Metalink != nil {
repoCustomization.Metalink = *repo.Metalink
}
if repo.Mirrorlist != nil {
repoCustomization.Mirrorlist = *repo.Mirrorlist
}
if repo.Sslverify != nil {
repoCustomization.SSLVerify = repo.Sslverify
}
if repo.Priority != nil {
repoCustomization.Priority = repo.Priority
}
if repo.ModuleHotfixes != nil {
repoCustomization.ModuleHotfixes = repo.ModuleHotfixes
}
repoCustomizations = append(repoCustomizations, repoCustomization)
}
c.Repositories = repoCustomizations
}
if rbpc.Fips != nil {
c.FIPS = rbpc.Fips
}
if installer := rbpc.Installer; installer != nil {
c.Installer = &blueprint.InstallerCustomization{}
if installer.Unattended != nil {
c.Installer.Unattended = *installer.Unattended
}
if installer.SudoNopasswd != nil {
c.Installer.SudoNopasswd = *installer.SudoNopasswd
}
}
return c, nil
}
// GetBlueprintFromCompose returns a base blueprint
// It is either constructed from the Blueprint passed in with the request, or it
// is an empty blueprint
func (request *ComposeRequest) GetBlueprintFromCompose() (blueprint.Blueprint, error) {
// nil or blank blueprint returns a valid empty blueprint
if request.Blueprint == nil || reflect.DeepEqual(*request.Blueprint, Blueprint{}) {
bp := blueprint.Blueprint{Name: "empty blueprint"}
err := bp.Initialize()
return bp, err
}
var bp blueprint.Blueprint
rbp := request.Blueprint
// Copy all the parts from the OpenAPI Blueprint into a blueprint.Blueprint
// NOTE: Openapi fields may be nil, test for that first.
bp.Name = rbp.Name
if rbp.Description != nil {
bp.Description = *rbp.Description
}
if rbp.Version != nil {
bp.Version = *rbp.Version
}
if rbp.Distro != nil {
bp.Distro = *rbp.Distro
}
if rbp.Packages != nil {
for _, pkg := range *rbp.Packages {
newPkg := blueprint.Package{Name: pkg.Name}
if pkg.Version != nil {
newPkg.Version = *pkg.Version
}
bp.Packages = append(bp.Packages, newPkg)
}
}
if rbp.Modules != nil {
for _, pkg := range *rbp.Modules {
newPkg := blueprint.Package{Name: pkg.Name}
if pkg.Version != nil {
newPkg.Version = *pkg.Version
}
bp.Modules = append(bp.Modules, newPkg)
}
}
if rbp.Groups != nil {
for _, group := range *rbp.Groups {
bp.Groups = append(bp.Groups, blueprint.Group{
Name: group.Name,
})
}
}
if rbp.Containers != nil {
for _, c := range *rbp.Containers {
newC := blueprint.Container{Source: c.Source, TLSVerify: c.TlsVerify}
if c.Name != nil {
newC.Name = *c.Name
}
bp.Containers = append(bp.Containers, newC)
}
}
customizations, err := request.GetCustomizationsFromBlueprintRequest()
if err != nil {
return bp, err
}
bp.Customizations = customizations
err = bp.Initialize()
if err != nil {
return bp, HTTPErrorWithInternal(ErrorFailedToInitializeBlueprint, err)
}
return bp, nil
}
// GetBlueprintFromCustomizations returns a new Blueprint with all of the
// customizations set from the ComposeRequest.Customizations
func (request *ComposeRequest) GetBlueprintFromCustomizations() (blueprint.Blueprint, error) {
bp := blueprint.Blueprint{Name: "empty blueprint"}
err := bp.Initialize()
if err != nil {
return bp, HTTPErrorWithInternal(ErrorFailedToInitializeBlueprint, err)
}
if request.Customizations == nil {
return bp, nil
}
bp.Customizations = &blueprint.Customizations{}
// Set the blueprint customisation to take care of the user
if request.Customizations.Users != nil {
var userCustomizations []blueprint.UserCustomization
for _, user := range *request.Customizations.Users {
var groups []string
if user.Groups != nil {
groups = *user.Groups
} else {
groups = nil
}
userCustomizations = append(userCustomizations,
blueprint.UserCustomization{
Name: user.Name,
Key: user.Key,
Password: user.Password,
Groups: groups,
},
)
}
bp.Customizations.User = userCustomizations
}
if request.Customizations.Packages != nil {
for _, p := range *request.Customizations.Packages {
bp.Packages = append(bp.Packages, blueprint.Package{
Name: p,
})
}
}
if request.Customizations.Containers != nil {
for _, c := range *request.Customizations.Containers {
bc := blueprint.Container{
Source: c.Source,
TLSVerify: c.TlsVerify,
}
if c.Name != nil {
bc.Name = *c.Name
}
bp.Containers = append(bp.Containers, bc)
}
}
if request.Customizations.Directories != nil {
var dirCustomizations []blueprint.DirectoryCustomization
for _, d := range *request.Customizations.Directories {
dirCustomization := blueprint.DirectoryCustomization{
Path: d.Path,
}
if d.Mode != nil {
dirCustomization.Mode = *d.Mode
}
if d.User != nil {
dirCustomization.User = *d.User
if uid, ok := dirCustomization.User.(float64); ok {
// check if uid can be converted to int64
if uid != float64(int64(uid)) {
return bp, fmt.Errorf("invalid user %f: must be an integer", uid)
}
dirCustomization.User = int64(uid)
}
}
if d.Group != nil {
dirCustomization.Group = *d.Group
if gid, ok := dirCustomization.Group.(float64); ok {
// check if gid can be converted to int64
if gid != float64(int64(gid)) {
return bp, fmt.Errorf("invalid group %f: must be an integer", gid)
}
dirCustomization.Group = int64(gid)
}
}
if d.EnsureParents != nil {
dirCustomization.EnsureParents = *d.EnsureParents
}
dirCustomizations = append(dirCustomizations, dirCustomization)
}
// Validate the directory customizations, because the Cloud API does not use the custom unmarshaller
_, err := blueprint.DirectoryCustomizationsToFsNodeDirectories(dirCustomizations)
if err != nil {
return bp, HTTPErrorWithInternal(ErrorInvalidCustomization, err)
}
bp.Customizations.Directories = dirCustomizations
}
if request.Customizations.Files != nil {
var fileCustomizations []blueprint.FileCustomization
for _, f := range *request.Customizations.Files {
fileCustomization := blueprint.FileCustomization{
Path: f.Path,
}
if f.Data != nil {
fileCustomization.Data = *f.Data
}
if f.Mode != nil {
fileCustomization.Mode = *f.Mode
}
if f.User != nil {
fileCustomization.User = *f.User
if uid, ok := fileCustomization.User.(float64); ok {
// check if uid can be converted to int64
if uid != float64(int64(uid)) {
return bp, fmt.Errorf("invalid user %f: must be an integer", uid)
}
fileCustomization.User = int64(uid)
}
}
if f.Group != nil {
fileCustomization.Group = *f.Group
if gid, ok := fileCustomization.Group.(float64); ok {
// check if gid can be converted to int64
if gid != float64(int64(gid)) {
return bp, fmt.Errorf("invalid group %f: must be an integer", gid)
}
fileCustomization.Group = int64(gid)
}
}
fileCustomizations = append(fileCustomizations, fileCustomization)
}
// Validate the file customizations, because the Cloud API does not use the custom unmarshaller
_, err := blueprint.FileCustomizationsToFsNodeFiles(fileCustomizations)
if err != nil {
return bp, HTTPErrorWithInternal(ErrorInvalidCustomization, err)
}
bp.Customizations.Files = fileCustomizations
}
if request.Customizations.Filesystem != nil {
var fsCustomizations []blueprint.FilesystemCustomization
for _, f := range *request.Customizations.Filesystem {
fsCustomizations = append(fsCustomizations,
blueprint.FilesystemCustomization{
Mountpoint: f.Mountpoint,
MinSize: f.MinSize,
},
)
}
bp.Customizations.Filesystem = fsCustomizations
}
if request.Customizations.Services != nil {
servicesCustomization := &blueprint.ServicesCustomization{}
if request.Customizations.Services.Enabled != nil {
servicesCustomization.Enabled = make([]string, len(*request.Customizations.Services.Enabled))
copy(servicesCustomization.Enabled, *request.Customizations.Services.Enabled)
}
if request.Customizations.Services.Disabled != nil {
servicesCustomization.Disabled = make([]string, len(*request.Customizations.Services.Disabled))
copy(servicesCustomization.Disabled, *request.Customizations.Services.Disabled)
}
if request.Customizations.Services.Masked != nil {
servicesCustomization.Masked = make([]string, len(*request.Customizations.Services.Masked))
copy(servicesCustomization.Masked, *request.Customizations.Services.Masked)
}
bp.Customizations.Services = servicesCustomization
}
if request.Customizations.Openscap != nil {
openSCAPCustomization := &blueprint.OpenSCAPCustomization{
ProfileID: request.Customizations.Openscap.ProfileId,
}
if request.Customizations.Openscap.Tailoring != nil && request.Customizations.Openscap.JsonTailoring != nil {
return bp, fmt.Errorf("OpenSCAP customization error: choose one option between OpenSCAP tailoring and OpenSCAP json tailoring")
}
if tailoring := request.Customizations.Openscap.Tailoring; tailoring != nil {
tailoringCustomizations := blueprint.OpenSCAPTailoringCustomizations{}
if tailoring.Selected != nil && len(*tailoring.Selected) > 0 {
tailoringCustomizations.Selected = *tailoring.Selected
}
if tailoring.Unselected != nil && len(*tailoring.Unselected) > 0 {
tailoringCustomizations.Unselected = *tailoring.Unselected
}
openSCAPCustomization.Tailoring = &tailoringCustomizations
}
if jsonTailoring := request.Customizations.Openscap.JsonTailoring; jsonTailoring != nil {
openSCAPCustomization.JSONTailoring = &blueprint.OpenSCAPJSONTailoringCustomizations{
ProfileID: jsonTailoring.ProfileId,
Filepath: jsonTailoring.Filepath,
}
}
bp.Customizations.OpenSCAP = openSCAPCustomization
}
if request.Customizations.CustomRepositories != nil {
repoCustomizations := []blueprint.RepositoryCustomization{}
for _, repo := range *request.Customizations.CustomRepositories {
repoCustomization := blueprint.RepositoryCustomization{
Id: repo.Id,
}
if repo.Name != nil {
repoCustomization.Name = *repo.Name
}
if repo.Filename != nil {
repoCustomization.Filename = *repo.Filename
}
if repo.Baseurl != nil && len(*repo.Baseurl) > 0 {
repoCustomization.BaseURLs = *repo.Baseurl
}
if repo.Gpgkey != nil && len(*repo.Gpgkey) > 0 {
repoCustomization.GPGKeys = *repo.Gpgkey
}
if repo.CheckGpg != nil {
repoCustomization.GPGCheck = repo.CheckGpg
}
if repo.CheckRepoGpg != nil {
repoCustomization.RepoGPGCheck = repo.CheckRepoGpg
}
if repo.Enabled != nil {
repoCustomization.Enabled = repo.Enabled
}
if repo.Metalink != nil {
repoCustomization.Metalink = *repo.Metalink
}
if repo.Mirrorlist != nil {
repoCustomization.Mirrorlist = *repo.Mirrorlist
}
if repo.SslVerify != nil {
repoCustomization.SSLVerify = repo.SslVerify
}
if repo.Priority != nil {
repoCustomization.Priority = repo.Priority
}
if repo.ModuleHotfixes != nil {
repoCustomization.ModuleHotfixes = repo.ModuleHotfixes
}
repoCustomizations = append(repoCustomizations, repoCustomization)
}
bp.Customizations.Repositories = repoCustomizations
}
if request.Customizations.Hostname != nil {
bp.Customizations.Hostname = request.Customizations.Hostname
}
if request.Customizations.Kernel != nil {
kernel := &blueprint.KernelCustomization{}
if request.Customizations.Kernel.Name != nil {
kernel.Name = *request.Customizations.Kernel.Name
}
if request.Customizations.Kernel.Append != nil {
kernel.Append = *request.Customizations.Kernel.Append
}
bp.Customizations.Kernel = kernel
}
if request.Customizations.Groups != nil {
groups := []blueprint.GroupCustomization{}
for _, group := range *request.Customizations.Groups {
groups = append(groups, blueprint.GroupCustomization{
Name: group.Name,
GID: group.Gid,
})
}
bp.Customizations.Group = groups
}
if request.Customizations.Timezone != nil {
tz := &blueprint.TimezoneCustomization{
Timezone: request.Customizations.Timezone.Timezone,
}
if request.Customizations.Timezone.Ntpservers != nil {
tz.NTPServers = append(tz.NTPServers, *request.Customizations.Timezone.Ntpservers...)
}
bp.Customizations.Timezone = tz
}
if request.Customizations.Locale != nil {
locale := &blueprint.LocaleCustomization{
Keyboard: request.Customizations.Locale.Keyboard,
}
if request.Customizations.Locale.Languages != nil {
locale.Languages = append(locale.Languages, *request.Customizations.Locale.Languages...)
}
bp.Customizations.Locale = locale
}
if request.Customizations.Firewall != nil {
firewall := &blueprint.FirewallCustomization{}
if request.Customizations.Firewall.Ports != nil {
firewall.Ports = append(firewall.Ports, *request.Customizations.Firewall.Ports...)
}
if request.Customizations.Firewall.Services != nil {
enabled := []string{}
if request.Customizations.Firewall.Services.Enabled != nil {
enabled = append(enabled, *request.Customizations.Firewall.Services.Enabled...)
}
disabled := []string{}
if request.Customizations.Firewall.Services.Disabled != nil {
disabled = append(disabled, *request.Customizations.Firewall.Services.Disabled...)
}
firewall.Services = &blueprint.FirewallServicesCustomization{
Enabled: enabled,
Disabled: disabled,
}
}
bp.Customizations.Firewall = firewall
}
if request.Customizations.InstallationDevice != nil {
if bp.Customizations == nil {
bp.Customizations = &blueprint.Customizations{
InstallationDevice: *request.Customizations.InstallationDevice,
}
} else {
bp.Customizations.InstallationDevice = *request.Customizations.InstallationDevice
}
}
if request.Customizations.Fdo != nil {
fdo := &blueprint.FDOCustomization{}
if request.Customizations.Fdo.DiunPubKeyHash != nil {
fdo.DiunPubKeyHash = *request.Customizations.Fdo.DiunPubKeyHash
}
if request.Customizations.Fdo.DiunPubKeyInsecure != nil {
fdo.DiunPubKeyInsecure = *request.Customizations.Fdo.DiunPubKeyInsecure
}
if request.Customizations.Fdo.DiunPubKeyRootCerts != nil {
fdo.DiunPubKeyRootCerts = *request.Customizations.Fdo.DiunPubKeyRootCerts
}
if request.Customizations.Fdo.ManufacturingServerUrl != nil {
fdo.ManufacturingServerURL = *request.Customizations.Fdo.ManufacturingServerUrl
}
if request.Customizations.Fdo.DiMfgStringTypeMacIface != nil {
fdo.DiMfgStringTypeMacIface = *request.Customizations.Fdo.DiMfgStringTypeMacIface
}
bp.Customizations.FDO = fdo
}
if request.Customizations.Ignition != nil {
ignition := &blueprint.IgnitionCustomization{}
if request.Customizations.Ignition.Embedded != nil {
ignition.Embedded = &blueprint.EmbeddedIgnitionCustomization{
Config: request.Customizations.Ignition.Embedded.Config,
}
}
if request.Customizations.Ignition.Firstboot != nil {
ignition.FirstBoot = &blueprint.FirstBootIgnitionCustomization{
ProvisioningURL: request.Customizations.Ignition.Firstboot.Url,
}
}
bp.Customizations.Ignition = ignition
}
if request.Customizations.Fips != nil {
bp.Customizations.FIPS = request.Customizations.Fips.Enabled
}
if request.Customizations.Installer != nil {
installer := &blueprint.InstallerCustomization{}
if request.Customizations.Installer.Unattended != nil {
installer.Unattended = *request.Customizations.Installer.Unattended
}
if request.Customizations.Installer.SudoNopasswd != nil {
installer.SudoNopasswd = *request.Customizations.Installer.SudoNopasswd
}
bp.Customizations.Installer = installer
}
// Did bp.Customizations get set at all? If not, set it back to nil
if reflect.DeepEqual(*bp.Customizations, blueprint.Customizations{}) {
bp.Customizations = nil
}
err = bp.CryptPasswords()
if err != nil {
return bp, fmt.Errorf("Error hashing passwords: %s", err.Error())
}
return bp, nil
}
// GetBlueprint returns a blueprint
// If the compose request includes a blueprint, return it, otherwise if it has
// customizations create a blueprint with those customizations. If it has neither
// return an empty blueprint.
func (request *ComposeRequest) GetBlueprint() (blueprint.Blueprint, error) {
if request.Blueprint != nil {
return request.GetBlueprintFromCompose()
}
return request.GetBlueprintFromCustomizations()
}
// GetPayloadRepositories returns the custom repos
// If there are none it returns a nil slice
func (request *ComposeRequest) GetPayloadRepositories() (repos []Repository) {
if request.Customizations != nil && request.Customizations.PayloadRepositories != nil {
repos = *request.Customizations.PayloadRepositories
}
return
}
// GetSubscription returns an ImageOptions struct populated by the subscription information
// included in the request, or nil if it has not been included.
func (request *ComposeRequest) GetSubscription() (sub *subscription.ImageOptions) {
if request.Customizations != nil && request.Customizations.Subscription != nil {
// Rhc is optional, default to false if not included
var rhc bool
if request.Customizations.Subscription.Rhc != nil {
rhc = *request.Customizations.Subscription.Rhc
}
sub = &subscription.ImageOptions{
Organization: request.Customizations.Subscription.Organization,
ActivationKey: request.Customizations.Subscription.ActivationKey,
ServerUrl: request.Customizations.Subscription.ServerUrl,
BaseUrl: request.Customizations.Subscription.BaseUrl,
Insights: request.Customizations.Subscription.Insights,
Rhc: rhc,
}
}
return
}
// GetPartitioningMode returns the partitioning mode included in the request
// or defaults to AutoLVMPartitioningMode if not included
func (request *ComposeRequest) GetPartitioningMode() (disk.PartitioningMode, error) {
if request.Customizations == nil || request.Customizations.PartitioningMode == nil {
return disk.AutoLVMPartitioningMode, nil
}
switch *request.Customizations.PartitioningMode {
case CustomizationsPartitioningModeRaw:
return disk.RawPartitioningMode, nil
case CustomizationsPartitioningModeLvm:
return disk.LVMPartitioningMode, nil
case CustomizationsPartitioningModeAutoLvm:
return disk.AutoLVMPartitioningMode, nil
}
return disk.AutoLVMPartitioningMode, HTTPError(ErrorInvalidPartitioningMode)
}
// GetImageRequests converts a composeRequest structure from the API to an intermediate imageRequest structure
// that's used for generating manifests and orchestrating worker jobs.
func (request *ComposeRequest) GetImageRequests(distroFactory *distrofactory.Factory, repoRegistry *reporegistry.RepoRegistry) ([]imageRequest, error) {
// OpenAPI enforces blueprint or customization, not both
// but check anyway
if request.Customizations != nil && request.Blueprint != nil {
return nil, HTTPError(ErrorBlueprintOrCustomNotBoth)
}
// Create a blueprint from the request
bp, err := request.GetBlueprint()
if err != nil {
return nil, err
}
// Used when no repositories are included. Must be the original name because it may
// be an alias and you cannot map back from the distrofactory to the original string.
originalDistroName := request.Distribution
// If there is a distribution in the blueprint it overrides the request's distro
if len(bp.Distro) > 0 {
originalDistroName = bp.Distro
}
distribution := distroFactory.GetDistro(originalDistroName)
if distribution == nil {
return nil, HTTPError(ErrorUnsupportedDistribution)
}
// add the user-defined repositories only to the depsolve job for the
// payload (the packages for the final image)
payloadRepositories := request.GetPayloadRepositories()
// use the same seed for all images so we get the same IDs
bigSeed, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
if err != nil {
return nil, HTTPError(ErrorFailedToGenerateManifestSeed)
}
manifestSeed := bigSeed.Int64()
// For backwards compatibility, we support both a single image request
// as well as an array of requests in the API. Exactly one must be
// specified.
if request.ImageRequest != nil {
if request.ImageRequests != nil {
// we should really be using oneOf in the spec
return nil, HTTPError(ErrorInvalidNumberOfImageBuilds)
}
request.ImageRequests = &[]ImageRequest{*request.ImageRequest}
}
if request.ImageRequests == nil {
return nil, HTTPError(ErrorInvalidNumberOfImageBuilds)
}
var irs []imageRequest
for _, ir := range *request.ImageRequests {
arch, err := distribution.GetArch(ir.Architecture)
if err != nil {
return nil, HTTPError(ErrorUnsupportedArchitecture)
}
imageType, err := arch.GetImageType(imageTypeFromApiImageType(ir.ImageType, arch))
if err != nil {
return nil, HTTPError(ErrorUnsupportedImageType)
}
repos, err := convertRepos(ir.Repositories, payloadRepositories, imageType.PayloadPackageSets())
if err != nil {
return nil, err
}
// If no repositories are included with the imageRequest use the defaults for
// the distro selected by the blueprint, or the compose request.
if len(ir.Repositories) == 0 {
dr, err := repoRegistry.ReposByImageTypeName(originalDistroName, arch.Name(), imageType.Name())
if err != nil {
return nil, err
}
repos = append(repos, dr...)
}
// Get the initial ImageOptions with image size set
imageOptions := ir.GetImageOptions(imageType, bp)
if request.Koji == nil {
imageOptions.Facts = &facts.ImageOptions{
APIType: facts.CLOUDV2_APITYPE,
}
}
// Set Subscription from the compose request
imageOptions.Subscription = request.GetSubscription()
// Set PartitioningMode from the compose request
imageOptions.PartitioningMode, err = request.GetPartitioningMode()
if err != nil {
return nil, err
}
// Set OSTree options from the image request
imageOptions.OSTree, err = ir.GetOSTreeOptions()
if err != nil {
return nil, err
}
// Check to see if local_save is enabled and set
localSave, err := isLocalSave(ir.UploadOptions)
if err != nil {
return nil, err
}
var irTargets []*target.Target
if ir.UploadOptions == nil && (ir.UploadTargets == nil || len(*ir.UploadTargets) == 0) {
// nowhere to put the image, this is a user error
if request.Koji == nil {
return nil, HTTPError(ErrorJSONUnMarshallingError)
}
} else if localSave {
// Override the image type upload selection and save it locally
// Final image is in /var/lib/osbuild-composer/artifacts/UUID/
srvTarget := target.NewWorkerServerTarget()
srvTarget.ImageName = imageType.Filename()
srvTarget.OsbuildArtifact.ExportFilename = imageType.Filename()
srvTarget.OsbuildArtifact.ExportName = imageType.Exports()[0]
irTargets = []*target.Target{srvTarget}
} else {
// Get the target for the selected image type
irTargets, err = ir.GetTargets(request, imageType)
if err != nil {
return nil, err
}
}
irs = append(irs, imageRequest{
imageType: imageType,
repositories: repos,
imageOptions: imageOptions,
targets: irTargets,
blueprint: bp,
manifestSeed: manifestSeed,
})
}
return irs, nil
}