From f9cbc8593f458fb2d01d68c96f7a3423b820a8af Mon Sep 17 00:00:00 2001 From: Lars Karlitski Date: Sat, 9 Nov 2019 10:59:28 +0000 Subject: [PATCH] distro: add DetectHost lorax-composer uses the host's repositories for building images. This is prone to accidental configuration errors and duplicates functionality (adding custom repositories via the source API is much more explicit). However, blueprints don't specify the distribution they're based on. This is something they should do in the future to enable cross-distro image builds. Until then, read `/etc/os-release` to detect the host OS and use that as the base distro for a blueprint. Detection works by concatenating the ID field with a `-` and the VERSION_ID field. This mandates how additional distributions should register themselves to the `distro` package. If composer cannot build the detected distro, fall back to fedora-30. Use this detection everywhere that fedora-30 was hard-coded before. --- cmd/osbuild-pipeline/main.go | 4 +- internal/distro/distro.go | 69 +++++++++++++++++++++++++++++++ internal/distro/osrelease_test.go | 54 ++++++++++++++++++++++++ internal/store/store.go | 8 ++-- internal/weldr/api.go | 4 +- 5 files changed, 131 insertions(+), 8 deletions(-) create mode 100644 internal/distro/osrelease_test.go diff --git a/cmd/osbuild-pipeline/main.go b/cmd/osbuild-pipeline/main.go index 7ac3de033..579ff3461 100644 --- a/cmd/osbuild-pipeline/main.go +++ b/cmd/osbuild-pipeline/main.go @@ -31,8 +31,8 @@ func main() { } } - f30 := distro.New("fedora-30") - pipeline, err := f30.Pipeline(blueprint, format) + d := distro.New("") + pipeline, err := d.Pipeline(blueprint, format) if err != nil { panic(err.Error()) } diff --git a/internal/distro/distro.go b/internal/distro/distro.go index 6ba2c385c..42bbd6dcf 100644 --- a/internal/distro/distro.go +++ b/internal/distro/distro.go @@ -1,6 +1,13 @@ package distro import ( + "bufio" + "errors" + "io" + "log" + "os" + "strings" + "github.com/osbuild/osbuild-composer/internal/blueprint" "github.com/osbuild/osbuild-composer/internal/pipeline" ) @@ -33,16 +40,78 @@ func (e *InvalidOutputFormatError) Error() string { var registered = map[string]Distro{} func New(name string) Distro { + if name == "" { + distro, err := FromHost() + if err == nil { + return distro + } else { + log.Println("cannot detect distro from host: " + err.Error()) + log.Println("falling back to 'fedora-30'") + return New("fedora-30") + } + } + distro, ok := registered[name] if !ok { panic("unknown distro: " + name) } + return distro } +func FromHost() (Distro, error) { + f, err := os.Open("/etc/os-release") + if err != nil { + return nil, err + } + defer f.Close() + + osrelease, err := readOSRelease(f) + if err != nil { + return nil, err + } + + name := osrelease["ID"] + "-" + osrelease["VERSION_ID"] + + distro, ok := registered[name] + if !ok { + return nil, errors.New("unknown distro: " + name) + } + return distro, nil +} + func Register(name string, distro Distro) { if _, exists := registered[name]; exists { panic("a distro with this name already exists: " + name) } registered[name] = distro } + +func readOSRelease(r io.Reader) (map[string]string, error) { + osrelease := make(map[string]string) + scanner := bufio.NewScanner(r) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if len(line) == 0 { + continue + } + + parts := strings.SplitN(line, "=", 2) + if len(parts) != 2 { + return nil, errors.New("readOSRelease: invalid input") + } + + key := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(parts[1]) + if value[0] == '"' { + if len(value) < 2 || value[len(value) - 1] != '"' { + return nil, errors.New("readOSRelease: invalid input") + } + value = value[1:len(value) - 1] + } + + osrelease[key] = value + } + + return osrelease, nil +} diff --git a/internal/distro/osrelease_test.go b/internal/distro/osrelease_test.go new file mode 100644 index 000000000..490040bf3 --- /dev/null +++ b/internal/distro/osrelease_test.go @@ -0,0 +1,54 @@ +package distro + +import ( + "reflect" + "strings" + "testing" +) + +func TestOSRelease(t *testing.T) { + var cases = []struct { + Input string + OSRelease map[string]string + }{ + { + ``, + map[string]string{}, + }, + { + `NAME=Fedora +VERSION="30 (Workstation Edition)" +ID=fedora +VERSION_ID=30 +VERSION_CODENAME="" +PLATFORM_ID="platform:f30" +PRETTY_NAME="Fedora 30 (Workstation Edition)" +VARIANT="Workstation Edition" +VARIANT_ID=workstation`, + map[string]string{ + "NAME": "Fedora", + "VERSION": "30 (Workstation Edition)", + "ID": "fedora", + "VERSION_ID": "30", + "VERSION_CODENAME": "", + "PLATFORM_ID": "platform:f30", + "PRETTY_NAME": "Fedora 30 (Workstation Edition)", + "VARIANT": "Workstation Edition", + "VARIANT_ID": "workstation", + }, + }, + } + + for i, c := range cases { + r := strings.NewReader(c.Input) + + osrelease, err := readOSRelease(r) + if err != nil { + t.Fatalf("%d: readOSRelease: %v", i, err) + } + + if !reflect.DeepEqual(osrelease, c.OSRelease) { + t.Fatalf("%d: readOSRelease returned unexpected result: %#v", i, osrelease) + } + } +} diff --git a/internal/store/store.go b/internal/store/store.go index 102dcd301..6816be987 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -396,8 +396,8 @@ func (s *Store) PushCompose(composeID uuid.UUID, bp *blueprint.Blueprint, compos targets := []*target.Target{ target.NewLocalTarget(target.NewLocalTargetOptions("/var/lib/osbuild-composer/outputs/" + composeID.String())), } - f30 := distro.New("fedora-30") - pipeline, err := f30.Pipeline(bp, composeType) + d := distro.New("") + pipeline, err := d.Pipeline(bp, composeType) if err != nil { return err } @@ -475,8 +475,8 @@ func (s *Store) GetImage(composeID uuid.UUID) (*Image, error) { if compose.QueueStatus != "FINISHED" { return nil, &InvalidRequestError{"compose was not finished"} } - f30 := distro.New("fedora-30") - name, mime, err := f30.FilenameFromType(compose.OutputType) + d := distro.New("") + name, mime, err := d.FilenameFromType(compose.OutputType) if err != nil { panic("invalid output type") } diff --git a/internal/weldr/api.go b/internal/weldr/api.go index fb451547a..818c7c1c6 100644 --- a/internal/weldr/api.go +++ b/internal/weldr/api.go @@ -850,8 +850,8 @@ func (api *API) composeTypesHandler(writer http.ResponseWriter, request *http.Re Types []composeType `json:"types"` } - f30 := distro.New("fedora-30") - for _, format := range f30.ListOutputFormats() { + d := distro.New("") + for _, format := range d.ListOutputFormats() { reply.Types = append(reply.Types, composeType{format, true}) }