This method is not particularly useful anymore. Its purpose was to initialise the ImageOptions from an ImageRequest with the appropriate size and partitioning mode. However, the partitioning mode was also being set later using request.GetPartitioningMode(). More importantly, setting the size on the ImageOptions caused issues with the interaction between filesystem and partitioning customizations as well as the image request size (see #4705). The correct thing to do here is to map the ImageRequest.Size directly onto ImageOptions.Size, without taking into account ImageType or the Blueprint Customizations. The rest are considered when generating the manifest in images, either when preparing the Manifest() call or when generating the partition table. This makes it easier to trace and reason about the effect of each option. This kind of decision making in the API layer makes it difficult to maintain the logic, since it requires duplicating the decision making or, as we had now, making certain specific combinations impossible.
1439 lines
42 KiB
Go
1439 lines
42 KiB
Go
package v2
|
|
|
|
// ComposeRequest methods to make it easier to use and test
|
|
import (
|
|
"crypto/rand"
|
|
"fmt"
|
|
"math"
|
|
"math/big"
|
|
"reflect"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/osbuild/blueprint/pkg/blueprint"
|
|
"github.com/osbuild/images/pkg/customizations/subscription"
|
|
"github.com/osbuild/images/pkg/datasizes"
|
|
"github.com/osbuild/images/pkg/disk"
|
|
"github.com/osbuild/images/pkg/distro"
|
|
"github.com/osbuild/images/pkg/distrofactory"
|
|
"github.com/osbuild/images/pkg/reporegistry"
|
|
"github.com/osbuild/images/pkg/rhsm/facts"
|
|
"github.com/osbuild/osbuild-composer/internal/common"
|
|
"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"
|
|
}
|
|
}
|
|
|
|
// GetCustomizationsFromBlueprint populates a blueprint customization struct
|
|
// with the data from request Blueprint, 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 (rbp *Blueprint) GetCustomizationsFromBlueprintRequest() (*blueprint.Customizations, error) {
|
|
if rbp.Customizations == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
c := &blueprint.Customizations{}
|
|
rbpc := rbp.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 {
|
|
minSize, err := decodeMinsize(&f.Minsize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fsCustomizations = append(fsCustomizations,
|
|
blueprint.FilesystemCustomization{
|
|
Mountpoint: f.Mountpoint,
|
|
MinSize: 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.PolicyId != nil {
|
|
oscap.PolicyID = rbpc.Openscap.PolicyId.String()
|
|
}
|
|
|
|
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 {
|
|
user0, err := d.User.AsDirectoryUser0()
|
|
if err == nil {
|
|
dirCustomization.User = user0
|
|
} else {
|
|
user1, err := d.User.AsDirectoryUser1()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid user: %w", err)
|
|
}
|
|
dirCustomization.User = user1
|
|
}
|
|
}
|
|
if d.Group != nil {
|
|
group0, err := d.Group.AsDirectoryGroup0()
|
|
if err == nil {
|
|
dirCustomization.Group = group0
|
|
} else {
|
|
group1, err := d.Group.AsDirectoryGroup1()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid group: %w", err)
|
|
}
|
|
dirCustomization.Group = group1
|
|
}
|
|
}
|
|
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 {
|
|
user0, err := f.User.AsBlueprintFileUser0()
|
|
if err == nil {
|
|
fileCustomization.User = user0
|
|
} else {
|
|
user1, err := f.User.AsBlueprintFileUser1()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid user: %w", err)
|
|
}
|
|
fileCustomization.User = user1
|
|
}
|
|
}
|
|
if f.Group != nil {
|
|
group0, err := f.Group.AsBlueprintFileGroup0()
|
|
if err == nil {
|
|
fileCustomization.Group = group0
|
|
} else {
|
|
group1, err := f.Group.AsBlueprintFileGroup1()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid group: %w", err)
|
|
}
|
|
if group1 != 0 {
|
|
fileCustomization.Group = group1
|
|
}
|
|
}
|
|
}
|
|
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
|
|
}
|
|
}
|
|
|
|
if rpm := rbpc.Rpm; rpm != nil && rpm.ImportKeys != nil {
|
|
c.RPM = &blueprint.RPMCustomization{
|
|
ImportKeys: &blueprint.RPMImportKeys{
|
|
Files: *rpm.ImportKeys.Files,
|
|
},
|
|
}
|
|
}
|
|
|
|
if rhsm := rbpc.Rhsm; rhsm != nil && rhsm.Config != nil {
|
|
bpRhsm := &blueprint.RHSMCustomization{
|
|
Config: &blueprint.RHSMConfig{},
|
|
}
|
|
|
|
conf := rhsm.Config
|
|
if dnfPlugins := conf.DnfPlugins; dnfPlugins != nil {
|
|
bpRhsm.Config.DNFPlugins = &blueprint.SubManDNFPluginsConfig{}
|
|
if dnfPlugins.ProductId != nil && dnfPlugins.ProductId.Enabled != nil {
|
|
bpRhsm.Config.DNFPlugins.ProductID = &blueprint.DNFPluginConfig{
|
|
Enabled: common.ToPtr(*dnfPlugins.ProductId.Enabled),
|
|
}
|
|
}
|
|
if dnfPlugins.SubscriptionManager != nil && dnfPlugins.SubscriptionManager.Enabled != nil {
|
|
bpRhsm.Config.DNFPlugins.SubscriptionManager = &blueprint.DNFPluginConfig{
|
|
Enabled: common.ToPtr(*dnfPlugins.SubscriptionManager.Enabled),
|
|
}
|
|
}
|
|
}
|
|
|
|
if subMan := conf.SubscriptionManager; subMan != nil {
|
|
bpSubMan := &blueprint.SubManConfig{}
|
|
if subMan.Rhsm != nil {
|
|
bpSubMan.RHSMConfig = &blueprint.SubManRHSMConfig{}
|
|
if subMan.Rhsm.ManageRepos != nil {
|
|
bpSubMan.RHSMConfig.ManageRepos = common.ToPtr(*subMan.Rhsm.ManageRepos)
|
|
}
|
|
if subMan.Rhsm.AutoEnableYumPlugins != nil {
|
|
bpSubMan.RHSMConfig.AutoEnableYumPlugins = common.ToPtr(*subMan.Rhsm.AutoEnableYumPlugins)
|
|
}
|
|
}
|
|
if subMan.Rhsmcertd != nil && subMan.Rhsmcertd.AutoRegistration != nil {
|
|
bpSubMan.RHSMCertdConfig = &blueprint.SubManRHSMCertdConfig{
|
|
AutoRegistration: common.ToPtr(*subMan.Rhsmcertd.AutoRegistration),
|
|
}
|
|
}
|
|
bpRhsm.Config.SubscriptionManager = bpSubMan
|
|
}
|
|
|
|
c.RHSM = bpRhsm
|
|
}
|
|
|
|
disk, err := convertDiskCustomizations(rbpc.Disk)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c.Disk = disk
|
|
bpDisk, err := convertDiskCustomizations(rbpc.Disk)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c.Disk = bpDisk
|
|
|
|
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
|
|
}
|
|
|
|
return ConvertRequestBP(*request.Blueprint)
|
|
}
|
|
|
|
// ConvertRequestBP takes a request Blueprint and returns a composer blueprint.Blueprint
|
|
func ConvertRequestBP(rbp Blueprint) (blueprint.Blueprint, error) {
|
|
var bp blueprint.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.EnabledModules != nil {
|
|
for _, em := range *rbp.EnabledModules {
|
|
bp.EnabledModules = append(bp.EnabledModules, blueprint.EnabledModule{
|
|
Name: em.Name,
|
|
Stream: em.Stream,
|
|
})
|
|
}
|
|
}
|
|
|
|
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 := rbp.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.EnabledModules != nil {
|
|
for _, em := range *request.Customizations.EnabledModules {
|
|
bp.EnabledModules = append(bp.EnabledModules, blueprint.EnabledModule{
|
|
Name: em.Name,
|
|
Stream: em.Stream,
|
|
})
|
|
}
|
|
}
|
|
|
|
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 {
|
|
user0, err := d.User.AsDirectoryUser0()
|
|
if err == nil {
|
|
dirCustomization.User = user0
|
|
} else {
|
|
user1, err := d.User.AsDirectoryUser1()
|
|
if err != nil {
|
|
return bp, fmt.Errorf("invalid user: %w", err)
|
|
}
|
|
dirCustomization.User = user1
|
|
}
|
|
}
|
|
if d.Group != nil {
|
|
group0, err := d.Group.AsDirectoryGroup0()
|
|
if err == nil {
|
|
dirCustomization.Group = group0
|
|
} else {
|
|
group1, err := d.Group.AsDirectoryGroup1()
|
|
if err != nil {
|
|
return bp, fmt.Errorf("invalid group: %w", err)
|
|
}
|
|
dirCustomization.Group = group1
|
|
}
|
|
}
|
|
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 {
|
|
user0, err := f.User.AsFileUser0()
|
|
if err == nil {
|
|
fileCustomization.User = user0
|
|
} else {
|
|
user1, err := f.User.AsFileUser1()
|
|
if err != nil {
|
|
return bp, fmt.Errorf("invalid user: %w", err)
|
|
}
|
|
fileCustomization.User = user1
|
|
}
|
|
}
|
|
if f.Group != nil {
|
|
group0, err := f.Group.AsFileGroup0()
|
|
if err == nil {
|
|
fileCustomization.Group = group0
|
|
} else {
|
|
group1, err := f.Group.AsFileGroup1()
|
|
if err != nil {
|
|
return bp, fmt.Errorf("invalid group: %w", err)
|
|
}
|
|
fileCustomization.Group = group1
|
|
}
|
|
}
|
|
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.PolicyId != nil {
|
|
openSCAPCustomization.PolicyID = request.Customizations.Openscap.PolicyId.String()
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
if request.Customizations.Rpm != nil && request.Customizations.Rpm.ImportKeys != nil {
|
|
bp.Customizations.RPM = &blueprint.RPMCustomization{
|
|
ImportKeys: &blueprint.RPMImportKeys{
|
|
Files: *request.Customizations.Rpm.ImportKeys.Files,
|
|
},
|
|
}
|
|
}
|
|
|
|
if rhsm := request.Customizations.Rhsm; rhsm != nil && rhsm.Config != nil {
|
|
bpRhsm := &blueprint.RHSMCustomization{
|
|
Config: &blueprint.RHSMConfig{},
|
|
}
|
|
|
|
conf := rhsm.Config
|
|
if conf.DnfPlugins != nil {
|
|
bpRhsm.Config.DNFPlugins = &blueprint.SubManDNFPluginsConfig{}
|
|
if conf.DnfPlugins.ProductId != nil && conf.DnfPlugins.ProductId.Enabled != nil {
|
|
bpRhsm.Config.DNFPlugins.ProductID = &blueprint.DNFPluginConfig{
|
|
Enabled: common.ToPtr(*conf.DnfPlugins.ProductId.Enabled),
|
|
}
|
|
}
|
|
if conf.DnfPlugins.SubscriptionManager != nil && conf.DnfPlugins.SubscriptionManager.Enabled != nil {
|
|
bpRhsm.Config.DNFPlugins.SubscriptionManager = &blueprint.DNFPluginConfig{
|
|
Enabled: common.ToPtr(*conf.DnfPlugins.SubscriptionManager.Enabled),
|
|
}
|
|
}
|
|
}
|
|
|
|
if subMan := conf.SubscriptionManager; subMan != nil {
|
|
bpSubMan := &blueprint.SubManConfig{}
|
|
if subMan.Rhsm != nil {
|
|
bpSubMan.RHSMConfig = &blueprint.SubManRHSMConfig{}
|
|
if subMan.Rhsm.ManageRepos != nil {
|
|
bpSubMan.RHSMConfig.ManageRepos = common.ToPtr(*subMan.Rhsm.ManageRepos)
|
|
}
|
|
if subMan.Rhsm.AutoEnableYumPlugins != nil {
|
|
bpSubMan.RHSMConfig.AutoEnableYumPlugins = common.ToPtr(*subMan.Rhsm.AutoEnableYumPlugins)
|
|
}
|
|
}
|
|
if subMan.Rhsmcertd != nil && subMan.Rhsmcertd.AutoRegistration != nil {
|
|
bpSubMan.RHSMCertdConfig = &blueprint.SubManRHSMCertdConfig{
|
|
AutoRegistration: common.ToPtr(*subMan.Rhsmcertd.AutoRegistration),
|
|
}
|
|
}
|
|
bpRhsm.Config.SubscriptionManager = bpSubMan
|
|
}
|
|
|
|
bp.Customizations.RHSM = bpRhsm
|
|
|
|
}
|
|
|
|
bp.Customizations.Disk, err = convertDiskCustomizations(request.Customizations.Disk)
|
|
if err != nil {
|
|
return bp, err
|
|
}
|
|
|
|
if cacerts := request.Customizations.Cacerts; cacerts != nil {
|
|
bp.Customizations.CACerts = &blueprint.CACustomization{
|
|
PEMCerts: cacerts.PemCerts,
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
var insightsClientProxy string
|
|
if request.Customizations.Subscription.InsightsClientProxy != nil {
|
|
insightsClientProxy = *request.Customizations.Subscription.InsightsClientProxy
|
|
}
|
|
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,
|
|
Proxy: insightsClientProxy,
|
|
}
|
|
}
|
|
|
|
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...)
|
|
}
|
|
|
|
// Initialise the image options from the image request and
|
|
// customizations
|
|
imageOptions := distro.ImageOptions{}
|
|
|
|
if ir.Size != nil {
|
|
imageOptions.Size = *ir.Size
|
|
}
|
|
|
|
if request.Koji == nil {
|
|
imageOptions.Facts = &facts.ImageOptions{
|
|
APIType: facts.CLOUDV2_APITYPE,
|
|
}
|
|
oscap := bp.Customizations.GetOpenSCAP()
|
|
if oscap != nil {
|
|
if oscap.ProfileID != "" {
|
|
imageOptions.Facts.OpenSCAPProfileID = oscap.ProfileID
|
|
}
|
|
if oscap.PolicyID != "" {
|
|
policyID, err := uuid.Parse(oscap.PolicyID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Unable to parse %s as a uuid: %w", oscap.PolicyID, err)
|
|
}
|
|
imageOptions.Facts.CompliancePolicyID = policyID
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
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 {
|
|
// 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
|
|
}
|
|
|
|
func convertDiskCustomizations(disk *Disk) (*blueprint.DiskCustomization, error) {
|
|
if disk == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
diskSize, err := decodeMinsize(disk.Minsize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bpDisk := &blueprint.DiskCustomization{
|
|
MinSize: diskSize,
|
|
Type: string(common.DerefOrDefault(disk.Type)),
|
|
}
|
|
|
|
for idx, partition := range disk.Partitions {
|
|
// partition successfully converts to all three types, so convert to
|
|
// filesystem to sniff the type string
|
|
sniffer, err := partition.AsFilesystemTyped()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to deserialize disk customization partition %d", idx)
|
|
}
|
|
|
|
var bpPartition blueprint.PartitionCustomization
|
|
switch partType := common.DerefOrDefault(sniffer.Type); string(partType) {
|
|
case string(Plain):
|
|
fs, err := partition.AsFilesystemTyped()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to deserialize disk customization partition %d with type %q", idx, partType)
|
|
}
|
|
fsSize, err := decodeMinsize(fs.Minsize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bpPartition = blueprint.PartitionCustomization{
|
|
Type: string(common.DerefOrDefault(fs.Type)),
|
|
PartType: common.DerefOrDefault(fs.PartType),
|
|
MinSize: fsSize,
|
|
FilesystemTypedCustomization: blueprint.FilesystemTypedCustomization{
|
|
Mountpoint: fs.Mountpoint,
|
|
Label: common.DerefOrDefault(fs.Label),
|
|
FSType: string(common.DerefOrDefault(fs.FsType)),
|
|
},
|
|
}
|
|
case string(Btrfs):
|
|
btrfsVol, err := partition.AsBtrfsVolume()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to deserialize disk customization partition %d with type %q", idx, partType)
|
|
}
|
|
volSize, err := decodeMinsize(btrfsVol.Minsize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bpPartition = blueprint.PartitionCustomization{
|
|
Type: string(common.DerefOrDefault(btrfsVol.Type)),
|
|
PartType: common.DerefOrDefault(btrfsVol.PartType),
|
|
MinSize: volSize,
|
|
}
|
|
|
|
for _, subvol := range btrfsVol.Subvolumes {
|
|
bpSubvol := blueprint.BtrfsSubvolumeCustomization{
|
|
Name: subvol.Name,
|
|
Mountpoint: subvol.Mountpoint,
|
|
}
|
|
bpPartition.Subvolumes = append(bpPartition.Subvolumes, bpSubvol)
|
|
}
|
|
case string(Lvm):
|
|
vg, err := partition.AsVolumeGroup()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to deserialize disk customization partition %d with type %q", idx, partType)
|
|
}
|
|
vgSize, err := decodeMinsize(vg.Minsize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bpPartition = blueprint.PartitionCustomization{
|
|
Type: string(common.DerefOrDefault(vg.Type)),
|
|
PartType: common.DerefOrDefault(vg.PartType),
|
|
MinSize: vgSize,
|
|
VGCustomization: blueprint.VGCustomization{
|
|
Name: common.DerefOrDefault(vg.Name),
|
|
},
|
|
}
|
|
|
|
for _, lv := range vg.LogicalVolumes {
|
|
lvSize, err := decodeMinsize(lv.Minsize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bpLV := blueprint.LVCustomization{
|
|
Name: common.DerefOrDefault(lv.Name),
|
|
MinSize: lvSize,
|
|
FilesystemTypedCustomization: blueprint.FilesystemTypedCustomization{
|
|
Mountpoint: lv.Mountpoint,
|
|
Label: common.DerefOrDefault(lv.Label),
|
|
FSType: string(common.DerefOrDefault(lv.FsType)),
|
|
},
|
|
}
|
|
bpPartition.LogicalVolumes = append(bpPartition.LogicalVolumes, bpLV)
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("disk customization partition %d has invalid or unknown type %q", idx, partType)
|
|
|
|
}
|
|
bpDisk.Partitions = append(bpDisk.Partitions, bpPartition)
|
|
}
|
|
|
|
return bpDisk, nil
|
|
}
|
|
|
|
func decodeMinsize(size *Minsize) (uint64, error) {
|
|
if size == nil {
|
|
return 0, nil
|
|
}
|
|
|
|
return datasizes.Parse(*size)
|
|
}
|