Blame internal/distro/distro.go

Packit 63bb0d
package distro
Packit 63bb0d
Packit 63bb0d
import (
Packit 63bb0d
	"bufio"
Packit 63bb0d
	"encoding/json"
Packit 63bb0d
	"errors"
Packit 63bb0d
	"fmt"
Packit 63bb0d
	"io"
Packit 63bb0d
	"os"
Packit 63bb0d
	"sort"
Packit 63bb0d
	"strings"
Packit 63bb0d
Packit 63bb0d
	"github.com/osbuild/osbuild-composer/internal/blueprint"
Packit 63bb0d
	"github.com/osbuild/osbuild-composer/internal/rpmmd"
Packit 63bb0d
)
Packit 63bb0d
Packit 63bb0d
// A Distro represents composer's notion of what a given distribution is.
Packit 63bb0d
type Distro interface {
Packit 63bb0d
	// Returns the name of the distro.
Packit 63bb0d
	Name() string
Packit 63bb0d
Packit 63bb0d
	// Returns the module platform id of the distro. This is used by DNF
Packit 63bb0d
	// for modularity support.
Packit 63bb0d
	ModulePlatformID() string
Packit 63bb0d
Packit 63bb0d
	// Returns a sorted list of the names of the architectures this distro
Packit 63bb0d
	// supports.
Packit 63bb0d
	ListArches() []string
Packit 63bb0d
Packit 63bb0d
	// Returns an object representing the given architecture as support
Packit 63bb0d
	// by this distro.
Packit 63bb0d
	GetArch(arch string) (Arch, error)
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// An Arch represents a given distribution's support for a given architecture.
Packit 63bb0d
type Arch interface {
Packit 63bb0d
	// Returns the name of the architecture.
Packit 63bb0d
	Name() string
Packit 63bb0d
Packit 63bb0d
	// Returns a sorted list of the names of the image types this architecture
Packit 63bb0d
	// supports.
Packit 63bb0d
	ListImageTypes() []string
Packit 63bb0d
Packit 63bb0d
	// Returns an object representing a given image format for this architecture,
Packit 63bb0d
	// on this distro.
Packit 63bb0d
	GetImageType(imageType string) (ImageType, error)
Packit 63bb0d
Packit 63bb0d
	// Returns the parent distro
Packit 63bb0d
	Distro() Distro
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// An ImageType represents a given distribution's support for a given Image Type
Packit 63bb0d
// for a given architecture.
Packit 63bb0d
type ImageType interface {
Packit 63bb0d
	// Returns the name of the image type.
Packit 63bb0d
	Name() string
Packit 63bb0d
Packit 63bb0d
	// Returns the parent architecture
Packit 63bb0d
	Arch() Arch
Packit 63bb0d
Packit 63bb0d
	// Returns the canonical filename for the image type.
Packit 63bb0d
	Filename() string
Packit 63bb0d
Packit 63bb0d
	// Retrns the MIME-type for the image type.
Packit 63bb0d
	MIMEType() string
Packit 63bb0d
Packit 63bb0d
	// Returns the proper image size for a given output format. If the input size
Packit 63bb0d
	// is 0 the default value for the format will be returned.
Packit 63bb0d
	Size(size uint64) uint64
Packit 63bb0d
Packit 63bb0d
	// Returns the default packages to include and exclude when making the image
Packit 63bb0d
	// type.
Packit 63bb0d
	Packages(bp blueprint.Blueprint) ([]string, []string)
Packit 63bb0d
Packit 63bb0d
	// Returns the build packages for the output type.
Packit 63bb0d
	BuildPackages() []string
Packit 63bb0d
Packit 63bb0d
	// Returns an osbuild manifest, containing the sources and pipeline necessary
Packit 63bb0d
	// to build an image, given output format with all packages and customizations
Packit 63bb0d
	// specified in the given blueprint.
Packit 63bb0d
	Manifest(b *blueprint.Customizations, options ImageOptions, repos []rpmmd.RepoConfig, packageSpecs, buildPackageSpecs []rpmmd.PackageSpec) (Manifest, error)
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// The ImageOptions specify options for a specific image build
Packit 63bb0d
type ImageOptions struct {
Packit 63bb0d
	OSTree OSTreeImageOptions
Packit 63bb0d
	Size   uint64
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// The OSTreeImageOptions specify ostree-specific image options
Packit 63bb0d
type OSTreeImageOptions struct {
Packit 63bb0d
	Ref    string
Packit 63bb0d
	Parent string
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// A Manifest is an opaque JSON object, which is a valid input to osbuild
Packit 63bb0d
type Manifest []byte
Packit 63bb0d
Packit 63bb0d
func (m Manifest) MarshalJSON() ([]byte, error) {
Packit 63bb0d
	return json.RawMessage(m).MarshalJSON()
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func (m *Manifest) UnmarshalJSON(payload []byte) error {
Packit 63bb0d
	var raw json.RawMessage
Packit 63bb0d
	err := (&raw).UnmarshalJSON(payload)
Packit 63bb0d
	if err != nil {
Packit 63bb0d
		return err
Packit 63bb0d
	}
Packit 63bb0d
	*m = Manifest(raw)
Packit 63bb0d
	return nil
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
type Registry struct {
Packit 63bb0d
	distros map[string]Distro
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func NewRegistry(distros ...Distro) (*Registry, error) {
Packit 63bb0d
	reg := &Registry{
Packit 63bb0d
		distros: make(map[string]Distro),
Packit 63bb0d
	}
Packit 63bb0d
	for _, distro := range distros {
Packit 63bb0d
		name := distro.Name()
Packit 63bb0d
		if _, exists := reg.distros[name]; exists {
Packit 63bb0d
			return nil, fmt.Errorf("NewRegistry: passed two distros with the same name: %s", distro.Name())
Packit 63bb0d
		}
Packit 63bb0d
		reg.distros[name] = distro
Packit 63bb0d
	}
Packit 63bb0d
	return reg, nil
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func (r *Registry) GetDistro(name string) Distro {
Packit 63bb0d
	distro, ok := r.distros[name]
Packit 63bb0d
	if !ok {
Packit 63bb0d
		return nil
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	return distro
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// List returns the names of all distros in a Registry, sorted alphabetically.
Packit 63bb0d
func (r *Registry) List() []string {
Packit 63bb0d
	list := []string{}
Packit 63bb0d
	for _, distro := range r.distros {
Packit 63bb0d
		list = append(list, distro.Name())
Packit 63bb0d
	}
Packit 63bb0d
	sort.Strings(list)
Packit 63bb0d
	return list
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func (r *Registry) FromHost() (Distro, bool, error) {
Packit 63bb0d
	name, beta, err := GetHostDistroName()
Packit 63bb0d
	if err != nil {
Packit 63bb0d
		return nil, false, err
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	d := r.GetDistro(name)
Packit 63bb0d
	if d == nil {
Packit 63bb0d
		return nil, false, errors.New("unknown distro: " + name)
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	return d, beta, nil
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func GetHostDistroName() (string, bool, error) {
Packit 63bb0d
	f, err := os.Open("/etc/os-release")
Packit 63bb0d
	if err != nil {
Packit 63bb0d
		return "", false, err
Packit 63bb0d
	}
Packit 63bb0d
	defer f.Close()
Packit 63bb0d
	osrelease, err := readOSRelease(f)
Packit 63bb0d
	if err != nil {
Packit 63bb0d
		return "", false, err
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	// NOTE: We only consider major releases
Packit 63bb0d
	version := strings.Split(osrelease["VERSION_ID"], ".")
Packit 63bb0d
	name := osrelease["ID"] + "-" + version[0]
Packit 63bb0d
Packit 63bb0d
	// TODO: We should probably index these things by the full CPE
Packit 63bb0d
	beta := strings.Contains(osrelease["CPE_NAME"], "beta")
Packit 63bb0d
	return name, beta, nil
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func readOSRelease(r io.Reader) (map[string]string, error) {
Packit 63bb0d
	osrelease := make(map[string]string)
Packit 63bb0d
	scanner := bufio.NewScanner(r)
Packit 63bb0d
	for scanner.Scan() {
Packit 63bb0d
		line := strings.TrimSpace(scanner.Text())
Packit 63bb0d
		if len(line) == 0 {
Packit 63bb0d
			continue
Packit 63bb0d
		}
Packit 63bb0d
Packit 63bb0d
		parts := strings.SplitN(line, "=", 2)
Packit 63bb0d
		if len(parts) != 2 {
Packit 63bb0d
			return nil, errors.New("readOSRelease: invalid input")
Packit 63bb0d
		}
Packit 63bb0d
Packit 63bb0d
		key := strings.TrimSpace(parts[0])
Packit 63bb0d
		value := strings.TrimSpace(parts[1])
Packit 63bb0d
		if value[0] == '"' {
Packit 63bb0d
			if len(value) < 2 || value[len(value)-1] != '"' {
Packit 63bb0d
				return nil, errors.New("readOSRelease: invalid input")
Packit 63bb0d
			}
Packit 63bb0d
			value = value[1 : len(value)-1]
Packit 63bb0d
		}
Packit 63bb0d
Packit 63bb0d
		osrelease[key] = value
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	return osrelease, nil
Packit 63bb0d
}