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 (#20)
Co-authored-by: Mauro Morales <mauro.morales@spectrocloud.com>
This commit is contained in:
parent
0b7fd24bc7
commit
ddfa30a4c6
.github/workflows
.goreleaser.yamlEarthfilego.modgo.suminternal/agent
main.gopkg
action
constants
elemental
elementalConfig
live
luet
types/v1
utils
tests/mocks
3
.github/workflows/unit-tests.yml
vendored
3
.github/workflows/unit-tests.yml
vendored
@ -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
|
||||
|
@ -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:
|
||||
|
12
Earthfile
12
Earthfile
@ -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
93
go.mod
@ -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
|
||||
|
@ -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
65
main.go
@ -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() {
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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"))
|
||||
})
|
||||
})
|
||||
})
|
@ -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() {
|
||||
|
@ -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())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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() {
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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())))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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())
|
||||
})
|
||||
})
|
@ -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")
|
||||
}
|
399
pkg/luet/luet.go
399
pkg/luet/luet.go
@ -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"))
|
||||
}
|
@ -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))
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
})
|
@ -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}
|
||||
}
|
||||
|
@ -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(""))
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
72
pkg/types/v1/image.go
Normal 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),
|
||||
)
|
||||
}
|
@ -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...)
|
||||
|
@ -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
134
pkg/types/v1/platform.go
Normal 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
|
||||
}
|
||||
}
|
@ -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:
|
||||
|
@ -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"))
|
||||
|
@ -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
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
|
155
pkg/utils/vhd.go
155
pkg/utils/vhd.go
@ -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 one’s 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)
|
||||
}
|
45
tests/mocks/extractor_mock.go
Normal file
45
tests/mocks/extractor_mock.go
Normal 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
|
||||
}
|
@ -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
|
||||
}
|
@ -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) {}
|
Loading…
Reference in New Issue
Block a user