1
0
mirror of https://github.com/kairos-io/kairos-agent.git synced 2025-05-07 07:46:46 +00:00

Drop luet, image extractor, drop build code and multiarch images ()

Co-authored-by: Mauro Morales <mauro.morales@spectrocloud.com>
This commit is contained in:
Itxaka 2023-05-16 16:06:49 +02:00 committed by GitHub
parent 0b7fd24bc7
commit ddfa30a4c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 495 additions and 4964 deletions

View File

@ -23,9 +23,6 @@ jobs:
with:
repository: quay.io/kairos/packages
packages: utils/earthly
- name: Run Build
run: |
earthly +build
- name: Run tests
run: |
earthly -P +test

View File

@ -2,7 +2,7 @@
project_name: kairos-agent
builds:
- ldflags:
- -w -s -X "github.com/kairos-io/kairos/v2/internal/common.VERSION={{.Env.VERSION}}"
- -w -s -X "github.com/kairos-io/kairos/v2/internal/common.VERSION={{.Tag}}"
env:
- CGO_ENABLED=0
goos:

View File

@ -14,23 +14,15 @@ go-deps:
SAVE ARTIFACT go.mod AS LOCAL go.mod
SAVE ARTIFACT go.sum AS LOCAL go.sum
luet:
FROM quay.io/luet/base:0.34.0
SAVE ARTIFACT /usr/bin/luet /luet
test:
FROM +go-deps
RUN apk add rsync gcc musl-dev docker jq bash
RUN apk add rsync gcc musl-dev bash
WORKDIR /build
COPY +luet/luet /usr/bin/luet
COPY . .
# Some test require the docker sock exposed
ARG TEST_PATHS=./...
ARG LABEL_FILTER=
ENV CGO_ENABLED=1
WITH DOCKER
RUN go run github.com/onsi/ginkgo/v2/ginkgo run --label-filter "$LABEL_FILTER" -v --output-interceptor-mode=none --fail-fast --race --covermode=atomic --coverprofile=coverage.out -r $TEST_PATHS
END
RUN go run github.com/onsi/ginkgo/v2/ginkgo run --label-filter "$LABEL_FILTER" --covermode=atomic --coverprofile=coverage.out -v --race -r $TEST_PATHS
SAVE ARTIFACT coverage.out AS LOCAL coverage.out
version:

93
go.mod
View File

