Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions cmd/build-extensions-container.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import (
"fmt"
"os/exec"

cosamodel "github.com/coreos/coreos-assembler/internal/pkg/cosa"
"github.com/coreos/coreos-assembler/internal/pkg/cosash"
cosa "github.com/coreos/coreos-assembler/pkg/builds"
"github.com/pkg/errors"
"gopkg.in/yaml.v2"

"crypto/sha256"
"encoding/json"
Expand All @@ -16,6 +18,108 @@ import (
"time"
)

// hotfix is an element in hotfixes.yaml which is a repo-locked RPM set.
type hotfix struct {
// URL for associated bug
Link string `json:"link"`
// The operating system major version (e.g. 8 or 9)
OsMajor string `json:"osmajor"`
// Repo used to download packages
Repo string `json:"repo"`
// Names of associated packages
Packages []string `json:"packages"`
}

type hotfixData struct {
Hotfixes []hotfix `json:"hotfixes"`
}

// downloadHotfixes basically just accepts as input a declarative JSON file
// format describing hotfixes, which are repo-locked RPM packages we want to download
// but without any dependencies.
func downloadHotfixes(srcdir, configpath, destdir string) error {
contents, err := os.ReadFile(configpath)
if err != nil {
return err
}

var h hotfixData
if err := yaml.Unmarshal(contents, &h); err != nil {
return fmt.Errorf("failed to deserialize hotfixes: %w", err)
}

fmt.Println("Downloading hotfixes")

for _, fix := range h.Hotfixes {
fmt.Printf("Downloading content for hotfix: %s\n", fix.Link)
// Only enable the repos required for download
reposdir := filepath.Join(srcdir, "yumrepos")
argv := []string{"--disablerepo=*", fmt.Sprintf("--enablerepo=%s", fix.Repo), "--setopt=reposdir=" + reposdir, "download"}
argv = append(argv, fix.Packages...)
cmd := exec.Command("dnf", argv...)
cmd.Dir = destdir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to invoke dnf download: %w", err)
}
}

serializedHotfixes, err := json.Marshal(h)
if err != nil {
return err
}
err = os.WriteFile(filepath.Join(destdir, "hotfixes.json"), serializedHotfixes, 0o644)
if err != nil {
return err
}

return nil
}

func generateHotfixes() (string, error) {
hotfixesTmpdir, err := os.MkdirTemp("", "")
if err != nil {
return "", err
}
defer os.RemoveAll(hotfixesTmpdir)

variant, err := cosamodel.GetVariant()
if err != nil {
return "", err
}

wd, err := os.Getwd()
if err != nil {
return "", err
}

srcdir := filepath.Join(wd, "src")
p := fmt.Sprintf("%s/config/hotfixes-%s.yaml", srcdir, variant)
if _, err := os.Stat(p); err == nil {
err := downloadHotfixes(srcdir, p, hotfixesTmpdir)
if err != nil {
return "", fmt.Errorf("failed to download hotfixes: %w", err)
}
} else {
fmt.Printf("No %s found\n", p)
}

out := filepath.Join(wd, "tmp/hotfixes.tar")

// Serialize the hotfix RPMs into a tarball which we can pass via a virtio
// device to the qemu process.
cmd := exec.Command("tar", "-c", "-C", hotfixesTmpdir, "-f", out, ".")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return "", err
}

return out, nil
}

func buildExtensionContainer() error {
cosaBuild, buildPath, err := cosa.ReadBuild("builds", "", "")
if err != nil {
Expand All @@ -24,6 +128,11 @@ func buildExtensionContainer() error {
buildID := cosaBuild.BuildID
fmt.Printf("Generating extensions container for build: %s\n", buildID)

hotfixPath, err := generateHotfixes()
if err != nil {
return fmt.Errorf("generating hotfixes failed: %w", err)
}

arch := cosa.BuilderArch()
sh, err := cosash.NewCosaSh()
if err != nil {
Expand All @@ -35,6 +144,8 @@ func buildExtensionContainer() error {
targetname := cosaBuild.Name + "-" + buildID + "-extensions-container" + "." + arch + ".ociarchive"
process := "runvm -chardev \"file,id=ociarchiveout,path=${tmp_builddir}/\"" + targetname +
" -device \"virtserialport,chardev=ociarchiveout,name=ociarchiveout\"" +
" -drive file=" + hotfixPath + ",if=none,id=hotfixes,format=raw,media=disk,read-only=on" +
" -device virtio-blk,serial=hotfixes,drive=hotfixes" +
" -- /usr/lib/coreos-assembler/build-extensions-container.sh " + arch +
" /dev/virtio-ports/ociarchiveout " + buildID
if err := sh.Process(process); err != nil {
Expand Down
31 changes: 31 additions & 0 deletions internal/pkg/cosa/variant.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package cosa

import (
"encoding/json"
"fmt"
"os"
)

const initConfigPath = "src/config.json"

type configVariant struct {
Variant string `json:"coreos-assembler.config-variant"`
}

// GetVariant finds the configured variant, or "" if unset
func GetVariant() (string, error) {
contents, err := os.ReadFile(initConfigPath)
if err != nil {
if !os.IsNotExist(err) {
return "", err
}
return "", nil
}

var variantData configVariant
if err := json.Unmarshal(contents, &variantData); err != nil {
return "", fmt.Errorf("parsing %s: %w", initConfigPath, err)
}

return variantData.Variant, nil
}
3 changes: 3 additions & 0 deletions src/build-extensions-container.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ if [[ -f "${workdir}/src/config.json" ]]; then
variant="$(jq --raw-output '."coreos-assembler.config-variant"' "${workdir}/src/config.json")"
fi

mkdir "${ctx_dir}/hotfixes"
tar -xC "${ctx_dir}/hotfixes" -f /dev/disk/by-id/virtio-hotfixes

# Build the image, replacing the FROM directive with the local image we have.
# The `variant` variable is explicitely unquoted to be skipped when empty.
img=localhost/extensions-container
Expand Down