|
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 Service |
509fd4 |
"io/ioutil"
|
|
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 Service |
bcdfb1 |
// Returns the default OSTree ref for the image type.
|
|
Packit Service |
bcdfb1 |
OSTreeRef() string
|
|
Packit Service |
bcdfb1 |
|
|
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 Service |
15f37d |
// Returns the sets of packages to include and exclude when building the image.
|
|
Packit Service |
15f37d |
// Indexed by a string label. How each set is labeled and used depends on the
|
|
Packit Service |
15f37d |
// image type.
|
|
Packit Service |
15f37d |
PackageSets(bp blueprint.Blueprint) map[string]rpmmd.PackageSet
|
|
Packit |
63bb0d |
|
|
Packit Service |
15f37d |
// Returns the names of the stages that will produce the build output.
|
|
Packit Service |
15f37d |
Exports() []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 Service |
15f37d |
// specified in the given blueprint. The packageSpecSets must be labelled in
|
|
Packit Service |
15f37d |
// the same way as the originating PackageSets.
|
|
Packit Service |
15f37d |
Manifest(b *blueprint.Customizations, options ImageOptions, repos []rpmmd.RepoConfig, packageSpecSets map[string][]rpmmd.PackageSpec, seed int64) (Manifest, error)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
// The ImageOptions specify options for a specific image build
|
|
Packit |
63bb0d |
type ImageOptions struct {
|
|
Packit Service |
509fd4 |
OSTree OSTreeImageOptions
|
|
Packit Service |
509fd4 |
Size uint64
|
|
Packit Service |
509fd4 |
Subscription *SubscriptionImageOptions
|
|
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 Service |
15f37d |
URL string
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit Service |
509fd4 |
// The SubscriptionImageOptions specify subscription-specific image options
|
|
Packit Service |
509fd4 |
// ServerUrl denotes the host to register the system with
|
|
Packit Service |
509fd4 |
// BaseUrl specifies the repository URL for DNF
|
|
Packit Service |
509fd4 |
type SubscriptionImageOptions struct {
|
|
Packit Service |
509fd4 |
Organization int
|
|
Packit Service |
509fd4 |
ActivationKey string
|
|
Packit Service |
509fd4 |
ServerUrl string
|
|
Packit Service |
509fd4 |
BaseUrl string
|
|
Packit Service |
509fd4 |
Insights bool
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
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 Service |
bcdfb1 |
func (r *Registry) FromHost() (Distro, bool, bool, error) {
|
|
Packit Service |
bcdfb1 |
name, beta, isStream, err := GetHostDistroName()
|
|
Packit |
63bb0d |
if err != nil {
|
|
Packit Service |
bcdfb1 |
return nil, false, false, err
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit |
63bb0d |
d := r.GetDistro(name)
|
|
Packit |
63bb0d |
if d == nil {
|
|
Packit Service |
bcdfb1 |
return nil, false, false, errors.New("unknown distro: " + name)
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit Service |
bcdfb1 |
return d, beta, isStream, nil
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit Service |
bcdfb1 |
func GetHostDistroName() (string, bool, bool, error) {
|
|
Packit |
63bb0d |
f, err := os.Open("/etc/os-release")
|
|
Packit |
63bb0d |
if err != nil {
|
|
Packit Service |
bcdfb1 |
return "", false, false, err
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
defer f.Close()
|
|
Packit |
63bb0d |
osrelease, err := readOSRelease(f)
|
|
Packit |
63bb0d |
if err != nil {
|
|
Packit Service |
bcdfb1 |
return "", false, false, err
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit Service |
bcdfb1 |
isStream := osrelease["NAME"] == "CentOS Stream"
|
|
Packit Service |
bcdfb1 |
|
|
Packit Service |
509fd4 |
// NOTE: We only consider major releases up until rhel 8.4
|
|
Packit |
63bb0d |
version := strings.Split(osrelease["VERSION_ID"], ".")
|
|
Packit |
63bb0d |
name := osrelease["ID"] + "-" + version[0]
|
|
Packit Service |
509fd4 |
if osrelease["ID"] == "rhel" && version[0] == "8" && version[1] >= "4" {
|
|
Packit Service |
509fd4 |
name = name + version[1]
|
|
Packit Service |
509fd4 |
}
|
|
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 Service |
bcdfb1 |
return name, beta, isStream, nil
|
|
Packit |
63bb0d |
}
|
|
Packit |
63bb0d |
|
|
Packit Service |
509fd4 |
// GetRedHatRelease returns the content of /etc/redhat-release
|
|
Packit Service |
509fd4 |
// without the trailing new-line.
|
|
Packit Service |
509fd4 |
func GetRedHatRelease() (string, error) {
|
|
Packit Service |
509fd4 |
raw, err := ioutil.ReadFile("/etc/redhat-release")
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return "", fmt.Errorf("cannot read /etc/redhat-release: %v", err)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
//Remove the trailing new-line.
|
|
Packit Service |
509fd4 |
redHatRelease := strings.TrimSpace(string(raw))
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
return redHatRelease, nil
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
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 |
}
|