@ -9,23 +9,22 @@ require (
github.com/Masterminds/semver/v3 v3.2.1
github.com/avast/retry-go v3.0.0+incompatible
github.com/cavaliergopher/grab/v3 v3.0.1
github.com/containerd/containerd v1.7.1
github.com/distribution/distribution v2.8.1+incompatible
github.com/docker/docker v20.10.20+incompatible
github.com/docker/go-units v0.5.0
github.com/erikgeiser/promptkit v0.8.0
github.com/google/go-containerregistry v0.14.0
github.com/google/go-github/v40 v40.0.0
github.com/hashicorp/go-multierror v1.1.1
github.com/itchyny/gojq v0.12.12
github.com/jaypipes/ghw v0.10.0
github.com/joho/godotenv v1.5.1
github.com/kairos-io/kairos-sdk v0.0.2-0.20230414094028-0c9d2bd9e6ae
github.com/kairos-io/kairos-sdk v0.0.2
github.com/labstack/echo/v4 v4.10.2
github.com/mitchellh/mapstructure v1.4.2
github.com/mudler/go-nodepair v0.0.0-20221223092639-ba399a66fdfb
github.com/mudler/go-pluggable v0.0.0-20230126220627-7710299a0ae5
github.com/mudler/go-processmanager v0.0.0-20220724164624-c45b5c61312d
github.com/mudler/luet v0.0.0-20221018082252-2513760b00de
github.com/mudler/yip v0.11.5-0.20230124143654-91e88dfb6648
github.com/mudler/yip v1.1.0
github.com/nxadm/tail v1.4.8
github.com/onsi/ginkgo/v2 v2.9.2
github.com/onsi/gomega v1.27.6
@ -33,7 +32,7 @@ require (
github.com/sanity-io/litter v1.5.5
github.com/santhosh-tekuri/jsonschema/v5 v5.3.0
github.com/sirupsen/logrus v1.9.0
github.com/spf13/cobra v1.5.0
github.com/spf13/cobra v1.7.0
github.com/spf13/viper v1.8.1
github.com/swaggest/jsonschema-go v0.3.49
github.com/twpayne/go-vfs v1.7.2
@ -49,50 +48,37 @@ require (
)
require (
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/MarvinJWendt/testza v0.4.2 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/sprig/v3 v3.2.2 // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/Microsoft/hcsshim v0.10.0-rc.1 // indirect
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/Microsoft/hcsshim v0.10.0-rc.8 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20220623141421-5afb4c282135 // indirect
github.com/Sabayon/pkgs-checker v0.8.4 // indirect
github.com/StackExchange/wmi v1.2.1 // indirect
github.com/acomagu/bufpipe v1.0.3 // indirect
github.com/asdine/storm v0.0.0-20190418133842-e0f77eada154 // indirect
github.com/asottile/dockerfile v3.1.0+incompatible // indirect
github.com/atomicgo/cursor v0.0.1 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 // indirect
github.com/aymanbagabas/go-osc52 v1.2.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cavaliercoder/grab v1.0.1-0.20201108051000-98a5bfe305ec // indirect
github.com/cavaliergopher/grab v2.0.0+incompatible // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/charmbracelet/bubbles v0.14.0 // indirect
github.com/charmbracelet/bubbletea v0.23.1 // indirect
github.com/charmbracelet/lipgloss v0.6.0 // indirect
github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9 // indirect
github.com/containerd/cgroups v1.1.0 // indirect
github.com/containerd/console v1.0.3 // indirect
github.com/containerd/containerd v1.6.20 // indirect
github.com/containerd/continuity v0.3.0 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.12.1 // indirect
github.com/containerd/typeurl v1.0.2 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/crillab/gophersat v1.3.2-0.20210701121804-72b19f5b6b38 // indirect
github.com/davidcassany/linuxkit/pkg/metadata v0.0.0-20230124104020-93ac9dd5b8e1 // indirect
github.com/denisbrodbeck/machineid v1.0.1 // indirect
github.com/disintegration/imaging v1.6.2 // indirect
github.com/diskfs/go-diskfs v1.2.1-0.20230123115902-fce1828bbbfa // indirect
github.com/docker/cli v20.10.20+incompatible // indirect
github.com/diskfs/go-diskfs v1.3.0 // indirect
github.com/docker/cli v23.0.3+incompatible // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/docker v23.0.6+incompatible // indirect
github.com/docker/docker-credential-helpers v0.7.0 // indirect
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/libnetwork v0.8.0-dev.2.0.20200917202933-d0951081b35f // indirect
github.com/ecooper/qlearning v0.0.0-20160612200101-3075011a69fd // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/eliukblau/pixterm v1.3.1 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
@ -107,36 +93,25 @@ require (
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-containerregistry v0.7.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 // indirect
github.com/google/renameio v1.0.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gookit/color v1.5.3 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-version v1.3.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/huandu/xstrings v1.3.3 // indirect
github.com/imdario/mergo v0.3.15 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/ipfs/go-log/v2 v2.5.1 // indirect
github.com/ishidawataru/sctp v0.0.0-20210707070123-9a39160e9062 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/itchyny/timefmt-go v0.1.5 // indirect
github.com/jaypipes/pcidb v1.0.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 // indirect
github.com/jinzhu/copier v0.0.0-20180308034124-7e38e58719c3 // indirect
github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329 // indirect
github.com/kendru/darwin/go/depgraph v0.0.0-20220319173517-8abc3541da93 // indirect
github.com/kendru/darwin/go/depgraph v0.0.0-20221105232959-877d6a81060c // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.16.0 // indirect
github.com/klauspost/pgzip v1.2.5 // indirect
github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d // indirect
github.com/kyokomi/emoji v2.1.0+incompatible // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
@ -146,23 +121,14 @@ require (
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/buildkit v0.10.1 // indirect
github.com/moby/libnetwork v0.8.0-dev.2.0.20200612180813-9e99af28df21 // indirect
github.com/moby/moby v23.0.0+incompatible // indirect
github.com/moby/patternmatcher v0.5.0 // indirect
github.com/moby/sys/mount v0.3.3 // indirect
github.com/moby/moby v23.0.2+incompatible // indirect
github.com/moby/sys/mountinfo v0.6.2 // indirect
github.com/moby/sys/sequential v0.5.0 // indirect
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/mudler/entities v0.0.0-20220905203055-68348bae0f49 // indirect
github.com/mudler/topsort v0.0.0-20201103161459-db5c7901c290 // indirect
github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
@ -170,35 +136,29 @@ require (
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect
github.com/opencontainers/runc v1.1.7 // indirect
github.com/otiai10/copy v1.9.0 // indirect
github.com/packethost/packngo v0.29.0 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee // indirect
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/xattr v0.4.9 // indirect
github.com/prometheus/client_golang v1.14.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/qeesung/image2ascii v1.0.1 // indirect
github.com/rancher-sandbox/gofilecache v0.0.0-20210330135715-becdeff5df15 // indirect
github.com/rancher-sandbox/linuxkit v1.0.0 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/satori/go.uuid v1.2.1-0.20180404165556-75cca531ea76 // indirect
github.com/samber/lo v1.37.0 // indirect
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
github.com/spectrocloud-labs/herd v0.4.2 // indirect
github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
github.com/swaggest/refl v1.1.0 // indirect
github.com/theupdateframework/notary v0.7.0 // indirect
github.com/tredoe/osutil/v2 v2.0.0-rc.16 // indirect
github.com/ulikunitz/xz v0.5.11 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
@ -206,22 +166,19 @@ require (
github.com/vbatts/tar-split v0.11.2 // indirect
github.com/vishvananda/netlink v1.2.1-beta.2 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
github.com/vmware/vmw-guestinfo v0.0.0-20220317130741-510905f0efa3 // indirect
github.com/wayneashleyberry/terminal-dimensions v1.1.0 // indirect
github.com/willdonnelly/passwd v0.0.0-20141013001024-7935dab3074c // indirect
github.com/xanzy/ssh-agent v0.3.1 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/zcalusic/sysinfo v0.9.5 // indirect
go.etcd.io/bbolt v1.3.7 // indirect
go.opencensus.io v0.24.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.7.0 // indirect
golang.org/x/image v0.0.0-20191206065243-da761ea9ff43 // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/term v0.7.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/tools v0.7.0 // indirect
@ -229,9 +186,9 @@ require (
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect
google.golang.org/grpc v1.53.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
google.golang.org/protobuf v1.29.1 // indirect
gopkg.in/djherbis/times.v1 v1.3.0 // indirect
gopkg.in/ini.v1 v1.66.6 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
howett.net/plist v1.0.0 // indirect

1619
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,6 @@ import (
"encoding/json"
"fmt"
"github.com/Masterminds/semver/v3"
"github.com/docker/docker/api/types"
events "github.com/kairos-io/kairos-sdk/bus"
"github.com/kairos-io/kairos-sdk/utils"
"github.com/kairos-io/kairos/v2/internal/bus"
@ -14,9 +13,7 @@ import (
"github.com/kairos-io/kairos/v2/pkg/config/collector"
"github.com/kairos-io/kairos/v2/pkg/elementalConfig"
"github.com/kairos-io/kairos/v2/pkg/github"
"github.com/kairos-io/kairos/v2/pkg/luet"
v1 "github.com/kairos-io/kairos/v2/pkg/types/v1"
elementalUtils "github.com/kairos-io/kairos/v2/pkg/utils"
"github.com/mudler/go-pluggable"
log "github.com/sirupsen/logrus"
)
@ -50,8 +47,7 @@ func ListReleases(includePrereleases bool) semver.Collection {
}
func Upgrade(
version, image string, force, debug, strictValidations bool, dirs []string, authUser string,
authPass string, authServer string, authType string, registryToken string, identityToken string, preReleases bool,
version, image string, force, debug, strictValidations bool, dirs []string, preReleases bool,
) error {
bus.Manager.Initialize()
@ -129,22 +125,6 @@ func Upgrade(
upgradeConfig.Logger.SetLevel(log.DebugLevel)
}
// Generate an auth object
auth := &types.AuthConfig{
Username: authUser,
Password: authPass,
ServerAddress: authServer,
Auth: authType,
IdentityToken: identityToken,
RegistryToken: registryToken,
}
// Override the default luet to pass the auth
// Remember to create the temp dir when creating a new luet object. Otherwise, things break with no temp dir.
tmpDir := elementalUtils.GetTempDir(&upgradeConfig.Config, "")
l := luet.NewLuet(luet.WithLogger(upgradeConfig.Logger), luet.WithAuth(auth), luet.WithLuetTempDir(tmpDir))
upgradeConfig.Luet = l
// Generate the upgrade spec
upgradeSpec, err := elementalConfig.NewUpgradeSpec(upgradeConfig.Config)
if err != nil {

65
main.go
View File

@ -4,6 +4,10 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/kairos-io/kairos/v2/pkg/elementalConfig"
v1 "github.com/kairos-io/kairos/v2/pkg/types/v1"
"path/filepath"
"runtime"
"os"
"strings"
@ -62,12 +66,6 @@ var cmds = []*cli.Command{
Name: "image",
Usage: "Specify an full image reference, e.g.: quay.io/some/image:tag",
},
&cli.StringFlag{Name: "auth-username", Usage: "Username to authenticate to registry"},
&cli.StringFlag{Name: "auth-password", Usage: "Password to authenticate to registry"},
&cli.StringFlag{Name: "auth-server-address", Usage: "Authentication server address"},
&cli.StringFlag{Name: "auth-type", Usage: "Auth type"},
&cli.StringFlag{Name: "auth-registry-token", Usage: "Authentication registry token"},
&cli.StringFlag{Name: "auth-identity-token", Usage: "Authentication identity token"},
&cli.BoolFlag{Name: "pre", Usage: "Include pre-releases (rc, beta, alpha)"},
},
Description: `
@ -116,8 +114,6 @@ See https://kairos.io/docs/upgrade/manual/ for documentation.
return agent.Upgrade(
v, c.String("image"), c.Bool("force"), c.Bool("debug"),
c.Bool("strict-validation"), configScanDir,
c.String("auth-username"), c.String("auth-password"), c.String("auth-server-address"),
c.String("auth-type"), c.String("auth-registry-token"), c.String("auth-identity-token"),
c.Bool("pre"),
)
},
@ -141,7 +137,6 @@ Sends a generic event payload with the configuration found in the scanned direct
return agent.Notify(c.Args().First(), dirs)
},
},
{
Name: "start",
Usage: "Starts the kairos agent",
@ -343,7 +338,6 @@ enabled: true`,
},
},
},
{
Name: "interactive-install",
Description: `
@ -400,7 +394,6 @@ This command is meant to be used from the boot GRUB menu, but can be also starte
return agent.ManualInstall(config, options, c.Bool("strict-validation"))
},
},
{
Name: "install",
Usage: "Starts the kairos pairing installation",
@ -434,7 +427,6 @@ See also https://kairos.io/after_install/recovery_mode/ for documentation.
This command is meant to be used from the boot GRUB menu, but can likely be used standalone`,
},
{
Name: "reset",
Action: func(c *cli.Context) error {
@ -491,6 +483,55 @@ The validate command expects a configuration file as its only argument. Local fi
Usage: "Print out Kairos' Cloud Configuration JSON Schema",
Description: `Prints out Kairos' Cloud Configuration JSON Schema`,
},
{
Name: "pull-image",
Description: "Pull remote image to local file",
UsageText: "pull-image [-l] IMAGE TARGET",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "local",
Usage: "Use an image from local cache",
Aliases: []string{"l"},
},
&cli.StringFlag{
Name: "platform",
Usage: "Platform/arch to pull image from",
Value: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
},
},
Before: func(c *cli.Context) error {
if c.Args().Len() != 2 {
cli.HelpPrinter(c.App.Writer, "Either Image or target argument missing\n\n", c.Command)
_ = cli.ShowSubcommandHelp(c)
return fmt.Errorf("")
}
if os.Geteuid() != 0 {
return fmt.Errorf("this command requires root privileges")
}
return nil
},
Action: func(c *cli.Context) error {
image := c.Args().Get(0)
destination, err := filepath.Abs(c.Args().Get(1))
if err != nil {
return fmt.Errorf("invalid path %s", destination)
}
cfg, err := elementalConfig.ReadConfigRun("/etc/elemental")
if err != nil {
return err
}
cfg.Logger.Infof("Starting download and extraction for image %s to %s\n", image, destination)
e := v1.OCIImageExtractor{}
if err = e.ExtractImage(image, destination, c.String("platform"), c.Bool("local")); err != nil {
return err
}
cfg.Logger.Infof("Image %s downloaded and extracted to %s correctly\n", image, destination)
return nil
},
},
}
func main() {

View File

@ -1,412 +0,0 @@
/*
Copyright © 2022 SUSE LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package action
import (
"archive/tar"
"compress/gzip"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"github.com/kairos-io/kairos/v2/pkg/constants"
"github.com/kairos-io/kairos/v2/pkg/elemental"
"github.com/kairos-io/kairos/v2/pkg/partitioner"
v1 "github.com/kairos-io/kairos/v2/pkg/types/v1"
"github.com/kairos-io/kairos/v2/pkg/utils"
)
const (
MB = int64(1024 * 1024)
GB = 1024 * MB
)
func BuildDiskRun(cfg *v1.BuildConfig, spec *v1.RawDiskArchEntry, imgType string, oemLabel string, recoveryLabel string, output string) (err error) {
cfg.Logger.Infof("Building disk image type %s for arch %s", imgType, cfg.Arch)
if len(spec.Packages) == 0 {
msg := fmt.Sprintf("no packages in the config for arch %s", cfg.Arch)
cfg.Logger.Error(msg)
return errors.New(msg)
}
if len(cfg.Config.Repos) == 0 {
msg := "no repositories configured"
cfg.Logger.Error(msg)
return errors.New(msg)
}
if oemLabel == "" {
oemLabel = constants.OEMLabel
}
if recoveryLabel == "" {
recoveryLabel = constants.RecoveryLabel
}
e := elemental.NewElemental(&cfg.Config)
cleanup := utils.NewCleanStack()
defer func() { err = cleanup.Cleanup(err) }()
// baseDir is where we are going install all packages
baseDir, err := utils.TempDir(cfg.Fs, "", "elemental-build-disk-files")
if err != nil {
return err
}
cleanup.Push(func() error { return cfg.Fs.RemoveAll(baseDir) })
// diskTempDir is where we are going to create all the disk parts
diskTempDir, err := utils.TempDir(cfg.Fs, "", "elemental-build-disk-parts")
if err != nil {
return err
}
cleanup.Push(func() error { return cfg.Fs.RemoveAll(diskTempDir) })
rootfsPart := filepath.Join(diskTempDir, "rootfs.part")
oemPart := filepath.Join(diskTempDir, "oem.part")
efiPart := filepath.Join(diskTempDir, "efi.part")
// Extract required packages to basedir
for _, pkg := range spec.Packages {
err = os.MkdirAll(filepath.Join(baseDir, pkg.Target), constants.DirPerm)
if err != nil {
cfg.Logger.Error(err)
return err
}
imgSource, err := v1.NewSrcFromURI(pkg.Name)
if err != nil {
cfg.Logger.Error(err)
return err
}
_, err = e.DumpSource(
filepath.Join(baseDir, pkg.Target),
imgSource,
)
if err != nil {
cfg.Logger.Error(err)
return err
}
}
// Create rootfs.part
err = CreatePart(
cfg,
rootfsPart,
filepath.Join(baseDir, "root"),
recoveryLabel,
constants.LinuxImgFs,
2048*MB,
)
if err != nil {
cfg.Logger.Error(err)
return err
}
// create EFI part
err = CreatePart(
cfg,
efiPart,
"",
constants.EfiLabel,
constants.EfiFs,
20*MB,
)
if err != nil {
cfg.Logger.Error(err)
return err
}
// copy files to efi with mcopy
_, err = cfg.Runner.Run("mcopy", "-s", "-i", efiPart, filepath.Join(baseDir, "efi", "EFI"), "::EFI")
if err != nil {
return err
}
// Create the oem part
// Create the grubenv forcing first boot to be on recovery system
_ = cfg.Fs.Mkdir(filepath.Join(baseDir, "oem"), constants.DirPerm)
err = utils.CopyFile(cfg.Fs, filepath.Join(baseDir, "root", "etc", "cos", "grubenv_firstboot"), filepath.Join(baseDir, "oem", "grubenv"))
if err != nil {
return err
}
err = CreatePart(
cfg,
oemPart,
filepath.Join(baseDir, "oem"),
oemLabel,
constants.LinuxImgFs,
64*MB,
)
if err != nil {
cfg.Logger.Error(err)
return err
}
// Create final image
err = CreateFinalImage(cfg, output, efiPart, oemPart, rootfsPart)
if err != nil {
cfg.Logger.Error(err)
return err
}
switch imgType {
case "raw":
// Nothing to do here
cfg.Logger.Infof("Done! Image created at %s", output)
case "azure":
err = Raw2Azure(output, cfg.Fs, cfg.Logger, false)
if err != nil {
return err
}
cfg.Logger.Infof("Done! Image created at %s", fmt.Sprintf("%s.vhd", output))
case "gce":
err = Raw2Gce(output, cfg.Fs, cfg.Logger, false)
if err != nil {
return err
}
cfg.Logger.Infof("Done! Image created at %s", fmt.Sprintf("%s.tar.gz", output))
}
return err
}
// Raw2Gce transforms an image from RAW format into GCE format
// THIS REMOVES THE SOURCE IMAGE BY DEFAULT
func Raw2Gce(source string, fs v1.FS, logger v1.Logger, keepOldImage bool) error {
// The RAW image file must have a size in an increment of 1 GB. For example, the file must be either 10 GB or 11 GB but not 10.5 GB.
// The disk image filename must be disk.raw.
// The compressed file must be a .tar.gz file that uses gzip compression and the --format=oldgnu option for the tar utility.
logger.Info("Transforming raw image into gce format")
actImg, err := fs.Open(source)
if err != nil {
return err
}
info, err := actImg.Stat()
if err != nil {
return err
}
actualSize := info.Size()
finalSizeGB := actualSize/GB + 1
finalSizeBytes := finalSizeGB * GB
logger.Infof("Resizing img from %d to %d", actualSize, finalSizeBytes)
// REMEMBER TO SEEK!
_, _ = actImg.Seek(0, io.SeekEnd)
_ = actImg.Truncate(finalSizeBytes)
_ = actImg.Close()
// Tar gz the image
logger.Infof("Compressing raw image into a tar.gz")
// Create destination file
file, err := fs.Create(fmt.Sprintf("%s.tar.gz", source))
logger.Debugf(fmt.Sprintf("destination: %s.tar.gz", source))
if err != nil {
return err
}
defer file.Close()
// Create gzip writer
gzipWriter, err := gzip.NewWriterLevel(file, gzip.BestSpeed)
if err != nil {
return err
}
defer gzipWriter.Close()
// Create tarwriter pointing to our gzip writer
tarWriter := tar.NewWriter(gzipWriter)
defer tarWriter.Close()
// Open disk.raw
sourceFile, _ := fs.Open(source)
sourceStat, _ := sourceFile.Stat()
defer sourceFile.Close()
// Add disk.raw file
header := &tar.Header{
Name: sourceStat.Name(),
Size: sourceStat.Size(),
Mode: int64(sourceStat.Mode()),
Format: tar.FormatGNU,
}
// Write header with all the info
err = tarWriter.WriteHeader(header)
if err != nil {
return err
}
// copy the actual data
_, err = io.Copy(tarWriter, sourceFile)
if err != nil {
return err
}
// Remove full raw image, we already got the compressed one
if !keepOldImage {
_ = fs.RemoveAll(source)
}
return nil
}
// Raw2Azure transforms an image from RAW format into Azure format
// THIS REMOVES THE SOURCE IMAGE BY DEFAULT
func Raw2Azure(source string, fs v1.FS, logger v1.Logger, keepOldImage bool) error {
// All VHDs on Azure must have a virtual size aligned to 1 MB (1024 × 1024 bytes)
// The Hyper-V virtual hard disk (VHDX) format isn't supported in Azure, only fixed VHD
logger.Info("Transforming raw image into azure format")
// Copy raw to new image with VHD appended
err := utils.CopyFile(fs, source, fmt.Sprintf("%s.vhd", source))
if err != nil {
return err
}
// Open it
vhdFile, _ := fs.OpenFile(fmt.Sprintf("%s.vhd", source), os.O_APPEND|os.O_WRONLY, 0600)
// Calculate rounded size
info, _ := vhdFile.Stat()
actualSize := info.Size()
finalSizeBytes := ((actualSize + MB - 1) / MB) * MB
// Don't forget to remove 512 bytes for the header that we are going to add afterwards!
finalSizeBytes = finalSizeBytes - 512
// For smaller than 1 MB images, this calculation doesn't work, so we round up to 1 MB
if finalSizeBytes == 0 {
finalSizeBytes = 1*1024*1024 - 512
}
if actualSize != finalSizeBytes {
logger.Infof("Resizing img from %d to %d", actualSize, finalSizeBytes+512)
_, _ = vhdFile.Seek(0, io.SeekEnd)
_ = vhdFile.Truncate(finalSizeBytes)
}
// Transform it to VHD
utils.RawDiskToFixedVhd(vhdFile)
_ = vhdFile.Close()
// Remove raw image
if !keepOldImage {
_ = fs.RemoveAll(source)
}
return nil
}
// CreateFinalImage creates the final image by truncating the image with the proper sizes, concatenating the contents of the
// given parts and creating the partition table on the image
func CreateFinalImage(c *v1.BuildConfig, img string, parts ...string) error {
err := utils.MkdirAll(c.Fs, filepath.Dir(img), constants.DirPerm)
if err != nil {
return err
}
actImg, err := c.Fs.Create(img)
if err != nil {
return err
}
// add 3MB of initial free space to disk, 1MB is for proper alignment, 2MB are for the hybrid legacy boot.
err = actImg.Truncate(3 * MB)
if err != nil {
actImg.Close()
_ = c.Fs.RemoveAll(img)
return err
}
// Seek to the end of the file, so we start copying the files at the end of those 3Mb that we truncated before
_, _ = actImg.Seek(0, io.SeekEnd)
for _, p := range parts {
c.Logger.Debugf("Copying %s", p)
toRead, _ := c.Fs.Open(p)
_, err = io.Copy(actImg, toRead)
if err != nil {
return err
}
}
info, _ := actImg.Stat()
finalSize := info.Size() + (1 * MB)
err = actImg.Truncate(finalSize)
if err != nil {
actImg.Close()
_ = c.Fs.RemoveAll(img)
return err
}
err = actImg.Close()
if err != nil {
_ = c.Fs.RemoveAll(img)
return err
}
// Partition table
/*
Where:
-c indicates change the name of the partition in partnum:name format
-n new partition in partnum:start:end format
-t type of the partition (EF02 bios, EF00 efi and 8300 linux)
*/
out, err := c.Runner.Run("sgdisk", "-n", "1:2048:+2M", "-c", "1:legacy", "-t", "1:EF02", img)
if err != nil {
c.Logger.Errorf("Error from sgdisk: %s", out)
return err
}
_, err = c.Runner.Run("sgdisk", "-n", "2:0:+20M", "-c", "2:UEFI", "-t", "2:EF00", img)
if err != nil {
c.Logger.Errorf("Error from sgdisk: %s", out)
return err
}
_, err = c.Runner.Run("sgdisk", "-n", "3:0:+64M", "-c", "3:oem", "-t", "3:8300", img)
if err != nil {
c.Logger.Errorf("Error from sgdisk: %s", out)
return err
}
_, err = c.Runner.Run("sgdisk", "-n", "4:0:+2048M", "-c", "4:root", "-t", "4:8300", img)
if err != nil {
c.Logger.Errorf("Error from sgdisk: %s", out)
return err
}
return err
}
// CreatePart creates, truncates, and formats an img.part file. if rootDir is passed it will use that as the rootdir for
// the part creation, thus copying the contents into the newly created part file
func CreatePart(c *v1.BuildConfig, img string, rootDir string, label string, fs string, size int64) error {
err := utils.MkdirAll(c.Fs, filepath.Dir(img), constants.DirPerm)
if err != nil {
return err
}
actImg, err := c.Fs.Create(img)
if err != nil {
return err
}
err = actImg.Truncate(size)
if err != nil {
actImg.Close()
_ = c.Fs.RemoveAll(img)
return err
}
err = actImg.Close()
if err != nil {
_ = c.Fs.RemoveAll(img)
return err
}
var extraOpts []string
// Only add the rootDir if it's not empty
if rootDir != "" {
extraOpts = []string{"-d", rootDir}
}
mkfs := partitioner.NewMkfsCall(img, fs, label, c.Runner, extraOpts...)
out, err := mkfs.Apply()
if err != nil {
_ = c.Fs.RemoveAll(img)
c.Logger.Errorf("Error applying mkfs call: %s", out)
return err
}
return err
}

View File

@ -1,287 +0,0 @@
/*
Copyright © 2022 SUSE LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package action
import (
"fmt"
"path/filepath"
"time"
"github.com/kairos-io/kairos/v2/pkg/constants"
"github.com/kairos-io/kairos/v2/pkg/elemental"
"github.com/kairos-io/kairos/v2/pkg/live"
v1 "github.com/kairos-io/kairos/v2/pkg/types/v1"
"github.com/kairos-io/kairos/v2/pkg/utils"
)
type LiveBootloader interface {
PrepareEFI(rootDir, uefiDir string) error
PrepareISO(rootDir, isoDir string) error
}
type BuildISOAction struct {
liveBoot LiveBootloader
cfg *v1.BuildConfig
spec *v1.LiveISO
e *elemental.Elemental
}
type BuildISOActionOption func(a *BuildISOAction)
func WithLiveBoot(l LiveBootloader) BuildISOActionOption {
return func(a *BuildISOAction) {
a.liveBoot = l
}
}
func NewBuildISOAction(cfg *v1.BuildConfig, spec *v1.LiveISO, opts ...BuildISOActionOption) *BuildISOAction {
b := &BuildISOAction{
cfg: cfg,
e: elemental.NewElemental(&cfg.Config),
spec: spec,
liveBoot: live.NewGreenLiveBootLoader(cfg, spec),
}
for _, opt := range opts {
opt(b)
}
return b
}
// BuildISORun will install the system from a given configuration
func (b *BuildISOAction) ISORun() (err error) {
cleanup := utils.NewCleanStack()
defer func() { err = cleanup.Cleanup(err) }()
isoTmpDir, err := utils.TempDir(b.cfg.Fs, "", "elemental-iso")
if err != nil {
return err
}
cleanup.Push(func() error { return b.cfg.Fs.RemoveAll(isoTmpDir) })
rootDir := filepath.Join(isoTmpDir, "rootfs")
err = utils.MkdirAll(b.cfg.Fs, rootDir, constants.DirPerm)
if err != nil {
return err
}
uefiDir := filepath.Join(isoTmpDir, "uefi")
err = utils.MkdirAll(b.cfg.Fs, uefiDir, constants.DirPerm)
if err != nil {
return err
}
isoDir := filepath.Join(isoTmpDir, "iso")
err = utils.MkdirAll(b.cfg.Fs, isoDir, constants.DirPerm)
if err != nil {
return err
}
if b.cfg.OutDir != "" {
err = utils.MkdirAll(b.cfg.Fs, b.cfg.OutDir, constants.DirPerm)
if err != nil {
b.cfg.Logger.Errorf("Failed creating output folder: %s", b.cfg.OutDir)
return err
}
}
b.cfg.Logger.Infof("Preparing squashfs root...")
err = b.applySources(rootDir, b.spec.RootFS...)
if err != nil {
b.cfg.Logger.Errorf("Failed installing OS packages: %v", err)
return err
}
err = utils.CreateDirStructure(b.cfg.Fs, rootDir)
if err != nil {
b.cfg.Logger.Errorf("Failed creating root directory structure: %v", err)
return err
}
b.cfg.Logger.Infof("Preparing EFI image...")
if b.spec.BootloaderInRootFs {
err = b.liveBoot.PrepareEFI(rootDir, uefiDir)
if err != nil {
b.cfg.Logger.Errorf("Failed fetching EFI data: %v", err)
return err
}
}
err = b.applySources(uefiDir, b.spec.UEFI...)
if err != nil {
b.cfg.Logger.Errorf("Failed installing EFI packages: %v", err)
return err
}
b.cfg.Logger.Infof("Preparing ISO image root tree...")
if b.spec.BootloaderInRootFs {
err = b.liveBoot.PrepareISO(rootDir, isoDir)
if err != nil {
b.cfg.Logger.Errorf("Failed fetching bootloader binaries: %v", err)
return err
}
}
err = b.applySources(isoDir, b.spec.Image...)
if err != nil {
b.cfg.Logger.Errorf("Failed installing ISO image packages: %v", err)
return err
}
err = b.prepareISORoot(isoDir, rootDir, uefiDir)
if err != nil {
b.cfg.Logger.Errorf("Failed preparing ISO's root tree: %v", err)
return err
}
b.cfg.Logger.Infof("Creating ISO image...")
err = b.burnISO(isoDir)
if err != nil {
b.cfg.Logger.Errorf("Failed preparing ISO's root tree: %v", err)
return err
}
return err
}
func (b BuildISOAction) prepareISORoot(isoDir string, rootDir string, uefiDir string) error {
kernel, initrd, err := b.e.FindKernelInitrd(rootDir)
if err != nil {
b.cfg.Logger.Error("Could not find kernel and/or initrd")
return err
}
err = utils.MkdirAll(b.cfg.Fs, filepath.Join(isoDir, "boot"), constants.DirPerm)
if err != nil {
return err
}
//TODO document boot/kernel and boot/initrd expectation in bootloader config
b.cfg.Logger.Debugf("Copying Kernel file %s to iso root tree", kernel)
err = utils.CopyFile(b.cfg.Fs, kernel, filepath.Join(isoDir, constants.IsoKernelPath))
if err != nil {
return err
}
b.cfg.Logger.Debugf("Copying initrd file %s to iso root tree", initrd)
err = utils.CopyFile(b.cfg.Fs, initrd, filepath.Join(isoDir, constants.IsoInitrdPath))
if err != nil {
return err
}
b.cfg.Logger.Info("Creating squashfs...")
squashOptions := append(constants.GetDefaultSquashfsOptions(), b.cfg.SquashFsCompressionConfig...)
err = utils.CreateSquashFS(b.cfg.Runner, b.cfg.Logger, rootDir, filepath.Join(isoDir, constants.IsoRootFile), squashOptions)
if err != nil {
return err
}
b.cfg.Logger.Info("Creating EFI image...")
err = b.createEFI(uefiDir, filepath.Join(isoDir, constants.IsoEFIPath))
if err != nil {
return err
}
return nil
}
func (b BuildISOAction) createEFI(root string, img string) error {
efiSize, err := utils.DirSize(b.cfg.Fs, root)
if err != nil {
return err
}
// align efiSize to the next 4MB slot
align := int64(4 * 1024 * 1024)
efiSizeMB := (efiSize/align*align + align) / (1024 * 1024)
err = b.e.CreateFileSystemImage(&v1.Image{
File: img,
Size: uint(efiSizeMB),
FS: constants.EfiFs,
Label: constants.EfiLabel,
})
if err != nil {
return err
}
files, err := b.cfg.Fs.ReadDir(root)
if err != nil {
return err
}
for _, f := range files {
_, err = b.cfg.Runner.Run("mcopy", "-s", "-i", img, filepath.Join(root, f.Name()), "::")
if err != nil {
return err
}
}
return nil
}
func (b BuildISOAction) burnISO(root string) error {
cmd := "xorriso"
var outputFile string
var isoFileName string
if b.cfg.Date {
currTime := time.Now()
isoFileName = fmt.Sprintf("%s.%s.iso", b.cfg.Name, currTime.Format("20060102"))
} else {
isoFileName = fmt.Sprintf("%s.iso", b.cfg.Name)
}
outputFile = isoFileName
if b.cfg.OutDir != "" {
outputFile = filepath.Join(b.cfg.OutDir, outputFile)
}
if exists, _ := utils.Exists(b.cfg.Fs, outputFile); exists {
b.cfg.Logger.Warnf("Overwriting already existing %s", outputFile)
err := b.cfg.Fs.Remove(outputFile)
if err != nil {
return err
}
}
args := []string{
"-volid", b.spec.Label, "-joliet", "on", "-padding", "0",
"-outdev", outputFile, "-map", root, "/", "-chmod", "0755", "--",
}
args = append(args, live.XorrisoBooloaderArgs(root)...)
out, err := b.cfg.Runner.Run(cmd, args...)
b.cfg.Logger.Debugf("Xorriso: %s", string(out))
if err != nil {
return err
}
checksum, err := utils.CalcFileChecksum(b.cfg.Fs, outputFile)
if err != nil {
return fmt.Errorf("checksum computation failed: %w", err)
}
err = b.cfg.Fs.WriteFile(fmt.Sprintf("%s.sha256", outputFile), []byte(fmt.Sprintf("%s %s\n", checksum, isoFileName)), 0644)
if err != nil {
return fmt.Errorf("cannot write checksum file: %w", err)
}
return nil
}
func (b BuildISOAction) applySources(target string, sources ...*v1.ImageSource) error {
for _, src := range sources {
_, err := b.e.DumpSource(target, src)
if err != nil {
return err
}
}
return nil
}

View File

@ -1,572 +0,0 @@
/*
Copyright © 2022 SUSE LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package action_test
import (
"bytes"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"os"
"path/filepath"
"strconv"
dockerArchive "github.com/docker/docker/pkg/archive"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/sirupsen/logrus"
"github.com/twpayne/go-vfs"
"github.com/twpayne/go-vfs/vfst"
"github.com/kairos-io/kairos/v2/pkg/action"
"github.com/kairos-io/kairos/v2/pkg/constants"
"github.com/kairos-io/kairos/v2/pkg/elementalConfig"
v1 "github.com/kairos-io/kairos/v2/pkg/types/v1"
"github.com/kairos-io/kairos/v2/pkg/utils"
v1mock "github.com/kairos-io/kairos/v2/tests/mocks"
)
var _ = Describe("Runtime Actions", func() {
var cfg *v1.BuildConfig
var runner *v1mock.FakeRunner
var fs vfs.FS
var logger v1.Logger
var mounter *v1mock.ErrorMounter
var syscall *v1mock.FakeSyscall
var client *v1mock.FakeHTTPClient
var cloudInit *v1mock.FakeCloudInitRunner
var luet *v1mock.FakeLuet
var cleanup func()
var memLog *bytes.Buffer
BeforeEach(func() {
runner = v1mock.NewFakeRunner()
syscall = &v1mock.FakeSyscall{}
mounter = v1mock.NewErrorMounter()
client = &v1mock.FakeHTTPClient{}
memLog = &bytes.Buffer{}
logger = v1.NewBufferLogger(memLog)
logger.SetLevel(logrus.DebugLevel)
cloudInit = &v1mock.FakeCloudInitRunner{}
luet = &v1mock.FakeLuet{}
fs, cleanup, _ = vfst.NewTestFS(map[string]interface{}{})
cfg = elementalConfig.NewBuildConfig(
elementalConfig.WithFs(fs),
elementalConfig.WithRunner(runner),
elementalConfig.WithLogger(logger),
elementalConfig.WithMounter(mounter),
elementalConfig.WithSyscall(syscall),
elementalConfig.WithClient(client),
elementalConfig.WithCloudInitRunner(cloudInit),
elementalConfig.WithLuet(luet),
)
})
AfterEach(func() {
cleanup()
})
Describe("Build ISO", Label("iso"), func() {
var iso *v1.LiveISO
BeforeEach(func() {
iso = elementalConfig.NewISO()
tmpDir, err := utils.TempDir(fs, "", "test")
Expect(err).ShouldNot(HaveOccurred())
cfg.Date = false
cfg.OutDir = tmpDir
runner.SideEffect = func(cmd string, args ...string) ([]byte, error) {
switch cmd {
case "xorriso":
err := fs.WriteFile(filepath.Join(tmpDir, "elemental.iso"), []byte("profound thoughts"), constants.FilePerm)
return []byte{}, err
default:
return []byte{}, nil
}
}
})
It("Successfully builds an ISO from a Docker image", func() {
rootSrc, _ := v1.NewSrcFromURI("oci:elementalos:latest")
iso.RootFS = []*v1.ImageSource{rootSrc}
uefiSrc, _ := v1.NewSrcFromURI("channel:live/efi")
iso.UEFI = []*v1.ImageSource{uefiSrc}
imageSrc, _ := v1.NewSrcFromURI("channel:live/bootloader")
iso.Image = []*v1.ImageSource{imageSrc}
luet.UnpackSideEffect = func(target string, image string, local bool) (*v1.DockerImageMeta, error) {
bootDir := filepath.Join(target, "boot")
err := utils.MkdirAll(fs, bootDir, constants.DirPerm)
if err != nil {
return nil, err
}
_, err = fs.Create(filepath.Join(bootDir, "vmlinuz"))
if err != nil {
return nil, err
}
_, err = fs.Create(filepath.Join(bootDir, "initrd"))
if err != nil {
return nil, err
}
return nil, nil
}
buildISO := action.NewBuildISOAction(cfg, iso)
err := buildISO.ISORun()
Expect(luet.UnpackCalled()).To(BeTrue())
Expect(luet.UnpackChannelCalled()).To(BeTrue())
Expect(err).ShouldNot(HaveOccurred())
})
It("Successfully builds an ISO from a luet channel including overlayed files", func() {
rootFs := []string{"channel:system/elemental", "dir:/overlay/dir"}
for _, src := range rootFs {
rootSrc, _ := v1.NewSrcFromURI(src)
iso.RootFS = append(iso.RootFS, rootSrc)
}
uefiSrc, _ := v1.NewSrcFromURI("channel:live/efi")
iso.UEFI = []*v1.ImageSource{uefiSrc}
imageSrc, _ := v1.NewSrcFromURI("channel:live/bootloader")
iso.Image = []*v1.ImageSource{imageSrc}
err := utils.MkdirAll(fs, "/overlay/dir/boot", constants.DirPerm)
Expect(err).ShouldNot(HaveOccurred())
_, err = fs.Create("/overlay/dir/boot/vmlinuz")
Expect(err).ShouldNot(HaveOccurred())
_, err = fs.Create("/overlay/dir/boot/initrd")
Expect(err).ShouldNot(HaveOccurred())
buildISO := action.NewBuildISOAction(cfg, iso)
err = buildISO.ISORun()
Expect(luet.UnpackChannelCalled()).To(BeTrue())
Expect(err).ShouldNot(HaveOccurred())
})
It("Successfully builds an ISO using self contained binaries and including overlayed files", func() {
iso.BootloaderInRootFs = true
rootFs := []string{"channel:system/elemental", "dir:/overlay/dir"}
for _, src := range rootFs {
rootSrc, _ := v1.NewSrcFromURI(src)
iso.RootFS = append(iso.RootFS, rootSrc)
}
err := utils.MkdirAll(fs, "/overlay/dir/boot", constants.DirPerm)
Expect(err).ShouldNot(HaveOccurred())
_, err = fs.Create("/overlay/dir/boot/vmlinuz")
Expect(err).ShouldNot(HaveOccurred())
_, err = fs.Create("/overlay/dir/boot/initrd")
Expect(err).ShouldNot(HaveOccurred())
liveBoot := &v1mock.LiveBootLoaderMock{}
buildISO := action.NewBuildISOAction(cfg, iso, action.WithLiveBoot(liveBoot))
err = buildISO.ISORun()
Expect(luet.UnpackChannelCalled()).To(BeTrue())
Expect(err).ShouldNot(HaveOccurred())
})
It("Fails on prepare EFI", func() {
iso.BootloaderInRootFs = true
rootSrc, _ := v1.NewSrcFromURI("channel:system/elemental")
iso.RootFS = append(iso.RootFS, rootSrc)
liveBoot := &v1mock.LiveBootLoaderMock{ErrorEFI: true}
buildISO := action.NewBuildISOAction(cfg, iso, action.WithLiveBoot(liveBoot))
err := buildISO.ISORun()
Expect(luet.UnpackChannelCalled()).To(BeTrue())
Expect(err).Should(HaveOccurred())
})
It("Fails on prepare ISO", func() {
iso.BootloaderInRootFs = true
rootSrc, _ := v1.NewSrcFromURI("channel:system/elemental")
iso.RootFS = append(iso.RootFS, rootSrc)
liveBoot := &v1mock.LiveBootLoaderMock{ErrorISO: true}
buildISO := action.NewBuildISOAction(cfg, iso, action.WithLiveBoot(liveBoot))
err := buildISO.ISORun()
Expect(luet.UnpackChannelCalled()).To(BeTrue())
Expect(err).Should(HaveOccurred())
})
It("Fails if kernel or initrd is not found in rootfs", func() {
rootSrc, _ := v1.NewSrcFromURI("dir:/local/dir")
iso.RootFS = []*v1.ImageSource{rootSrc}
uefiSrc, _ := v1.NewSrcFromURI("channel:live/efi")
iso.UEFI = []*v1.ImageSource{uefiSrc}
imageSrc, _ := v1.NewSrcFromURI("channel:live/bootloader")
iso.Image = []*v1.ImageSource{imageSrc}
err := utils.MkdirAll(fs, "/local/dir/boot", constants.DirPerm)
Expect(err).ShouldNot(HaveOccurred())
By("fails without kernel")
buildISO := action.NewBuildISOAction(cfg, iso)
err = buildISO.ISORun()
Expect(err).Should(HaveOccurred())
By("fails without initrd")
_, err = fs.Create("/local/dir/boot/vmlinuz")
Expect(err).ShouldNot(HaveOccurred())
buildISO = action.NewBuildISOAction(cfg, iso)
err = buildISO.ISORun()
Expect(err).Should(HaveOccurred())
})
It("Fails installing rootfs sources", func() {
rootSrc, _ := v1.NewSrcFromURI("channel:system/elemental")
iso.RootFS = []*v1.ImageSource{rootSrc}
luet.OnUnpackFromChannelError = true
buildISO := action.NewBuildISOAction(cfg, iso)
err := buildISO.ISORun()
Expect(err).Should(HaveOccurred())
Expect(luet.UnpackChannelCalled()).To(BeTrue())
})
It("Fails installing uefi sources", func() {
rootSrc, _ := v1.NewSrcFromURI("docker:elemental:latest")
iso.RootFS = []*v1.ImageSource{rootSrc}
uefiSrc, _ := v1.NewSrcFromURI("channel:live/efi")
iso.UEFI = []*v1.ImageSource{uefiSrc}
luet.OnUnpackFromChannelError = true
buildISO := action.NewBuildISOAction(cfg, iso)
err := buildISO.ISORun()
Expect(err).Should(HaveOccurred())
Expect(luet.UnpackCalled()).To(BeTrue())
Expect(luet.UnpackChannelCalled()).To(BeTrue())
})
It("Fails installing image sources", func() {
rootSrc, _ := v1.NewSrcFromURI("docker:elemental:latest")
iso.RootFS = []*v1.ImageSource{rootSrc}
uefiSrc, _ := v1.NewSrcFromURI("docker:registry.suse.com/custom-uefi:v0.1")
iso.UEFI = []*v1.ImageSource{uefiSrc}
imageSrc, _ := v1.NewSrcFromURI("channel:live/bootloader")
iso.Image = []*v1.ImageSource{imageSrc}
luet.OnUnpackFromChannelError = true
buildISO := action.NewBuildISOAction(cfg, iso)
err := buildISO.ISORun()
Expect(err).Should(HaveOccurred())
Expect(luet.UnpackCalled()).To(BeTrue())
Expect(luet.UnpackChannelCalled()).To(BeTrue())
})
It("Fails on ISO filesystem creation", func() {
rootSrc, _ := v1.NewSrcFromURI("dir:/local/dir")
iso.RootFS = []*v1.ImageSource{rootSrc}
uefiSrc, _ := v1.NewSrcFromURI("channel:live/efi")
iso.UEFI = []*v1.ImageSource{uefiSrc}
imageSrc, _ := v1.NewSrcFromURI("channel:live/bootloader")
iso.Image = []*v1.ImageSource{imageSrc}
err := utils.MkdirAll(fs, "/local/dir/boot", constants.DirPerm)
Expect(err).ShouldNot(HaveOccurred())
_, err = fs.Create("/local/dir/boot/vmlinuz")
Expect(err).ShouldNot(HaveOccurred())
_, err = fs.Create("/local/dir/boot/initrd")
Expect(err).ShouldNot(HaveOccurred())
runner.SideEffect = func(command string, args ...string) ([]byte, error) {
if command == "xorriso" {
return []byte{}, errors.New("Burn ISO error")
}
return []byte{}, nil
}
buildISO := action.NewBuildISOAction(cfg, iso)
err = buildISO.ISORun()
Expect(luet.UnpackChannelCalled()).To(BeTrue())
Expect(err).Should(HaveOccurred())
})
})
Describe("Build disk", Label("disk", "build"), func() {
var rawDisk *v1.RawDisk
BeforeEach(func() {
rawDisk = elementalConfig.NewRawDisk()
rawDisk.X86_64.Packages = []v1.RawDiskPackage{{Name: "oci:what", Target: "what"}}
cfg.Repos = []v1.Repository{{URI: "test"}}
})
It("Sets default labels if empty", func() {
// temp dir for output, otherwise we write to .
outputDir, _ := utils.TempDir(fs, "", "output")
// temp dir for package files, create needed file
filesDir, _ := utils.TempDir(fs, "", "elemental-build-disk-files")
_ = utils.MkdirAll(fs, filepath.Join(filesDir, "root", "etc", "cos"), constants.DirPerm)
_ = fs.WriteFile(filepath.Join(filesDir, "root", "etc", "cos", "grubenv_firstboot"), []byte(""), os.ModePerm)
// temp dir for part files, create parts
partsDir, _ := utils.TempDir(fs, "", "elemental-build-disk-parts")
_ = fs.WriteFile(filepath.Join(partsDir, "rootfs.part"), []byte(""), os.ModePerm)
_ = fs.WriteFile(filepath.Join(partsDir, "oem.part"), []byte(""), os.ModePerm)
_ = fs.WriteFile(filepath.Join(partsDir, "efi.part"), []byte(""), os.ModePerm)
err := action.BuildDiskRun(cfg, rawDisk.X86_64, "raw", "", "", filepath.Join(outputDir, "disk.raw"))
Expect(err).ToNot(HaveOccurred())
_ = fs.RemoveAll(filesDir)
_ = fs.RemoveAll(partsDir)
// Check that we copied all needed files to final image
Expect(memLog.String()).To(ContainSubstring("efi.part"))
Expect(memLog.String()).To(ContainSubstring("rootfs.part"))
Expect(memLog.String()).To(ContainSubstring("oem.part"))
output, err := fs.Stat(filepath.Join(outputDir, "disk.raw"))
Expect(err).ToNot(HaveOccurred())
// Even with empty parts, output image should never be zero due to the truncating
// it should be exactly 20Mb(efi size) + 64Mb(oem size) + 2048Mb(recovery size) + 3Mb(hybrid boot) + 1Mb(GPT)
partsSize := (20 + 64 + 2048 + 3 + 1) * 1024 * 1024
Expect(output.Size()).To(BeNumerically("==", partsSize))
_ = fs.RemoveAll(outputDir)
// Check that mkfs commands set the label properly and copied the proper dirs
err = runner.IncludesCmds([][]string{
{"mkfs.ext2", "-L", constants.RecoveryLabel, "-d", "/tmp/elemental-build-disk-files/root", "/tmp/elemental-build-disk-parts/rootfs.part"},
{"mkfs.vfat", "-n", constants.EfiLabel, "/tmp/elemental-build-disk-parts/efi.part"},
{"mkfs.ext2", "-L", constants.OEMLabel, "-d", "/tmp/elemental-build-disk-files/oem", "/tmp/elemental-build-disk-parts/oem.part"},
// files should be copied to EFI
{"mcopy", "-s", "-i", "/tmp/elemental-build-disk-parts/efi.part", "/tmp/elemental-build-disk-files/efi/EFI", "::EFI"},
})
Expect(err).ToNot(HaveOccurred())
})
It("Builds a raw image", func() {
// temp dir for output, otherwise we write to .
outputDir, _ := utils.TempDir(fs, "", "output")
// temp dir for package files, create needed file
filesDir, _ := utils.TempDir(fs, "", "elemental-build-disk-files")
_ = utils.MkdirAll(fs, filepath.Join(filesDir, "root", "etc", "cos"), constants.DirPerm)
_ = fs.WriteFile(filepath.Join(filesDir, "root", "etc", "cos", "grubenv_firstboot"), []byte(""), os.ModePerm)
// temp dir for part files, create parts
partsDir, _ := utils.TempDir(fs, "", "elemental-build-disk-parts")
_ = fs.WriteFile(filepath.Join(partsDir, "rootfs.part"), []byte(""), os.ModePerm)
_ = fs.WriteFile(filepath.Join(partsDir, "oem.part"), []byte(""), os.ModePerm)
_ = fs.WriteFile(filepath.Join(partsDir, "efi.part"), []byte(""), os.ModePerm)
err := action.BuildDiskRun(cfg, rawDisk.X86_64, "raw", "OEM", "REC", filepath.Join(outputDir, "disk.raw"))
Expect(err).ToNot(HaveOccurred())
_ = fs.RemoveAll(filesDir)
_ = fs.RemoveAll(partsDir)
// Check that we copied all needed files to final image
Expect(memLog.String()).To(ContainSubstring("efi.part"))
Expect(memLog.String()).To(ContainSubstring("rootfs.part"))
Expect(memLog.String()).To(ContainSubstring("oem.part"))
output, err := fs.Stat(filepath.Join(outputDir, "disk.raw"))
Expect(err).ToNot(HaveOccurred())
// Even with empty parts, output image should never be zero due to the truncating
// it should be exactly 20Mb(efi size) + 64Mb(oem size) + 2048Mb(recovery size) + 3Mb(hybrid boot) + 1Mb(GPT)
partsSize := (20 + 64 + 2048 + 3 + 1) * 1024 * 1024
Expect(output.Size()).To(BeNumerically("==", partsSize))
_ = fs.RemoveAll(outputDir)
// Check that mkfs commands set the label properly and copied the proper dirs
err = runner.IncludesCmds([][]string{
{"mkfs.ext2", "-L", "REC", "-d", "/tmp/elemental-build-disk-files/root", "/tmp/elemental-build-disk-parts/rootfs.part"},
{"mkfs.vfat", "-n", constants.EfiLabel, "/tmp/elemental-build-disk-parts/efi.part"},
{"mkfs.ext2", "-L", "OEM", "-d", "/tmp/elemental-build-disk-files/oem", "/tmp/elemental-build-disk-parts/oem.part"},
// files should be copied to EFI
{"mcopy", "-s", "-i", "/tmp/elemental-build-disk-parts/efi.part", "/tmp/elemental-build-disk-files/efi/EFI", "::EFI"},
})
Expect(err).ToNot(HaveOccurred())
})
It("Builds a raw image with GCE output", func() {
// temp dir for output, otherwise we write to .
outputDir, _ := utils.TempDir(fs, "", "output")
// temp dir for package files, create needed file
filesDir, _ := utils.TempDir(fs, "", "elemental-build-disk-files")
_ = utils.MkdirAll(fs, filepath.Join(filesDir, "root", "etc", "cos"), constants.DirPerm)
_ = fs.WriteFile(filepath.Join(filesDir, "root", "etc", "cos", "grubenv_firstboot"), []byte(""), os.ModePerm)
// temp dir for part files, create parts
partsDir, _ := utils.TempDir(fs, "", "elemental-build-disk-parts")
_ = fs.WriteFile(filepath.Join(partsDir, "rootfs.part"), []byte(""), os.ModePerm)
_ = fs.WriteFile(filepath.Join(partsDir, "oem.part"), []byte(""), os.ModePerm)
_ = fs.WriteFile(filepath.Join(partsDir, "efi.part"), []byte(""), os.ModePerm)
err := action.BuildDiskRun(cfg, rawDisk.X86_64, "gce", "OEM", "REC", filepath.Join(outputDir, "disk.raw"))
Expect(err).ToNot(HaveOccurred())
_ = fs.RemoveAll(filesDir)
_ = fs.RemoveAll(partsDir)
// Check that we copied all needed files to final image
Expect(memLog.String()).To(ContainSubstring("efi.part"))
Expect(memLog.String()).To(ContainSubstring("rootfs.part"))
Expect(memLog.String()).To(ContainSubstring("oem.part"))
realPath, _ := fs.RawPath(outputDir)
Expect(dockerArchive.IsArchivePath(filepath.Join(realPath, "disk.raw.tar.gz"))).To(BeTrue())
_ = fs.RemoveAll(outputDir)
// Check that mkfs commands set the label properly and copied the proper dirs
err = runner.IncludesCmds([][]string{
{"mkfs.ext2", "-L", "REC", "-d", "/tmp/elemental-build-disk-files/root", "/tmp/elemental-build-disk-parts/rootfs.part"},
{"mkfs.vfat", "-n", constants.EfiLabel, "/tmp/elemental-build-disk-parts/efi.part"},
{"mkfs.ext2", "-L", "OEM", "-d", "/tmp/elemental-build-disk-files/oem", "/tmp/elemental-build-disk-parts/oem.part"},
// files should be copied to EFI
{"mcopy", "-s", "-i", "/tmp/elemental-build-disk-parts/efi.part", "/tmp/elemental-build-disk-files/efi/EFI", "::EFI"},
})
Expect(err).ToNot(HaveOccurred())
})
It("Builds a raw image with Azure output", func() {
// temp dir for output, otherwise we write to .
outputDir, _ := utils.TempDir(fs, "", "output")
// temp dir for package files, create needed file
filesDir, _ := utils.TempDir(fs, "", "elemental-build-disk-files")
_ = utils.MkdirAll(fs, filepath.Join(filesDir, "root", "etc", "cos"), constants.DirPerm)
_ = fs.WriteFile(filepath.Join(filesDir, "root", "etc", "cos", "grubenv_firstboot"), []byte(""), os.ModePerm)
// temp dir for part files, create parts
partsDir, _ := utils.TempDir(fs, "", "elemental-build-disk-parts")
_ = fs.WriteFile(filepath.Join(partsDir, "rootfs.part"), []byte(""), os.ModePerm)
_ = fs.WriteFile(filepath.Join(partsDir, "oem.part"), []byte(""), os.ModePerm)
_ = fs.WriteFile(filepath.Join(partsDir, "efi.part"), []byte(""), os.ModePerm)
err := action.BuildDiskRun(cfg, rawDisk.X86_64, "azure", "OEM", "REC", filepath.Join(outputDir, "disk.raw"))
Expect(err).ToNot(HaveOccurred())
_ = fs.RemoveAll(filesDir)
_ = fs.RemoveAll(partsDir)
// Check that we copied all needed files to final image
Expect(memLog.String()).To(ContainSubstring("efi.part"))
Expect(memLog.String()).To(ContainSubstring("rootfs.part"))
Expect(memLog.String()).To(ContainSubstring("oem.part"))
f, _ := fs.Open(filepath.Join(outputDir, "disk.raw.vhd"))
info, _ := f.Stat()
// Dump the header from the file into our VHDHeader
buff := make([]byte, 512)
_, _ = f.ReadAt(buff, info.Size()-512)
_ = f.Close()
header := utils.VHDHeader{}
err = binary.Read(bytes.NewBuffer(buff[:]), binary.BigEndian, &header)
Expect(err).ToNot(HaveOccurred())
// Just check the fields that we know the value of, that should indicate that the header is valid
Expect(hex.EncodeToString(header.DiskType[:])).To(Equal("00000002"))
Expect(hex.EncodeToString(header.Features[:])).To(Equal("00000002"))
Expect(hex.EncodeToString(header.DataOffset[:])).To(Equal("ffffffffffffffff"))
_ = fs.RemoveAll(outputDir)
// Check that mkfs commands set the label properly and copied the proper dirs
err = runner.IncludesCmds([][]string{
{"mkfs.ext2", "-L", "REC", "-d", "/tmp/elemental-build-disk-files/root", "/tmp/elemental-build-disk-parts/rootfs.part"},
{"mkfs.vfat", "-n", constants.EfiLabel, "/tmp/elemental-build-disk-parts/efi.part"},
{"mkfs.ext2", "-L", "OEM", "-d", "/tmp/elemental-build-disk-files/oem", "/tmp/elemental-build-disk-parts/oem.part"},
// files should be copied to EFI
{"mcopy", "-s", "-i", "/tmp/elemental-build-disk-parts/efi.part", "/tmp/elemental-build-disk-files/efi/EFI", "::EFI"},
})
Expect(err).ToNot(HaveOccurred())
})
It("Transforms raw image into GCE image", Label("gce"), func() {
tmpDir, err := utils.TempDir(fs, "", "")
defer fs.RemoveAll(tmpDir)
Expect(err).ToNot(HaveOccurred())
Expect(err).ToNot(HaveOccurred())
f, err := fs.Create(filepath.Join(tmpDir, "disk.raw"))
Expect(err).ToNot(HaveOccurred())
// Set a non rounded size
f.Truncate(34 * 1024 * 1024)
f.Close()
err = action.Raw2Gce(filepath.Join(tmpDir, "disk.raw"), fs, logger, false)
Expect(err).ToNot(HaveOccurred())
// Log should have the rounded size (1Gb)
Expect(memLog.String()).To(ContainSubstring(strconv.Itoa(1 * 1024 * 1024 * 1024)))
// Should be a tar file
realPath, _ := fs.RawPath(tmpDir)
Expect(dockerArchive.IsArchivePath(filepath.Join(realPath, "disk.raw.tar.gz"))).To(BeTrue())
})
It("Transforms raw image into Azure image", func() {
tmpDir, err := utils.TempDir(fs, "", "")
defer fs.RemoveAll(tmpDir)
Expect(err).ToNot(HaveOccurred())
Expect(err).ToNot(HaveOccurred())
f, err := fs.Create(filepath.Join(tmpDir, "disk.raw"))
Expect(err).ToNot(HaveOccurred())
// write something
_ = f.Truncate(23 * 1024 * 1024)
_ = f.Close()
err = action.Raw2Azure(filepath.Join(tmpDir, "disk.raw"), fs, logger, true)
Expect(err).ToNot(HaveOccurred())
info, err := fs.Stat(filepath.Join(tmpDir, "disk.raw.vhd"))
Expect(err).ToNot(HaveOccurred())
// Should have be rounded up to the next MB
Expect(info.Size()).To(BeNumerically("==", 23*1024*1024))
// Read the header
f, _ = fs.Open(filepath.Join(tmpDir, "disk.raw.vhd"))
info, _ = f.Stat()
// Dump the header from the file into our VHDHeader
buff := make([]byte, 512)
_, _ = f.ReadAt(buff, info.Size()-512)
_ = f.Close()
header := utils.VHDHeader{}
err = binary.Read(bytes.NewBuffer(buff[:]), binary.BigEndian, &header)
Expect(err).ToNot(HaveOccurred())
// Just check the fields that we know the value of, that should indicate that the header is valid
Expect(hex.EncodeToString(header.DiskType[:])).To(Equal("00000002"))
Expect(hex.EncodeToString(header.Features[:])).To(Equal("00000002"))
Expect(hex.EncodeToString(header.DataOffset[:])).To(Equal("ffffffffffffffff"))
})
It("Transforms raw image into Azure image (tiny image)", func() {
// This tests that the resize works for tiny images
// Not sure if we ever will encounter them (less than 1 Mb images?) but just in case
tmpDir, err := utils.TempDir(fs, "", "")
defer fs.RemoveAll(tmpDir)
Expect(err).ToNot(HaveOccurred())
Expect(err).ToNot(HaveOccurred())
f, err := fs.Create(filepath.Join(tmpDir, "disk.raw"))
Expect(err).ToNot(HaveOccurred())
// write something
_, _ = f.WriteString("Hi")
_ = f.Close()
err = action.Raw2Azure(filepath.Join(tmpDir, "disk.raw"), fs, logger, true)
Expect(err).ToNot(HaveOccurred())
info, err := fs.Stat(filepath.Join(tmpDir, "disk.raw"))
Expect(err).ToNot(HaveOccurred())
// Should be smaller than 1Mb
Expect(info.Size()).To(BeNumerically("<", 1*1024*1024))
info, err = fs.Stat(filepath.Join(tmpDir, "disk.raw.vhd"))
Expect(err).ToNot(HaveOccurred())
// Should have be rounded up to the next MB
Expect(info.Size()).To(BeNumerically("==", 1*1024*1024))
// Read the header
f, _ = fs.Open(filepath.Join(tmpDir, "disk.raw.vhd"))
info, _ = f.Stat()
// Dump the header from the file into our VHDHeader
buff := make([]byte, 512)
_, _ = f.ReadAt(buff, info.Size()-512)
_ = f.Close()
header := utils.VHDHeader{}
err = binary.Read(bytes.NewBuffer(buff[:]), binary.BigEndian, &header)
Expect(err).ToNot(HaveOccurred())
// Just check the fields that we know the value of, that should indicate that the header is valid
Expect(hex.EncodeToString(header.DiskType[:])).To(Equal("00000002"))
Expect(hex.EncodeToString(header.Features[:])).To(Equal("00000002"))
Expect(hex.EncodeToString(header.DataOffset[:])).To(Equal("ffffffffffffffff"))
})
It("Fails if the specs does not have packages", func() {
rawDisk.X86_64.Packages = []v1.RawDiskPackage{}
err := action.BuildDiskRun(cfg, rawDisk.X86_64, "raw", "OEM", "REC", "disk.raw")
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("no packages in the config for arch %s", cfg.Arch)))
})
It("Fails if config has no repos", func() {
cfg.Repos = []v1.Repository{}
err := action.BuildDiskRun(cfg, rawDisk.X86_64, "raw", "OEM", "REC", "disk.raw")
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("no repositories configured"))
})
})
})

View File

@ -52,6 +52,7 @@ var _ = Describe("Install action tests", func() {
var cleanup func()
var memLog *bytes.Buffer
var ghwTest v1mock.GhwMock
var extractor *v1mock.FakeImageExtractor
BeforeEach(func() {
runner = v1mock.NewFakeRunner()
@ -60,6 +61,7 @@ var _ = Describe("Install action tests", func() {
client = &v1mock.FakeHTTPClient{}
memLog = &bytes.Buffer{}
logger = v1.NewBufferLogger(memLog)
extractor = v1mock.NewFakeImageExtractor(logger)
//logger.SetLevel(v1.DebugLevel())
var err error
fs, cleanup, err = vfst.NewTestFS(map[string]interface{}{})
@ -74,6 +76,7 @@ var _ = Describe("Install action tests", func() {
conf.WithSyscall(syscall),
conf.WithClient(client),
conf.WithCloudInitRunner(cloudInit),
conf.WithImageExtractor(extractor),
)
})
@ -259,10 +262,7 @@ var _ = Describe("Install action tests", func() {
It("Successfully installs a docker image", Label("docker"), func() {
spec.Target = device
spec.Active.Source = v1.NewDockerSrc("my/image:latest")
luet := v1mock.NewFakeLuet()
config.Luet = luet
Expect(installer.Run()).To(BeNil())
Expect(luet.UnpackCalled()).To(BeTrue())
})
It("Successfully installs and adds remote cloud-config", Label("cloud-config"), func() {
@ -343,12 +343,11 @@ var _ = Describe("Install action tests", func() {
It("Fails if luet fails to unpack image", Label("image", "luet", "unpack"), func() {
spec.Target = device
extractor.SideEffect = func(imageRef, destination, platformRef string, local bool) error {
return fmt.Errorf("error")
}
spec.Active.Source = v1.NewDockerSrc("my/image:latest")
luet := v1mock.NewFakeLuet()
luet.OnUnpackError = true
config.Luet = luet
Expect(installer.Run()).NotTo(BeNil())
Expect(luet.UnpackCalled()).To(BeTrue())
})
It("Fails if requested remote cloud config can't be downloaded", Label("cloud-config"), func() {

View File

@ -19,6 +19,7 @@ package action_test
import (
"bytes"
"errors"
"fmt"
"path/filepath"
"regexp"
@ -47,6 +48,7 @@ var _ = Describe("Reset action tests", func() {
var cleanup func()
var memLog *bytes.Buffer
var ghwTest v1mock.GhwMock
var extractor *v1mock.FakeImageExtractor
BeforeEach(func() {
runner = v1mock.NewFakeRunner()
@ -55,6 +57,7 @@ var _ = Describe("Reset action tests", func() {
client = &v1mock.FakeHTTPClient{}
memLog = &bytes.Buffer{}
logger = v1.NewBufferLogger(memLog)
extractor = v1mock.NewFakeImageExtractor(logger)
var err error
fs, cleanup, err = vfst.NewTestFS(map[string]interface{}{})
Expect(err).Should(BeNil())
@ -68,6 +71,7 @@ var _ = Describe("Reset action tests", func() {
conf.WithSyscall(syscall),
conf.WithClient(client),
conf.WithCloudInitRunner(cloudInit),
conf.WithImageExtractor(extractor),
)
})
@ -186,17 +190,8 @@ var _ = Describe("Reset action tests", func() {
})
It("Successfully resets from a docker image", Label("docker"), func() {
spec.Active.Source = v1.NewDockerSrc("my/image:latest")
luet := v1mock.NewFakeLuet()
config.Luet = luet
Expect(reset.Run()).To(BeNil())
Expect(luet.UnpackCalled()).To(BeTrue())
})
It("Successfully resets from a channel package", Label("channel"), func() {
spec.Active.Source = v1.NewChannelSrc("system/cos")
luet := v1mock.NewFakeLuet()
config.Luet = luet
Expect(reset.Run()).To(BeNil())
Expect(luet.UnpackChannelCalled()).To(BeTrue())
})
It("Fails installing grub", func() {
cmdFail = utils.FindCommand("grub2-install", []string{"grub2-install", "grub-install"})
@ -227,11 +222,10 @@ var _ = Describe("Reset action tests", func() {
})
It("Fails unpacking docker image ", func() {
spec.Active.Source = v1.NewDockerSrc("my/image:latest")
luet := v1mock.NewFakeLuet()
luet.OnUnpackError = true
config.Luet = luet
extractor.SideEffect = func(imageRef, destination, platformRef string, local bool) error {
return fmt.Errorf("error")
}
Expect(reset.Run()).NotTo(BeNil())
Expect(luet.UnpackCalled()).To(BeTrue())
})
})
})

View File

@ -160,13 +160,6 @@ func (u *UpgradeAction) Run() (err error) {
umount, err = e.MountRWPartition(persistentPart)
if err != nil {
u.config.Logger.Warn("could not mount persistent partition: %s", err.Error())
} else {
// Set luet tempdir
tmpdir := utils.GetTempDir(&u.config.Config, "")
u.config.Luet.SetTempDir(tmpdir)
// Remove the tmpdir before unmounting
cleanup.Push(func() error { return u.config.Fs.RemoveAll(tmpdir) })
cleanup.Push(umount)
}
}
}

View File

@ -47,6 +47,7 @@ var _ = Describe("Runtime Actions", func() {
var cleanup func()
var memLog *bytes.Buffer
var ghwTest v1mock.GhwMock
var extractor *v1mock.FakeImageExtractor
BeforeEach(func() {
runner = v1mock.NewFakeRunner()
@ -55,6 +56,8 @@ var _ = Describe("Runtime Actions", func() {
client = &v1mock.FakeHTTPClient{}
memLog = &bytes.Buffer{}
logger = v1.NewBufferLogger(memLog)
logger.SetLevel(logrus.DebugLevel)
extractor = v1mock.NewFakeImageExtractor(logger)
var err error
fs, cleanup, err = vfst.NewTestFS(map[string]interface{}{})
Expect(err).Should(BeNil())
@ -68,16 +71,19 @@ var _ = Describe("Runtime Actions", func() {
conf.WithSyscall(syscall),
conf.WithClient(client),
conf.WithCloudInitRunner(cloudInit),
conf.WithImageExtractor(extractor),
conf.WithPlatform("linux/amd64"),
)
})
AfterEach(func() { cleanup() })
AfterEach(func() {
cleanup()
})
Describe("Upgrade Action", Label("upgrade"), func() {
var spec *v1.UpgradeSpec
var upgrade *action.UpgradeAction
var memLog *bytes.Buffer
var l *v1mock.FakeLuet
activeImg := fmt.Sprintf("%s/cOS/%s", constants.RunningStateDir, constants.ActiveImgFile)
passiveImg := fmt.Sprintf("%s/cOS/%s", constants.RunningStateDir, constants.PassiveImgFile)
recoveryImgSquash := fmt.Sprintf("%s/cOS/%s", constants.LiveDir, constants.RecoverySquashFile)
@ -86,10 +92,10 @@ var _ = Describe("Runtime Actions", func() {
BeforeEach(func() {
memLog = &bytes.Buffer{}
logger = v1.NewBufferLogger(memLog)
extractor = v1mock.NewFakeImageExtractor(logger)
config.Logger = logger
config.ImageExtractor = extractor
logger.SetLevel(logrus.DebugLevel)
l = &v1mock.FakeLuet{}
config.Luet = l
// Create paths used by tests
utils.MkdirAll(fs, fmt.Sprintf("%s/cOS", constants.RunningStateDir), constants.DirPerm)
@ -140,9 +146,6 @@ var _ = Describe("Runtime Actions", func() {
spec, err = conf.NewUpgradeSpec(config.Config)
Expect(err).ShouldNot(HaveOccurred())
spec.Active.Source = v1.NewChannelSrc("system/cos-config")
spec.Active.Size = 16
err = utils.MkdirAll(config.Fs, filepath.Join(spec.Active.MountPoint, "etc"), constants.DirPerm)
Expect(err).ShouldNot(HaveOccurred())
@ -153,6 +156,10 @@ var _ = Describe("Runtime Actions", func() {
)
Expect(err).ShouldNot(HaveOccurred())
spec.Active.Size = 10
spec.Passive.Size = 10
spec.Recovery.Size = 10
runner.SideEffect = func(command string, args ...string) ([]byte, error) {
if command == "cat" && args[0] == "/proc/cmdline" {
return []byte(constants.ActiveLabel), nil
@ -181,6 +188,7 @@ var _ = Describe("Runtime Actions", func() {
AfterEach(func() {
_ = fs.RemoveAll(activeImg)
_ = fs.RemoveAll(passiveImg)
mounter.Unmount("device2")
})
It("Fails if some hook fails and strict is set", func() {
runner.SideEffect = func(command string, args ...string) ([]byte, error) {
@ -203,9 +211,6 @@ var _ = Describe("Runtime Actions", func() {
err := upgrade.Run()
Expect(err).ToNot(HaveOccurred())
// Check luet was called to unpack a docker image
Expect(l.UnpackCalled()).To(BeTrue())
// Check that the rebrand worked with our os-release value
Expect(memLog).To(ContainSubstring("default_menu_entry=TESTOS"))
@ -235,15 +240,14 @@ var _ = Describe("Runtime Actions", func() {
spec.Active.Source = v1.NewDockerSrc("alpine")
config.Reboot = true
upgrade = action.NewUpgradeAction(config, spec)
By("Upgrading")
err := upgrade.Run()
Expect(err).ToNot(HaveOccurred())
// Check luet was called to unpack a docker image
Expect(l.UnpackCalled()).To(BeTrue())
By("Checking the log")
// Check that the rebrand worked with our os-release value
Expect(memLog).To(ContainSubstring("default_menu_entry=TESTOS"))
By("checking active image")
// This should be the new image
info, err := fs.Stat(activeImg)
Expect(err).ToNot(HaveOccurred())
@ -251,6 +255,7 @@ var _ = Describe("Runtime Actions", func() {
Expect(info.Size()).To(BeNumerically("==", int64(spec.Active.Size*1024*1024)))
Expect(info.IsDir()).To(BeFalse())
By("Checking passive image")
// Should have backed up active to passive
info, err = fs.Stat(passiveImg)
Expect(err).ToNot(HaveOccurred())
@ -261,10 +266,11 @@ var _ = Describe("Runtime Actions", func() {
f, _ := fs.ReadFile(passiveImg)
// This should be a backup so it should read active
Expect(f).To(ContainSubstring("active"))
By("checking transition image")
// Expect transition image to be gone
_, err = fs.Stat(spec.Active.File)
Expect(err).To(HaveOccurred())
By("checking it called reboot")
Expect(runner.IncludesCmds([][]string{{"reboot", "-f"}})).To(BeNil())
})
It("Successfully powers off after upgrade from docker image", Label("docker"), func() {
@ -274,9 +280,6 @@ var _ = Describe("Runtime Actions", func() {
err := upgrade.Run()
Expect(err).ToNot(HaveOccurred())
// Check luet was called to unpack a docker image
Expect(l.UnpackCalled()).To(BeTrue())
// Check that the rebrand worked with our os-release value
Expect(memLog).To(ContainSubstring("default_menu_entry=TESTOS"))
@ -343,41 +346,6 @@ var _ = Describe("Runtime Actions", func() {
Expect(err).To(HaveOccurred())
})
It("Successfully upgrades from channel upgrade", Label("channel"), func() {
upgrade = action.NewUpgradeAction(config, spec)
err := upgrade.Run()
Expect(err).ToNot(HaveOccurred())
// Check that the rebrand worked with our os-release value
Expect(memLog).To(ContainSubstring("default_menu_entry=TESTOS"))
// Not much that we can create here as the dir copy was done on the real os, but we do the rest of the ops on a mem one
// This should be the new image
// Should probably do well in mounting the image and checking contents to make sure everything worked
info, err := fs.Stat(activeImg)
Expect(err).ToNot(HaveOccurred())
// Image size should not be empty
Expect(info.Size()).To(BeNumerically("==", int64(spec.Active.Size*1024*1024)))
Expect(info.IsDir()).To(BeFalse())
// Should have backed up active to passive
info, err = fs.Stat(passiveImg)
Expect(err).ToNot(HaveOccurred())
// Should be an really small image as it should only contain our text
// As this was generated by us at the start test and moved by the upgrade from active.iomg
Expect(info.Size()).To(BeNumerically(">", 0))
Expect(info.Size()).To(BeNumerically("<", int64(spec.Active.Size*1024*1024)))
f, _ := fs.ReadFile(passiveImg)
// This should be a backup so it should read active
Expect(f).To(ContainSubstring("active"))
// Expect transition image to be gone
_, err = fs.Stat(spec.Active.File)
Expect(err).To(HaveOccurred())
})
It("Successfully upgrades with cosign", Pending, Label("channel", "cosign"), func() {})
It("Successfully upgrades with mtree", Pending, Label("channel", "mtree"), func() {})
It("Successfully upgrades with strict", Pending, Label("channel", "strict"), func() {})
})
Describe(fmt.Sprintf("Booting from %s", constants.PassiveLabel), Label("passive_label"), func() {
var err error
@ -385,9 +353,6 @@ var _ = Describe("Runtime Actions", func() {
spec, err = conf.NewUpgradeSpec(config.Config)
Expect(err).ShouldNot(HaveOccurred())
spec.Active.Source = v1.NewChannelSrc("system/cos-config")
spec.Active.Size = 16
err = utils.MkdirAll(config.Fs, filepath.Join(spec.Active.MountPoint, "etc"), constants.DirPerm)
Expect(err).ShouldNot(HaveOccurred())
@ -398,6 +363,10 @@ var _ = Describe("Runtime Actions", func() {
)
Expect(err).ShouldNot(HaveOccurred())
spec.Active.Size = 10
spec.Passive.Size = 10
spec.Recovery.Size = 10
runner.SideEffect = func(command string, args ...string) ([]byte, error) {
if command == "cat" && args[0] == "/proc/cmdline" {
return []byte(constants.PassiveLabel), nil
@ -465,10 +434,11 @@ var _ = Describe("Runtime Actions", func() {
spec, err = conf.NewUpgradeSpec(config.Config)
Expect(err).ShouldNot(HaveOccurred())
spec.Active.Size = 10
spec.Passive.Size = 10
spec.Recovery.Size = 10
spec.RecoveryUpgrade = true
spec.Recovery.Source = v1.NewChannelSrc("system/cos-config")
spec.Recovery.Size = 16
runner.SideEffect = func(command string, args ...string) ([]byte, error) {
if command == "cat" && args[0] == "/proc/cmdline" {
@ -503,8 +473,6 @@ var _ = Describe("Runtime Actions", func() {
err = upgrade.Run()
Expect(err).ToNot(HaveOccurred())
Expect(l.UnpackCalled()).To(BeTrue())
// This should be the new image
info, err = fs.Stat(recoveryImgSquash)
Expect(err).ToNot(HaveOccurred())
@ -541,35 +509,6 @@ var _ = Describe("Runtime Actions", func() {
Expect(err).To(HaveOccurred())
})
It("Successfully upgrades recovery from channel upgrade", Label("channel"), func() {
// This should be the old image
info, err := fs.Stat(recoveryImgSquash)
Expect(err).ToNot(HaveOccurred())
// Image size should be empty
Expect(info.Size()).To(BeNumerically(">", 0))
Expect(info.IsDir()).To(BeFalse())
f, _ := fs.ReadFile(recoveryImgSquash)
Expect(f).To(ContainSubstring("recovery"))
upgrade = action.NewUpgradeAction(config, spec)
err = upgrade.Run()
Expect(err).ToNot(HaveOccurred())
Expect(l.UnpackChannelCalled()).To(BeTrue())
// This should be the new image
info, err = fs.Stat(recoveryImgSquash)
Expect(err).ToNot(HaveOccurred())
// Image size should be empty
Expect(info.Size()).To(BeNumerically("==", 0))
Expect(info.IsDir()).To(BeFalse())
f, _ = fs.ReadFile(recoveryImgSquash)
Expect(f).ToNot(ContainSubstring("recovery"))
// Transition squash should not exist
info, err = fs.Stat(spec.Recovery.File)
Expect(err).To(HaveOccurred())
})
})
Describe("Not using squashfs", Label("non-squashfs"), func() {
var err error
@ -581,9 +520,11 @@ var _ = Describe("Runtime Actions", func() {
spec, err = conf.NewUpgradeSpec(config.Config)
Expect(err).ShouldNot(HaveOccurred())
spec.Active.Size = 10
spec.Passive.Size = 10
spec.Recovery.Size = 10
spec.RecoveryUpgrade = true
spec.Recovery.Source = v1.NewChannelSrc("system/cos-config")
spec.Recovery.Size = 16
runner.SideEffect = func(command string, args ...string) ([]byte, error) {
if command == "cat" && args[0] == "/proc/cmdline" {
@ -619,8 +560,6 @@ var _ = Describe("Runtime Actions", func() {
err = upgrade.Run()
Expect(err).ToNot(HaveOccurred())
Expect(l.UnpackCalled()).To(BeTrue())
// Should have created recovery image
info, err = fs.Stat(recoveryImg)
Expect(err).ToNot(HaveOccurred())
@ -655,35 +594,6 @@ var _ = Describe("Runtime Actions", func() {
info, err = fs.Stat(spec.Recovery.File)
Expect(err).To(HaveOccurred())
})
It("Successfully upgrades recovery from channel upgrade", Label("channel"), func() {
// This should be the old image
info, err := fs.Stat(recoveryImg)
Expect(err).ToNot(HaveOccurred())
// Image size should not be empty
Expect(info.Size()).To(BeNumerically(">", 0))
Expect(info.Size()).To(BeNumerically("<", int64(spec.Recovery.Size*1024*1024)))
Expect(info.IsDir()).To(BeFalse())
f, _ := fs.ReadFile(recoveryImg)
Expect(f).To(ContainSubstring("recovery"))
upgrade = action.NewUpgradeAction(config, spec)
err = upgrade.Run()
Expect(err).ToNot(HaveOccurred())
Expect(l.UnpackChannelCalled()).To(BeTrue())
// Should have created recovery image
info, err = fs.Stat(recoveryImg)
Expect(err).ToNot(HaveOccurred())
// Should have default image size
Expect(info.Size()).To(BeNumerically("==", int64(spec.Recovery.Size*1024*1024)))
// Expect the rest of the images to not be there
for _, img := range []string{activeImg, passiveImg, recoveryImgSquash} {
_, err := fs.Stat(img)
Expect(err).To(HaveOccurred())
}
})
})
})
})

View File

@ -19,8 +19,6 @@ package constants
import (
"fmt"
"os"
"path/filepath"
"strings"
)
const (
@ -50,7 +48,6 @@ const (
LinuxImgFs = "ext2"
SquashFs = "squashfs"
EfiFs = "vfat"
BiosFs = ""
EfiSize = uint(64)
OEMSize = uint(64)
StateSize = uint(15360)
@ -59,7 +56,6 @@ const (
BiosSize = uint(1)
ImgSize = uint(3072)
HTTPTimeout = 60
PartStage = "partitioning"
LiveDir = "/run/initramfs/live"
RecoveryDir = "/run/cos/recovery"
StateDir = "/run/cos/state"
@ -75,7 +71,6 @@ const (
PassiveImgFile = "passive.img"
RecoveryImgFile = "recovery.img"
IsoBaseTree = "/run/rootfsbase"
CosSetup = "/usr/bin/cos-setup"
AfterInstallChrootHook = "after-install-chroot"
AfterInstallHook = "after-install"
BeforeInstallHook = "before-install"
@ -85,16 +80,9 @@ const (
AfterUpgradeChrootHook = "after-upgrade-chroot"
AfterUpgradeHook = "after-upgrade"
BeforeUpgradeHook = "before-upgrade"
LuetCosignPlugin = "luet-cosign"
LuetMtreePlugin = "luet-mtree"
LuetDefaultRepoURI = "quay.io/costoolkit/releases-green"
LuetRepoMaxPrio = 1
LuetDefaultRepoPrio = 90
UpgradeActive = "active"
UpgradeRecovery = "recovery"
ChannelSource = "system/cos"
TransitionImgFile = "transition.img"
TransitionSquashFile = "transition.squashfs"
RunningStateDir = "/run/initramfs/cos-state" // TODO: converge this constant with StateDir/RecoveryDir in dracut module from cos-toolkit
RunningRecoveryStateDir = "/run/initramfs/isoscan" // TODO: converge this constant with StateDir/RecoveryDir in dracut module from cos-toolkit
ActiveImgName = "active"
@ -142,42 +130,6 @@ func GetDefaultSquashfsCompressionOptions() []string {
return []string{"-comp", "gzip"}
}
func GetDefaultXorrisoBooloaderArgs(root, bootFile, bootCatalog, hybridMBR string) []string {
args := []string{}
// TODO: make this detection more robust or explicit
// Assume ISOLINUX bootloader is used if boot file is includes 'isolinux'
// in its name, otherwise assume an eltorito based grub2 setup
if strings.Contains(bootFile, "isolinux") {
args = append(args, []string{
"-boot_image", "isolinux", fmt.Sprintf("bin_path=%s", bootFile),
"-boot_image", "isolinux", fmt.Sprintf("system_area=%s/%s", root, hybridMBR),
"-boot_image", "isolinux", "partition_table=on",
}...)
} else {
args = append(args, []string{
"-boot_image", "grub", fmt.Sprintf("bin_path=%s", bootFile),
"-boot_image", "grub", fmt.Sprintf("grub2_mbr=%s/%s", root, hybridMBR),
"-boot_image", "grub", "grub2_boot_info=on",
}...)
}
args = append(args, []string{
"-boot_image", "any", "partition_offset=16",
"-boot_image", "any", fmt.Sprintf("cat_path=%s", bootCatalog),
"-boot_image", "any", "cat_hidden=on",
"-boot_image", "any", "boot_info_table=on",
"-boot_image", "any", "platform_id=0x00",
"-boot_image", "any", "emul_type=no_emulation",
"-boot_image", "any", "load_size=2048",
"-append_partition", "2", "0xef", filepath.Join(root, IsoEFIPath),
"-boot_image", "any", "next",
"-boot_image", "any", "efi_path=--interval:appended_partition_2:all::",
"-boot_image", "any", "platform_id=0xef",
"-boot_image", "any", "emul_type=no_emulation",
}...)
return args
}
func GetBuildDiskDefaultPackages() map[string]string {
return map[string]string{
"channel:system/grub2-efi-image": "efi",
@ -187,70 +139,6 @@ func GetBuildDiskDefaultPackages() map[string]string {
}
}
// GetRunKeyEnvMap returns environment variable bindings to RunConfig data
func GetRunKeyEnvMap() map[string]string {
return map[string]string{
"poweroff": "POWEROFF",
"reboot": "REBOOT",
"strict": "STRICT",
"eject-cd": "EJECT_CD",
}
}
// GetInstallKeyEnvMap returns environment variable bindings to InstallSpec data
func GetInstallKeyEnvMap() map[string]string {
return map[string]string{
"target": "TARGET",
"system.uri": "SYSTEM",
"recovery-system.uri": "RECOVERY_SYSTEM",
"cloud-init": "CLOUD_INIT",
"iso": "ISO",
"firmware": "FIRMWARE",
"part-table": "PART_TABLE",
"no-format": "NO_FORMAT",
"tty": "TTY",
"grub-entry-name": "GRUB_ENTRY_NAME",
}
}
// GetResetKeyEnvMap returns environment variable bindings to ResetSpec data
func GetResetKeyEnvMap() map[string]string {
return map[string]string{
"target": "TARGET",
"system.uri": "SYSTEM",
"tty": "TTY",
"grub-entry-name": "GRUB_ENTRY_NAME",
}
}
// GetUpgradeKeyEnvMap returns environment variable bindings to UpgradeSpec data
func GetUpgradeKeyEnvMap() map[string]string {
return map[string]string{
"recovery": "RECOVERY",
"system.uri": "SYSTEM",
"recovery-system.uri": "RECOVERY_SYSTEM",
}
}
// GetBuildKeyEnvMap returns environment variable bindings to BuildConfig data
func GetBuildKeyEnvMap() map[string]string {
return map[string]string{
"name": "NAME",
}
}
// GetISOKeyEnvMap returns environment variable bindings to LiveISO data
func GetISOKeyEnvMap() map[string]string {
// None for the time being
return map[string]string{}
}
// GetDiskKeyEnvMap returns environment variable bindings to RawDisk data
func GetDiskKeyEnvMap() map[string]string {
// None for the time being
return map[string]string{}
}
func GetGrubFilePaths(arch string) []string {
var archPath string
switch arch {

View File

@ -363,7 +363,7 @@ func (e *Elemental) DumpSource(target string, imgSrc *v1.ImageSource) (info inte
return nil, err
}
}
info, err = e.config.Luet.Unpack(target, imgSrc.Value(), e.config.LocalImage)
err = e.config.ImageExtractor.ExtractImage(imgSrc.Value(), target, e.config.Platform.String(), e.config.LocalImage)
if err != nil {
return nil, err
}
@ -373,11 +373,6 @@ func (e *Elemental) DumpSource(target string, imgSrc *v1.ImageSource) (info inte
if err != nil {
return nil, err
}
} else if imgSrc.IsChannel() {
info, err = e.config.Luet.UnpackFromChannel(target, imgSrc.Value(), e.config.Repos...)
if err != nil {
return nil, err
}
} else if imgSrc.IsFile() {
err := utils.MkdirAll(e.config.Fs, filepath.Dir(target), cnst.DirPerm)
if err != nil {

View File

@ -57,6 +57,8 @@ var _ = Describe("Elemental", Label("elemental"), func() {
var mounter *v1mock.ErrorMounter
var fs *vfst.TestFS
var cleanup func()
var extractor *v1mock.FakeImageExtractor
BeforeEach(func() {
runner = v1mock.NewFakeRunner()
syscall = &v1mock.FakeSyscall{}
@ -64,6 +66,7 @@ var _ = Describe("Elemental", Label("elemental"), func() {
client = &v1mock.FakeHTTPClient{}
logger = v1.NewNullLogger()
fs, cleanup, _ = vfst.NewTestFS(nil)
extractor = v1mock.NewFakeImageExtractor(logger)
config = conf.NewConfig(
conf.WithFs(fs),
conf.WithRunner(runner),
@ -71,6 +74,7 @@ var _ = Describe("Elemental", Label("elemental"), func() {
conf.WithMounter(mounter),
conf.WithSyscall(syscall),
conf.WithClient(client),
conf.WithImageExtractor(extractor),
)
})
AfterEach(func() { cleanup() })
@ -594,11 +598,8 @@ var _ = Describe("Elemental", Label("elemental"), func() {
Describe("DumpSource", Label("dump"), func() {
var e *elemental.Elemental
var destDir string
var luet *v1mock.FakeLuet
BeforeEach(func() {
var err error
luet = v1mock.NewFakeLuet()
config.Luet = luet
e = elemental.NewElemental(config)
destDir, err = utils.TempDir(fs, "", "elemental")
Expect(err).ShouldNot(HaveOccurred())
@ -616,13 +617,11 @@ var _ = Describe("Elemental", Label("elemental"), func() {
It("Unpacks a docker image to target", Label("docker"), func() {
_, err := e.DumpSource(destDir, v1.NewDockerSrc("docker/image:latest"))
Expect(err).To(BeNil())
Expect(luet.UnpackCalled()).To(BeTrue())
})
It("Unpacks a docker image to target with cosign validation", Label("docker", "cosign"), func() {
config.Cosign = true
_, err := e.DumpSource(destDir, v1.NewDockerSrc("docker/image:latest"))
Expect(err).To(BeNil())
Expect(luet.UnpackCalled()).To(BeTrue())
Expect(runner.CmdsMatch([][]string{{"cosign", "verify", "docker/image:latest"}}))
})
It("Fails cosign validation", Label("cosign"), func() {
@ -632,12 +631,6 @@ var _ = Describe("Elemental", Label("elemental"), func() {
Expect(err).NotTo(BeNil())
Expect(runner.CmdsMatch([][]string{{"cosign", "verify", "docker/image:latest"}}))
})
It("Fails to unpack a docker image to target", Label("docker"), func() {
luet.OnUnpackError = true
_, err := e.DumpSource(destDir, v1.NewDockerSrc("docker/image:latest"))
Expect(err).NotTo(BeNil())
Expect(luet.UnpackCalled()).To(BeTrue())
})
It("Copies image file to target", func() {
sourceImg := "/source.img"
_, err := fs.Create(sourceImg)
@ -654,17 +647,6 @@ var _ = Describe("Elemental", Label("elemental"), func() {
_, err := e.DumpSource("whatever", v1.NewFileSrc("/source.img"))
Expect(err).NotTo(BeNil())
})
It("Unpacks from channel to target", func() {
_, err := e.DumpSource(destDir, v1.NewChannelSrc("some/package"))
Expect(err).To(BeNil())
Expect(luet.UnpackChannelCalled()).To(BeTrue())
})
It("Fails to unpack from channel to target", func() {
luet.OnUnpackFromChannelError = true
_, err := e.DumpSource(destDir, v1.NewChannelSrc("some/package"))
Expect(err).NotTo(BeNil())
Expect(luet.UnpackChannelCalled()).To(BeTrue())
})
})
Describe("CheckActiveDeployment", Label("check"), func() {
It("deployment found", func() {

View File

@ -30,7 +30,6 @@ import (
"github.com/kairos-io/kairos/v2/pkg/cloudinit"
"github.com/kairos-io/kairos/v2/pkg/constants"
"github.com/kairos-io/kairos/v2/pkg/http"
"github.com/kairos-io/kairos/v2/pkg/luet"
v1 "github.com/kairos-io/kairos/v2/pkg/types/v1"
"github.com/kairos-io/kairos/v2/pkg/utils"
"github.com/mitchellh/mapstructure"
@ -93,13 +92,6 @@ func WithCloudInitRunner(ci v1.CloudInitRunner) func(r *v1.Config) error {
}
}
func WithLuet(luet v1.LuetInterface) func(r *v1.Config) error {
return func(r *v1.Config) error {
r.Luet = luet
return nil
}
}
func WithArch(arch string) func(r *v1.Config) error {
return func(r *v1.Config) error {
r.Arch = arch
@ -107,8 +99,37 @@ func WithArch(arch string) func(r *v1.Config) error {
}
}
func WithPlatform(platform string) func(r *v1.Config) error {
return func(r *v1.Config) error {
p, err := v1.ParsePlatform(platform)
r.Platform = p
return err
}
}
func WithOCIImageExtractor() func(r *v1.Config) error {
return func(r *v1.Config) error {
r.ImageExtractor = v1.OCIImageExtractor{}
return nil
}
}
func WithImageExtractor(extractor v1.ImageExtractor) func(r *v1.Config) error {
return func(r *v1.Config) error {
r.ImageExtractor = extractor
return nil
}
}
func NewConfig(opts ...GenericOptions) *v1.Config {
log := v1.NewLogger()
defaultPlatform, err := v1.NewPlatformFromArch(runtime.GOARCH)
if err != nil {
log.Errorf("error parsing default platform (%s): %s", runtime.GOARCH, err.Error())
return nil
}
arch, err := utils.GolangArchToArch(runtime.GOARCH)
if err != nil {
log.Errorf("invalid arch: %s", err.Error())
@ -122,6 +143,7 @@ func NewConfig(opts ...GenericOptions) *v1.Config {
Client: http.NewClient(),
Repos: []v1.Repository{},
Arch: arch,
Platform: defaultPlatform,
SquashFsCompressionConfig: constants.GetDefaultSquashfsCompressionOptions(),
}
for _, o := range opts {
@ -154,10 +176,6 @@ func NewConfig(opts ...GenericOptions) *v1.Config {
c.Mounter = mount.New(constants.MountBinary)
}
if c.Luet == nil {
tmpDir := utils.GetTempDir(c, "")
c.Luet = luet.NewLuet(luet.WithFs(c.Fs), luet.WithLogger(log), luet.WithLuetTempDir(tmpDir))
}
return c
}
@ -169,16 +187,6 @@ func NewRunConfig(opts ...GenericOptions) *v1.RunConfig {
return r
}
// CoOccurrenceConfig sets further configurations once config files and other
// runtime configurations are read. This is mostly a method to call once the
// mapstructure unmarshal already took place.
func CoOccurrenceConfig(cfg *v1.Config) {
// Set Luet plugins, we only use the mtree plugin for now
if cfg.Verify {
cfg.Luet.SetPlugins(constants.LuetMtreePlugin)
}
}
// NewInstallSpec returns an InstallSpec struct all based on defaults and basic host checks (e.g. EFI vs BIOS)
func NewInstallSpec(cfg v1.Config) *v1.InstallSpec {
var firmware string
@ -479,20 +487,6 @@ func NewResetSpec(cfg v1.Config) (*v1.ResetSpec, error) {
}, nil
}
func NewRawDisk() *v1.RawDisk {
var packages []v1.RawDiskPackage
defaultPackages := constants.GetBuildDiskDefaultPackages()
for pkg, target := range defaultPackages {
packages = append(packages, v1.RawDiskPackage{Name: pkg, Target: target})
}
return &v1.RawDisk{
X86_64: &v1.RawDiskArchEntry{Packages: packages},
Arm64: &v1.RawDiskArchEntry{Packages: packages},
}
}
func NewISO() *v1.LiveISO {
return &v1.LiveISO{
Label: constants.ISOLabel,
@ -524,7 +518,7 @@ func NewBuildConfig(opts ...GenericOptions) *v1.BuildConfig {
}
func ReadConfigRun(configDir string) (*v1.RunConfig, error) {
cfg := NewRunConfig(WithLogger(v1.NewLogger()))
cfg := NewRunConfig(WithLogger(v1.NewLogger()), WithOCIImageExtractor())
configLogger(cfg.Logger, cfg.Fs)

View File

@ -42,7 +42,6 @@ var _ = Describe("Types", Label("types", "config"), func() {
var sysc *v1mock.FakeSyscall
var logger v1.Logger
var ci *v1mock.FakeCloudInitRunner
var luet *v1mock.FakeLuet
var c *v1.Config
BeforeEach(func() {
fs, cleanup, err = vfst.NewTestFS(nil)
@ -53,7 +52,6 @@ var _ = Describe("Types", Label("types", "config"), func() {
sysc = &v1mock.FakeSyscall{}
logger = v1.NewNullLogger()
ci = &v1mock.FakeCloudInitRunner{}
luet = &v1mock.FakeLuet{}
c = elementalConfig.NewConfig(
elementalConfig.WithFs(fs),
elementalConfig.WithMounter(mounter),
@ -62,7 +60,6 @@ var _ = Describe("Types", Label("types", "config"), func() {
elementalConfig.WithLogger(logger),
elementalConfig.WithCloudInitRunner(ci),
elementalConfig.WithClient(client),
elementalConfig.WithLuet(luet),
)
})
AfterEach(func() {
@ -77,7 +74,6 @@ var _ = Describe("Types", Label("types", "config"), func() {
Expect(c.Logger).To(Equal(logger))
Expect(c.CloudInitRunner).To(Equal(ci))
Expect(c.Client).To(Equal(client))
Expect(c.Luet).To(Equal(luet))
})
It("Sets the runner if we dont pass one", func() {
fs, cleanup, err := vfst.NewTestFS(nil)
@ -401,27 +397,5 @@ var _ = Describe("Types", Label("types", "config"), func() {
})
})
})
Describe("BuildConfig", Label("build"), func() {
It("initiates a new build config", func() {
build := elementalConfig.NewBuildConfig()
Expect(build.Name).To(Equal(constants.BuildImgName))
Expect(len(build.Repos)).To(Equal(1))
Expect(build.Repos[0].URI).To(ContainSubstring(constants.LuetDefaultRepoURI))
})
})
Describe("LiveISO", Label("iso"), func() {
It("initiates a new LiveISO", func() {
iso := elementalConfig.NewISO()
Expect(iso.Label).To(Equal(constants.ISOLabel))
Expect(len(iso.UEFI)).To(Equal(0))
Expect(len(iso.Image)).To(Equal(0))
})
})
Describe("RawDisk", Label("disk"), func() {
It("initiates a new RawDisk", func() {
disk := elementalConfig.NewRawDisk()
Expect(len(disk.X86_64.Packages)).To(Equal(len(constants.GetBuildDiskDefaultPackages())))
})
})
})
})

View File

@ -1,103 +0,0 @@
/*
Copyright © 2022 SUSE LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package live
import (
"fmt"
"path/filepath"
"github.com/kairos-io/kairos/v2/pkg/constants"
)
const (
isoEFIPath = "/boot/uefi.img"
efiBootPath = "/EFI/BOOT"
isoLoaderPath = "/boot/x86_64/loader"
grubArm64Path = grubPrefixDir + "/arm64-efi"
grubEfiImagex86Dest = efiBootPath + "/bootx64.efi"
grubEfiImageArm64Dest = efiBootPath + "/bootaa64.efi"
grubCfg = "grub.cfg"
grubPrefixDir = "/boot/grub2"
// TODO document any custom bootloader must match this setup as these are not configurable
// and coupled with the xorriso call
isoHybridMBR = "/boot/x86_64/loader/boot_hybrid.img"
isoBootCatalog = "/boot/x86_64/boot.catalog"
isoBootFile = "/boot/x86_64/loader/eltorito.img"
//TODO use some identifer known to be unique
grubEfiCfg = "search --no-floppy --file --set=root " + constants.IsoKernelPath +
"\nset prefix=($root)" + grubPrefixDir +
"\nconfigfile $prefix/" + grubCfg
// TODO not convinced having such a config here is the best idea
grubCfgTemplate = `search --no-floppy --file --set=root /boot/kernel
set default=0
set timeout=10
set timeout_style=menu
set linux=linux
set initrd=initrd
if [ "${grub_cpu}" = "x86_64" -o "${grub_cpu}" = "i386" -o "${grub_cpu}" = "arm64" ];then
if [ "${grub_platform}" = "efi" ]; then
if [ "${grub_cpu}" != "arm64" ]; then
set linux=linuxefi
set initrd=initrdefi
fi
fi
fi
if [ "${grub_platform}" = "efi" ]; then
echo "Please press 't' to show the boot menu on this console"
fi
set font=($root)/boot/${grub_cpu}/loader/grub2/fonts/unicode.pf2
if [ -f ${font} ];then
loadfont ${font}
fi
menuentry "%s" --class os --unrestricted {
echo Loading kernel...
$linux ($root)/boot/kernel cdroot root=live:CDLABEL=%s rd.live.dir=/ rd.live.squashimg=rootfs.squashfs console=tty1 console=ttyS0 rd.cos.disable
echo Loading initrd...
$initrd ($root)/boot/initrd
}
if [ "${grub_platform}" = "efi" ]; then
hiddenentry "Text mode" --hotkey "t" {
set textmode=true
terminal_output console
}
fi`
)
func XorrisoBooloaderArgs(root string) []string {
args := []string{
"-boot_image", "grub", fmt.Sprintf("bin_path=%s", isoBootFile),
"-boot_image", "grub", fmt.Sprintf("grub2_mbr=%s/%s", root, isoHybridMBR),
"-boot_image", "grub", "grub2_boot_info=on",
"-boot_image", "any", "partition_offset=16",
"-boot_image", "any", fmt.Sprintf("cat_path=%s", isoBootCatalog),
"-boot_image", "any", "cat_hidden=on",
"-boot_image", "any", "boot_info_table=on",
"-boot_image", "any", "platform_id=0x00",
"-boot_image", "any", "emul_type=no_emulation",
"-boot_image", "any", "load_size=2048",
"-append_partition", "2", "0xef", filepath.Join(root, isoEFIPath),
"-boot_image", "any", "next",
"-boot_image", "any", "efi_path=--interval:appended_partition_2:all::",
"-boot_image", "any", "platform_id=0xef",
"-boot_image", "any", "emul_type=no_emulation",
}
return args
}

View File

@ -1,178 +0,0 @@
/*
Copyright © 2022 SUSE LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package live
import (
"fmt"
"path/filepath"
"strings"
"github.com/kairos-io/kairos/v2/pkg/constants"
v1 "github.com/kairos-io/kairos/v2/pkg/types/v1"
"github.com/kairos-io/kairos/v2/pkg/utils"
)
type GreenLiveBootLoader struct {
buildCfg *v1.BuildConfig
spec *v1.LiveISO
}
func NewGreenLiveBootLoader(cfg *v1.BuildConfig, spec *v1.LiveISO) *GreenLiveBootLoader {
return &GreenLiveBootLoader{buildCfg: cfg, spec: spec}
}
func (g *GreenLiveBootLoader) PrepareEFI(rootDir, uefiDir string) error {
const (
grubEfiImagex86 = "/usr/share/grub2/x86_64-efi/grub.efi"
grubEfiImageArm64 = "/usr/share/grub2/arm64-efi/grub.efi"
)
err := utils.MkdirAll(g.buildCfg.Fs, filepath.Join(uefiDir, efiBootPath), constants.DirPerm)
if err != nil {
return err
}
switch g.buildCfg.Arch {
case constants.ArchAmd64, constants.Archx86:
err = utils.CopyFile(
g.buildCfg.Fs,
filepath.Join(rootDir, grubEfiImagex86),
filepath.Join(uefiDir, grubEfiImagex86Dest),
)
case constants.ArchArm64:
err = utils.CopyFile(
g.buildCfg.Fs,
filepath.Join(rootDir, grubEfiImageArm64),
filepath.Join(uefiDir, grubEfiImageArm64Dest),
)
default:
err = fmt.Errorf("Not supported architecture: %v", g.buildCfg.Arch)
}
if err != nil {
return err
}
return g.buildCfg.Fs.WriteFile(filepath.Join(uefiDir, efiBootPath, grubCfg), []byte(grubEfiCfg), constants.FilePerm)
}
func (g *GreenLiveBootLoader) PrepareISO(rootDir, imageDir string) error {
const (
grubFont = "/usr/share/grub2/unicode.pf2"
grubBootHybridImg = "/usr/share/grub2/i386-pc/boot_hybrid.img"
syslinuxFiles = "/usr/share/syslinux/isolinux.bin " +
"/usr/share/syslinux/menu.c32 " +
"/usr/share/syslinux/chain.c32 " +
"/usr/share/syslinux/mboot.c32"
)
err := utils.MkdirAll(g.buildCfg.Fs, filepath.Join(imageDir, grubPrefixDir), constants.DirPerm)
if err != nil {
return err
}
switch g.buildCfg.Arch {
case constants.ArchAmd64, constants.Archx86:
// Create eltorito image
eltorito, err := g.BuildEltoritoImg(rootDir)
if err != nil {
return err
}
// Inlude loaders in expected paths
loaderDir := filepath.Join(imageDir, isoLoaderPath)
err = utils.MkdirAll(g.buildCfg.Fs, loaderDir, constants.DirPerm)
if err != nil {
return err
}
loaderFiles := []string{eltorito, grubBootHybridImg}
loaderFiles = append(loaderFiles, strings.Split(syslinuxFiles, " ")...)
for _, f := range loaderFiles {
err = utils.CopyFile(g.buildCfg.Fs, filepath.Join(rootDir, f), loaderDir)
if err != nil {
return err
}
}
fontsDir := filepath.Join(loaderDir, "/grub2/fonts")
err = utils.MkdirAll(g.buildCfg.Fs, fontsDir, constants.DirPerm)
if err != nil {
return err
}
err = utils.CopyFile(g.buildCfg.Fs, filepath.Join(rootDir, grubFont), fontsDir)
if err != nil {
return err
}
case constants.ArchArm64:
// TBC
default:
return fmt.Errorf("Not supported architecture: %v", g.buildCfg.Arch)
}
// Write grub.cfg file
err = g.buildCfg.Fs.WriteFile(
filepath.Join(imageDir, grubPrefixDir, grubCfg),
[]byte(fmt.Sprintf(grubCfgTemplate, g.spec.GrubEntry, g.spec.Label)),
constants.FilePerm,
)
if err != nil {
return err
}
// Include EFI contents in iso root too
return g.PrepareEFI(rootDir, imageDir)
}
func (g *GreenLiveBootLoader) BuildEltoritoImg(rootDir string) (string, error) {
const (
grubBiosTarget = "i386-pc"
grubI386BinDir = "/usr/share/grub2/i386-pc"
grubBiosImg = grubI386BinDir + "/core.img"
grubBiosCDBoot = grubI386BinDir + "/cdboot.img"
grubEltoritoImg = grubI386BinDir + "/eltorito.img"
//TODO this list could be optimized
grubModules = "ext2 iso9660 linux echo configfile search_label search_fs_file search search_fs_uuid " +
"ls normal gzio png fat gettext font minicmd gfxterm gfxmenu all_video xfs btrfs lvm luks " +
"gcry_rijndael gcry_sha256 gcry_sha512 crypto cryptodisk test true loadenv part_gpt " +
"part_msdos biosdisk vga vbe chain boot"
)
var args []string
args = append(args, "-O", grubBiosTarget)
args = append(args, "-o", grubBiosImg)
args = append(args, "-p", grubPrefixDir)
args = append(args, "-d", grubI386BinDir)
args = append(args, strings.Split(grubModules, " ")...)
chRoot := utils.NewChroot(rootDir, &g.buildCfg.Config)
out, err := chRoot.Run("grub2-mkimage", args...)
if err != nil {
g.buildCfg.Logger.Errorf("grub2-mkimage failed: %s", string(out))
g.buildCfg.Logger.Errorf("Error: %v", err)
return "", err
}
concatFiles := func() error {
return utils.ConcatFiles(
g.buildCfg.Fs, []string{grubBiosCDBoot, grubBiosImg},
grubEltoritoImg,
)
}
err = chRoot.RunCallback(concatFiles)
if err != nil {
return "", err
}
return grubEltoritoImg, nil
}

View File

@ -1,216 +0,0 @@
/*
Copyright © 2022 SUSE LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package live_test
import (
"bytes"
"fmt"
"path/filepath"
"github.com/kairos-io/kairos/v2/pkg/constants"
"github.com/kairos-io/kairos/v2/pkg/elementalConfig"
"github.com/kairos-io/kairos/v2/pkg/live"
v1 "github.com/kairos-io/kairos/v2/pkg/types/v1"
"github.com/kairos-io/kairos/v2/pkg/utils"
v1mock "github.com/kairos-io/kairos/v2/tests/mocks"
"github.com/sirupsen/logrus"
"github.com/twpayne/go-vfs"
"github.com/twpayne/go-vfs/vfst"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("GreenLiveBootloader", Label("green", "live"), func() {
var cfg *v1.BuildConfig
var runner *v1mock.FakeRunner
var fs vfs.FS
var logger v1.Logger
var mounter *v1mock.ErrorMounter
var syscall *v1mock.FakeSyscall
var client *v1mock.FakeHTTPClient
var cloudInit *v1mock.FakeCloudInitRunner
var luet *v1mock.FakeLuet
var cleanup func()
var memLog *bytes.Buffer
var iso *v1.LiveISO
var rootDir, imageDir, uefiDir string
var i386BinChrootPath string
BeforeEach(func() {
var err error
runner = v1mock.NewFakeRunner()
syscall = &v1mock.FakeSyscall{}
mounter = v1mock.NewErrorMounter()
client = &v1mock.FakeHTTPClient{}
memLog = &bytes.Buffer{}
logger = v1.NewBufferLogger(memLog)
logger.SetLevel(logrus.DebugLevel)
cloudInit = &v1mock.FakeCloudInitRunner{}
luet = &v1mock.FakeLuet{}
fs, cleanup, _ = vfst.NewTestFS(map[string]interface{}{})
cfg = elementalConfig.NewBuildConfig(
elementalConfig.WithFs(fs),
elementalConfig.WithRunner(runner),
elementalConfig.WithLogger(logger),
elementalConfig.WithMounter(mounter),
elementalConfig.WithSyscall(syscall),
elementalConfig.WithClient(client),
elementalConfig.WithCloudInitRunner(cloudInit),
elementalConfig.WithLuet(luet),
)
iso = elementalConfig.NewISO()
rootDir, err = utils.TempDir(fs, "", "rootDir")
Expect(err).ShouldNot(HaveOccurred())
imageDir, err = utils.TempDir(fs, "", "imageDir")
Expect(err).ShouldNot(HaveOccurred())
uefiDir, err = utils.TempDir(fs, "", "uefiDir")
Expect(err).ShouldNot(HaveOccurred())
i386BinChrootPath = "/usr/share/grub2/i386-pc"
err = utils.MkdirAll(fs, i386BinChrootPath, constants.DirPerm)
Expect(err).ShouldNot(HaveOccurred())
err = fs.WriteFile(filepath.Join(i386BinChrootPath, "cdboot.img"), []byte("cdboot.img"), constants.FilePerm)
Expect(err).ShouldNot(HaveOccurred())
})
AfterEach(func() {
cleanup()
})
It("Creates eltorito image", func() {
runner.SideEffect = func(cmd string, args ...string) ([]byte, error) {
switch cmd {
case "grub2-mkimage":
err := fs.WriteFile(filepath.Join(i386BinChrootPath, "core.img"), []byte("core.img"), constants.FilePerm)
return []byte{}, err
default:
return []byte{}, nil
}
}
green := live.NewGreenLiveBootLoader(cfg, iso)
eltorito, err := green.BuildEltoritoImg(rootDir)
Expect(err).ShouldNot(HaveOccurred())
Expect(eltorito).To(Equal("/usr/share/grub2/i386-pc/eltorito.img"))
out, err := fs.ReadFile(eltorito)
Expect(err).ShouldNot(HaveOccurred())
Expect(string(out)).To(Equal("cdboot.imgcore.img"))
})
It("Fails creating eltorito image, grub2-mkimage failure", func() {
runner.SideEffect = func(cmd string, args ...string) ([]byte, error) {
switch cmd {
case "grub2-mkimage":
return []byte{}, fmt.Errorf("failed creating core image")
default:
return []byte{}, nil
}
}
green := live.NewGreenLiveBootLoader(cfg, iso)
_, err := green.BuildEltoritoImg(rootDir)
Expect(err).Should(HaveOccurred())
})
It("Fails creating eltorito image, concatenating files failure", func() {
// fake runner does not create a fake core.img
green := live.NewGreenLiveBootLoader(cfg, iso)
_, err := green.BuildEltoritoImg(rootDir)
Expect(err).Should(HaveOccurred())
})
It("Copies the EFI image binaries for x86_64", func() {
err := utils.MkdirAll(fs, filepath.Join(rootDir, "/usr/share/grub2/x86_64-efi"), constants.DirPerm)
Expect(err).ShouldNot(HaveOccurred())
err = fs.WriteFile(
filepath.Join(rootDir, "/usr/share/grub2/x86_64-efi/grub.efi"),
[]byte("x86_64_efi"), constants.FilePerm,
)
Expect(err).ShouldNot(HaveOccurred())
green := live.NewGreenLiveBootLoader(cfg, iso)
err = green.PrepareEFI(rootDir, uefiDir)
Expect(err).ShouldNot(HaveOccurred())
exists, _ := utils.Exists(fs, filepath.Join(uefiDir, "EFI/BOOT/grub.cfg"))
Expect(exists).To(BeTrue())
})
It("Copies the EFI image binaries for arm64", func() {
cfg.Arch = constants.ArchArm64
err := utils.MkdirAll(fs, filepath.Join(rootDir, "/usr/share/grub2/arm64-efi"), constants.DirPerm)
Expect(err).ShouldNot(HaveOccurred())
err = fs.WriteFile(
filepath.Join(rootDir, "/usr/share/grub2/arm64-efi/grub.efi"),
[]byte("arm64-efi"), constants.FilePerm,
)
Expect(err).ShouldNot(HaveOccurred())
green := live.NewGreenLiveBootLoader(cfg, iso)
err = green.PrepareEFI(rootDir, uefiDir)
Expect(err).ShouldNot(HaveOccurred())
exists, _ := utils.Exists(fs, filepath.Join(uefiDir, "EFI/BOOT/grub.cfg"))
Expect(exists).To(BeTrue())
})
It("Prepares ISO root with bootloader files", func() {
i386BinPath := filepath.Join(rootDir, i386BinChrootPath)
err := utils.MkdirAll(fs, i386BinPath, constants.DirPerm)
Expect(err).ShouldNot(HaveOccurred())
err = fs.WriteFile(filepath.Join(i386BinPath, "eltorito.img"), []byte("eltorito"), constants.FilePerm)
Expect(err).ShouldNot(HaveOccurred())
err = fs.WriteFile(filepath.Join(i386BinPath, "boot_hybrid.img"), []byte("boot_hybrid"), constants.FilePerm)
Expect(err).ShouldNot(HaveOccurred())
syslinuxPath := filepath.Join(rootDir, "/usr/share/syslinux")
err = utils.MkdirAll(fs, syslinuxPath, constants.DirPerm)
Expect(err).ShouldNot(HaveOccurred())
err = fs.WriteFile(filepath.Join(syslinuxPath, "isolinux.bin"), []byte("isolinux"), constants.FilePerm)
Expect(err).ShouldNot(HaveOccurred())
err = fs.WriteFile(filepath.Join(syslinuxPath, "menu.c32"), []byte("menu"), constants.FilePerm)
Expect(err).ShouldNot(HaveOccurred())
err = fs.WriteFile(filepath.Join(syslinuxPath, "chain.c32"), []byte("chain"), constants.FilePerm)
Expect(err).ShouldNot(HaveOccurred())
err = fs.WriteFile(filepath.Join(syslinuxPath, "mboot.c32"), []byte("mboot"), constants.FilePerm)
Expect(err).ShouldNot(HaveOccurred())
fontsPath := filepath.Join(rootDir, "/usr/share/grub2")
err = utils.MkdirAll(fs, fontsPath, constants.DirPerm)
Expect(err).ShouldNot(HaveOccurred())
err = fs.WriteFile(filepath.Join(fontsPath, "unicode.pf2"), []byte("unicode"), constants.FilePerm)
Expect(err).ShouldNot(HaveOccurred())
runner.SideEffect = func(cmd string, args ...string) ([]byte, error) {
switch cmd {
case "grub2-mkimage":
err := fs.WriteFile(filepath.Join(i386BinChrootPath, "core.img"), []byte("core.img"), constants.FilePerm)
return []byte{}, err
default:
return []byte{}, nil
}
}
err = utils.MkdirAll(fs, filepath.Join(rootDir, "/usr/share/grub2/x86_64-efi"), constants.DirPerm)
Expect(err).ShouldNot(HaveOccurred())
err = fs.WriteFile(
filepath.Join(rootDir, "/usr/share/grub2/x86_64-efi/grub.efi"),
[]byte("x86_64_efi"), constants.FilePerm,
)
Expect(err).ShouldNot(HaveOccurred())
green := live.NewGreenLiveBootLoader(cfg, iso)
err = green.PrepareISO(rootDir, imageDir)
Expect(err).ShouldNot(HaveOccurred())
exists, _ := utils.Exists(fs, filepath.Join(imageDir, "EFI/BOOT/grub.cfg"))
Expect(exists).To(BeTrue())
})
})

View File

@ -1,29 +0,0 @@
/*
Copyright © 2022 SUSE LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package live_test
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestTypes(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "live test suite")
}

View File

@ -1,399 +0,0 @@
/*
Copyright © 2022 SUSE LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package luet
import (
"crypto/md5"
"errors"
"fmt"
"path/filepath"
"runtime"
"strings"
dockTypes "github.com/docker/docker/api/types"
"github.com/docker/go-units"
"github.com/mudler/luet/pkg/api/core/bus"
"github.com/mudler/luet/pkg/api/core/context"
gc "github.com/mudler/luet/pkg/api/core/garbagecollector"
luetTypes "github.com/mudler/luet/pkg/api/core/types"
"github.com/mudler/luet/pkg/database"
"github.com/mudler/luet/pkg/helpers/docker"
"github.com/mudler/luet/pkg/installer"
"github.com/twpayne/go-vfs"
"gopkg.in/yaml.v3"
"github.com/kairos-io/kairos/v2/pkg/constants"
v1 "github.com/kairos-io/kairos/v2/pkg/types/v1"
"github.com/kairos-io/kairos/v2/pkg/utils"
)
type Luet struct {
log v1.Logger
context *context.Context
auth *dockTypes.AuthConfig
fs v1.FS
plugins []string
VerifyImageUnpack bool
TmpDir string
arch string
}
type Options func(l *Luet) error
func WithPlugins(plugins ...string) func(r *Luet) error {
return func(l *Luet) error {
l.SetPlugins(plugins...)
return nil
}
}
func WithConfig(cfg *luetTypes.LuetConfig) func(r *Luet) error {
return func(l *Luet) error {
ctx := context.NewContext(
context.WithConfig(cfg),
)
l.context = ctx
return nil
}
}
func WithAuth(auth *dockTypes.AuthConfig) func(r *Luet) error {
return func(l *Luet) error {
l.auth = auth
return nil
}
}
func WithLogger(log v1.Logger) func(r *Luet) error {
return func(l *Luet) error {
l.log = log
return nil
}
}
func WithFs(fs v1.FS) func(r *Luet) error {
return func(l *Luet) error {
l.fs = fs
return nil
}
}
func WithLuetTempDir(tmpDir string) func(r *Luet) error {
return func(r *Luet) error {
r.TmpDir = tmpDir
return nil
}
}
func WithArch(arch string) func(r *Luet) error {
return func(l *Luet) error {
l.arch = arch
return nil
}
}
func NewLuet(opts ...Options) *Luet {
luet := &Luet{}
for _, o := range opts {
err := o(luet)
if err != nil {
return nil
}
}
if luet.log == nil {
luet.log = v1.NewNullLogger()
}
if luet.fs == nil {
luet.fs = vfs.OSFS
}
if luet.context == nil {
luetConfig := luet.createLuetConfig()
luet.context = context.NewContext(context.WithConfig(luetConfig), context.WithLogger(luet.log))
}
if luet.auth == nil {
luet.auth = &dockTypes.AuthConfig{}
}
return luet
}
func (l *Luet) SetArch(arch string) {
l.arch = arch
}
func (l *Luet) SetPlugins(plugins ...string) {
l.plugins = plugins
}
func (l *Luet) GetPlugins() []string {
return l.plugins
}
func (l *Luet) InitPlugins() {
if len(l.plugins) > 0 {
bus.Manager.Initialize(l.context, l.plugins...)
l.log.Infof("Enabled plugins:")
for _, p := range bus.Manager.Plugins {
l.log.Infof("* %s (at %s)", p.Name, p.Executable)
}
}
}
func (l Luet) Unpack(target string, image string, local bool) (*v1.DockerImageMeta, error) {
l.log.Infof("Unpacking a container image: %s", image)
l.InitPlugins()
meta := &v1.DockerImageMeta{}
if local {
l.log.Infof("Using an image from local cache")
info, err := docker.ExtractDockerImage(l.context, image, target)
if err != nil {
if strings.Contains(err.Error(), "reference does not exist") {
return nil, errors.New("Container image does not exist locally")
}
return nil, err
}
l.log.Infof("Size: %s", units.BytesSize(float64(info.Target.Size)))
meta.Size = info.Target.Size
meta.Digest = info.Target.Digest.String()
} else {
l.log.Infof("Pulling an image from remote repository")
info, err := docker.DownloadAndExtractDockerImage(l.context, image, target, l.auth, l.VerifyImageUnpack)
if err != nil {
return nil, err
}
l.log.Infof("Pulled: %s %s", info.Target.Digest, info.Name)
l.log.Infof("Size: %s", units.BytesSize(float64(info.Target.Size)))
meta.Size = info.Target.Size
meta.Digest = info.Target.Digest.String()
}
return meta, nil
}
// initLuetRepository returns a Luet repository from a given v1.Repository. It runs heuristics
// to determine the type from the URL if this is not provided:
// 1. Repo type is disk if the URL is an existing local path
// 2. Repo type is http is scheme is 'http' or 'https'
// 3. Repo type is docker if the URL is of type [<dommain>[:<port>]]/<path>
// Returns error if the type does not match any of any criteria.
func (l Luet) initLuetRepository(repo v1.Repository) (luetTypes.LuetRepository, error) {
var err error
if repo.URI == "" {
return luetTypes.LuetRepository{}, fmt.Errorf("Invalid repository, no URI is provided: %v", repo)
}
name := repo.Name
if name != "" {
// Compute a deterministic name from URI
name = fmt.Sprintf("%x", md5.Sum([]byte(repo.URI)))
}
repoType := repo.Type
if repoType == "" {
if exists, _ := utils.Exists(l.fs, repo.URI); exists {
repoType = "disk"
} else if http, _ := utils.IsHTTPURI(repo.URI); http {
repoType = "http"
} else if utils.ValidContainerReference(repo.URI) {
repoType = "docker"
} else {
return luetTypes.LuetRepository{}, fmt.Errorf("Invalid Luet repository URI: %s", repo.URI)
}
}
arch := l.arch
if repo.Arch != "" {
arch, err = utils.ArchToGolangArch(repo.Arch)
if err != nil {
return luetTypes.LuetRepository{}, err
}
}
if repo.ReferenceID == "" {
repo.ReferenceID = "repository.yaml"
}
priority := repo.Priority
if repo.Priority == 0 {
priority = constants.LuetDefaultRepoPrio
}
return luetTypes.LuetRepository{
Name: name,
Priority: priority,
Enable: true,
Urls: []string{repo.URI},
Type: repoType,
ReferenceID: repo.ReferenceID,
Arch: arch,
}, nil
}
// UnpackFromChannel unpacks/installs a package from the release channel into the target dir by leveraging the
// luet install action to install to a local dir
func (l Luet) UnpackFromChannel(target string, pkg string, repositories ...v1.Repository) (*v1.ChannelImageMeta, error) {
var toInstall luetTypes.Packages
l.InitPlugins()
toInstall = append(toInstall, l.parsePackage(pkg))
repos := l.context.Config.SystemRepositories
if len(repositories) > 0 {
repos = luetTypes.LuetRepositories{}
for _, r := range repositories {
// If the repository has no arch assigned matches all
if r.Arch != "" && l.arch != r.Arch {
l.log.Debugf("skipping repository '%s' for arch '%s'", r.Name, r.Arch)
continue
}
repo, err := l.initLuetRepository(r)
if err != nil {
return nil, err
}
repos = append(repos, repo)
}
}
inst := installer.NewLuetInstaller(installer.LuetInstallerOptions{
Concurrency: l.context.Config.General.Concurrency,
SolverOptions: l.context.Config.Solver,
NoDeps: false,
Force: true,
OnlyDeps: false,
PreserveSystemEssentialData: true,
DownloadOnly: false,
Ask: false,
Relaxed: false,
PackageRepositories: repos,
Context: l.context,
})
system := &installer.System{
Database: database.NewInMemoryDatabase(false),
Target: target,
}
err := inst.Install(toInstall, system)
if err != nil {
return nil, err
}
pkgs, err := system.Database.FindPackageMatch(pkg)
if err != nil {
l.log.Error(err.Error())
return nil, err
}
var meta *v1.ChannelImageMeta
if len(pkgs) > 0 {
meta = &v1.ChannelImageMeta{
Category: pkgs[0].GetCategory(),
Name: pkgs[0].GetName(),
Version: pkgs[0].GetVersion(),
FingerPrint: pkgs[0].GetFingerPrint(),
}
//TODO: ideally we should only include the repository being used
for _, r := range repos {
meta.Repos = append(meta.Repos, v1.Repository{
Name: r.Name,
Priority: r.Priority,
URI: r.Urls[0],
Type: r.Type,
Arch: r.Arch,
ReferenceID: r.ReferenceID,
})
}
}
return meta, nil
}
func (l Luet) parsePackage(p string) *luetTypes.Package {
var cat, name string
ver := ">=0"
if strings.Contains(p, "@") {
packageinfo := strings.Split(p, "@")
ver = packageinfo[1]
cat, name = packageData(packageinfo[0])
} else {
cat, name = packageData(p)
}
return &luetTypes.Package{Name: name, Category: cat, Version: ver, Uri: make([]string, 0)}
}
func packageData(p string) (string, string) {
cat := ""
name := ""
if strings.Contains(p, "/") {
packagedata := strings.Split(p, "/")
cat = packagedata[0]
name = packagedata[1]
} else {
name = p
}
return cat, name
}
func (l Luet) createLuetConfig() *luetTypes.LuetConfig {
config := &luetTypes.LuetConfig{}
// if there is a luet.yaml file, load the data from there
if _, err := l.fs.Stat("/etc/luet/luet.yaml"); err == nil {
l.log.Debugf("Loading luet config from /etc/luet/luet.yaml")
f, err := l.fs.ReadFile("/etc/luet/luet.yaml")
if err != nil {
l.log.Errorf("Error reading luet.yaml file: %s", err)
}
err = yaml.Unmarshal(f, config)
if err != nil {
l.log.Errorf("Error unmarshalling luet.yaml file: %s", err)
}
} else {
l.log.Debugf("Creating empty luet config")
}
err := config.Init()
if err != nil {
l.log.Debug("Error running init on luet config: %s", err)
}
// This is set on luet CLI to runtime.NumCPU but on here we have to manually set it
if config.General.Concurrency == 0 {
config.General.Concurrency = runtime.NumCPU()
}
if l.TmpDir != "" {
config.System.TmpDirBase = l.TmpDir
config.System.PkgsCachePath = l.TmpDir
config.System.DatabasePath = l.TmpDir
}
return config
}
// SetTempDir re-sets the temp dir for all the luet stuff
func (l *Luet) SetTempDir(tmpdir string) {
l.TmpDir = tmpdir
l.context.Config.System.TmpDirBase = l.TmpDir
l.context.Config.System.PkgsCachePath = l.TmpDir
l.context.Config.System.DatabasePath = l.TmpDir
l.context.GarbageCollector = gc.GarbageCollector(filepath.Join(tmpdir, "tmpluet"))
}

View File

@ -1,198 +0,0 @@
/*
Copyright © 2021 SUSE LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package luet_test
import (
"bytes"
"context"
"testing"
"github.com/kairos-io/kairos/v2/pkg/constants"
"github.com/kairos-io/kairos/v2/pkg/utils"
dockTypes "github.com/docker/docker/api/types"
dockClient "github.com/docker/docker/client"
"github.com/mudler/go-pluggable"
"github.com/mudler/luet/pkg/api/core/bus"
"github.com/twpayne/go-vfs"
"github.com/twpayne/go-vfs/vfst"
"io"
"io/ioutil"
"os"
luetTypes "github.com/mudler/luet/pkg/api/core/types"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/sirupsen/logrus"
"github.com/kairos-io/kairos/v2/pkg/luet"
v1 "github.com/kairos-io/kairos/v2/pkg/types/v1"
)
func TestElementalSuite(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Luet test suite")
}
var _ = Describe("Types", Label("luet", "types"), func() {
var l *luet.Luet
var target string
var fs vfs.FS
var cleanup func()
BeforeEach(func() {
var err error
fs, cleanup, _ = vfst.NewTestFS(nil)
fs.Mkdir("/etc", os.ModePerm)
fs.Mkdir("/etc/luet", os.ModePerm)
target, err = os.MkdirTemp("", "elemental")
Expect(err).To(BeNil())
l = luet.NewLuet(
luet.WithLogger(v1.NewNullLogger()),
luet.WithArch(constants.Archx86),
)
})
AfterEach(func() {
Expect(os.RemoveAll(target)).To(BeNil())
cleanup()
})
Describe("Luet", func() {
It("Check that luet can unpack the remote image", Label("unpack", "root"), func() {
image := "registry.opensuse.org/opensuse/redis"
// Check that luet can unpack the remote image
_, err := l.Unpack(target, image, false)
Expect(err).To(BeNil())
})
It("Check that luet can unpack the local image", Label("unpack", "root"), func() {
image := "docker.io/library/alpine"
ctx := context.Background()
cli, err := dockClient.NewClientWithOpts(dockClient.FromEnv, dockClient.WithAPIVersionNegotiation())
Expect(err).ToNot(HaveOccurred())
// Pull image
reader, err := cli.ImagePull(ctx, image, dockTypes.ImagePullOptions{})
Expect(err).ToNot(HaveOccurred())
defer reader.Close()
_, _ = io.Copy(ioutil.Discard, reader)
// Check that luet can unpack the local image
_, err = l.Unpack(target, image, true)
Expect(err).To(BeNil())
})
Describe("UnpackFromChannel", Label("unpack", "channel"), func() {
It("Check that luet can unpack from channel", Label("root"), func() {
repo := v1.Repository{URI: "quay.io/costoolkit/releases-teal", Arch: constants.Archx86}
_, err := l.UnpackFromChannel(target, "utils/gomplate", repo)
Expect(err).To(BeNil())
})
It("Fails to unpack with a repository with no URI", func() {
repo := v1.Repository{Arch: constants.Archx86}
_, err := l.UnpackFromChannel(target, "utils/gomplate", repo)
Expect(err.Error()).To(ContainSubstring("no URI is provided"))
Expect(err).NotTo(BeNil())
})
It("Fails to unpack with a dir repository that doesnt have anything", func() {
dir, _ := utils.TempDir(fs, "", "")
repo := v1.Repository{URI: dir}
_, err := l.UnpackFromChannel(target, "utils/gomplate", repo)
Expect(err).NotTo(BeNil())
})
It("Fails to unpack with a http repository that doesnt exists", func() {
repo := v1.Repository{URI: "http://jojo.bizarre.adventure"}
_, err := l.UnpackFromChannel(target, "utils/gomplate", repo)
Expect(err).NotTo(BeNil())
})
It("Fails to unpack with a strange repository that cant get the type for", func() {
repo := v1.Repository{URI: "is:this:real:life", Arch: constants.Archx86}
_, err := l.UnpackFromChannel(target, "utils/gomplate", repo)
Expect(err.Error()).To(ContainSubstring("Invalid Luet repository URI"))
Expect(err).NotTo(BeNil())
})
It("Fails to unpack from channel without matching arch", func() {
repo := v1.Repository{URI: "quay.io/costoolkit/releases-teal-arm-64", Arch: constants.ArchArm64}
_, err := l.UnpackFromChannel(target, "utils/gomplate", repo)
Expect(err.Error()).To(ContainSubstring("package 'utils/gomplate->=0' not found"))
Expect(err).NotTo(BeNil())
})
})
Describe("Luet config", Label("config"), func() {
It("Create empty config if there is no luet.yaml", func() {
memLog := bytes.Buffer{}
log := v1.NewBufferLogger(&memLog)
log.SetLevel(logrus.DebugLevel)
luet.NewLuet(luet.WithLogger(log), luet.WithFs(fs))
Expect(memLog.String()).To(ContainSubstring("Creating empty luet config"))
})
It("Fail to parse wrong luet.yaml", func() {
memLog := bytes.Buffer{}
log := v1.NewBufferLogger(&memLog)
log.SetLevel(logrus.DebugLevel)
Expect(fs.WriteFile("/etc/luet/luet.yaml", []byte("not valid I think? Maybe yes, who knows, only the yaml gods"), os.ModePerm)).ShouldNot(HaveOccurred())
luet.NewLuet(luet.WithLogger(log), luet.WithFs(fs))
Expect(memLog.String()).To(ContainSubstring("Loading luet config from /etc/luet/luet.yaml"))
Expect(memLog.String()).To(ContainSubstring("Error unmarshalling luet.yaml"))
})
It("Loads default luet.yaml", func() {
memLog := bytes.Buffer{}
log := v1.NewBufferLogger(&memLog)
log.SetLevel(logrus.DebugLevel)
_ = fs.WriteFile("/etc/luet/luet.yaml", []byte("general:\n debug: false\n enable_emoji: false"), os.ModePerm)
luet.NewLuet(luet.WithLogger(log), luet.WithFs(fs))
Expect(memLog.String()).To(ContainSubstring("Loading luet config from /etc/luet/luet.yaml"))
})
})
Describe("Luet options", Label("options"), func() {
It("Sets plugins correctly", func() {
l = luet.NewLuet(luet.WithPlugins("mkdir"))
l.InitPlugins()
p := pluggable.Plugin{
Name: "mkdir",
Executable: "/bin/mkdir",
}
Expect(bus.Manager.Plugins).To(ContainElement(p))
})
It("Sets plugins correctly with log", func() {
l = luet.NewLuet(luet.WithLogger(v1.NewNullLogger()), luet.WithPlugins("cat"))
l.InitPlugins()
p := pluggable.Plugin{
Name: "cat",
Executable: "/bin/cat",
}
Expect(bus.Manager.Plugins).To(ContainElement(p))
})
It("Sets logger correctly", func() {
memLog := bytes.Buffer{}
log := v1.NewBufferLogger(&memLog)
log.SetLevel(logrus.DebugLevel)
luet.NewLuet(luet.WithFs(fs), luet.WithLogger(log))
// Check if the debug stuff was logged to the buffer
Expect(memLog.String()).To(ContainSubstring("Creating empty luet config"))
})
It("Sets config", func() {
cfg := luetTypes.LuetConfig{}
luet.NewLuet(luet.WithConfig(&cfg))
})
It("Sets Auth", func() {
auth := dockTypes.AuthConfig{}
luet.NewLuet(luet.WithAuth(&auth))
})
It("Sets FS", func() {
luet.NewLuet(luet.WithFs(fs))
})
})
})
})

View File

@ -27,11 +27,10 @@ import (
)
const (
docker = "docker"
oci = "oci"
file = "file"
dir = "dir"
channel = "channel"
docker = "docker"
oci = "oci"
file = "file"
dir = "dir"
)
// ImageSource represents the source from where an image is created for easy identification
@ -48,10 +47,6 @@ func (i ImageSource) IsDocker() bool {
return i.srcType == oci
}
func (i ImageSource) IsChannel() bool {
return i.srcType == channel
}
func (i ImageSource) IsDir() bool {
return i.srcType == dir
}
@ -107,9 +102,6 @@ func (i *ImageSource) updateFromURI(uri string) error {
switch scheme {
case oci, docker:
return i.parseImageReference(value)
case channel:
i.srcType = channel
i.source = value
case dir:
i.srcType = dir
i.source = value
@ -152,10 +144,6 @@ func NewFileSrc(src string) *ImageSource {
return &ImageSource{source: src, srcType: file}
}
func NewChannelSrc(src string) *ImageSource {
return &ImageSource{source: src, srcType: channel}
}
func NewDirSrc(src string) *ImageSource {
return &ImageSource{source: src, srcType: dir}
}

View File

@ -28,7 +28,6 @@ var _ = Describe("Types", Label("types", "common"), func() {
o := &v1.ImageSource{}
Expect(o.Value()).To(Equal(""))
Expect(o.IsDir()).To(BeFalse())
Expect(o.IsChannel()).To(BeFalse())
Expect(o.IsDocker()).To(BeFalse())
Expect(o.IsFile()).To(BeFalse())
o = v1.NewDirSrc("dir")
@ -37,8 +36,6 @@ var _ = Describe("Types", Label("types", "common"), func() {
Expect(o.IsFile()).To(BeTrue())
o = v1.NewDockerSrc("image")
Expect(o.IsDocker()).To(BeTrue())
o = v1.NewChannelSrc("channel")
Expect(o.IsChannel()).To(BeTrue())
o = v1.NewEmptySrc()
Expect(o.IsEmpty()).To(BeTrue())
o, err := v1.NewSrcFromURI("registry.company.org/image")
@ -55,9 +52,6 @@ var _ = Describe("Types", Label("types", "common"), func() {
_, err := o.CustomUnmarshal("docker://some/image")
Expect(err).ShouldNot(HaveOccurred())
Expect(o.IsDocker()).To(BeTrue())
_, err = o.CustomUnmarshal("channel://some/package")
Expect(err).ShouldNot(HaveOccurred())
Expect(o.IsChannel()).To(BeTrue())
_, err = o.CustomUnmarshal("dir:///some/absolute/path")
Expect(err).ShouldNot(HaveOccurred())
Expect(o.IsDir()).To(BeTrue())
@ -94,9 +88,6 @@ var _ = Describe("Types", Label("types", "common"), func() {
o = v1.NewDockerSrc("container/image")
Expect(o.IsDocker()).To(BeTrue())
Expect(o.String()).To(Equal("oci://container/image"))
o = v1.NewChannelSrc("luetPackage")
Expect(o.IsChannel()).To(BeTrue())
Expect(o.String()).To(Equal("channel://luetPackage"))
o = v1.NewEmptySrc()
Expect(o.IsEmpty()).To(BeTrue())
Expect(o.String()).To(Equal(""))

View File

@ -19,6 +19,7 @@ package v1
import (
"fmt"
"path/filepath"
"runtime"
"sort"
"github.com/kairos-io/kairos/v2/pkg/constants"
@ -45,8 +46,9 @@ type Config struct {
Runner Runner
Syscall SyscallInterface
CloudInitRunner CloudInitRunner
Luet LuetInterface
ImageExtractor ImageExtractor
Client HTTPClient
Platform *Platform `yaml:"platform,omitempty" mapstructure:"platform"`
Cosign bool `yaml:"cosign,omitempty" mapstructure:"cosign"`
Verify bool `yaml:"verify,omitempty" mapstructure:"verify"`
CosignPubKey string `yaml:"cosign-key,omitempty" mapstructure:"cosign-key"`
@ -96,18 +98,27 @@ func (c Config) LoadInstallState() (*InstallState, error) {
// Sanitize checks the consistency of the struct, returns error
// if unsolvable inconsistencies are found
func (c *Config) Sanitize() error {
// Set Luet plugins, we only use the mtree plugin for now
if c.Verify {
c.Luet.SetPlugins(constants.LuetMtreePlugin)
}
// If no squashcompression is set, zero the compression parameters
// By default on NewConfig the SquashFsCompressionConfig is set to the default values, and then override
// on config unmarshall.
if c.SquashFsNoCompression {
c.SquashFsCompressionConfig = []string{}
}
// Ensure luet arch matches Config.Arch
c.Luet.SetArch(c.Arch)
if c.Arch != "" {
p, err := NewPlatformFromArch(c.Arch)
if err != nil {
return err
}
c.Platform = p
}
if c.Platform == nil {
p, err := NewPlatformFromArch(runtime.GOARCH)
if err != nil {
return err
}
c.Platform = p
}
return nil
}

View File

@ -63,20 +63,6 @@ var _ = Describe("Types", Label("types", "config"), func() {
Size: 23452345,
},
}
channelState = &v1.ImageState{
Source: v1.NewChannelSrc("cat/mypkg"),
Label: "active_label",
FS: "ext2",
SourceMetadata: &v1.ChannelImageMeta{
Category: "cat",
Name: "mypkg",
Version: "0.2.1",
FingerPrint: "mypkg-cat-0.2.1",
Repos: []v1.Repository{{
Name: "myrepo",
}},
},
}
installState = &v1.InstallState{
Date: "somedate",
Partitions: map[string]*v1.PartitionState{
@ -338,10 +324,8 @@ var _ = Describe("Types", Label("types", "config"), func() {
cfg := elementalConfig.NewRunConfig(elementalConfig.WithMounter(v1mocks.NewErrorMounter()))
cfg.Config.Verify = true
// Sets the luet mtree pluing
err := cfg.Sanitize()
Expect(err).ShouldNot(HaveOccurred())
Expect(cfg.Luet.GetPlugins()).To(Equal([]string{constants.LuetMtreePlugin}))
})
})
Describe("BuildConfig", func() {
@ -349,10 +333,8 @@ var _ = Describe("Types", Label("types", "config"), func() {
cfg := elementalConfig.NewBuildConfig(elementalConfig.WithMounter(v1mocks.NewErrorMounter()))
cfg.Config.Verify = true
// Sets the luet mtree pluing
err := cfg.Sanitize()
Expect(err).ShouldNot(HaveOccurred())
Expect(cfg.Luet.GetPlugins()).To(Equal([]string{constants.LuetMtreePlugin}))
})
})
Describe("InstallSpec", func() {
@ -502,14 +484,9 @@ var _ = Describe("Types", Label("types", "config"), func() {
spec := &v1.LiveISO{
RootFS: []*v1.ImageSource{
v1.NewDirSrc("/system/os"),
v1.NewChannelSrc("system/os"),
},
UEFI: []*v1.ImageSource{
v1.NewChannelSrc("live/grub2-efi-image"),
},
Image: []*v1.ImageSource{
v1.NewChannelSrc("live/grub2"),
},
UEFI: []*v1.ImageSource{},
Image: []*v1.ImageSource{},
}
Expect(spec.Sanitize()).ShouldNot(HaveOccurred())
Expect(iso.Sanitize()).ShouldNot(HaveOccurred())

72
pkg/types/v1/image.go Normal file
View File

@ -0,0 +1,72 @@
/*
Copyright © 2022 - 2023 SUSE LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1
import (
"context"
"net/http"
"github.com/containerd/containerd/archive"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/daemon"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/remote"
)
type ImageExtractor interface {
ExtractImage(imageRef, destination, platformRef string, local bool) error
}
type OCIImageExtractor struct{}
var _ ImageExtractor = OCIImageExtractor{}
func (e OCIImageExtractor) ExtractImage(imageRef, destination, platformRef string, local bool) error {
platform, err := v1.ParsePlatform(platformRef)
if err != nil {
return err
}
ref, err := name.ParseReference(imageRef)
if err != nil {
return err
}
image, err := image(ref, *platform, local)
if err != nil {
return err
}
reader := mutate.Extract(image)
_, err = archive.Apply(context.Background(), destination, reader)
return err
}
func image(ref name.Reference, platform v1.Platform, local bool) (v1.Image, error) {
if local {
return daemon.Image(ref)
}
return remote.Image(ref,
remote.WithTransport(http.DefaultTransport),
remote.WithPlatform(platform),
remote.WithAuthFromKeychain(authn.DefaultKeychain),
)
}

View File

@ -23,7 +23,6 @@ import (
"regexp"
"strings"
"github.com/mudler/luet/pkg/api/core/types"
log "github.com/sirupsen/logrus"
)
@ -50,7 +49,6 @@ type Logger interface {
SetOutput(writer io.Writer)
SetFormatter(formatter log.Formatter)
Copy() (types.Logger, error)
SetContext(string)
SpinnerStop()
Spinner()
@ -110,12 +108,6 @@ func (w *logrusWrapper) Ask() bool {
return false
}
func (w *logrusWrapper) Copy() (types.Logger, error) {
c := *w
copy := &c
return copy, nil
}
func (w *logrusWrapper) Success(r ...interface{}) {
// Will redirect to the Info method below and be cleaned there
w.Info(r...)

View File

@ -1,26 +0,0 @@
/*
Copyright © 2022 SUSE LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1
type LuetInterface interface {
Unpack(string, string, bool) (*DockerImageMeta, error)
UnpackFromChannel(string, string, ...Repository) (*ChannelImageMeta, error)
SetPlugins(...string)
GetPlugins() []string
SetArch(string)
SetTempDir(string)
}

134
pkg/types/v1/platform.go Normal file
View File

@ -0,0 +1,134 @@
/*
Copyright © 2022 - 2023 SUSE LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1
import (
"fmt"
"strings"
registry "github.com/google/go-containerregistry/pkg/v1"
"gopkg.in/yaml.v3"
"github.com/kairos-io/kairos/v2/pkg/constants"
)
type Platform struct {
OS string
Arch string
GolangArch string
}
func NewPlatform(os, arch string) (*Platform, error) {
golangArch, err := archToGolangArch(arch)
if err != nil {
return nil, err
}
arch, err = golangArchToArch(arch)
if err != nil {
return nil, err
}
return &Platform{
OS: os,
Arch: arch,
GolangArch: golangArch,
}, nil
}
func NewPlatformFromArch(arch string) (*Platform, error) {
return NewPlatform("linux", arch)
}
func ParsePlatform(platform string) (*Platform, error) {
p, err := registry.ParsePlatform(platform)
if err != nil {
return nil, err
}
return NewPlatform(p.OS, p.Architecture)
}
func (p *Platform) updateFrom(platform *Platform) {
if platform == nil || p == nil {
return
}
p.OS = platform.OS
p.Arch = platform.Arch
p.GolangArch = platform.GolangArch
}
func (p *Platform) String() string {
if p == nil {
return ""
}
return fmt.Sprintf("%s/%s", p.OS, p.GolangArch)
}
func (p Platform) MarshalYAML() (interface{}, error) {
return p.String(), nil
}
func (p *Platform) UnmarshalYAML(value *yaml.Node) error {
parsed, err := ParsePlatform(value.Value)
if err != nil {
return err
}
p.updateFrom(parsed)
return nil
}
func (p *Platform) CustomUnmarshal(data interface{}) (bool, error) {
str, ok := data.(string)
if !ok {
return false, fmt.Errorf("can't unmarshal %+v to a Platform type", data)
}
parsed, err := ParsePlatform(str)
p.updateFrom(parsed)
return false, err
}
var errInvalidArch = fmt.Errorf("invalid arch")
func archToGolangArch(arch string) (string, error) {
switch strings.ToLower(arch) {
case constants.ArchAmd64:
return constants.ArchAmd64, nil
case constants.Archx86:
return constants.ArchAmd64, nil
case constants.ArchArm64:
return constants.ArchArm64, nil
default:
return "", errInvalidArch
}
}
func golangArchToArch(arch string) (string, error) {
switch strings.ToLower(arch) {
case constants.Archx86:
return constants.Archx86, nil
case constants.ArchAmd64:
return constants.Archx86, nil
case constants.ArchArm64:
return constants.ArchArm64, nil
default:
return "", errInvalidArch
}
}

View File

@ -499,17 +499,6 @@ func FindFileWithPrefix(fs v1.FS, path string, prefixes ...string) (string, erro
var errInvalidArch = fmt.Errorf("invalid arch")
func ArchToGolangArch(arch string) (string, error) {
switch strings.ToLower(arch) {
case cnst.Archx86:
return cnst.ArchAmd64, nil
case cnst.ArchArm64:
return cnst.ArchArm64, nil
default:
return "", errInvalidArch
}
}
func GolangArchToArch(arch string) (string, error) {
switch strings.ToLower(arch) {
case cnst.ArchAmd64:

View File

@ -60,6 +60,7 @@ var _ = Describe("run stage", Label("RunStage"), func() {
// We also use the real fs
memLog = &bytes.Buffer{}
logger = v1.NewBufferLogger(memLog)
logger.SetLevel(log.DebugLevel)
fs, cleanup, _ = vfst.NewTestFS(nil)
config = conf.NewConfig(
@ -95,12 +96,10 @@ var _ = Describe("run stage", Label("RunStage"), func() {
It("Goes over extra paths", func() {
d, err := utils.TempDir(fs, "", "elemental")
Expect(err).ToNot(HaveOccurred())
err = fs.WriteFile(fmt.Sprintf("%s/extra.yaml", d), []byte{}, os.ModePerm)
Expect(err).ShouldNot(HaveOccurred())
config.Logger.SetLevel(log.DebugLevel)
Expect(utils.RunStage(config, "luke", strict, d)).To(BeNil())
Expect(memLog.String()).To(ContainSubstring(fmt.Sprintf("Executing %s/extra.yaml", d)))
Expect(memLog.String()).To(ContainSubstring(d))
Expect(memLog).To(ContainSubstring("luke"))
Expect(memLog).To(ContainSubstring("luke.before"))
Expect(memLog).To(ContainSubstring("luke.after"))

View File

@ -18,8 +18,6 @@ package utils_test
import (
"bytes"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"os"
@ -1126,154 +1124,4 @@ var _ = Describe("Utils", Label("utils"), func() {
Expect(err.Error()).To(ContainSubstring("Cleanup error 3"))
})
})
Describe("VHD utils", Label("vhd"), func() {
It("creates a valid header", func() {
tmpDir, _ := utils.TempDir(fs, "", "")
f, _ := fs.OpenFile(filepath.Join(tmpDir, "test.vhd"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
utils.RawDiskToFixedVhd(f)
_ = f.Close()
f, _ = fs.Open(filepath.Join(tmpDir, "test.vhd"))
info, _ := f.Stat()
// Should only have the footer in teh file, hence 512 bytes
Expect(info.Size()).To(BeNumerically("==", 512))
// Dump the header from the file into our VHDHeader
buff := make([]byte, 512)
_, _ = f.ReadAt(buff, info.Size()-512)
_ = f.Close()
header := utils.VHDHeader{}
err := binary.Read(bytes.NewBuffer(buff[:]), binary.BigEndian, &header)
Expect(err).ToNot(HaveOccurred())
// Just check the fields that we know the value of, that should indicate that the header is valid
Expect(hex.EncodeToString(header.DiskType[:])).To(Equal("00000002"))
Expect(hex.EncodeToString(header.Features[:])).To(Equal("00000002"))
Expect(hex.EncodeToString(header.DataOffset[:])).To(Equal("ffffffffffffffff"))
Expect(hex.EncodeToString(header.CreatorApplication[:])).To(Equal("656c656d"))
})
Describe("CHS calculation", func() {
It("limits the number of sectors", func() {
tmpDir, _ := utils.TempDir(fs, "", "")
f, _ := fs.Create(filepath.Join(tmpDir, "test.vhd"))
// This size would make the chs calculation break, but we have a guard for it
f.Truncate(500 * 1024 * 1024 * 1024)
f.Close()
f, _ = fs.OpenFile(filepath.Join(tmpDir, "test.vhd"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
utils.RawDiskToFixedVhd(f)
_ = f.Close()
f, _ = fs.Open(filepath.Join(tmpDir, "test.vhd"))
info, _ := f.Stat()
// Dump the header from the file into our VHDHeader
buff := make([]byte, 512)
_, _ = f.ReadAt(buff, info.Size()-512)
_ = f.Close()
header := utils.VHDHeader{}
err := binary.Read(bytes.NewBuffer(buff[:]), binary.BigEndian, &header)
Expect(err).ToNot(HaveOccurred())
// Just check the fields that we know the value of, that should indicate that the header is valid
Expect(hex.EncodeToString(header.DiskType[:])).To(Equal("00000002"))
Expect(hex.EncodeToString(header.Features[:])).To(Equal("00000002"))
Expect(hex.EncodeToString(header.DataOffset[:])).To(Equal("ffffffffffffffff"))
// cylinders which is (totalSectors / sectorsPerTrack) / heads
// and totalsectors is 65535 * 16 * 255 due to hitting the max sector
// This turns out to be 65535 or ffff in hex or [2]byte{255,255}
Expect(hex.EncodeToString(header.DiskGeometry[:2])).To(Equal("ffff"))
Expect(header.DiskGeometry[2]).To(Equal(uint8(16))) // heads
Expect(header.DiskGeometry[3]).To(Equal(uint8(255))) // sectors per track
})
// The tests below test the different routes that the chs calculation can take to get the disk geometry
// it's all based on number of sectors, so we have to try with different known sizes to see if the
// geometry changes are properly reflected on the final VHD header
It("sets the disk geometry correctly based on sector number", func() {
tmpDir, _ := utils.TempDir(fs, "", "")
f, _ := fs.Create(filepath.Join(tmpDir, "test.vhd"))
// one route of the chs calculation
f.Truncate(1 * 1024 * 1024)
f.Close()
f, _ = fs.OpenFile(filepath.Join(tmpDir, "test.vhd"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
utils.RawDiskToFixedVhd(f)
_ = f.Close()
f, _ = fs.Open(filepath.Join(tmpDir, "test.vhd"))
info, _ := f.Stat()
// Dump the header from the file into our VHDHeader
buff := make([]byte, 512)
_, _ = f.ReadAt(buff, info.Size()-512)
_ = f.Close()
header := utils.VHDHeader{}
err := binary.Read(bytes.NewBuffer(buff[:]), binary.BigEndian, &header)
Expect(err).ToNot(HaveOccurred())
// Just check the fields that we know the value of, that should indicate that the header is valid
Expect(hex.EncodeToString(header.DiskType[:])).To(Equal("00000002"))
Expect(hex.EncodeToString(header.Features[:])).To(Equal("00000002"))
Expect(hex.EncodeToString(header.DataOffset[:])).To(Equal("ffffffffffffffff"))
// should not be the max value
Expect(hex.EncodeToString(header.DiskGeometry[:2])).ToNot(Equal("ffff"))
Expect(header.DiskGeometry[2]).To(Equal(uint8(4))) // heads
Expect(header.DiskGeometry[3]).To(Equal(uint8(17))) // sectors per track
})
It("sets the disk geometry correctly based on sector number", func() {
tmpDir, _ := utils.TempDir(fs, "", "")
f, _ := fs.Create(filepath.Join(tmpDir, "test.vhd"))
// one route of the chs calculation
f.Truncate(1 * 1024 * 1024 * 1024)
f.Close()
f, _ = fs.OpenFile(filepath.Join(tmpDir, "test.vhd"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
utils.RawDiskToFixedVhd(f)
_ = f.Close()
f, _ = fs.Open(filepath.Join(tmpDir, "test.vhd"))
info, _ := f.Stat()
// Dump the header from the file into our VHDHeader
buff := make([]byte, 512)
_, _ = f.ReadAt(buff, info.Size()-512)
_ = f.Close()
header := utils.VHDHeader{}
err := binary.Read(bytes.NewBuffer(buff[:]), binary.BigEndian, &header)
Expect(err).ToNot(HaveOccurred())
// Just check the fields that we know the value of, that should indicate that the header is valid
Expect(hex.EncodeToString(header.DiskType[:])).To(Equal("00000002"))
Expect(hex.EncodeToString(header.Features[:])).To(Equal("00000002"))
Expect(hex.EncodeToString(header.DataOffset[:])).To(Equal("ffffffffffffffff"))
// should not be the max value
Expect(hex.EncodeToString(header.DiskGeometry[:2])).ToNot(Equal("ffff"))
Expect(header.DiskGeometry[2]).To(Equal(uint8(16))) // heads
Expect(header.DiskGeometry[3]).To(Equal(uint8(63))) // sectors per track
})
It("sets the disk geometry correctly based on sector number", func() {
tmpDir, _ := utils.TempDir(fs, "", "")
f, _ := fs.Create(filepath.Join(tmpDir, "test.vhd"))
// another route of the chs calculation
f.Truncate(220 * 1024 * 1024)
f.Close()
f, _ = fs.OpenFile(filepath.Join(tmpDir, "test.vhd"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
utils.RawDiskToFixedVhd(f)
_ = f.Close()
f, _ = fs.Open(filepath.Join(tmpDir, "test.vhd"))
info, _ := f.Stat()
// Dump the header from the file into our VHDHeader
buff := make([]byte, 512)
_, _ = f.ReadAt(buff, info.Size()-512)
_ = f.Close()
header := utils.VHDHeader{}
err := binary.Read(bytes.NewBuffer(buff[:]), binary.BigEndian, &header)
Expect(err).ToNot(HaveOccurred())
// Just check the fields that we know the value of, that should indicate that the header is valid
Expect(hex.EncodeToString(header.DiskType[:])).To(Equal("00000002"))
Expect(hex.EncodeToString(header.Features[:])).To(Equal("00000002"))
Expect(hex.EncodeToString(header.DataOffset[:])).To(Equal("ffffffffffffffff"))
// should not be the max value
Expect(hex.EncodeToString(header.DiskGeometry[:2])).ToNot(Equal("ffff"))
Expect(header.DiskGeometry[2]).To(Equal(uint8(16))) // heads
Expect(header.DiskGeometry[3]).To(Equal(uint8(31))) // sectors per track
})
})
})
})

View File

@ -1,155 +0,0 @@
/*
Copyright © 2022 SUSE LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package utils
import (
"bytes"
"encoding/binary"
"encoding/hex"
"math"
"os"
"time"
uuidPkg "github.com/distribution/distribution/uuid"
)
// This file contains utils to work with VHD disks
type VHDHeader struct {
Cookie [8]byte // Cookies are used to uniquely identify the original creator of the hard disk image
Features [4]byte // This is a bit field used to indicate specific feature support.
// Can be 0x00000000 (no features), 0x00000001 (Temporary, candidate for deletion on shutdown) or 0x00000002 (Reserved)
FileFormatVersion [4]byte // Divided into a major/minor version and matches the version of the specification used in creating the file.
DataOffset [8]byte // For fixed disks, this field should be set to 0xFFFFFFFF.
Timestamp [4]byte // Sstores the creation time of a hard disk image. This is the number of seconds since January 1, 2000 12:00:00 AM in UTC/GMT.
CreatorApplication [4]byte // Used to document which application created the hard disk.
CreatorVersion [4]byte // This field holds the major/minor version of the application that created the hard disk image.
CreatorHostOS [4]byte // This field stores the type of host operating system this disk image is created on.
OriginalSize [8]byte // This field stores the size of the hard disk in bytes, from the perspective of the virtual machine, at creation time. Info only
CurrentSize [8]byte // This field stores the current size of the hard disk, in bytes, from the perspective of the virtual machine.
DiskGeometry [4]byte // This field stores the cylinder, heads, and sectors per track value for the hard disk.
DiskType [4]byte // Fixed = 2, Dynamic = 3, Differencing = 4
Checksum [4]byte // This field holds a basic checksum of the hard disk footer. It is just a ones complement of the sum of all the bytes in the footer without the checksum field.
UniqueID [16]byte // This is a 128-bit universally unique identifier (UUID).
SavedState [1]byte // This field holds a one-byte flag that describes whether the system is in saved state. If the hard disk is in the saved state the value is set to 1
Reserved [427]byte // This field contains zeroes.
}
func newVHDFixed(size uint64) VHDHeader {
header := VHDHeader{}
hexToField("00000002", header.Features[:])
hexToField("00010000", header.FileFormatVersion[:])
hexToField("ffffffffffffffff", header.DataOffset[:])
t := uint32(time.Now().Unix() - 946684800)
binary.BigEndian.PutUint32(header.Timestamp[:], t)
hexToField("656c656d", header.CreatorApplication[:]) // Cos
hexToField("73757365", header.CreatorHostOS[:]) // SUSE
binary.BigEndian.PutUint64(header.OriginalSize[:], size)
binary.BigEndian.PutUint64(header.CurrentSize[:], size)
// Divide size into 512 to get the total sectors
totalSectors := float64(size / 512)
geometry := chsCalculation(uint64(totalSectors))
binary.BigEndian.PutUint16(header.DiskGeometry[:2], uint16(geometry.cylinders))
header.DiskGeometry[2] = uint8(geometry.heads)
header.DiskGeometry[3] = uint8(geometry.sectorsPerTrack)
hexToField("00000002", header.DiskType[:]) // Fixed 0x00000002
hexToField("00000000", header.Checksum[:])
uuid := uuidPkg.Generate()
copy(header.UniqueID[:], uuid.String())
generateChecksum(&header)
return header
}
// generateChecksum generates the checksum of the vhd header
// Lifted from the official VHD Format Spec
func generateChecksum(header *VHDHeader) {
buffer := new(bytes.Buffer)
_ = binary.Write(buffer, binary.BigEndian, header)
checksum := 0
bb := buffer.Bytes()
for counter := 0; counter < 512; counter++ {
checksum += int(bb[counter])
}
binary.BigEndian.PutUint32(header.Checksum[:], uint32(^checksum))
}
// hexToField decodes an hex to bytes and copies it to the given header field
func hexToField(hexs string, field []byte) {
h, _ := hex.DecodeString(hexs)
copy(field, h)
}
// chs is a simple struct to represent the cylinders/heads/sectors for a given sector count
type chs struct {
cylinders uint
heads uint
sectorsPerTrack uint
}
// chsCalculation calculates the cylinders, headers and sectors per track for a given sector count
// Exactly the same code on the official VHD format spec
func chsCalculation(sectors uint64) chs {
var sectorsPerTrack,
heads,
cylinderTimesHeads,
cylinders float64
totalSectors := float64(sectors)
if totalSectors > 65535*16*255 {
totalSectors = 65535 * 16 * 255
}
if totalSectors >= 65535*16*63 {
sectorsPerTrack = 255
heads = 16
cylinderTimesHeads = math.Floor(totalSectors / sectorsPerTrack)
} else {
sectorsPerTrack = 17
cylinderTimesHeads = math.Floor(totalSectors / sectorsPerTrack)
heads = math.Floor((cylinderTimesHeads + 1023) / 1024)
if heads < 4 {
heads = 4
}
if (cylinderTimesHeads >= (heads * 1024)) || heads > 16 {
sectorsPerTrack = 31
heads = 16
cylinderTimesHeads = math.Floor(totalSectors / sectorsPerTrack)
}
if cylinderTimesHeads >= (heads * 1024) {
sectorsPerTrack = 63
heads = 16
cylinderTimesHeads = math.Floor(totalSectors / sectorsPerTrack)
}
}
cylinders = cylinderTimesHeads / heads
return chs{
cylinders: uint(cylinders),
heads: uint(heads),
sectorsPerTrack: uint(sectorsPerTrack),
}
}
// RawDiskToFixedVhd will write the proper header to a given os.File to convert it from a simple raw disk to a Fixed VHD
// RawDiskToFixedVhd makes no effort into opening/closing/checking if the file exists
func RawDiskToFixedVhd(diskFile *os.File) {
info, _ := diskFile.Stat()
size := uint64(info.Size())
header := newVHDFixed(size)
_ = binary.Write(diskFile, binary.BigEndian, header)
}

View File

@ -0,0 +1,45 @@
/*
Copyright © 2022 - 2023 SUSE LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package mocks
import v1 "github.com/kairos-io/kairos/v2/pkg/types/v1"
type FakeImageExtractor struct {
Logger v1.Logger
SideEffect func(imageRef, destination, platformRef string, local bool) error
}
var _ v1.ImageExtractor = FakeImageExtractor{}
func NewFakeImageExtractor(logger v1.Logger) *FakeImageExtractor {
if logger == nil {
logger = v1.NewNullLogger()
}
return &FakeImageExtractor{
Logger: logger,
}
}
func (f FakeImageExtractor) ExtractImage(imageRef, destination, platformRef string, local bool) error {
f.Logger.Debugf("extracting %s to %s in platform %s", imageRef, destination, platformRef)
if f.SideEffect != nil {
f.Logger.Debugf("running side effect")
return f.SideEffect(imageRef, destination, platformRef, local)
}
return nil
}

View File

@ -1,40 +0,0 @@
/*
Copyright © 2022 SUSE LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package mocks
import (
"fmt"
)
type LiveBootLoaderMock struct {
ErrorEFI bool
ErrorISO bool
}
func (g *LiveBootLoaderMock) PrepareEFI(rootDir, uefiDir string) error {
if g.ErrorEFI {
return fmt.Errorf("failed preparing EFI binaries")
}
return nil
}
func (g *LiveBootLoaderMock) PrepareISO(rootDir, imageDir string) error {
if g.ErrorISO {
return fmt.Errorf("failed preparing ISO bootloader binaries")
}
return nil
}

View File

@ -1,85 +0,0 @@
/*
Copyright © 2022 SUSE LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package mocks
import (
"errors"
v1 "github.com/kairos-io/kairos/v2/pkg/types/v1"
luetTypes "github.com/mudler/luet/pkg/api/core/types"
)
type FakeLuet struct {
OnUnpackError bool
OnUnpackFromChannelError bool
UnpackSideEffect func(string, string, bool) (*v1.DockerImageMeta, error)
UnpackFromChannelSideEffect func(string, string, ...v1.Repository) (*v1.ChannelImageMeta, error)
unpackCalled bool
unpackFromChannelCalled bool
plugins []string
arch string
}
func NewFakeLuet() *FakeLuet {
return &FakeLuet{}
}
func (l *FakeLuet) Unpack(target string, image string, local bool) (*v1.DockerImageMeta, error) {
l.unpackCalled = true
if l.OnUnpackError {
return nil, errors.New("Luet install error")
}
if l.UnpackSideEffect != nil {
return l.UnpackSideEffect(target, image, local)
}
return nil, nil
}
func (l *FakeLuet) UnpackFromChannel(target string, pkg string, repos ...v1.Repository) (*v1.ChannelImageMeta, error) {
l.unpackFromChannelCalled = true
if l.OnUnpackFromChannelError {
return nil, errors.New("Luet install error")
}
if l.UnpackFromChannelSideEffect != nil {
return l.UnpackFromChannelSideEffect(target, pkg, repos...)
}
return nil, nil
}
func (l FakeLuet) UnpackCalled() bool {
return l.unpackCalled
}
func (l FakeLuet) UnpackChannelCalled() bool {
return l.unpackFromChannelCalled
}
func (l FakeLuet) OverrideConfig(config *luetTypes.LuetConfig) {}
func (l *FakeLuet) SetPlugins(plugins ...string) {
l.plugins = plugins
}
func (l *FakeLuet) GetPlugins() []string {
return l.plugins
}
func (l *FakeLuet) SetArch(arch string) {
l.arch = arch
}
func (l *FakeLuet) SetTempDir(s string) {}