/* Copyright (c) Edgeless Systems GmbH SPDX-License-Identifier: AGPL-3.0-only */ package deploy import ( "bufio" "fmt" "regexp" "strings" "github.com/spf13/afero" ) // ImageInfo retrieves OS image information. type ImageInfo struct { fs *afero.Afero } // NewImageInfo creates a new imageInfo. func NewImageInfo() *ImageInfo { return &ImageInfo{ fs: &afero.Afero{Fs: afero.NewOsFs()}, } } // ImageVersion tries to parse the image version from the host mounted os-release file. // If the file is not present or does not contain the version, a fallback lookup is performed. func (i *ImageInfo) ImageVersion() (string, error) { var version string var err error for _, path := range osReleasePaths { version, err = i.getOSReleaseImageVersion(path) if err == nil { break } } if err != nil { return "", err } if version == "" { return "", fmt.Errorf("IMAGE_VERSION not found in %s", strings.Join(osReleasePaths, ", ")) } return version, nil } // getOSReleaseImageVersion reads the os-release file and returns the image version (if present). func (i *ImageInfo) getOSReleaseImageVersion(path string) (string, error) { osRelease, err := i.fs.Open(path) if err != nil { return "", err } defer osRelease.Close() osReleaseMap, err := parseOSRelease(bufio.NewScanner(osRelease)) if err != nil { return "", err } version, ok := osReleaseMap[versionKey] if !ok { return "", fmt.Errorf("IMAGE_VERSION not found in %s", path) } return version, nil } // parseOSRelease parses the os-release file and returns a map of key-value pairs. // The os-release file is a simple key-value file. // The format is specified in https://www.freedesktop.org/software/systemd/man/os-release.html. func parseOSRelease(osRelease *bufio.Scanner) (map[string]string, error) { osReleaseMap := make(map[string]string) for osRelease.Scan() { line := osRelease.Text() matches := osReleaseLine.FindStringSubmatch(line) if len(matches) < 6 { continue } key := matches[1] var value string // group 3 is the value with double quotes // group 4 is the value with single quotes // group 5 is the value without quotes for i := 3; i < 6; i++ { if matches[i] != "" { value = matches[i] break } } // unescape the following characters: \\, \$, \", \', \` value = osReleaseUnescape.ReplaceAllString(value, "$1") osReleaseMap[key] = value } if err := osRelease.Err(); err != nil { return nil, err } return osReleaseMap, nil } const versionKey = "IMAGE_VERSION" var ( osReleaseLine = regexp.MustCompile(`^(?P[a-zA-Z0-9_]+)=("(?P.*)"|'(?P.*)'|(?P[^\n"']+))$`) osReleaseUnescape = regexp.MustCompile(`\\([\\\$\"\'` + "`" + `])`) osReleasePaths = []string{ "/host/etc/os-release", "/host/usr/lib/os-release", } )